Feeds

1935 items (1935 unread) in 16 feeds

绿岛成员 绿岛成员
web技术 web技术
杂七杂八 杂七杂八
经济观察 经济观察
 «  Expand/Collapse

web技术 (88 unread)

  • Permalink for '哥俩好:oninput & onpropertychange'

    哥俩好:oninput & onpropertychange

    Posted: 10 17th, 2011, 10:09pm PDT by 怿飞

    传统对于文本框(input)的输入可通过键盘的 onkeydown / onkeypress / onkeyup 来监测,但在处理较多细节时存在诟病比如: cut(剪切) / paste(复制) / undo(撤销) / redo(重做) / drag & drop(拖拽)/ 输入法等。

    而 oninput & onpropertychange 事件基本可以解决上面的诟病:

    oninput 事件作为 HTML5 中的标准事件,基本除了IE6 / IE7 / IE8 外的最新浏览器均支持(注:1、原先的 Opera 的虽支持,但依然存在部分传统的诟病,从 Opera 11+ 开始,已修复,更加完美;2、IE9 也支持)。

    function(input, callback){
        if ("onpropertychange" in input) { //IE6/IE7/IE8
            input.onpropertychange = function(){
                if (window.event.propertyName == "value"){
                    callback.call(this, window.event)
                }
            }
        } else {
            // Fix Firefox Bug: [https:]         input.addEventListener("input", callback, false);
        }
    }

    提示:

    1. oninput 事件:当 JS 改变 value 值或从浏览器的自动下拉提示中选值时,不会触发。
    2. onpropertychange 事件:当 input 设置为不可用(disable=true)时,不会触发。

    参考文章:

  • Permalink for '带图案的二维码'

    带图案的二维码

    Posted: 08 11th, 2011, 7:39am PDT by aoao

    那个黑不溜秋的二维码大家都见过吧。最近随着高档手机才稍微有人知道,大家用这个 QR-Code Generator,生成自己想要的信息,比如生成这样:

    ^_^

    如果你用iPhone 的话。可以用ScanScanIt 这样的app来解码。其他手机自己想办法。

    好了,现在来说明为什么那个二维码有图案,据说二维码的容错非常高,所以,只把图片或者字找个相似的地方放上去就行。。这里有令人拍案叫绝的15个二维码,可以看看,我最喜欢的是这个:

  • Permalink for '判断一个网页是否适合手机浏览'

    判断一个网页是否适合手机浏览

    Posted: 07 24th, 2011, 2:19am PDT by aoao

    最近在做一个项目,需要知道一个网页是否适合手机浏览,想了点方案。

    1.域名: 二、三级域名中包含有m 3g wap mobile 的算是。当然像 mobile也有坑爹的 比如像mobile.qq.com,mobile.yahoo.com

    2.网页的DTD: XHTML MP 或 wap

    <!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
    <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.dtd">

    但是wap并不一定适合手机浏览,有种破手机叫iPhone。他有个破浏览器叫Safari

    3.网页的meta里有viewport 如

    <meta name="viewport" content="width=xxx; ..... "/>

    但是,可能只适合某些手机。适合手机访问不代表一定是手机版,某搜索就直接用这个判定为网页是手机版

    4.网页标题包含有“手机版” “触屏版”

    接着另一个问题,怎样让不适合手机浏览的网页变得适合呢?

    5.让spider 装手机去逛,看会不会适配。

    6.特定网站集的把topN的人肉找出手机版,找出与pc版匹配的规则,配

    7.使用中转服务

  • Permalink for '将字符实体引用转换成 Unicode 字符'

    将字符实体引用转换成 Unicode 字符

    Posted: 06 4th, 2011, 6:38am PDT by 怿飞

    首先我们一起来看下 Character entities references (HTML Entities)和 Numeric Character Reference (NCR)的异同:

    HTML Entities 的格式如:&lt;,NCR 的格式如:<<,均都表示“<” 字符。

    HTML 中规定了 Character entity references,在 “24.2.1 The list of characters” 列出了 HTML Entities 和 NCR 的对应关系,例如:

    <!ENTITY nbsp   CDATA " " -- no-break space = non-breaking space, U+00A0 ISOnum -->
    <!ENTITY iexcl  CDATA "¡" -- inverted exclamation mark, U+00A1 ISOnum -->
    <!ENTITY yen    CDATA "¥" -- yen sign = yuan sign, U+00A5 ISOnum --> 

    那在 Python 中我们如何将 HTML Entities 和 NCR 转换成普通字符呢?

    在回答这个问题之前,我们做一些简单的回顾:

    group 方法

    group([group1,…]) 

    group 属于 Match Object 对象拥有的方法,返回匹配到的一个或者多个子组。如果是一个参数,那么结果返回字符串,如果是多个参数,则返回元组。group1 的默认值为 0 (将返回所有的匹配值),如果 groupX 的值是 [1…99] 范围之内的,那么将匹配对应括号组的字符串。如果组号是负的或者比 pattern 中定义的组号大,那么将抛出 IndexError 异常。若 pattern 没有匹配到,但 group 匹配到,那么 group 的值也为 None。如果一个 pattern 可以匹配多个,那么组对应匹配的最后一个。

    re.sub 方法

    re.sub(pattern , replace , string [, count])

    sub 属于 re 模块的字符串替换和修改函数,其在目标字符串中查找与正则相匹配的字符串,并将其替换成指定的字符串。

    • pattern 参数——需要匹配的正则规则
    • replace 参数——指定用来替换的字符串或函数。如果 replace 是函数,则会对所有的匹配都回调此函数,这个函数使用单个 Match Object 作为参数,然后返回替换后的字符串。
    • string 参数——目标字符串
    • count 参数——最多替换的次数,未指定,则将替换所有匹配到的字符串

    re.sub() 的使用案例如下:

    import re
    def dashrepl(matchobj):
        if matchobj.group(0) == '-':
            return ' '
        else:
            return '-'
    re.sub('-{1,2}', dashrepl, 'pro----gram-files')
    
    # result: 'pro--gram files'

    htmlentitydefs

    htmlentitydefs 有三个属性,详细如下:

    • entitydefs:A dictionary mapping XHTML 1.0 entity definitions to their replacement text in ISO Latin-1.
    • name2codepoint:A dictionary that maps HTML entity names to the Unicode codepoints. New in version 2.3.
    • codepoint2name:A dictionary that maps Unicode codepoints to HTML entity names.

    实际存在的形式大致如下:

    entitydefs = {'AElig': '\xc6', 'Aacute': '\xc1', 'Acirc': '\xc2', ...}
    name2codepoint = {'AElig': 198, 'Aacute': 193, 'Acirc': 194, ...}
    codepoint2name = {34: 'quot', 38: 'amp', 60: 'lt', 62: 'gt', ...}

    对于我们来说,此时最有用的是 name2codepoint 属性,比如:“<”,name 是 lt,我们可以通过 name2codepoint[lt] 获得其 code point:60。

    unichr 方法

    unichr 是字符串的方法(unichr(int)),可以将整数转化成相应的 Unicode 字符,比如: unichr(60) –> u'\u003c' or u'<'

    import re, htmlentitydefs
    
    ##
    # Removes HTML or XML character references and entities from a text string.
    #
    # @param text The HTML (or XML) source text.
    # @return The plain text, as a Unicode string, if necessary.
    
    def unescape(text):
        def convert(matchobj):
            text = matchobj.group(0)
            if text[:2] == "&#":
                # Numeric Character Reference
                try:
                    if text[:3] == "&#x":
                        return unichr(int(text[3:-1], 16))
                    else:
                        return unichr(int(text[2:-1]))
                except ValueError:
                    pass
            else:
                # Character entities references
                try:
                    text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
                except KeyError:
                    pass
            return text # Return Unicode characters
        return re.sub("&#?\w+;", convert, text)

    扩展阅读:

  • Permalink for '使用 JScript 创建 .exe 或 .dll 文件'

    使用 JScript 创建 .exe 或 .dll 文件

    Posted: 06 3rd, 2011, 9:59am PDT by 怿飞

    什么是 JScript?

    JScript 是由微软开发的活动脚本语言,基于 ECMAScript 规范实现。Internet Explorer 中的 JavaScript,实际上是指 JScript。JScript 已被 Windows Script Host(WSH)支持(WSH 中的 JavaScript shell scripting:C:\> cscript jslint.js)。JScript 最新的版本(JScript.NET)基于 ECMAScript 4.0 ,并且可以在 .Net 环境下编译。

    .NET Framwork 中包含有 JScript 编译器 :JScriptCompiler (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\jsc.exe),其可以将 JScript 文件编译为一个 .exe 或者 .dll 文件。

    为了方便使用,我们可以将 JScriptCompiler 的路径加入到环境变量(环境变量 –> 系统变量 –> Path)中。在 CMD 程序运行窗口中直接调用命令 “jsc”,就可以看到编译器相关的帮助选项。

    jsc [选项] [[选项] …]

    JScript 编译器选项

    – 输出文件 -
    /out:<file> 指定二进制输出文件的名称
    /t[arget]:exe 创建控制台应用程序(默认)
    /t[arget]:winexe 创建 Windows 应用程序
    /t[arget]:library 创建库程序集
    /platform:<platform> 限制此代码可以在其上运行的平台;必须是 x86、Itanium、x64 或 anycpu。默认为 anycpu

    – 输入文件 -
    /autoref[+|-] 基于导入的命名空间和完全限定名称自动引用程序集(默认情
    况下为 on)
    /lib:<path> 指定要在其中搜索引用的附加目录
    /r[eference]:<file list> 从指定的程序集文件引用元数据 <file list>: <assembly name>[;<assembly name>...]

    – 资源 -
    /win32res:<file> 指定 Win32 资源文件(.res)
    /res[ource]:<info> 嵌入指定的资源 <info>: <filename>[,<name>[,public|private]]
    /linkres[ource]:<info> 将指定的资源链接到此程序集 <info>: <filename>[,<name>[,public|private]]

    – 代码生成 -
    /debug[+|-] 发出调试信息
    /fast[+|-] 禁用语言功能以使代码更好地生成
    /warnaserror[+|-] 将警告视为错误
    /w[arn]:<level> 设置警告等级(0-4)

    – 杂项 -
    @<filename> 有关更多选项,请阅读响应文件
    /? 显示帮助
    /help 显示帮助
    /d[efine]:<symbols> 定义条件编译符号
    /nologo 不显示编译器版权标志
    /print[+|-] 提供 print() 函数

    – 高级 -
    /codepage:<id> 使用指定的代码页 ID 打开源文件
    /lcid:<id> 将指定的 LCID 用于消息和默认代码页
    /nostdlib[+|-] 不导入标准库(mscorlib.dll)并将 autoref 默认值更改为 off
    /utf8output[+|-] 以 UTF-8 字符编码形式发出编译器输出
    /versionsafe[+|-] 为没有标记为“override”或“hide”的成员指定默认值

    创建 .exe 文件

    先创建 JS 文件(C:\test\helloWorld.js),内容如下:

    var date = new Date();
    print('Hello World! \nToday is ' + date );

    接着我们进行编译:

    C:\test>jsc helloWorld.js
    
    Microsoft (R) JScript Compiler version 8.00.50727
    for Microsoft (R) .NET Framework version 2.0.50727
    Copyright (C) Microsoft Corporation 1996-2005。保留所有权利。

    你会惊讶的发现,C:\test 目录下多了个 helloWorld.exe 文件,非常简单吧,呵呵

    最后我们可以直接执行 helloWorld.exe 文件:

    C:\test>helloWorld
    
    Hello World!
    Today is Fri Jun 3 23:13:20 UTC+8 2011

    大功告成!!

    创建 .dll 文件

    .dll 文件的创建也同样非常简单:

    package LibHW {
        class HelloWorld {
            function run() {
                var date = new Date();
                return 'Hello World! \nToday is ' + date;
            }
        }
    }

    编译语句:

    C:\test>jsc /t:library LibHW.js

    对于生成的 LibHW.dll 文件,我们可以通过创建新的 .exe 文件(consumer.js –> consumer.exe)以导入模块的方式调用(类似于 Python)。

    先创建 consumer.js 文件:

    import LibHW;
    var hw = new LibHW.HelloWorld();
    print(hw.run());

    然后编译 consumer.js 文件,执行 consumer.exe:

    C:\test>jsc consumer.js
    
    Microsoft (R) JScript Compiler version 8.00.50727
    for Microsoft (R) .NET Framework version 2.0.50727
    Copyright (C) Microsoft Corporation 1996-2005。保留所有权利。
    
    C:\test>consumer
    
    Hello World!
    Today is Sat Jun 4 00:42:35 UTC+8 2011

    当然你还可以创建 windows 的应用,上例中的 consumer.js 文件修改如下:

    import System.Windows.Forms; // this has a MessageBox class
    import LibHW;
    
    var hw = new LibHW.HelloWorld();
    MessageBox.Show(
        hw.run(),
        "Dude!",
        MessageBoxButtons.OK,
        MessageBoxIcon.Exclamation
    );

    编译语句:

    C:\test>jsc /t:winexe consumer.js

    双击新生成的 consumer.exe 文件,哈哈,是不是很有成就感!

    从上面的例子可以看到 JScript 的潜力无限,没有做不到,只有想不到。

    扩展阅读:

  • Permalink for 'Simple JavaScript Template : substitute'

    Simple JavaScript Template : substitute

    Posted: 05 30th, 2011, 11:54pm PDT by 怿飞

    我们在平常使用字符串拼接的时候(如下例),会发现代码的可维护性和易读性将变得更加糟糕(代码中一堆的变量、双引号、单引号, 加号等,相信当情况更为复杂时,头一定发晕):

    var url= 'http://www.plannabc.net/',
        title= '落草为根——专注前端技术&关注用户体验',
        text = '怿飞's Blog';
    
    var link = '<a href="' + url + '" title="' + title+ '">' + text+ '</a>';

    如果上述代码变为:

    var obj = {
        url: "http://www.plannabc.net/",
        title: "落草为根——专注前端技术&关注用户体验",
        text: "怿飞's Blog"
    };
    var link = '<a href="{url}" title="{title}">{text}</a>';
    substitute(link, obj)

    一切变得怡然自得。

    substitute 函数的实现思路其实很简单:使用 String 的 replace 函数,在 replace 函数中用正则匹配除模板中的要替换的标签(“{key}”),并进行替换:

    function substitute (str, obj) {
        if (!(Object.prototype.toString.call(str) === '[object String]')) {
            return '';
        }
    
        // {}, new Object(), new Class()
        // Object.prototype.toString.call(node=document.getElementById("xx")) : ie678 == '[object Object]', other =='[object HTMLElement]'
        // 'isPrototypeOf' in node : ie678 === false , other === true
        if(!(Object.prototype.toString.call(obj) === '[object Object]' && 'isPrototypeOf' in obj)) {
            return str;
        }
    
        // [https:]     return str.replace(/\{([^{}]+)\}/g, function(match, key) {
            var value = obj[key];
            return ( value !== undefined) ? ''+value :'';
        });
    }

    substitute 函数将模板中的标签 {key} 替换成 obj 中对应的 value(obj[key] or obj[key].toString()),如果不存在,则替换成空字符。

    如果模板中某些内容不需要替换的怎么办?比如:{some text need brace}

    1. 可以增加新的语法,人为控制不需要替换的模板标签,比如:\{key\}
    2. 使用更为少见的字符作为模板标签,避免与常规情况撞车,比如:{{key}}

    当然如果输入的数据 obj 为不完全信任的数据(比如:XSS)时,可以增加字符的转义:

    function escape(s) {
        s = String(s === null ? "" : s);
        return s.replace(/&(?!\w+;)|["'\\]/g, function(s) {
            switch(s) {
                case "&": return "&amp;";
                case "\\": return "\\\\";
                case '"': return '&quot;';
                case "'": return ''';
                case "": return "&gt;";
                default: return s;
            }
        });
    }

    我们再看看 YUI3 中的实现 : [https:]]

    YUI3中做了更多的处理 substitute(s, o, f, recurse)

    1. 允许传入 fn,fn 将对 obj 中对应的 key/value 进行处理,返回新的 value。
    2. 如果支持 Y.dump 函数,将把 value 是对象的转成一定格式的字符串,如果不支持,直接返回对象的 toString。
    3. value 值为非对象、非字符串和非数字时 ,保持原标签不作替换。
    4. 如果 recurse 参数设置为 true,将进行标签的递归替换。

    其实一般的时候简单的方式就够用了。面对自己编写库或组件的时候,都会有如下的选择:

    1. 大而全,涵盖所有的扩展,能捕捉到所有的异常。
    2. 小而精,满足一般的需求,特殊情况可定制,异常可通过约定控制。

    选择最合适的才是最重要的,对于自己,通常偏向后者。

  • Permalink for '用python将文本转成图片'

    用python将文本转成图片

    Posted: 05 28th, 2011, 1:20am PDT by 怿飞
    #-*- coding:utf-8 -*-
    
    import Image, ImageDraw, ImageFont, uuid
    
    def text2png(text):
        # config:
        adTexts = ['---------------', 'http://www.planabc.net']
        imgBg = '#FFFFFF'
        textColor = "#000000"
        adColor = "#FF0000"
        ttf = "C:\Windows\Fonts\STXIHEI.TTF"
        fontSize = 20
        tmp = 'tmp/'
    
        # Build rich text for ads
        ads = []
        for adText in adTexts:
            ads += [(adText.decode('utf-8'), adColor)]
    
        # Format wrapped lines to rich text
        bodyTexts = [""]
        l = 0
        # x.decode() ==> unicode
        for character in text.decode('utf-8'):
            c = character
            delta = len(c)
            if c == '\n':
                bodyTexts += [""]
                l = 0
            elif l + delta > 40:
                bodyTexts += [c]
                l = delta
            else:
                bodyTexts[-1] += c
                l += delta
    
        body = [(text, textColor) for text in bodyTexts]
        body += ads
    
        # Draw picture
        img = Image.new("RGB", (330, len(body) * fontSize + 5), imgBg)
        # Ref: [blog.163.com]     draw = ImageDraw.Draw(img)
        font = ImageFont.truetype(ttf, fontSize)
        for num, (text, color) in enumerate(body):
            draw.text((2, fontSize * num), text, font=font, fill=color)
    
        # Write result to a temp file
        filename = uuid.uuid4().hex + ".png"
        file = open(tmp + filename, "wb")
        img.save(file, "PNG")
    
        return tmp + filename
    
    if __name__ == '__main__':
        text2png( "怿飞")
    

    参考自:http://simple-is-better.com/news/detail-289

  • Permalink for 'domReady Function'

    domReady Function

    Posted: 05 26th, 2011, 4:57am PDT by 怿飞

    上次写过 《模拟兼容性的 addDOMLoadEvent 事件》,昨天抽时间整理成了 domReady 函数

    使用非常方便:

    domReady(function () {
        // Dom is loaded! You can do anything!
    });

    测试案例: [blank.github.com]

    代码如下(注释较为详尽,就不再说明了):

    /**
     *  domready.js - Specify a function to execute when the DOM is fully loaded.
     *  Copyright (c) 2011 Blank Zheng (blankzheng@gmail.com)
     *  [www.planabc.net]  */
    
    (function (doc, win) {
        var isReady = 0,
            isBind = 0,
            fns = [],
            testEl = doc.createElement('p'),
            bindReady,
            init;
    
            win.domReady = function(fn){
                bindReady(fn);
    
                if (isReady) {
                    fn();
                } else {
                    fns.push(fn);
                }
            };
    
            bindReady = function (){
                if(isBind) return;
                isBind = 1;
    
                // Catch cases where domReady is called after the browser event has already occurred.
                // readyState: "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
                if(doc.readyState === "complete") {
                    init();
                } else if (doc.addEventListener) {
                    doc.addEventListener("DOMContentLoaded", function () {
                        doc.removeEventListener("DOMContentLoaded", arguments.callee, false);
                        init();
                    }, false);
                    win.addEventListener("onload", init, false);
                } else if(doc.attachEvent) {
                    // In IE, ensure firing before onload, maybe late but safe also for iframes.
                    doc.attachEvent("onreadystatechange", function() {
                        if (doc.readyState === "complete") {
                            doc.detachEvent("onreadystatechange", arguments.callee);
                            init();
                        }
                    });
                    win.attachEvent("onload", init);
    
                    // If IE and not a frame, continually check to see if the document is ready.
                    if(testEl.doScroll && win == win.top){
                        doScrollCheck();
                    }
                }
            };
    
            // Process items when the DOM is ready.
            init = function () {
                isReady = 1;
    
                // Make sure body exists, at least, in case IE gets a little overzealous.
                // This is taked directly from jQuery's implementation.
                if (!doc.body) {
                    setTimeout(init, 10);
                    return;
                }
    
                for (var i = 0, l = fns.length; i 

    如果平时做一些简单测试,可以使用下面 MINI 的 domReady 函数:

    function domReady(fn) {
        // "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
        /in/.test(document.readyState) ? window.setTimeout(function() { domReady(fn); }, 10) : fn();
    }
  • Permalink for 'URI 中的一些规范'

    URI 中的一些规范

    Posted: 05 25th, 2011, 10:13pm PDT by 怿飞

    In RFC 3986

    //URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 
    
    gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
    sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
    
    reserved = gen-delims / sub-delims
    unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
    percent-encoded = "%" HEXDIG HEXDIG
    
    pchar = unreserved / percent-encoded / sub-delims / ":" / "@"
    
    query = *( pchar / "/" / "?" ) // "?" query
    fragment = *( pchar / "/" / "?" ) //"#" fragment
  • Permalink for '如何用 JavaScript 开发迷宫游戏'

    如何用 JavaScript 开发迷宫游戏

    Posted: 03 22nd, 2011, 4:51am PDT by 怿飞

    3月6日在杭州 Google GTUG 活动上的分享《Maze Game》,本想讲 《Android 手机开发及HTML5在手机开发中的应用》,但由于 Google 邀约得比较晚,来不及准备,就在原先准备在团队内分享的未完成 PPT ——《Maze Game》的基础上,做了补充和完善。

    游戏部分思路来源于同事 季札 开发的迷宫游戏:www.sansi.org/static/maze/maze.html

  • Permalink for 'Script 元素 type 属性的妙用'

    Script 元素 type 属性的妙用

    Posted: 03 4th, 2011, 4:23am PST by 怿飞

    我们经常使用动态创建 JavaScript 的方式来实现 JavaScript 文件的无阻塞(Non-blocking)、并行下载(Parallel )。其实还可以添加自定义的 type 属性(比如 text/cache),达到预加载但不执行的效果:

    var doc = document,
        el = doc.createElement("script"),
        head = doc.getElementsByTagName('head')[0];
    
        el.src = "http://yui.yahooapis.com/2.8.2r1/build/yahoo-dom-event/yahoo-dom-event.js";
        el.type = "text/cache";
        head.insertBefore(el, head.firstChild);

    注:Firefox 3.6.x 不请求 JavaScript 文件。

    扩展阅读:

  • Permalink for '前端代码 Review 的一些思考'

    前端代码 Review 的一些思考

    Posted: 02 23rd, 2011, 2:33am PST by 怿飞

    前端代码 Review 的一些思考

    查看大图: [www.planabc.net]

    欢迎大家针对其中的问题,一起思考与交流!

    扩展阅读:

  • Permalink for 'iPhone Web App 动态 icon'

    iPhone Web App 动态 icon

    Posted: 01 1st, 2011, 7:11am PST by aoao

    不记得什么时候发现 iPhone 收藏到桌面的网页,在每次打开后会更新apple-touch-icon 的图片,也正是因为这个特性,我们可以用来动态更新icon

    看下这个例子 [aoao.ws] 这个真的是iPhone Web App哦。不是iOS的,不信用iPad试下。。XD

    测试时,发现启动图也可以变更。但是,切记 iPhone 的启动图大小只能是320*460 。为什么不是320*480呢,这是为了让web app启动时,如果是横屏,能留20px的白边玩的。iPad 的启动图是768*1004,目前的测试发现只支持竖屏,囧rz

    然后又想了半天,发现除了日历、天气外,好像没啥啥应用要动态更新icon的样子。

    做了个体验性的天气 [aoao.ws]

    iphone weather icon qrcode

    写了才发现。。canvas在iOS3.x上真不给力。。fillText可以考虑用canvastext不然你的文字不知道飞到哪。不过这个也只能解决英文的问题,中文就别折腾了,真的要做服务的话,可以考虑服务端生成对应的图片。

    如果app太久没开的话,它还会坏掉,就成了这样

    iphone weather bug icon

    T_T 自己加上系统的高光,初步估计原因可是为 iOS会在没开app的情况,自己去更新apple-touch-icon-precomposed.png ,但因为这图片实际又不存在,然后就自做主张了,加入收藏时,桌面图标是好的,但其实已经坏掉了,双击home键时可以看到,只要动态改的就会坏,准确的说用data:image/png;base64,这种方式写入的会坏,但对于桌面的什么时候会变坏还是不清楚,同时发现我的高光画的不好,为什么要自己画高光呢,因为原生带的天气也不是用系统的高光,不过绘下边的文字还是偷懒了,其实可以做得更好。

    =。= 目前不支持 Retina Display ,其实写过测试,但发现图片得大点,Native App的优势体现了。

  • Permalink for '模拟实现 Range 的 insertNode() 方法'

    模拟实现 Range 的 insertNode() 方法

    Posted: 11 26th, 2010, 1:53am PST by 怿飞

    最近对 RangeSelection 比较感兴趣。

    基本非 IE 的浏览器都支持 DOM Level2 中的 Range,而 IE 中仅有自己的简单处理方法(Text Rang)。

    扩展阅读:

    而 IE 下的 Text Rang 主要用来处理文本,并非 DOM 节点,那如何在 IE 下模拟 DOM Level2 中的 Range 呢?

    根据规范的 API,我们需要模拟下述属性和方法:

    function zRange() {
        // Inital states
        this.startContainer = document;
        this.startOffset = 0;
        this.endContainer = document;
        this.endOffset = 0;
        this.commonAncestorContainer = document;
        this.collapsed = true; 
    
        // Range constants
        this.START_TO_START = 0;
        this.START_TO_END = 1;
        this.END_TO_END = 2;
        this.END_TO_START = 3;
    }
    
    zRange.prototype = {
        // Public methods
        setStart : function(node, offset){},
        setEnd : function(node, offset){},
        setStartBefore : function(node){},
        setStartAfter : function(node){},
        setEndBefore : function(node){},
        setEndAfter : function(node){},
        collapse : function(toStart) {},
        selectNode : function(node) {},
        selectNodeContents : function(node){},
        deleteContents : function() {},
        extractContents : function(){},
        cloneContents : function() {},
        surroundContents : function () {},
        insertNode : function(node) {},
        cloneRange : function() {},
        detach : function() {},
        compareBoundaryPoints : function (how, sourceRange) {},
        constructor : zRange
    }

    我们还可以看一组使用 Range 方法和属性的统计数据,对于 2/8 原则的实践或许有帮助:

    /**
     * Resource reference : [kb.operachina.com]  *
     * collapse             51,435
     * setStartBefore       43,138
     * setStartAfter        40,270
     * selectNodeContents   37,027
     * collapsed            12,862
     * selectNode            4,636
     * deleteContents        3,935
     * setStart              3,171
     * startOffset           3,150
     * setEnd                3,086
     * detach                2,732
     * startContainer        2,659
     * endOffset             2,647
     * insertNode            2,321
     * cloneContents         2,261
     * endContainer          2,236
     * cloneRange            1,993
     * setEndAfter           1,911
     *
     */

    下面我们简单讨论一下 Range 的 insertNode() 方法的模拟实现:

    The insertNode() method inserts the specified node into the Range’s context tree. The node is inserted at the start boundary-point of the Range, without modifying it.

    If the start boundary point of the Range is in a Text node, the insertNode operation splits the Text node at the boundary point. If the node to be inserted is also a Text node, the resulting adjacent Text nodes are not normalized automatically; this operation is left to the application.

    The Node passed into this method can be a DocumentFragment. In that case, the contents of the DocumentFragment are inserted at the start boundary-point of the Range, but the DocumentFragment itself is not. Note that if the Node represents the root of a sub-tree, the entire sub-tree is inserted.

    从上面的引用得知 insertNode() 方法用来在选区的开头插入节点,固我们先获取 Range 对象的 startContainer(Range 是从那个节点中开始的,即选区中第一个节点的父节点) 和 startOffset(在 startContainer 中 Range 开始的偏移位置) 属性。

    var sc = this.startContainer,
        so = this.startOffset;

    注: 如果 startContainer 是文本节点、注释节点或者是 CData 节点,startOffset 是指 Range 开始前的字符数,否则,偏移是 Range 中的第一个节点在其父节点中的索引。

    接下来,我们需将情况分为下面两种:

    第一种情形:startContainer 节点为文本节点、注释节点或者是 CData 节点。“startOffset 是指 Range 开始前的字符数”。

    • 如果 startOffset 等于 0,则表示 Range 是从 startContainer 起始位置开始,应将 node 插入到 startContainer 节点之前 sc.parentNode.insertBefore(node, sc);
    • 如果 startOffset 大于等于 startContainer 本身的节点长度,则表示 Range 是从 startContainer 末尾位置开始,应将 node 插入到 startContainer 节点之后,即如果存在下一节点,则插入到下一之前 sc.parentNode.insertBefore(node, sc.nextSibling);,如果不存在下一节点,则加入到 startContainer父节点最后 sc.parentNode.appendChild(node);
    • 如果 startOffset 在 startContainer 本身的节点长度之内,我们通过oSplitNode = object.splitText( [iIndex])将节点在startOffset 一分为二 nn = sc.splitText(so);,分为两个节点,则应将 node 插入到新生成的第二个节点之前 sc.parentNode.insertBefore(node, nn);

      The text node that invokes the splitText method has a nodeValue equal to the substring of the value, from zero to iIndex. The new text node has a nodeValue of the substring of the original value, from the specified index to the value length. Text node integrity is not preserved when the document is saved or persisted.

    if (so===0) {
        // At the start of text
        sc.parentNode.insertBefore(node, sc);
    } else if (so >= sc.nodeValue.length) {
        // At the end of text
        if (ns) {
            sc.parentNode.insertBefore(node, sc.nextSibling);
        } else {
            sc.parentNode.appendChild(node);
        }
    } else {
        // Middle, need to split
        // [msdn.microsoft.com]     nn = sc.splitText(so);
        sc.parentNode.insertBefore(node, nn);
    }

    第二种情形:startContainer 节点为非 第一种情形中的节点。“偏移是 Range 中的第一个节点在其父节点中的索引”。获取 startContainer 中子节点偏移量为 startOffset 的节点 cn = sc.childNodes[so];,如果存在,则按照 insertNode 方法的定义,应将 node 插入到该节点之前 sc.insertBefore(node, cn);,如果不存在,即 startOffset 大于等于 sc.childNodes.length,则应将 node 插入到 startContainer 的子节点最后sc.appendChild(node);

    if (sc.childNodes.length > 0) {
        cn = sc.childNodes[so];
    }
    
    if (cn) {
        sc.insertBefore(node, cn);
    } else {
        sc.appendChild(node);
    }

    详细代码实现如下:

    zRange.prototype.insertNode = function(node) {
        var sc = this.startContainer,
            so = this.startOffset,
            p = sc.parentNode,
            ns = sc.nextSibling,
            nn, cn;
    
        // 如果节点是 TEXT_NODE 或者 CDATA_SECTION_NODE
        if ((sc.nodeType === 3 || sc.nodeType === 4 || sc.nodeType === 8 ) && sc.nodeValue) {
            if (so === 0) {
                // At the start of text
                p.insertBefore(node, sc);
            } else if (so >= sc.nodeValue.length) {
                // At the end of text
                if (ns) {
                    p.insertBefore(node, ns);
                } else {
                    p.appendChild(node);
                }
            } else {
                // Middle, need to split
                // [msdn.microsoft.com]             nn = sc.splitText(so);
                p.insertBefore(node, nn);
            }
        } else {
            if (sc.childNodes.length > 0) {
                cn = sc.childNodes[so];
            }
    
            if (cn) {
                sc.insertBefore(node, cn);
            } else {
                sc.appendChild(node);
            }
        }
    }

    剩下的方法,大家可以尝试着去模拟一把,其实并不复杂,也许会其乐无穷,呵呵

  • Permalink for 'DOM 元素如何获得焦点'

    DOM 元素如何获得焦点

    Posted: 11 17th, 2010, 11:30pm PST by 怿飞

    首先让我们看看哪些元素可以直接获得焦点(element..focus()):

    // Form : [www.w3.org]

    • a elements that have an href attribute
    • link elements that have an href attribute
    • button elements that are not disabled
    • input elements whose type attribute are not in the Hidden state and that are not disabled
    • select elements that are not disabled
    • textarea elements that are not disabled
    • command elements that do not have a disabled attribute
    • Elements with a draggable attribute set, if that would enable the user agent to allow the user to begin a drag operations for those elements without the use of a pointing device
    • Each shape that is generated for an area element

    而除上面以外的元素(比如:div,p)一般都无法直接获得焦点,那如何处理呢?

    1. 给元素添加 contenteditable 属性。

      // From : [www.w3.org]

      User agents must make editing hosts focusable (which typically means they enter the tab order).

    2. 给元素添加 tabindex 属性。

      // From : [www.w3.org]

      The tabindex content attribute specifies whether the element is focusable, whether it can be reached using sequential focus navigation, and the relative order of the element for the purposes of sequential focus navigation.

      非常推荐使用 tabindex = -1 ,基本无副作用!!

      // From : [www.w3.org]

      If the value is a negative integer: The user agent must allow the element to be focused, but should not allow the element to be reached using sequential focus navigation.

    综述,使无法直接获得焦点的元素获得焦点的最佳实践就是:给元素添加 tabindex = -1 属性。

    当然你还可以通过子元素(focusable)的 focus 事件的捕获或冒泡来模拟,但需要处理事件的浏览器兼容( Permalink for '获取 Textarea 的光标位置'

    获取 Textarea 的光标位置

    Posted: 11 17th, 2010, 3:47am PST by 怿飞

    在任何编辑器中,获取光标位置都是非常重要的,很多人可能认为较难,其实只要处理好浏览器的兼容,还是比较容易实现的。

    下面我们一起来看看如何获取到 Textarea 元素中的光标位置(测试地址)。

    首先,我们用 rangeData 对象作为数据存储,并获得焦点:

    var rangeData = {start: 0, end: 0, text: "" };
    textarea.focus();

    对于非 IE 浏览器获取选区的起始和末尾位置其实非常容易:

    rangeData.start= el.selectionStart;
    rangeData.end = el.selectionEnd;

    通过截取我们可以得到光标的选区内容:

    rangeData.text = (rangeData.start != rangeData.end) ? el.value.substring(rangeData.start, rangeData.end): "";

    而对于 IE 浏览器处理起来就比较麻烦了,但我们依旧可以获取到选区:

    oS = document.selection.createRange();

    同时还可获取 Textarea 元素的选区:

    // 为了使 oR 与 oS 在同一等级上比较,请勿使用:oR = textarea.createTextRange()
    oR = document.body.createTextRange();
    oR.moveToElementText(textarea);

    如果光标在 Textarea 元素内,很自然 oS.text 就是我们需要的选区内容:

    rangeData.text = oS.text;

    并且我们可以通过 oS.getBookmark() 方法获取到选区的位置数据,该位置数据可以通过 moveToBookmark() 方法设置回去。

    getBookmark: Retrieves a bookmark (opaque string) that can be used with moveToBookmark to return to the same range.

    moveToBookmark: Moves to a bookmark.

    我们用 rangeData.bookmark 来记录该位置数据:

    rangeData.bookmark = oS.getBookmark();

    下面是最重要的步骤:我们比较 oR 与 oS 的选区起始位置(使用 object.compareEndPoints(sType, oRange) 方法比较),如果 oR 的起始位置在 oS 之前,我们向前移动 oS 的起始位置1个字符(使用 object.moveStart(sUnit [, iCount]) 方法移动),一直当 oS 的起始位置在 oR 之前停止,移动的位置,则是选区的起始位置。

    compareEndPoints: Compares an end point of a TextRange object with an end point of another range.

    moveStart: Changes the start position of the range.

    for (i = 0; oR.compareEndPoints('StartToStart', oS) 

    但由于在 IE 中,Textarea 元素中的所有换行符都占 1 个字符,可以通过 alert(textarea.value.length) 查看,故要对上面的代码做部分处理:

    for (i = 0; oR.compareEndPoints('StartToStart', oS) 

    既然得到了选区的起始位置和选区字符串的字符,很自然我们可以计算得到选区的末尾位置:

    rangeData.end = rangeData.text.length + rangeData.start;

    获取 Textarea 的光标位置的 getCursorPosition 函数方法整理如下:

    /**
    * getCursorPosition Method
    *
    * Created by Blank Zheng on 2010/11/12.
    * Copyright (c) 2010 PlanABC.net. All rights reserved.
    *
    * The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
    */
    function getCursorPosition(textarea) {
        var rangeData = {text: "", start: 0, end: 0 };
    	textarea.focus();
        if (textarea.setSelectionRange) { // W3C
            rangeData.start= textarea.selectionStart;
            rangeData.end = textarea.selectionEnd;
            rangeData.text = (rangeData.start != rangeData.end) ? textarea.value.substring(rangeData.start, rangeData.end): "";
        } else if (document.selection) { // IE
            var i,
                oS = document.selection.createRange(),
                // Don't: oR = textarea.createTextRange()
                oR = document.body.createTextRange();
            oR.moveToElementText(textarea);
    
            rangeData.text = oS.text;
            rangeData.bookmark = oS.getBookmark();
    
            // object.moveStart(sUnit [, iCount])
            // Return Value: Integer that returns the number of units moved.
            for (i = 0; oR.compareEndPoints('StartToStart', oS) 

    得到 Textarea 元素光标位置,当Textarea 中的光标丢失了,再设置回来就简单多了:

    /**
    * setCursorPosition Method
    *
    * Created by Blank Zheng on 2010/11/12.
    * Copyright (c) 2010 PlanABC.net. All rights reserved.
    *
    * The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
    */
    function setCursorPosition(textarea, rangeData) {
        if(!rangeData) {
            alert("You must get cursor position first.")
        }
        if (textarea.setSelectionRange) { // W3C
            textarea.focus();
            textarea.setSelectionRange(rangeData.start, rangeData.end);
        } else if (textarea.createTextRange) { // IE
            var oR = textarea.createTextRange();
            // Fixbug :
            // In IE, if cursor position at the end of textarea, the setCursorPosition function don't work
            if(textarea.value.length === rangeData.start) {
                oR.collapse(false)
                oR.select();
            } else {
                oR.moveToBookmark(rangeData.bookmark);
                oR.select();
            }
        }
    }

    测试地址: [www.planabc.net]

    扩展阅读:

  • Permalink for '用 Python 架设一个简单的服务器'

    用 Python 架设一个简单的服务器

    Posted: 11 4th, 2010, 10:35pm PDT by 怿飞
    import string,cgi,time
    from os import curdir, sep
    from Bas [HTTPServer] import Bas [HTTPRequestHandler,] HTTPServer
    
    class MyRequestHandler(Bas [HTTPRequestHandler):]     def do_GET(self):
            try:
                if self.path.endswith('.html'):
                    f = open(curdir + sep + self.path)
                    self.send_response(200)
                    self.send_header('Content-type','text/html')
                    self.end_headers()
                    self.wfile.write(f.read())
                    f.close()
                    return
    
                elif self.path.endswith('.py'):
                    self.send_response(200)
                    self.send_header('Content-type','text/html')
                    self.end_headers()
                    self.wfile.write("hey, today is the" + str(time.localtime()[7]))
                    self.wfile.write(" day in the year " + str(time.localtime()[0]))
                    return
    
                else:
                    self.send_error(404, 'File Not Found %s' % self.path)  
    
            except IOError:
                self.send_error(404,'File Not Found: %s' % self.path)
    
        def do_POST(self):
            global rootnode
            try:
                ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) #('multipart/form-data', {'boundary': '303161840321948'})
                if ctype == 'multipart/form-data':
                    query = cgi.parse_multipart(self.rfile, pdict)
                self.send_response(301)
                self.end_headers()
    
                upfilecontent = query.get('upfile')
                print "filecontent", upfilecontent[0]
                self.wfile.write('POST OK!')
                self.wfile.write(upfilecontent[0])
    
            except :
                pass
    
    def main():
        try:
            server = [HTTPServer(('',] 80), MyRequestHandler)
            print 'started [httpserver...']         server.serve_forever()
    
        except KeyboardInterrupt:
            print '^C received, shutting down server'
            server.socket.close()
    
    if __name__ == '__main__':
        main()

    扩展阅读:

  • Permalink for '类型转换的小乐趣'

    类型转换的小乐趣

    Posted: 10 31st, 2010, 9:50pm PDT by 怿飞

    玉伯 的文章 《一道大题目,嘿嘿》 中有这样一段代码:

    [] == ![]

    也许很多同学迷惑:咦,这个如何转换呢?

    首先,我们了解下逻辑 NOT(!)运算与等号(==)运算的一些基本规则:

    1. 逻辑 NOT 运算中,如果运算数是对象,返回 fasle;
    2. 等号运算中,如果一个运算数是 Boolean 值,在运算前,会将其转换成数字:false -> 0,true -> 1;
    3. 等号运算中,如果一个运算数是对象,另一个是数字,在运算前,会将对象转换成数字。

    2010年11月2日补充:对象不能直接转换成数字型,可以将其转换成字符型,再将字符型转换成数字型。

    根据上面的一些规则,我们再来看看原来的表达式如何演变:

    Step01: [] == ![] // 由于[]是对象,则 ![] 返回fasle,演变为 Step02。
    Step02: [] == fasle // 由于 fasle 是 Boolean 值,则转换成数字 0,演变为 Step03。
    Step03: [] == 0 // 由于 0是数字,[]是对象,[] 转换成数字 Number([]) -> 0,[] 转换成字符型 [].toString() -> "","" 再转换成数字型 -> 0 ,演变为 Step04。
    Step04: 0 == 0 // 返回最终结果:true

    思考题:

    {} == ! {}; //返回什么值?
    [] == [] //返回什么值?
    

    PS:如果你对玉伯的题目很感兴趣,推荐阅读:

  • Permalink for 'Google DevFest 2010 Beijing 之行'

    Google DevFest 2010 Beijing 之行

    Posted: 09 6th, 2010, 11:05pm PDT by 怿飞

    非常荣幸作为嘉宾参加了 Google DevFest 2010 Beijing,感谢 Google 胡坤的邀请,感谢 Google 寒蕊的合作,简单的接触,能够感受到 Google 的开放与细腻,或许这就是 Google 特有的品质。

    Google DevFest 2010 Beijing 嘉宾卡

    下午《如何用 HTML5 做应用开发》的话题,Google 寒蕊 主要分享了 HTML5 的 What ,而我主要针对 HTML5 的 Where、Why 和 How 做了简单的演示。

    PPT:HTML5 in Action

    分享《如何用 HTML5 做应用开发》

    《如何用 HTML5 做应用开发》Q&A 环节

    《HTML5 in Action》PPT 地址 : [www.planabc.net] ,PPT 基于 HTML5 开发,为了最佳效果,建议使用 Chrome 浏览器,小键盘左右键前后翻页。由于原来在本地演示,跨域演示的环境已改变,可以自己模拟。

    寒蕊的《HTML5下一代的 Web 开发标准》PPT地址: [look-into-html5.appspot.com]

    会议结束后所有的同事一起合了张影:

    Taobao 同事合影

    另外的收获:

    1. 见到了认识了几年但一直未曾谋面的 Opera Zibin,从他那里也获知了他正在向 W3C 申请 HTML5中文兴趣小组,期待好消息。
    2. 从这次的举办中学到了许多不错的会议细节,相信下次 D2 前端技术论坛 的举办,我们将会做得更好。
    3. 见到了熟悉的老朋友 Tencent 梁璟彪,约定明年 D2 前端技术论坛 将与 WebReBuild 联手合作,明年注定属于前端年。
  • Permalink for '浅谈 Mousewheel 事件'

    浅谈 Mousewheel 事件

    Posted: 08 12th, 2010, 4:48am PDT by 怿飞

    当需要制作转动鼠标滚轮放大页面字体这样的交互效果时,会用到 Mousewheel 事件。其实在大多数浏览器(IE6, IE7, IE8, Opera 10+, Safari 5+)中,都提供了 “mousewheel” 事件。但杯具的是 Firefox 3.5+ 却不支持此事件,不过庆幸 Firefox 3.5+ 中提供了另外一个等同的事件:”DOMMouseScroll” (事件和事件属性的测试案例)。

    OK,我们现在已经知道了不同浏览器之间实现的差别,兼容代码如下:

    var addEvent = (function(){
            if (window.addEventListener) {
                return function(el, sType, fn, capture) {
                    el.addEventListener(sType, fn, (capture));
                };
            } else if (window.attachEvent) {
                return function(el, sType, fn, capture) {
                    el.attachEvent("on" + sType, fn);
                };
            } else {
                return function(){};
            }
        })(),
        // isFirefox 是伪代码,大家可以自行实现
        mousewheel = isFirefox ? "DOMMouseScroll" : "mousewheel";
    
    // object 也是伪代码,你需要注册 Mousewheel 事件的元素
    addEvent(object, mousewheel, function(event){
        event = window.event || event;
        // todo something
    }, false);

    我们再回到要实现的交互效果上,现在还有其他一些问题需要来解决:

    1. 页面字体到底是放大还是缩小呢? ==> 鼠标滚轮是向上滚动还是向下滚动呢?
    2. 页面字体缩放的倍数到底是多少呢? ==> 鼠标滚轮滚动的幅度大小是多少呢?

    还好,我们可以通过 event 的某些属性值得到这些信息:

    1. “mousewheel” 事件中的 “event.wheelDelta” 属性值:返回的值,如果是正值说明滚轮是向上滚动,如果是负值说明滚轮是向下滚动;返回的值,均为 120 的倍数,即:幅度大小 = 返回的值 / 120。
    2. “DOMMouseScroll” 事件中的 “event.detail” 属性值:返回的值,如果是负值说明滚轮是向上滚动(与 “event.wheelDelta” 正好相反),如果是正值说明滚轮是向下滚动;返回的值,均为 3 的倍数,即:幅度大小 = 返回的值 / 3。
    3. “mousewheel” 事件在 Opera 10+ 中却是个特例,既有 “event.wheelDelta” 属性,也有 “event.detail” 属性。

    注:上面第三点,在《The onmousewheel event of JavaScript》一文中有这样一段批注:

    In Opera, “detail” returns the same value as it does in FF, so for the big O you should rely on “detail” instead of “wheelDelta”, which depending on the Opera version may return a different value than in IE’s.

    但经测试, Opera 9+ 和 Opera 10+ 中的 event.wheelDelta 属性与其他浏览器中的表现完全一致,未发现异常与错误,从接口角度来说,代码中应优先使用 “event.wheelDelta” 属性。

    此时代码如下:

    var addEvent = (function(){
            if (window.addEventListener) {
                return function(el, sType, fn, capture) {
                    el.addEventListener(sType, fn, (capture));
                };
            } else if (window.attachEvent) {
                return function(el, sType, fn, capture) {
                    el.attachEvent("on" + sType, fn);
                };
            } else {
                return function(){};
            }
        })(),
        stopEvent: function(event) {
            if (event.stopPropagation) {
                event.stopPropagation();
            } else {
                event.cancelBubble = true;
            }
    
            if (event.preventDefault) {
                event.preventDefault();
            } else {
                event.returnValue = false;
            }
        },
        zoomIn = function(){},
        zoomOut = function(){},
        // isFirefox 是伪代码,大家可以自行实现
        mousewheel = isFirefox ? "DOMMouseScroll" : "mousewheel";
    
    // object 是伪代码,你需要注册 Mousewheel 事件的元素
    addEvent(object, mousewheel, function(event){
        var delta = 0;
        event = window.event || event;
        stopEvent(event);
    
        delta = event.wheelDelta ? (event.wheelDelta / 120) : (- event.detail / 3);
        // zoomIn, zoomOut 是伪代码,需要实现的缩放事件
        delta > 0 ? zoomIn(delta): zoomOut(Math.abs(delta));
    } , false);

    事件和事件属性的测试案例:http://www.planabc.net/demo/event/mousewheel.html

    扩展阅读:

  • Permalink for '在线 Base64 编/解码小工具'

    在线 Base64 编/解码小工具

    Posted: 07 22nd, 2010, 2:03am PDT by 怿飞

    抽时间做了一个简单的基于 JavaScript 的在线 Base64 编/解码小工具:

    http://www.planabc.net/lab/tools/base64.html

    原理详见上篇文章:07 21, 2010

  • Permalink for 'JavaScript 中的 Base64 编码(一):Encoding'

    JavaScript 中的 Base64 编码(一):Encoding

    Posted: 07 21st, 2010, 8:05am PDT by 怿飞

    除了IE浏览器,其他所有主流的浏览器均支持原生的 Base64 编码:

    • btoa(text) – base64 encodes text.
    • atob(text) – base64 decodes text.

    而对于 IE 我们可以根据已知的 Base64 编码原理进行编写:

    Base64 编码将每三个 8Bit 的字节(注:由于要求输入的字符为 8Bit 字节,故范围应该在 ASCII 字符范围内,即:\u0000-\u00ff)转换为四个 6Bit 的字节(3*8 = 4*6 = 24),然后在每个 6Bit 字节前添两位高位 0,组成四个 8Bit 的字节,最后再将每个 8Bit 字节转换成十进制的数字,对应 Base64 编码表(为了保证所输出的编码为可读字符,Base64制定了一个编码表,以便进行统一转换,编码表的大小为 2^6=64,即 Base64 名称的由来)输出编码后的字符。

    如果原字节不足 3 的倍数,则用 0 填充,输出字符使用“=”,因此编码后输出的文本末尾可能会出现 1 或 2 个“=”(余数 = 原文字节数 MOD 3 ,如果余数为 1,则要补 2 个“=”,为 2,则补 1 个“=”)。

    /* base64table class for skinning normal tables */ table.base64table { margin: 1em 1em 1em 30px; background: #f9f9f9; border: 1px #aaa solid; } .base64table th, .base64table td { border: 1px #aaa solid; padding: 0.2em; } .base64table th { background: #f2f2f2; text-align: center; } .base64table caption { font-weight: bold; }
    Base64 编码表
    Value Char   Value Char   Value Char   Value Char
    0 A 16 Q 32 g 48 w
    1 B 17 R 33 h 49 x
    2 C 18 S 34 i 50 y
    3 D 19 T 35 j 51 z
    4 E 20 U 36 k 52 0
    5 F 21 V 37 l 53 1
    6 G 22 W 38 m 54 2
    7 H 23 X 39 n 55 3
    8 I 24 Y 40 o 56 4
    9 J 25 Z 41 p 57 5
    10 K 26 a 42 q 58 6
    11 L 27 b 43 r 59 7
    12 M 28 c 44 s 60 8
    13 N 29 d 45 t 61 9
    14 O 30 e 46 u 62 +
    15 P 31 f 47 v 63 /

    比如:

    字符:               f        2         e
    ASCII:              102      50        101
    3个8Bit字节:        01100110 00110010  01100101
    4个6Bit字节:          011001   100011    001001    100101
    高位补0:            00011001 00100011  00001001  00100101
    十进制:             25       35        9         37
    对应码表值:         Z        j         J         l
    
    最终: btoa('f2e') = ZjJl
    字符:              b        a        s        e
    ASCII:             98       97       115      101
    3个8Bit字节:       01100010 01100001 01110011 01100101 00000000 00000000
    4个6Bit字节:         011000   100110   000101   110011   011001   010000    000000   000000
    高位补0:           00011000 00100110 00000101 00110011 00011001 00010000 00000000 00000000
    十进制:            24       38       5        51       25       16
    对应码表值:        Y        m        F        z        Z        Q        =        =
    
    最终: btoa('base') = YmFzZQ==

    如果将上面的 Base64 编码原理换成接近于编程的思维,过程大致如下(以f2e为例):

    注:Base64 编码表我们可以简化为字符串,并通过其进行位置索引:

    table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

    第一步:

    1. 第一个字符 f(102 -> 01100110)通过右移 2 位(first >> 2)获得第一个目标字符(00011001 -> 25),索引编码表中对应的目标字符 Z(table.charAt(first >> 2))。

    第二步:

    1. 将第一个字符 f(102 -> 01100110)与 3(00000011)进行 AND 运算(first & 3)):
          01100110
      AND 00000011
      ------------
          00000010
    2. 第一个字符运算后(00000010)再左移 4 位((first & 3)
    3. 将第二个字符 2 (50 -> 00110010)右移 4 位(second >> 4),得:00000011。
    4. 最后将运算后的两个字符进行 OR 运算((first & 3) > 4):
          00100000
      OR  00000011
      ------------
          00100011

      获得第二个目标字符(00100011 -> 35),索引编码表中对应的目标字符 j(table.charAt((first & 3) > 4))。

    第三步:

    1. 将第二个字符 2 (50 -> 00110010)与 15(00001111,十六进制:0x0f)进行 AND 运算(second & 0x0f):
          00110010
      AND 00001111
      ------------
          00000010
      
    2. 第二个字符运算后(00000010)再左移 2 位((second & 0x0f)
    3. 第三个字符 e(101 -> 01100101)右移 6 位(third >> 6),得:00000001。
    4. 最后将运算后的两个字符进行 OR 运算((second & 0x0f) > 6):
          00001000
      OR  00000001
      ------------
          00001001

      即获得第三个目标字符(00001001 -> 9),索引编码表中对应的目标字符 J(table.charAt((second & 0x0f) > 6))。

    第四步:

    1. 取第三个字符 e(101 -> 01100101)的右 6 位,即与 63 (00111111,16进制:0x3f)进行 AND 运算(third & 0x3f):
          01100101
      AND 00111111
      ------------
          00100101

      获得第四个目标字符(00100101 -> 37),索引编码表中对应的目标字符 l(table.charAt(third & 0x3f))。

    异常情况:

    1. 当第二个字符不存在时(即:余数 = 原文字节数 MOD 3 ,余数为 1),截止至第二步的第 2 小步,然后在最终输出的目标字符后添加两个“=”。
    2. 当第三个字符不存在时(即:余数 = 原文字节数 MOD 3 ,余数为 2),截止至第三步的第 2 小步,然后在最终输出的目标字符后添加两个“=”。

    代码实现如下:

    if(!window.btoa) {
        window.btoa  = function(text) {
            if (/([^\u0000-\u00ff])/.test(text)) return;
            var table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
                i = 0,
                cur, prev, byteNum,
                result=[];    
    
            while(i > 2));
                        break;
    
                    case 2: //second byte
                        result.push(table.charAt((prev & 3) > 4)));
                        break;
    
                    case 0: //third byte
                        result.push(table.charAt((prev & 0x0f) > 6)));
                        result.push(table.charAt(cur & 0x3f));
                        break;
                }
    
                prev = cur;
                i++;
            }
    
            if (byteNum == 1){
                result.push(table.charAt((prev & 3)  

    -----------------------------------------------------------------------------

    下篇:《JavaScript 中的 Base64 编码(二):Decoding》

    -----------------------------------------------------------------------------

    扩展阅读:

  • Permalink for '淘宝海量图片存储与CDN系统'

    淘宝海量图片存储与CDN系统

    Posted: 06 29th, 2010, 10:44pm PDT by 怿飞
    ...

  • Permalink for '用 JavaScript 解数学题'

    用 JavaScript 解数学题

    Posted: 05 26th, 2010, 3:42am PDT by 怿飞

    题目:一个六位数,分别用2,3,4,5,6乘它,得到的五个新数仍是由原数中的六个数字组成,只是位置不同,则此六位数是多少?

    function evaluate() {
        var multiplier = [2,3,4,5,6],
            sortNumber = function (number) {
                return parseInt((number + '').split('').sort().join(''), 10);
            },
            gameOver = false, // 判断是否已经找到,如果找到,结束循环
            determine = function(origin) {
                var result, i, len = multiplier.length;
                for( i = 0; i  999999 || sortNumber(result) !== sortNumber(origin)) {
                        return;
                    }
                    if(i === (len-1)){
                        gameOver = true;
                        alert('此六位数是 ' + origin);
                    }
                }
            };
    
        // Math.floor(1000000/6) 缩小范围
        for(var j = Math.floor(1000000/6); j >= 100000  && !gameOver; j--) {
            determine(j);
        }
    }

    扩展阅读:

  • Permalink for '各浏览器的并行连接数(同域名)'

    各浏览器的并行连接数(同域名)

    Posted: 05 6th, 2010, 7:21pm PDT by 怿飞
    Browser HTTP/1.1 HTTP/1.0
    IE 6,7 2 4
    IE 8 6 6
    Firefox 3+ 6 6
    Safari 3+ 4 4
    Chrome 3+ 4 4
    Opera 10+ 4 4

    扩展阅读:《Roundup on Parallel Connections》

  • Permalink for '与 Function 和  Object 相关的有趣代码'

    与 Function 和 Object 相关的有趣代码

    Posted: 05 5th, 2010, 8:33pm PDT by 怿飞

    代码片段一:

    alert(Function instanceof Object); // true
    alert(Object instanceof Function); // true

    Function 是 Object 的实例,Object 也是 Function 的实例,好“纠缠”的关系。

    代码片段一:

    alert(Object.forEach); // undefined
    
    Function.prototype.forEach = function(object, block, context) {
        for (var key in object) {
            if (typeof this.prototype[key] == "undefined") {
                block.call(context, object[key], key, object);
            }
        }
    };
    
    alert(Object.forEach);
    alert(Function.forEach);
    alert(Object.forEach === Function.forEach); // true

    给 Function 设置的原型方法 forEach,“有趣”的是 Object 也能够获取。

    至于为什么可以从 JavaScript Object layout 图中获取部分解答:

    JavaScript Object layout

    看懂上图了没?如果看懂了,留道思考题给大家:

    function Foo() {};
    var foo = new Foo();
    
    alert(foo instanceof Foo); // ?
    alert(foo instanceof Object); // ?
    alert(foo instanceof Function); // ?
    alert(Foo instanceof Function); // ?
    alert(Foo instanceof Object); // ?

  • Permalink for '学习 YUI3 中的沙箱机制'

    学习 YUI3 中的沙箱机制

    Posted: 04 8th, 2010, 5:40am PDT by 怿飞

    简化了一下 YUI3 中的沙箱实现 方式:

    if (typeof Sandbox === 'undefined' || !Sandbox) {
        Sandbox = function(o) {
            var self = this;
    
            if (!(self instanceof Sandbox)) { // 允许没有 new 操作符的实例化
                return new Sandbox(o);
            } else {
                self._init();
                self._config(o);
    
                // 预加载某些指定模块
                /* self._setup(); */
    
                // 返回实例本身,支持链式模式
                return self;
            }
        };
    }
    
    // Sandbox的(类)静态属性
    Sandbox.Env = {
        /* sidx: 0 , */
        mods: {}
    };
    
    (function() {
        var p, i,
            SLICE         = Array.prototype.slice,
            /* instances     = {}, */
            time          = new Date().getTime(),
            win           = window,
            doc           = document;
    
        Sandbox.prototype = {
    
            // 格式化配置参数
            // NOTE:本着简单适用的原则,取消了原先 YUI 中对 loader 的支持
            _config: function(o) {
    
                var c = this.config, i, j, m, mods;
    
                o = o || {};
    
                // mods = c.modules;
    
                for (i in o) {
                    if (i == 'win') {
                        c[i]  = o[i].contentWindow || o[i];
                        c.doc = c[i].document;
                    } else {
                        c[i]  = o[i];
                    }
                }
            },
    
            /**
             * 初始化沙箱实例
             * @private
             */
            _init: function() {
    
                var self  = this,
                    G_Env = Sandbox.Env,
                    Env   = self.Env;
    
                if(!Env) {
                    self.Env = {
                        mods: {},
                        _used: {},
                        _attached: {},
                        _loaded: {}
                    };
    
                    Env = self.Env;
    
                    /* if (G_Env && self !== Sandbox ) {
                        Env._sidx  = ++ G_Env.sidx;
                        Env._guid = ('sandbox_' + Env._sidx + '_' + time).replace(/\./g, '_');
                    }
    
                    self.id = Env._guid;
                    instances[self.id] = self; */
                }
    
                self.constructor = Sandbox;
    
                self.config = {
                    win: win || {},
                    doc: doc || {}
                };
    
            },
    
            // 预留预加载某些指定模块,接口可根据实际需要扩展
            /* _setup: function(o) {}, */
    
            /**
             * 添加模块
             * @method add
             * @param name {string} 模块名
             * @param fn {Function} 模块对应的函数
             * @param version {string}
             * @param details 可选配置:
             *     requires   -  {array}  在本模块执行之前附加的必须的模块数组
             *     use  - {array} 在本模块执行之后附加的模块数组
             *
             */
            add: function(name, fn, details) {
                Sandbox.Env.mods[name] = {
                    name: name,
                    fn: fn,
                    details: details || {}
                };
    
                return this; // chain support
            },
    
            /**
             * 执行与 Sandbox 实例相关联的模块:details.requires--》fn--》details.use
             * @method _attach
             * @param r {array} 模块列表数组
             * @private
             */
    
            _attach: function(r) {
    
                var mods = Sandbox.Env.mods,
                    self = this,
                    attached = self.Env._attached,
                    i, l = r.length, name, m, fn, d, req, use;
    
                for (i = 0; i 

    测试页面:04 05, 2010

  • Permalink for 'JavaScript组件打包模式'

    JavaScript组件打包模式

    Posted: 04 5th, 2010, 1:37am PDT by aoao

    js组件通常带着css image ,但这样使用起来可能会有些小麻烦,为了让组件足够的solo,有了把css image也打包在js的想法,然后顺便把请求数变少,这个顺便好像更重要,呵呵。

    那怎样打包呢,虽然有resource-packages这样的方案,可是我们的核心用户还在玩ie6这种时尚的浏览器呢!

    我的方案:
    • CSS:CSS可以当成字符串存在js里,并由js动态加到页面上,页面用的可能不适合,但组件通常不会影响。
    • image:CSS里用的图片用dataURI(RFC 2397)跟MHTML(RFC 2557)的方式编到js里面。(秦歌写的dataURI和MHTML依然推荐给不知道是什么东东的同学看)。CSS里如果要绝对路径同样可以存js,js如果有直接用到的理论上也是可以,但js通常只改className会比较好。
    • flash:一些比较小的flash,比如存储,复制等也可以选择打包,不过现在米解决非IE的问题,非IE使用外链吧

    这个有个打包测试的例子

    一些细节和纠结的地方
    1. 所有图片都打包到js里不一定合理,打包进去的应该是必用的图片。
      那排除的图片是再打一个包好呢还是直接用图片?
    2. 图片可以先压后编,我选用的图片压缩工具是pngout,而且一般是用-s5
      有人做了些测试Uncompressed data in base64? Probably not,大家自行判断。
    3. 重复的图片引用直接用dataURI会搞得很大很大,gzip又笨得跟猪一样不会压掉。
      我是选用存成js变量,淘宝的同学是用提class的方式
    4. MHTML在ie7+/vista缺少结束分割符无法显示,win03sp2缺少Content-Type会有安全提示,原因都MIME不标准,不是所有的东西都可以省。
    5. 是选择把所有的东西都打包在一个文件还是按MHTML跟dataURI分类型打包成两份在server按ua派文件或由类库智能去读取,好像后者比较和谐。
    6. swf用dataURI编入有问题,据说fp8没问题,现在都fp10了。
      另一种solo的方案就是把js打包到swf,不过感觉不和谐
    multipart/related例子

    不算标准但能跑,换行也是很重要的

    Content-Type:multipart/related;boundary="_BAIDU_YOUA_BB_YEP"
    
    --_BAIDU_YOUA_BB_YEP
    Content-Type:image/png
    Content-Location:logo.png
    Content-Transfer-Encoding:base64
    
    ...base64...
    
    --_BAIDU_YOUA_BB_YEP
    Content-Type:image/png
    Content-Location:nono.png
    Content-Transfer-Encoding:base64
    
    ...base64...
    
    --_BAIDU_YOUA_BB_YEP--

    btw:上篇文章的评论里发现了这个打包的站,输出界面很帅

  • Permalink for '前端 JavaScript 相关的小Tips'

    前端 JavaScript 相关的小Tips

    Posted: 04 1st, 2010, 12:27am PDT by 怿飞

    受 SNS 后台开发同学的邀请,做的一次小分享,希望够简单、明了、有效。其实在给别人分享的同时,自己也能收获很多!

  • Permalink for '如何获取当前 select 元素的值'

    如何获取当前 select 元素的值

    Posted: 03 26th, 2010, 11:05pm PDT by 怿飞
    1. 如果 select 元素下的所有 option 元素均没有指定 selected 属性,会默认选中第一个。
    2. 可以通过 select.selectedIndex 获取到选中的 option 元素的索引。
    3. 可以通过 select.options[select.selectedIndex] 获取到选中的 option 元素。
    4. option 元素 <option selected="selected" value="value3">text3</option>,可以通过 option.value 获得 option 元素的 value 属性值,即 value3;可以通过 option.text 获得 option 元素内的文本,即 text3。
    5. 如果 option 元素没有定义 value 属性,则 IE 中 option.value 无法获得,但 Safari、Opera、FireFox 依旧可以通过 option.value 获得,值同于 option.text 。
    6. 可以通过 option.attributes.value && option.attributes.value.specified 来判断 option 元素是否定义了 value 属性。

    故,获得当前 select 元素值的脚本如下:

    var getSelectValue = function(select) {
        var idx = select.selectedIndex,
            option,
            value;
        if (idx > -1) {
            option = select.options[idx];
            value = option.attributes.value;
            return (value && value.specified) ? option.value : option.text;
        }
        return null;
    }

  • Permalink for '人民币的符号的正确表示法?一杠?两杠?¥还是¥呢?'

    人民币的符号的正确表示法?一杠?两杠?¥还是¥呢?

    Posted: 03 20th, 2010, 4:30am PDT by aoao

    因为做的项目会跟钱打了交道,所以被研究了。

    那是一杠还是两杠呢?最简单的方式就是拿出钱包,掏出张“新”的50或100,翻到背面的银线上,就会发现两杠的符号。这个是以前跟同事在讨论是一杠还是两杠时,发现最有效的方案。

    杠杠数的问题解决了,可¥(指的是通常输入法中文全角模式下按shift+4的那个)在宋体(v3.03, v5.0)的情况下是显示一杠 囧rz。不过还好的是,常见字体:微软雅黑(Microsoft YaHei),华文细黑(STXihei),細明體(MingLiu)能显示两杠,理论上以下写法可以解决大部分问题。

    font-family: 'Microsoft YaHei',STXihei,MingLiu 

    虽然部分用unicode表示的中文字体名可能会更好(via 中文字体在 CSS 中的写法),例如这样:

    font-family: '\5FAE\8F6F\96C5\9ED1','\534E\6587\7EC6\9ED1',MingLiu 

    由于MingLiu也不是“盖”的,而且在12px上还算挺好看滴,看人个喜好,反正我不喜欢“乱码”。

    当然,如果你有更好的方案也可以介绍给我,还有一些想到的方案:

    • 编个字体,里面只有一个¥,用font-face的方式来使用,不过我也就想想,有兴趣的同学可以玩下。
    • 在研究过程中还发现了 ¥ (在HTML中,“¥”的命名实体是“&yen;”,字符代码是“¥”和“¥”。mac os 按alt+y可输入 via ),虽然从命名可以知道是yen而不是yuan,好像现在变能通用了,又不过很多情况别人会把¥当成JPY 而不是CNY。

    在找资料时还发现这个:有关于该符号的另一种诙谐讲法,说女人一旦嫁“到”了丈“夫”了就有钱了,所以“倒夫”字就是货币的符号了(via

  • Permalink for 'MHTML在ie7/vista bug 解决方案'

    MHTML在ie7/vista bug 解决方案

    Posted: 01 30th, 2010, 5:55am PST by aoao

    在多数情况下,以增加文件大小代价来换连接数是有价值。而MHTML+dataURI = 省连接数,如果你不知道这是什么东东,可以看秦歌写的Data URI 和 MHTML

    因为之前发现在vista上的ie7有bug,且没有好解决方案,所以一直不敢项目上使用,之前虽然有看到有个用304的方案(via Data URIs, MHTML and IE7/Win7/Vista blues),但觉一来做起来折腾,一来效果也不好,特别是hover时。

    ie7 vista的bug大约这样,相应文件一旦local cache 后,浏览器就读不到。

    今天,我要骄傲的跟大家,有两种可行的解决方案

    方案1

    Content-Type:multipart/related;boundary="_aoao_org_cn"
    --_aoao_org_cn
    Content-Location:aoao.gif
    Content-Transfer-Encoding:base64
    ... base64...
    --_aoao_org_cn--

    注意最后的“--_aoao_org_cn--” ,查了不少资料,原来标准写法就是这样!(via rfc2557 第7页) 囧rz

    这个故事跟我们说一件事:有问题,查文档。 我之前一直以为cache到本地后浏览器木权限用

    方案2

    ------=_NextPart_000_0000_12345678.12345678
    Content-Location:aoao.gif
    Content-Transfer-Encoding:base64
    ... base64...
    

    如果你有用ie存mht并研究结构的话,应该会觉得很眼熟,很神奇,但唯一的问题就是只能用一张图片,囧rz 反正有方案1,无视之。

    伴随这个不是bug的bug的终结,相应的工具与应用也要浮出水面了。

  • Permalink for '如何在事件代理中正确使用 focus 和 blur 事件'

    如何在事件代理中正确使用 focus 和 blur 事件

    Posted: 01 29th, 2010, 9:36am PST by 怿飞

    什么是事件代理(Event Delegation)?

    如果不太了解的朋友,可详细阅读:《Event delegation in JavaScript》,这里不再累述。

    首先让我们一起来回顾一些常识:

    1. 通常支持事件冒泡(Event Bubbling)的事件类型为鼠标事件和键盘事件,例如:mouseover, mouseout, click, keydown, keypress。
    2. 接口事件则通常不支持事件冒泡(Event Bubbling),例如:load, change, submit, focus, blur。

    很明显:focus 和 blur 都属于不支持冒泡的接口事件。既然都不支持冒泡,那又如何实现事件代理呢?

    可以换个角度,逆向思维,尝试事件捕获(Event Capturing,除了IE,现在流行的标准浏览器均支持)。

    测试后会发现,如果你捕获 focus 或 blur 事件,目标元素的祖先元素均执行事件函数。至于为什么?或许是实现的一个 BUG。

    el.addEventListener('focus', focusHandler, true);
    el.addEventListener('blur', blurHandler, true);

    那对于 IE ,我们如何实现呢?

    非常幸运,IE 下支持 focusin 和 focusout 事件,非常类似于 focus 和 blur 事件,唯一不同的是,这两种事件支持事件冒泡(Event Bubbling)。

    el.onfocusin = focusHandler;
    el.onfocusout = blurHandler;

    很完美的解决方案:

    if (document.addEventListener) {
        el.addEventListener('focus', focusHandler, true);
        el.addEventListener('blur', blurHandler, true);
    } else {
        el.onfocusin = focusHandler;
        el.onfocusout = blurHandler;
    }

    当你下次看到 YUI 2.8 的 event/event-debug.js 源码中下面几段代码时,一定会清晰很多:

    576    // String constants used by the addFocusListener and removeFocusListener methods
    577
    578     FOCUSIN = "focusin",
    579     FOCUSOUT = "focusout";
    
            ....
    
    750     _specialTypes: {
    751         focusin: (isIE ? "focusin" : "focus"),
    752         focusout: (isIE ? "focusout" : "blur")
    753      },
    
            ....
    
    1053    addListener: function (el, sType, fn, obj, overrideContext) {
    1054
    1055	    var capture = ((sType == FOCUSIN || sType == FOCUSOUT) && !YAHOO.env.ua.ie) ? true : false;
    1056
    1057        return this._addListener(el, this._getType(sType), fn, obj, overrideContext, capture);
    1058
    1059    },

  • Permalink for '修复 jQuery 中 isFunction 方法的 BUG'

    修复 jQuery 中 isFunction 方法的 BUG

    Posted: 01 23rd, 2010, 1:34am PST by 怿飞

    jQuery 1.4 源码 449 行(core.js 431 行),判断是否为函数的方法如下(思路来源于 Douglas Crockford 的《The Miller Device》):

    isFunction: function( obj ) {
        return toString.call(obj) === "[object Function]";
    },

    同时 jQuery 的作者也作了部分注释:

    See test/unit/core.js for details concerning isFunction. Since version 1.3, DOM methods and functions like alert aren’t supported. They return false on IE (#2968).

    即:此方法在 IE 下无法正确识别 DOM 方法和一些函数(例如 alert 方法等)。

    为什么会这样呢?

    详细看测试页面: [www.planabc.net]

    会发现在 IE 下用 typeof 检测 alert、confirm 方法以及 DOM 的方法显示 object,而其他浏览器下显示 function。

    那如何完善这个问题呢?

    1. typeof 检测某个方法(例如:document.getElementById) 是否是 object,如何是,则重写 isFunction 函数;
    2. 怎样重写呢?正则判断传入的对象字符串后(”" + fn),是否起始位置含有 function,即:/^\s*\bfunction\b/.test(” + fn)。

    OK,看下根据以上思路修改后的 isFunction 函数:

    var isFunction = (function() {
        // Performance optimization: Lazy Function Definition
        return "object"  === typeof document.getElementById ?
               isFunction = function(fn){
                    try {
                        return /^\s*\bfunction\b/.test("" + fn);
                    } catch (x) {
                        return false
                    }
               }:
               isFunction = function(fn){
                   return "[object Function]" === Object.prototype.toString.call(fn);
               };
    })()

    参考阅读:

  • Permalink for '如何将函数的实际参数转换成数组'

    如何将函数的实际参数转换成数组

    Posted: 01 6th, 2010, 2:21am PST by 怿飞

    实际参数在函数中我们可以使用 arguments 对象获得 (注:形参可通过 arguments.callee 获得),虽然 arguments 对象与数组形似,但仍不是真正意义上的数组。

    值得庆幸的是,我们可以通过数组的 slice 方法将 arguments 对象转换成真正的数组:

    var args = Array.prototype.slice.call(arguments, 0);

    对于slice 方法,ECMAScript 262 中 15.4.4.10 Array.prototype.slice (start, end) 章节有备注:

    The slice function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the slice function can be applied successfully to a host object is implementation-dependent.

    《Pro JavaScript Design Patterns》(《JavaScript 设计模式》)的作者 Dustin Diaz 曾指出:

    instead of…
    var args = Array.prototype.slice.call(arguments, 0); // 怿飞注:下称方法一
    do this…
    var args = [].slice.call(arguments, 0); // 怿飞注:下称方法二

    但二者的性能差异真的存在吗?经过个人简单测试发现:

    arguments.length 较小的时候,方法二性能上稍有一点点优势,而在arguments.length 较大的时候,方法一却又稍有优势。

    2010年1月30日更新(测试地址):几经验证,性能差异不大,反而第一张方法性能稍优势一点,或许由于第二种方法创建新数组产生开销。

    最后附上方法三,最老土的方式:

    var args = [];
    for (var i = 1; i 

    不过对于平常来说,个人建议使用第一种方法,但任何解决方案,没有最好的,只有最合适:

    var args = Array.prototype.slice.call(arguments, 0); 

    ------------------------------------------------------------------

    如何将 NodeList (比如:document.getElementsByTagName('div'))转换成数组呢?

    解决方案简单如下:

    function nodeListToArray(nodes){
        var arr, length;
    
        try {
            // works in every browser except IE
            arr = [].slice.call(nodes);
            return arr;
        } catch(err){
            // slower, but works in IE
            arr = [];
            length = nodes.length;
    
            for(var i = 0; i 

    为什么 IE 中 NodeList 不可以使用 [].slice.call(nodes) 方法转换呢?

    In Internet Explorer it throws an error that it can't run Array.prototype.slice.call(nodes) because a DOM NodeList is not a JavaScript object.

  • Permalink for '删除数组中重复项(uniq)'

    删除数组中重复项(uniq)

    Posted: 12 26th, 2009, 3:08am PST by 怿飞

    首先让我们看下 YUI 是如何处理的:

    var toObject = function(a) {
        var o = {};
        for (var i = 0; i 

    详细分析,见同事 长天 的分享 12 05, 2009

  • Permalink for '第4届D2前端技术论坛开始报名'

    第4届D2前端技术论坛开始报名

    Posted: 12 5th, 2009, 1:24am PST by 怿飞

    2005年前端工程师的职位开始在中国出现,2007年第一届D2前端技术论坛在杭州举行,从此D2伴随着中国的前端一起成长,从此越来越多的互联网产品的背后有着职业前端工程师坚定的身影。

    在D2上我们曾交流过某项具体的前端技术,也曾探讨过团队协作的方式,那些美妙的话题都深入到我们频繁敲键盘的指尖。回首我们过去四年的坚持和梦想,现在是时候分享我们的积累和成果了。当架构、安全、性能等等在前端的领域扎根的时候,我们自己也在悄然发生质变,而推动这些变化的人和事正是D2想告诉你的。

  • Permalink for 'Firefox 的 Jetpack 扩展案例分析:Gmail 邮件提醒'

    Firefox 的 Jetpack 扩展案例分析:Gmail 邮件提醒

    Posted: 10 14th, 2009, 3:11am PDT by 怿飞

    Gtalk 软件的最下方有个很好又很实用的功能,就是 Gmail 邮件提醒功能。会定时更新你 Gmail 中未读新邮件的数量。

    Gtalk

    试想如果我们将此功能移植到 Firefox 上一定有趣!

    第一步,在状态栏中显示图标和数据。

    通过 《如何创建 Firefox 的 Jetpack 扩展》 这篇文章,我们可以轻易的创建:

    jetpack.statusBar.append({
        html: '<img src="http://mail.google.com/mail/images/favicon.ico"/><span id="count"></span>', //Gmail邮件图标和未读新邮件数
        width: 55, //状态栏上的宽度为55
        onReady: function(widget) {
            $("#count", widget).css({ //给未读新邮件数添加样式
                cursor: "pointer",
                paddingLeft:"4px",
                fontFamily: "Tahoma, Arial, sans-serif",
                verticalAlign: "top",
                fontSize: "10px",
                lineHeight:"18px",
            });
        }
    });

    第二步,获取 Gmail 的数据,更新未读新邮件数。

    可以通过 Gmail 邮件的 Feed 获得(需登录): [https:]]

    Feed 源码中的 fullcount 标签是用来记录当前的未读新邮件数。

    OK,首先数据源有了。接着,我们使用再熟悉不过的 Ajax 技术,获取到数据并赋给指定的元素。

    function update(widget) {
        var widget = $(widget);
    
        $.get("https://mail.google.com/mail/feed/atom", function(xml) {
            var el = $(xml).find("fullcount"); // 记录未读新邮件数的节点
            if(el){
                var newcount = parseInt(el.get(0).textContent);
                widget.find("#count").text(newcount); //赋给指定的元素
            } else { //如果未登录,显示“Login”
                widget.find("#count").text( "Login" );
    	}
        });
    }

    我们还可以通过进行一些优化:比如当未读新邮件数大于原来的邮件数时,增加提示信息等。
    提示信息这里使用 jetpack.notifications.show(options) 方法,options 参数有三个属性:title (String):通知的标题;icon (URL):通知 icon 的 URL;body (String):通知的主题内容。

    优化后的代码如下:

    var count = 0;
    
    function update(widget) {
        var widget = $(widget),
            notify = function(msg) { // 定义通知的公用方法
                jetpack.notifications.show({
                    title: "Gmail",
                    body: msg,
                    icon: "http://mail.google.com/mail/images/favicon.ico"
                });
            };
    
        $.get("https://mail.google.com/mail/feed/atom", function(xml) {
            var el = $(xml).find("fullcount"); // 记录未读新邮件数的节点
            if(el){
                var newcount = parseInt(el.get(0).textContent);
                if(newcount > count) { // 如果未读新邮件数大于原来的邮件数,则提示来自哪里
                    var sender = $(xml).find("name").get(0).textContent;
                    notify("New message from "+sender);
                }
                count = newcount;
                widget.find("#count").text(count); //赋给指定的元素
    
            } else { //如果未登录,提示登录
                widget.find("#count").text( "Login" );
                notify("Please login to Gmail");
    	}
        });
    }

    第三步:设置定时更新数据。

    我们设置每 1 分钟更新一次数据:

    setInterval( function() { update(widget) }, 60*1000 );

    第四步:设置点击扩展后的链接窗口。

    $(widget).click(function() { //设置点击扩展后的链接窗口
        jetpack.tabs.open("http://mail.google.com");
        jetpack.tabs[ jetpack.tabs.length-1 ].focus();
    });

    jetpack.tabs 为浏览器窗口的标签对象,.open(url) 为新打开浏览器窗口标签的方法,.focus()为选中此标签为当前标签的方法。

    OK,Firefox 的 Jetpack 扩展——Gmail 邮件提醒,经过简单的四步轻松完成。

    全部代码如下:

    var count = 0;
    
    function update(widget) {
        var widget = $(widget),
            notify = function(msg) { // 定义通知的公用方法
                jetpack.notifications.show({
                    title: "Gmail",
                    body: msg,
                    icon: "http://mail.google.com/mail/images/favicon.ico"
                });
            };
    
        $.get("https://mail.google.com/mail/feed/atom", function(xml) {
            var el = $(xml).find("fullcount"); // 记录未读新邮件数的节点
            if(el){
                var newcount = parseInt(el.get(0).textContent);
                if(newcount > count) { // 如果未读新邮件数大于原来的邮件数,则提示来自哪里
                    var sender = $(xml).find("name").get(0).textContent;
                    notify("New message from "+sender);
                }
                count = newcount;
                widget.find("#count").text(count); //赋给指定的元素
    
            } else { //如果未登录,提示登录
                widget.find("#count").text( "Login" );
                notify("Please login to Gmail");
    	}
        });
    }
    
    jetpack.statusBar.append({
        html: '<img src="http://mail.google.com/mail/images/favicon.ico"/><span id="count"></span>', //Gmail邮件图标和未读新邮件数
        width: 40, //状态栏上的宽度为40,预留3位数的宽度
        onReady: function(widget) {
            $("#count", widget).css({ //给未读新邮件数添加样式
                cursor: "pointer",
                paddingLeft:"4px",
                fontFamily: "Tahoma, Arial, sans-serif",
                verticalAlign: "top",
                fontSize: "10px",
                lineHeight:"18px",
            });
    
            $(widget).click(function() { //设置点击扩展后的链接窗口
                jetpack.tabs.open("http://mail.google.com");
                jetpack.tabs[ jetpack.tabs.length-1 ].focus();
            });
    
            update(widget);
    	setInterval( function() {update(widget) }, 60*1000 );
        }
    });

    测试Demo:http://www.planabc.net/lab/jetpack/gmail/

    对于 Jetpack 详细的 API,可以阅读 about:jetpack 页面的 API Reference 标签部分。

    案例源码来自: [https:]]

  • Permalink for '如何创建 Firefox 的 Jetpack 扩展'

    如何创建 Firefox 的 Jetpack 扩展

    Posted: 10 13th, 2009, 2:31am PDT by 怿飞

    Firefox 的 Jetpack 可以让我们很轻松地创建 Firefox 插件,仅通过已掌握的前端技能(HTML/CSS/JS),估计让人更兴奋的一点—— Jetpack 还集成了 jQuery 框架。

    Adobe Air 也好,Web OS 也好, Jetpack 也好,至少让我们肯定一点,前端的价值将会在机遇中越来越大的被展现。

    下面让我们一起来看看如何一步步创建你的第一个 Firefox 的 Jetpack 扩展

    第一步:安装 Jetpack 插件

    Jetpack 插件地址:https://jetpack.mozillalabs.com/install.html

    安装完成 Jetpack 插件后,你就可以通过在地址栏中输入 about:jetpack 来访问 Jetpack 本地界面了。

    jetpack

    第二步:创建 planabc.js 文件。

    planabc.js 的详细代码为:

    jetpack.statusBar.append({
        html: '<img src="http://www.planabc.net/favicon.ico"/>',
        width: 16,
        onReady: function(widget){
            $(widget).click(function(){
                jetpack.tabs.focused.contentWindow.location = "http://www.planabc.net/";
            });
        }
    });

    jetpack.statusBar.append 将执行 JavaScript 对象(该JavaScript 对象有四个属性:html、url、width 和 onReady)。

    • html 属性:定义初始的 HTML,将显示在状态栏。此样例中,将显示一个简单的 IMG 元素。
    • url 属性:定义将在状态栏上显示的外部 HTML 内容的 URL。此样例中,未使用该属性。
    • width 属性:定义内容在状态栏上的宽度(单位:像素)。此样例中,定义为 16 像素,也就是 IMG 元素本身的宽度。
    • onReady 属性:定义被调用的函数(一旦状态栏被创建,此函数将会被调用)。由于 Jetpack 集成了 jQuery 框架,所以可以直接使用 jQuery 的属性和方法。在此样例中定义了这样一个函数,当点击该 Jetpack 扩展时,我们将修改 jetpack.tabs.focused.contentWindow.location 属性为 http://www.planabc.net/jetpack.tabs.focused.contentWindow 对象相当于 window 对象,你可以访问通过 JavaScript 访问网页。

    第三步:测试 planabc.js 代码。

    通过在地址栏中输入 about:jetpack 访问到 Jetpack 本地界面,点击 Develop 标签,然后将 planabc.js 文件中的代码拷贝到页面上的输入框中。点击 “try out this code” 按钮,你将会看到一个新的 icon 出现在 Firefox 窗口的右下角。

    Develop 非常方便在代码部署前,在 Firefox 中测试你的代码。

    Develop

    第四步:部署 Jetpack 扩展。

    创建一个简单的页面,在页面的 HEAD 元素内添加如下的 LINK 元素:

    <link rel="jetpack" href="planabc.js" name="怿飞的博客"/>

    HTML 文件源码如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta [http-equiv="Content-Type"] content="text/html; charset=utf-8" />
            <title>Mozilla Jetpack 案例</title>
            <link rel="jetpack" href="planabc.js" name="怿飞的博客"/>
        </head>
        <body>
            <a href="http://www.planabc.net/2009/10/13/build_firefox_extensions_with_jetpack/">《如何创建 Firefox 的 Jetpack 扩展》 </a>
        </body>
    </html>

    最后,将 HTML 文件和 planabc.js 文件都上传到服务器。

    第五步:安装 Jetpack 扩展。

    在 Firefox 中浏览该 HTML 页面时,Jetpack 插件将会在屏幕的最上方显示一个安装条,点击 “install” 按钮。将会提示安装不受信任的特性,点击 “I know what I’m doing. Install It!” 按钮。

    安装完成和第三步测试一样,将会看到一个新的 icon 出现在 Firefox 窗口的右下角。如果此时你发现状态栏有2个相同的新 icon,那意味着原先的测试代码依然还在 about:jetpack 页面的 Develop 标签页中,清空输入框的代码即可消除。

    返回到 about:jetpack 页面,点击 Installed Features 标签,将在列表中显示有新安装的 Jetpack 扩展。

    测试Demo:http://www.planabc.net/lab/jetpack/planabc/

  • Permalink for '一次炮轰引发的深思'

    一次炮轰引发的深思

    Posted: 09 23rd, 2009, 12:31pm PDT by aoao

    话说前文,有人在无忧发了个书的推荐,然后hax与爱民同学讲解其样章N处不解(疑是错误,主要是作者认为不是错误,偶js又水平不行,不敢乱下定论)之处。。然后因为众所周知的原因,出现了炮轰事件,又见-

    那样多人推荐的一本书为什么会炮轰呢? 怎没人来炮轰我的书呢? T_T 我经过深思后感觉原因有3点:

    1. 我没RP
    2. 水平烂到了没人理的地步
    3. 没那样多人给我推荐

    当然3是最不可能的原因,肯定不是因为很多人推荐本书(因为没看过书,还不敢说是好书,当然也有可能是烂书,想当时,好像我也光荣的被邀请写书评,不过因为时间关系导致没写)就被乱轰,

    有人说是书的原因,这书到底好不好。个人很难断定,各有说法,当然像hax同学这种只看看样章就评价的行为并不是非常推荐的,不就在样章里找出一堆问题呢?难道整本书都跟样章一样,样章能代表这本书吗?再看看人家的专题站引用的评价的都是好的,哪来像你这样多问题,连taobao的阿当都说这书好(其实我不认识他,没听过,只是见作者引用,又失败一下),理论这样多人推荐应该算好吧,我们暂时先不讨论这书到底好不好。

    又有人说书有没有错误并不是主要原因,说的好像也是。好像我的书也有一堆错别字,还有属性打少字母的。其实很多书第一版都会有些错误(当然错误也有分不同的错法),以前也没见到这样大的事件,有错误应该不是核心问题。

    那又是什么原因呢?这让我大思不得其解,最后,不小心看到一篇名为《一些技术图书编写、推荐、出版人员需要自重

    好了。。不兜了 大家也勿盲目围观。围观的关键在于能否在围观过程中了解到有价值的东西。

    最后希望大家买书时不要买到 “脑白金”。

    扩展阅读:从“装B被雷劈”讲起

  • Permalink for '判断 iframe 是否加载完成的完美方法'

    判断 iframe 是否加载完成的完美方法

    Posted: 09 21st, 2009, 8:06pm PDT by 怿飞

    一般来说,我们判断 iframe 是否加载完成其实与 Permalink for 'CSS Expression 的优化'

    CSS Expression 的优化

    Posted: 09 21st, 2009, 1:41am PDT by 怿飞

    IE 浏览器中 CSS Expression 特性的最大的问题:会反复执行,每秒钟可能执行了成百上千次,有严重的性能问题。

    如何对 CSS Expression 进行优化呢?

    至少:如果我们将 CSS Expression 在匹配的元素中仅执行一次,性能将会提升很大。

    old9《CSS Expression Reloaded》一文中提供了一个解决方案:

    在 CSS Expression 语句体里,将触发该 Expression 的 CSS 属性重置。

    例如:

    div {
        zoom: expression(function(el){el.style.zoom = "1"; alert(el.tagName);}(this));
    }

    补充几点:

    1. CSS Expression 执行在任意一个匹配的元素上。
    2. 在 CSS expression 内, “this”关键字指向当前匹配的 HTML 元素。
    3. CSS 属性选用一些不常用的属性来触发,触发完重置回默认值。

    最近在 Ajaxian 的文章《Creating a querySelector for IE that runs at “native speed”》 中看到作者 Dion Almaer 也提供了一个类似的解决方式:

    div {
        -singlex: expression(this.singlex ? 0 : (function(t) { alert(t.tagName); t.singlex = 0; } )(this));
    }

    但此代码并没有完全解决 CSS Expression 最大的性能问题,因为每次触发还是要去执行 Expression 脚本。甚至你可以滚动下你鼠标的中间滚轮,意向不到的问题将会发生。

    最后强调:仅是对 CSS Expression 做了优化,但并未说 CSS Expression 就不存在其他方面的问题。

  • Permalink for 'How to detect double taps in UIWebView'

    How to detect double taps in UIWebView

    Posted: 08 12th, 2009, 8:07pm PDT by realazy

    You can use this robust approach, or this smart method. But the first is using the UNDOCUMENTED (private) API, and the second is not working for me (and for somebody according to the comments on that blog post). So this is my solution.

    Subclass UIWebView and put these lines under your implementation:

    - (UIView *)hitTest:(CGPoint)point
             withEvent:(UIEvent *)event
    {
      CGPoint p = currentPoint;
      NSTimeInterval t = currentTimestamp;
      currentPoint = point;
      currentTimestamp = [event timestamp];
    
      if (CGPointEqualToPoint(p, point) &&
          [event timestamp] - t < 0.2){
        NSLog(@"double taps");
        return self.superview;
      }
    
      return [super hitTest:point withEvent:event];
    }

    Notice: declare ivar currentPoint and currentPoint in your header file.

    It works like a charm.

  • Permalink for '《重构 HTML: 改善 Web 应用的设计》上市'

    《重构 HTML: 改善 Web 应用的设计》上市

    Posted: 08 3rd, 2009, 9:05pm PDT by realazy

    为给 HTML 5 让路,W3C 解散了 XHTML 2.0 工作组。这造成了对 XTHML 和 HTML 之间的很多误解,所以就有人画了漫画来解释(英文中文)。为了避免更大的混乱,我就不多作评论了。

    关于本书,可以参考一下我去年的书评。对于前端工程师来说,很多情况下都需要重新捡起以前的代码,不管是不是自己写的,都会面临抉择:重新来过还是基于现状?大部分情况下,后者是唯一选择。勿怨天尤人,重构是你的好朋友。即便是在全新的工作进程中,当你闻到坏坏味道时,也需要很多小型的重构。那么,到底什么是重构?这可不是前几年流行的《网站重构(Designing with Web Stardards)》那本书里说的“重构”,而是”Refactor”,编程术语上的“重构”……说到底,还是得买一本才能了解透彻的,哈哈。

    随着网页的程序化(就是 Web Application 越来越多了),眼观六路,耳听八方,前端工程师跟浏览器打交道,除了在掌握浏览器在展示页面上的知识,还需要有浏览器跟后台交互的基本知识,本书涉猎 HTTP 的基本要点,虽不深入但亦可管中窥豹了。

    作为第一本在 HTML 领域探讨重构的《重构 HTML: 改善 Web 应用的设计》终于上市了,各大网上书店均已铺货。

  • Permalink for 'YUI Compressor 组件压缩 JavaScript 的一些原理'

    YUI Compressor 组件压缩 JavaScript 的一些原理

    Posted: 08 2nd, 2009, 1:40am PDT by 怿飞

    YUI Compressor 压缩 JavaScript 的内容包括:

    1. 移除注释
    2. 移除额外的空格
    3. 细微优化
    4. 标识符替换(Identifier Replacement)

    YUI Compressor 包括哪些细微优化呢?

    • object["property"] ,如果属性名是合法的 JavaScript 标识符(注:合法的 JavaScript 标识符——由一个字母开头,其后选择性地加上一个或者多个字母、数字或下划线)且不是保留字,将优化为: object.property
    • {"property":123} ,如果属性名是合法的 JavaScript 标识符且不是保留字,将优化为 {property:123} (注:在对象字面量中,如果属性名是一个合法的 JavaScript 标识符且不是保留字,并不强制要求用引号引住属性名)。
    • 'abcd\'efgh',将优化为 "abcd'efgh"
    • "abcd" + "efgh",如果是字符串相连接,将优化成 "abcdefgh"(注:所有在使用 YUI Compressor 的前提下,对于脚本中的字符串连接,使用连接符 “+” 的效率和可维护性最高)。

    对于 JavaScript 最有效的压缩优化,当属标识符替换。

    比如:

    (function(){
        function add(num1, num2) {
            return num1 + num2;
        }
    })();

    进行属标识符替换后:

    (function(){
        function A(C, B) {
            return C+ B;
        }
    })();

    再移除额外的空格,最终成了:

    (function(){function A(C,B){return C+B;}})();

    YUI Compressor 标识符替换仅替换函数名和变量名,那哪些不能被替代呢?

    1. 原始值:字符串、布尔值、数字、null 和 undefined。一般来说字符串占的空间最多,而非数字字面量其次(true、false,null,underfinded)。
    2. 全局变量:window、document、XM [HttpRequest等等。使用最多的就是] document、window。
    3. 属性名,比如:foo.bar。占据的空间仅次于字符串,”.” 操作符无法被代替,且 a.b.c 更加费空间。
    4. 关键字。经常被过度使用的关键字有:var、return。最好的优化方法:一个函数仅出现一次 var 和 return 关键字。

    对于原始值、全局变量、属性名的优化处理方式大致相同:任何字面量值、全局变量或者属性名被使用超过 2 次(包括2次),都应该用局部变量存储代替。

    但有部分情况下是禁止使用标识符替换的:

    1. 使用 eval() 函数。解决方法:不使用或者创建一个全局函数封装 eval()。
    2. 使用 with 语句。解决方法:方法同上。
    3. JScript 的条件注释。唯一解决的方法:不使用。

    由于 YUI Compressor 是建立在 rhino interpreter 基础上的,所以上述所有的优化都是安全的。

    扩展阅读:

  • Permalink for 'CHMate 上线'

    CHMate 上线

    Posted: 07 31st, 2009, 5:54am PDT by realazy

    拥有 MacBook 已经一年半了吧,有机会接触 Mac 的编程环境,兴趣盎然,一发不可收拾,经过一年的 C 和 Objective-C 语言入门,今年初即开始一些最基本的 Mac 编程,先后做了可可饭饭否 iPhone 客户端

    有一天想用 iPhone 看一本只有 CHM 格式的电子书(Quartz 2D Graphics for Mac OS X’s Developers),发现 App Store 存在两款软件,试用其中一款,觉得非常不满意,甚至无法满足最基本的阅读需求,就产生了做一个更好的 CHM 阅读器的念头。满足自己的需求,也希望藉此赚点零花钱买个 iPhone 3GS.

    于是有了 CHMate, 今天已经可在 App Store 安装,定价 $4.99. 如果有机会对比一下 CHMate 跟其他 iPhone CHM 阅读器,你会发现 $4.99 不会花得冤枉。根据我的老本行积攒的知识,我可以对 CHM 文件内的 HTML 做到最适合 iPhone 的屏幕阅读。CHMate 的优势是:对文档进行了重排,原来文档内的样式,除了那些依附在标签(tag)上的,基本上都被重写优化了,而且通过设置让你有控制样式的机会。当前尚未有同类产品有这个功能,这是我作为一个前端开发者所引以自豪的一件事情。

  • Permalink for '模拟兼容性的 addDOMLoadEvent 事件'

    模拟兼容性的 addDOMLoadEvent 事件

    Posted: 07 29th, 2009, 11:46pm PDT by 怿飞

    由于 window.onload 事件需要在页面所有内容(包括图片等)加载完后,才执行,但往往我们更希望在 DOM 一加载完就执行脚本。其实在现在大部分主流浏览器上(Firefox 3+,Opera 9+,Safari 3+,Chrome 2+)都提供了这一事件方法:addDOMLoadEvent

    document.addEventListener("DOMContentLoaded", init, false);

    那对于 IE 我们如何模拟 addDOMLoadEvent 事件呢?

    Matthias Miller 最早提供了如下的解决方案:

    // for Internet Explorer (using conditional comments)
    /*@cc_on @*/
    /*@if (@_win32)
    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
    var script = document.getElementById("__ie_onload");
    script.onreadystatechange = function() {
        if (this.readyState == "complete") {
            init(); // call the onload handler
        }
    };
    /*@end @*/

    Diego Perini 在其后提供了一种利用 doScroll() 方法来模拟 addDOMLoadEvent 事件的方案,且现在主流的 JavaScript 框架(JQuery、YUI等)基本都采用的这一解决方案。

    原理基本如下:

    当 ondocumentready 事件触发,文档( document )已经完全解析和建立。如果组件需要操作最初的文档结构,初始化代码需被安置在这之后。ondocumentready 事件告知组件,整个页面已被加载,且在 初始文档的 onload 事件触发之前立即触发。

    一些方法,例如 doScroll,要求最初的文档被完全加载。如果这些方法是初始化函数的一部分,当ondocumentready 事件触发,他们将被执行。

    /*
     *
     * IEContentLoaded.js
     *
     * Author: Diego Perini (diego.perini at gmail.com) NWBOX S.r.l.
     * Summary: DOMContentLoaded emulation for IE browsers
     * Updated: 05/10/2007
     * License: GPL/CC
     * Version: TBD
     *
     */
    
    // @w    window reference
    // @fn    function reference
    function IEContentLoaded (w, fn) {
        var d = w.document, done = false,
        // only fire once
        init = function () {
            if (!done) {
                done = true;
                fn();
            }
        };
        // polling for no errors
        (function () {
            try {
                // throws errors until after ondocumentready
                d.documentElement.doScroll('left');
            } catch (e) {
                setTimeout(arguments.callee, 50);
                return;
            }
            // no errors, fire
            init();
        })();
        // trying to always fire before onload
        d.onreadystatechange = function() {
            if (d.readyState == 'complete') {
                d.onreadystatechange = null;
                init();
            }
        };
    }

    JQuery 1.3.2 中源码实现如下:

    // If IE and not an iframe
    // continually check to see if the document is ready
    if ( document.documentElement.doScroll && window == window.top ) (function(){
        if ( jQuery.isReady ) return;
    
        try {
            // If IE is used, use the trick by Diego Perini
            // [javascript.nwbox.com]         document.documentElement.doScroll("left");
        } catch( error ) {
            setTimeout( arguments.callee, 0 );
            return;
        }
    
        // and execute any waiting functions
        jQuery.ready();
    })();

    YUI 2.7.0 中源码实现如下:

    if (EU.isIE) {
    
        // Process onAvailable/onContentReady items when the
        // DOM is ready.
        YAHOO.util.Event.onDOMReady(
                YAHOO.util.Event._tryPreloadAttach,
                YAHOO.util.Event, true);
    
        var n = document.createElement('p');  
    
        EU._dri = setInterval(function() {
            try {
                // throws an error if doc is not ready
                n.doScroll('left');
                clearInterval(EU._dri);
                EU._dri = null;
                EU._ready();
                n = null;
            } catch (ex) {
            }
        }, EU.POLL_INTERVAL); 
    
    }

    另外对于版本小于 Safari 3+ 的 Safari 浏览器,John Resig 也提供了一个解决方案:

    if (/WebKit/i.test(navigator.userAgent)) { // sniff
        var _timer = setInterval(function() {
            if (/loaded|complete/.test(document.readyState)) {
                clearInterval(_timer);
                init(); // call the onload handler
            }
        }, 10);
    }

    怿飞提示:

    1. 如果脚本是动态注入到页面上,则原生的 DOMContentReady 事件是不会被触发(即:IE 除外)。
    2. IE 下对于在 iframe 里的使用 addDOMLoadEvent 事件,需做处理和慎用(这一点 YUI 做得不如 JQuery 细致)。
      // form JQuery 1.3.2
      // ensure firing before onload, maybe late but safe also for iframes
      document.attachEvent("onreadystatechange", function(){
          if ( document.readyState === "complete" ) {
              document.detachEvent( "onreadystatechange", arguments.callee );
              jQuery.ready();
          }
      });

    扩展阅读:

  • Permalink for 'JavaScript 获取事件对象的一个注意点'

    JavaScript 获取事件对象的一个注意点

    Posted: 07 23rd, 2009, 6:59pm PDT by 怿飞

    平时我们获取事件对象一般写法如下:

    function getEvent(event) {
        return event || window.event  // IE:window.event
    }

    如果没有参数,也可写成(非IE :事件对象会自动传递给对应的事件处理函数,且为第一个参数):

    function getEvent() {
        return arguments[0] || window.event // IE:window.event
    }

    这样的写法在除 Firefox(测试版本:3.0.12,下同) 外的浏览器上运行都不会有问题,但 Firefox 为什么例外呢?让我们这样一种情形:

    <button id="btn" onclick="foo()">按钮</button>
    
    <script>
    function foo(){
       var e =  getEvent();
       alert(e);
    }
    </script>

    运行结果在 Firefox 中是 undefined,为什么呢?

    在 Firefox 中调用其实是这样的,先调用执行的是:

    function onclick(event) {
        foo();
    }

    然后调用执行的是:

    function foo(){
       var e =  getEvent();
       alert(e);
    }

    会发现在 Firefox 下 onclick="foo()" 中的 foo() 无法自动传入事件对象参数,而默认传递给了系统生成的 onclick 函数,那本例我们可以通过 getEvent.caller.caller.arguments[0] 获得事件对象。

    因此,我们的 getEvent 可以优化成(参照 yui_2.7.0b 中的 event/event-debug.js 中 getEvent 方法):

    function getEvent(event) {
        var ev = event || window.event;
    
        if (!ev) {
            var c = this.getEvent.caller;
            while (c) {
                ev = c.arguments[0];
                if (ev && (Event == ev.constructor || MouseEvent  == ev.constructor)) { //怿飞注:YUI 源码 BUG,ev.constructor 也可能是 MouseEvent,不一定是 Event
                    break;
                }
                c = c.caller;
            }
        }
    
        return ev;
    }

    当然还有一个很简单的解决方法,就是手动将参数传递给 onclick="foo()"

    <button id="btn" onclick="foo(event)">按钮</button>

  • Permalink for '工程师与科学家'

    工程师与科学家

    Posted: 06 28th, 2009, 11:53pm PDT by realazy

    入行已久,做的领域也从浏览器扩展到桌面端甚至是手机端,对 Web 标准多少有些自己的看法,今日斗胆一说。

    两种家

    我们困惑不解、迷惑不安,很大程度上源于没有指导思想。要摆正自己的位置,我们究竟是想做科学家,还是想做工程师。简明扼要,科学家经常要问“为什么”,他们关心了解人类不懂的知识;工程师则利用科学家发现的知识,制造对人类有用的物体或工具。前者研究,后者实战。很明显,我们大多数人属于工程师,W3C 那一群才是科学家。端正自己的态度,很多疑问就会迎刃而解。

    两种用法

    HTML 生为标记语言,是组织文档的一种格式。随着技术和社会的不断进步,HTML 的用途也逐渐升级。今天它不仅出现在浏览器上(普通网页),它还出现在桌面程序上(Adobe AIR),出现在手机程序上(PalmPre WebOS);它不仅用来展示网页,也用来构建程序的用户界面。Web 标准要求我们,HTML 必须有良好的语义化,对于展示内容的文档来说,这是毋庸置疑的,但对于只是作为构建用户界面的程序来说,强调语义是没有多大意义的。要注重语义的时候一定不能松懈,只是用户界面而已的话,怎么方便怎么来,利用最方便的手段做最适合的布局。

    实用主义的前提

    工程师信奉的是实用主义,但不等于可以放弃原则和规范。工程师关键任务是在遵守规范的前提下,发现、理解并结合实际的局限来达到满意的结果。作为一个流量巨大的网站,Google 对待 HTML 的态度是一个非常好的例子,省略</body></html> 的做法我们何曾想过呢?但这却是符合 HTML 4 规范的。详见: [code.google.com] (需自行翻墙)。

  • Permalink for '如何在 IE 中使用 HTML5 元素'

    如何在 IE 中使用 HTML5 元素

    Posted: 06 12th, 2009, 11:47pm PDT by 怿飞

    Sjoerd Visscher 发现了一个简洁的 方法 让样式在 IE 中作用到未知的元素上——仅需 JS 创建此未知元素即可:

    document.createElement(elementName)

    同理(对于 IE 来说 HTML5 元素即是未知元素),该方法也可顺延到 HTML5 的元素上(详细见:John Resig 写的 《HTML5 Shiv》 一文):

    <html>
    <head>
    <style>section { color: red; }</style>
    <script>document.createElement("section")</script>
    </head>
    <body>
    <section>Hello World!</section>
    </body>
    </html>

    在 IE 中,为了更方便使用 HTML5 元素,我们可以引入这样的脚本:

    (function(){
        // from: [dean.edwards.name]     if(!/*@cc_on!@*/0) return; 
    
        var html5 = "abbr,article,aside,audio,bb,canvas,datagrid,datalist,details,dialog,eventsource,figure,footer,hgroup,header,mark,menu,meter,nav,output,progress,section,time,video".split(',');
        for(var i = 0, len = html5.length; i 

    详细具体应用的案例如下:

  • Permalink for 'superLink,让伪链接更有可用性'

    superLink,让伪链接更有可用性

    Posted: 05 31st, 2009, 10:48am PDT by aoao

    前几天,看到有人写了个superLink的东东,主要的做什么用呢?

    我们有时会给在大块元素加个window.location='http://www.aoao.org.cn',还顺便把光标定义成移上去会变成手势还代替链接,为什么不直接用<a>因为有时东西太多,不能放在链接里,比如hxpli之类的。那问题来了,用js处理会让链接的功能没了一堆,作者在他的文章说到:

    • 中键:新tab打开 (aoao注:中键功能变更滴同学无视)
    • CTRL/SHIFT 单点:新窗口 新tab打开(aoao注:自己浏览器改过设置以自己的为准)
    • 右键菜单:虾米虾米方式打开
    • 浏览器状态栏
    • 其他,等(aoao注:好像也没其他的)

    那看看效果: superLink Demo,试一下左中右键功能。

    实现其实不难,就是把你原来加window.location的元素上盖上一个链接,透明为0,这样就可以把上面4条功能还原,-_-!。

    总的来说,想法很好,有总比没有好,还封装好让别人用 ^_^,不过如果有机会再遇到类同的应用时,我应该会在原来那块元素里面的a里加个无用的span(语义愤青别BS我),虽然定位可能有点麻烦,但总是能定准的,直接样式搞定。

  • Permalink for 'JavaScript 实现 Konami Code'

    JavaScript 实现 Konami Code

    Posted: 05 21st, 2009, 2:21am PDT by 怿飞

    在 Facebook 上有一个彩蛋:

    登录 facebook.com ,点击你首页的任何地方,键盘输入 Up, Up, Down, Down, Left, Right, Left, Right, B, A, Enter 后,再点击页面或滚动一下滚动条,你会发现特殊的变化(如下图),嘿嘿 ^^

    Facebook 上有一个彩蛋

    玩过“魂斗罗”的朋友,肯定一眼就能看出输入的字符原来就是“魂斗罗”中的“秘技”。其实“秘技”的术语叫 Konami Code,详细见图示:

    Konami Code

    那如何用 JavaScript 也在自己的页面上添加一个类似的彩蛋呢?

    Abhi《Konami Code on Facebook : How to implement it on your site》 一文中提供了大体思路:

    var $ = {
        enabled: false,
        tmp: Array(),
        _konamiCode: Array(65,66,39,37,39,37,40,40,38,38),
        init: function() {
            this.tmp = Array(65,66,39,37,39,37,40,40,38,38);
        },
        konamiCode: function(e) {
            if(!this.enabled) {
                var t = this.tmp.pop();
                if((e.keyCode-t) == 0) {
                    if(this.tmp.length == 0) {
                        this.enabled = true;
                    }
                } else {
                    this.init();
                }
            } else {
                this.action();
            }
        },
        // Change the action() function to whatever you want to
        action: function() {
            //alert("Konami Code Activated");
        }
    }

    然后在 load 的时候调用 $.init() 方法,在 keydown 的时候调用 $.konamiCode(event) 方法。

    不过 Abhi 的方法还是冗余了点,Jan Jarfalk 在留言中提供了一个短小精悍的代码:

    // Tweetable Konami code
    var k=[];
    function(e){
        k.push(e.keyCode);
        if(k.toString().indexOf("38,38,40,40,37,39,37,39,66,65")>=0) {
            //alert("Konami Code Activated");
        }
    }

    PS:有兴趣的还可以在 Google Reader 上尝试下此“秘技”,一般人我还不告诉他,嘿嘿。

  • Permalink for 'yslow 2 更新(含可下载地址)'

    yslow 2 更新(含可下载地址)

    Posted: 05 1st, 2009, 7:29am PDT by aoao

    在前端开发中,Yslow 是个很不错的参考,对于网站的优化也能提出一些不错的建议。

    这次的版本做了些很不错的修改,除了官方提供的地址在国内下载不了外没什么不好,详细的大家自己体验了。下载地址:

  • Permalink for 'Douglas Crockford 确认参加第四届 D2 前端技术论坛'

    Douglas Crockford 确认参加第四届 D2 前端技术论坛

    Posted: 04 13th, 2009, 5:06am PDT by 怿飞

    好消息:D2 前端技术论坛组委会很荣幸地邀请到 Yahoo! 的资深 JavaScript 架构师、JSONJSLintJSMinADSafe 之父 Douglas Crockford将来杭参加第四届 D2 前端技术论坛: [crockford.com]

    如果想知道最新关于第四届 D2 前端技术论坛,请访问官方网站:www.d2forum.org 或者订阅官方的 Twitter:d2forum

    Douglas Crockford 的详细介绍(节选自《JavaScript大牛:Douglas Crockford》):

    Douglas Crockford 是 JavaScript 开发社区最知名的权威,是JSONJSLintJSMinADSafe 之父,是《JavaScript:The Good Parts》的作者。JavaScript 的发明人 Brendan Eich 说他是“Yoda of lambda programming and JavaScript(lambda 编程和 JavaScript 的精神领袖)”。他同时也服务于ECMA 的 JavaScript2.0 技术委员会(TC39)。其个人站点和 Blog 是 [www.crockford.com][blog.360.yahoo.com]

    Douglas Crockford 现在是 Yahoo 的资深 JavaScript 架构师,曾在Atari公司(视频游戏工业的领袖)开发了办公自动化系统,同时也完成了对于游戏和音乐的研究;曾任Lucasfilm(卢卡斯)电影公司技术总监;在Paramount(派拉蒙)公司任职过新媒体执行主管;在电气社区,通常为人所知的是 communities.com 的创始人兼首席执行官;他还创办了 State 软件公司并作为该公司 CTO,这也是他发现 JSON 时所在的地方。他同时也对 Blissymbolics 非常感兴趣,这是一种图形、象征性的语言,并且对 永久减肥(Permanent Weight Loss) 做过研究。

    YUI 团队 2007 年底的合影,中间的长者即为 Douglas Crockford:

    yui-team

  • Permalink for '影响 reflow 的因素及其优化'

    影响 reflow 的因素及其优化

    Posted: 04 13th, 2009, 3:25am PDT by 怿飞

    如果对于 reflow 这个概念你还不太清楚或者不知道,请先阅读:

    Yahoo! 性能工程师 Nicole Sullivan 在最新的文章 《Reflows & Repaints: CSS Performance making your JavaScript slow?》 中总结了导致 reflow 发生的一些因素:

    1. 调整窗口大小(Resizing the window)
    2. 改变字体(Changing the font)
    3. 增加或者移除样式表(Adding or removing a stylesheet)
    4. 内容变化,比如用户在input框中输入文字(Content changes, such as a user typing text in
      an input box)
    5. 激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
    6. 操作 class 属性(Manipulating the class attribute)
    7. 脚本操作 DOM(A script manipulating the DOM)
    8. 计算 offsetWidth 和 offsetHeight 属性(Calculating offsetWidth and offsetHeight)
    9. 设置 style 属性的值 (Setting a property of the style attribute)

    reflow 会引起开销,影响页面的性能,那如何才能做到合理的优化呢?Nicole Sullivan 也提供了部分建议:

    1. 如果想设定元素的样式,通过改变元素的 class 名 (尽可能在 DOM 树的最里层)(Change classes on the element you wish to style (as low in the dom tree as possible))
    2. 避免设置多项内联样式(Avoid setting multiple inline styles)
    3. 应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(Apply animations to elements that are position fixed or absolute)
    4. 权衡平滑和速度(Trade smoothness for speed)
    5. 避免使用 table 布局(Avoid tables for layout)
    6. 避免使用CSS的 JavaScript 表达式 (仅 IE 浏览器)(Avoid JavaScript expressions in the CSS (IE only))

    详细阅读:

  • Permalink for '淘宝UED招聘前端工程师'

    淘宝UED招聘前端工程师

    Posted: 04 2nd, 2009, 1:32am PDT by 怿飞

    淘宝UED团队淘宝网站 一起,正在几千万会员的支持下高速发展,这种高速的发展使我们对于人才的渴求是如此的强烈。

    我们最想告诉你的是——

    在这里,工作早已不是我们考虑的问题,我们挥洒青春为之奋斗的,是我们的事业,一件让中国人自豪的事业!

    如果你接受我们的苦和乐,如果你想战斗而不是糊口,如果你和我们的要求有那么一点像。来加入我们吧!

    淘宝的工作环境:

    (一)淘宝前台

    淘宝前台

    (二)淘宝产品技术中心

    产品技术	</div>
</li>
</ul>
<ul>

<li>
	<div class=03 31, 2009

  • Permalink for 'CocoaFan alpha1'

    CocoaFan alpha1

    Posted: 03 31st, 2009, 8:56am PDT by realazy

    最近总算有精力投入到 Cocoa 的学习了,做了一个饭否专用的客户端,名曰可可饭,倒腾了一个多月算是有了一个基本可用的版本: [code.google.com] .

    门还在入,欢迎各界人士多多指教。

  • Permalink for '纯CSS在Firefox模拟text-overflow: ellipsis效果'

    纯CSS在Firefox模拟text-overflow: ellipsis效果

    Posted: 03 15th, 2009, 7:09am PDT by aoao

    text-overflow这个属性真让Firefox折腾,虽然之前有写过Firefox通过XUL实现text-overflow:ellipsis的效果,不过要外挂xbl来用也就一直没用,虽然想过Data URI,但其实还不如外挂xbl。

    这两天在论坛看到14px同学写的超过宽度显示省略号(无js全兼容),想法很有创意,使用一张图片来做“…”,另一张图片用来在字不够长的情况下盖掉“…”,原文攻略很完整,大家可以去看一下思路,个人认为的缺点就是用了图片,不够灵活,所以我就悄悄修改了代码,思路是用父一层的元素的伪类:after来完成“…”,再用元素自身伪类:after来盖掉不应该出来的“…”,看一下效果:

    纯CSS在firefox模拟text-overflow: ellipsis的效果

    看上去好像很理想,但是text-overflow也不是那样好模拟的,有兴趣的同学看看demo就知道还有多少问题。

    当成技术练手还是不错滴,不过估计应该不会拿着应用。

  • Permalink for '用私有属性来拯救IE7缩放图片的失真'

    用私有属性来拯救IE7缩放图片的失真

    Posted: 03 1st, 2009, 9:13am PST by aoao

    图片缩放会失真是真理,在浏览器里也一样,貌似使用传说中的双三次插值可以让失真看起来比较不明显,但是真的想不通IE7已经实现了,却不默认打开,是体谅我们的CPU还是内存呢?

    简单来说,我们在图片的CSS 加-ms-interpolation-mode: bicubic 就可以让IE7的图片在有缩放的情况下,失真变得不怎样明显,

    更多的测试可以到可以到MSDN做的例子看一下效果,或者到血統の森-web実験小屋看一下效果。

    没想到IE7平滑的效果居然可以比Safari好。更让我想不到的是,居然还有另一个属性值nearest-neighbor,难道为了让人节省CPU而使用吗?最少我现在还没想到为什么要用的理由。

    BTW:那时发现这个属性后给同事笑到说,人家某部门去年就发现了,居然我开始火星鸟,不过发现了不用也没什么作用,呵呵,谨以此文献给比我还火星滴同学

  • Permalink for '省市级联菜单的可用性'

    省市级联菜单的可用性

    Posted: 02 14th, 2009, 11:02am PST by aoao

    省市级联这东西基本是网注一份,而且基本是全是js写的,js写唯一坏处就是JS无效时不可用,我所说的js无效包括不支持js,js加载未完成或者失败。

    为了避免不可用,我曾经跟同事讨论过这样的实现方案。

    1. 默认显示为 input , 没加载完或者XX时,用户也可以填;
    2. js有效时,用select代替掉input;
    3. 如果input里已经有内容,要记得拿给select;
    4. 某些情况可以考虑把第一个直接输出在html上。

    还要考虑的问题

    1. input 输入的地址数据的修正与格式(结构)化;
    2. 不能格式化的前端反应。

    以前只是想想,最终因为XX原因米完成,今天不小心看到adsense 的注册页面也做了类似的处理。很好很强大。

    有些东西,并不是你想得到就一定得做得了,比如某网站全站都不支持html的form提交,哎。

    推荐本书:《Web标准设计》

  • Permalink for '语义化的label?'

    语义化的label?

    Posted: 02 8th, 2009, 7:39am PST by aoao

    大家应该经常看到在文本框里提示文字,然后一点就没了。通常做法都是默认给个value,通过js来处理。详细实现都不介绍了,大家都会。现在来看一下一个比较创意的实现。

    就是直接用label的内容盖在input上面,然后用js来处理一下。详细实现就不用说了,反正我想尝试纯CSS是没办法实现滴。从结构上来看,还是比较理想的,充分利用了label

    <label id="usernameLabel" for="username" class="placeholder"><span>Member Name</span></label>
    <input id="username" name="username" class="text" type="text" maxlength="128" value="" />

    MobileMe的登录界面

    其实看图看不出什么,点图去网站看一下。

    我还特别试了一下noscript的效果,跳转 囧rz。

    有些细节的,比如提示文字从灰变得更灰的过渡效果,是-webkit-transition的应用,要用webkit的浏览器才能体现。

    再其实,input有个叫placeholder的属性,参考以前类型为search的input及相关属性,可以直接用在type="text"input上,不过哪种方式在哪些情况应用会更适合呢,大家自己决定吧。

    推荐本书:《Web标准设计》

  • Permalink for 'JavaScript 在各个浏览器中执行的耐性'

    JavaScript 在各个浏览器中执行的耐性

    Posted: 02 4th, 2009, 3:40am PST by 怿飞

    经常会遇到这样一个情况:浏览器弹出对话框,提示脚本运行时间过长,询问“停止”还是“继续”。那究竟各个浏览器是如何判断在什么时候才弹出此对话框呢?

    • IE:执行超过500W条JScript引擎语句出现提示。
    • Firefox:执行超过10秒出现提示。
    • Safari:执行超过5秒出现提示。
    • Opera:无论执行多久都不会出现提示,最有耐性。
    • Chrome:执行超过约8秒(估计值)出现提示。

    注:当弹出类似alert的模式对话框的时候,是不计时。

    扩展阅读:

  • Permalink for '有趣而诡异的数组'

    有趣而诡异的数组

    Posted: 02 2nd, 2009, 1:00am PST by 怿飞

    年前在重写淘宝旺铺里的会员卡脚本的时候,无意中发现了一个有趣的事情。代码类似:

    var associative_array = new Array();
    associative_array["one"] = "1";
    associative_array["two"] = "2";
    associative_array["three"] = "3";
    if(associative_array.length > 0) {
        // to do
    }

    会发现 associative_array.length 始终等于 0,当时有点迷惑,后来才知道这就像大家认为 IE 中支持 CSS 属性 display:inline-block 一样,纯属巧合和误解。

    实际上(引自《JavaScript “Associative Arrays” Considered Harmful》):

    JavaScript arrays (which are meant to be numeric) are often used to hold key/value pairs. This is bad practice. Object should be used instead.

    //大意:数组只支持数字的,键值对应使用于对象上。

    • There is no way to specify string keys in an array constructor. //在数组构造函数中无法定义字符串键值
    • There is no way to specify string keys in an array literal. //在数组字面量中无法定义字符串键值
    • Array.length does not count them as items. // Array.length 不会计算字符串键值

    进一步窥探数组:

    1、数组可以根据所赋的值自动调整大小

    var ar = [];
    ar[2] = 1;
    alert(ar.length)

    发现这个数组的长度为 3,就像一个经过初始化的数组一样。所有没有赋值的数组对象,都将被定义为 undefined

    扩展阅读:

    2、可使用 “The Miller Device” 方法来判断是否是数组

    function isArray(o) {
      return Object.prototype.toString.call(o) === '[object Array]';
    }

    “The Miller Device” 的妙用不仅仅在于判断数组:

    var is = {
        types : ["Array","RegExp","Date","Number","String","Object"]
    };
    
    for(var i=0,c;c=is.types[i++];){
        is[c] = (function(type){
            return function(obj){
                return Object.prototype.toString.call(obj) == “[object "+type+"]“;
            }
        })(c);
    }

    扩展阅读:

  • Permalink for 'Objective-C 学习笔记(一)——简介'

    Objective-C 学习笔记(一)——简介

    Posted: 02 1st, 2009, 9:43am PST by realazy

    C 可能是世界上最简洁且功能强大的语言,同为 C 的超集(C++现在可能不认为自己是 C 的超集或者相反,我们姑且如此认为吧,至少历史上有过这样的共识),Objective-C 比 C++ 要简洁得多。Objective, 顾名思义,对象者也,为不提供面向对象编程基本功能的 C 添加面向对象的支持,但它不像 C++ 繁冗复杂,最大限度上保持 C 的简洁性。

    Objective-C 不像 C 有国际标准,它和 C++ 一样不存在标准。目前主要是 Apple 在维护,最近衍生了 Objective-C 2.0. 使用 Objective-C 的大户也当属 Apple, 因此 Apple 提供的库属于准标准库了。对于开发 Mac 内在观感(native)的 UI 程序来说,使用的是 Cocoa 这个框架,包含 Foundation 和 Application Kit (AppKit).

    Objective-C 也使用头文件(header files),后缀为 .h, 但使用 .m(即 message, 其他面向对象编程语言也叫 method),作为源文件的后缀。

    Objective-C 引入了 #import 指令,它的作用不仅解决头文件重复包含的问题,而且只要引入框架的单个主头文件,就可以使用框架的所有功能。

    使用和查看 Cocoa 提供的 API 时,通常会发现它们大部分以 NS 打头,它是 NeXTStep 的缩写,至于 NeXTStep 和 Apple 的渊薮,大家还是自己 Wikipedia 吧。这是因为 Objective-C 没有命名空间(name space),为避免冲突而加上的。作为一个良好的编程习惯,开发者不应再以 NS 开头命名自己的类和函数等,通常的习惯是使用自己或公司名称的缩写(Realazy 是否可以缩写成 RZ? 呵呵)。

    另外,Cocoa 的编程风格鼓励表达清晰而非含糊的命名,所以你会看到 Cocoa 中非常长的、极少看到缩写的类名或消息名。

  • Permalink for '如何快速定位页面中复杂 CSS BUG 问题'

    如何快速定位页面中复杂 CSS BUG 问题

    Posted: 01 13th, 2009, 7:40pm PST by 怿飞

    相信大家对于常见 CSS BUG 的处理已经相对比较熟悉,例如:IE6 Three Pixel GapIE5/6 Doubled Float-Margin Bug 等等。但时常我们也会碰到复杂的 CSS BUG 问题,所谓“复杂”实质是指触发的条件很复杂,而“BUG” 也并非指一定是浏览器的 BUG 。对于此类问题,我们首先要解决的是如何定位到问题,只有快速的定位到问题,才能更好的解决问题。

    对于快速定位,个人的经验处理一般如下(基本可以定位到我在 淘宝 遇到的 90% 以上的复杂 CSS BUG 问题):

    1、检查页面的标签是否闭合

    不要小看这条,也许折腾了你两天都没有解决的 CSS BUG 问题,却仅仅源于这里。毕竟页面的模板一般都是由开发来嵌套的,而他们很容易犯此类问题。

    快捷提示:可以用 Dreamweaver 打开文件检查,一般没有闭合的标签,会黄色背景高亮。

    2、样式排除法

    有些复杂的页面也许加载了 N 个外链 CSS 文件,那么逐个删除 CSS 文件,找到 BUG 触发的具体 CSS 文件,缩小锁定的范围。

    对于刚才锁定的问题 CSS 样式文件,逐行删除具体的样式定义,定位到具体的触发样式定义,甚至是具体的触发样式属性。

    3、模块确认法

    有时候我们也可以从页面的 HTML 元素出发。删除页面中不同的 HTML 模块,寻找到触发问题的 HTML 模块。

    4、检查是否清除浮动

    其实有不少的 CSS BUG 问题是因为没有清除浮动造成的。养成良好的清除浮动的习惯是必要的,推荐使用 01 11, 2009

  • Permalink for 'AIR 的尝试'

    AIR 的尝试

    Posted: 01 11th, 2009, 2:46am PST by realazy

    最近利用 Adobe AIR 做了一个饭否客户端:爱饭,并将之开源。使用 HTML, CSS 和 JavaScript 对着 API 文档照虎画猫,大概三个星期完工,有一些感想和总结。

    1. AIR 的开发对 Web 开发者非常友好,基本上不需要额外的程序知识了,甚至可以使用已有的 JS 库,爱饭就使用了 YUI。但是生成的程序有一通病,那就是占用内存高(爱饭在 Windows 下占用 40m 左右),而且不存在优化之说。做严肃的应用 AIR 还是上不了台面。很多时候觉得,打开一个 AIR 程序,其实就是打开了一个浏览器。
    2. absolute 的 CSS 布局方式非常灵活,对窗口缩放这种情况具有非常好的适应性。使用 webkit 引擎的 AIR 对 absolute 完全支持。如果是 IE 这种支持残缺的引擎,那得费非常多的 JS 代码。在 AIR 下写 CSS 有一种莫名的快感。正好 24ways 上发布了一篇关于 absolute 方式布局的文章,免却了我的罗嗦,见:Absolute Columns
    3. AIR 对 Linux 的支持还是存在缺失,比如无法给窗口加阴影,看来是 Linux 下的 Flash 支持跟不上。

    完毕。

  • Permalink for '兼容所有浏览器的设为首页与显示小策略'

    兼容所有浏览器的设为首页与显示小策略

    Posted: 01 9th, 2009, 10:32am PST by aoao

    在IE比较简单,大家都知道用setHomePage来设置,懒人写法:

    <a href="#setHomePage" onclick="this.style.behavior='url(#default#homepage)';this.setHomePage('http://www.aoao.org.cn');return false;">Set HomePage</a>

    IE的结束了没?当然没有,其实M$还提供了一个神奇的接口给我们:isHomePage,它是用来干啥呢?它是用来检查一下IE的首页是不是你的,这样我们就可以做设为首页为后就不再显示“设为首页”的效果了。挖哈哈,这里有rank同学写的现成代码,不过我修改了两下,只是写法不同,跟悄悄的加入了广告。

    <a href="http://www.aoao.org.cn/setHomePage" id="setHomePage">设为首页</a>
    <!--[if IE]>
    <script type="text/javascript">
    (function(){
      var el=document.getElementById('setHomePage'),url='http://www.aoao.org.cn/',isHp=false;
      el.style.behavior='url(#default#homepage)';
      function check(){
        try{
          isHp=el.isHomePage(url);
        }catch(exia){}
        if(isHp){
          //el.style.visibility='hidden'
          /*AD*/el.innerHTML="《Web标准设计》雷人进行时";
          el.href="http://www.baidu.com/";/**/
          el=null;url=null;isHp=null;check=null;
        }else{
          el.onclick=function(){
            try{
              this.setHomePage(url);
              check();
              return false;
            }catch(exia){return true}
          }
        }
      }
      check();
    })();
    </script>
    <![endif]-->
    

    在inline的情况下,条件注释还是比较帅滴,可能你会问,为什么不把a也放在脚本里输出呢? 其实,我们也可以解决其他浏览器的问题。

    Firefox可没有现成的接口可以实现哦,但是,牛人们还是找出来解决方案,看一下Yahoo!的解决方案吧?

    make y! home page@firefox

    囧rz,被雷到的同学自觉举手,我用到这样久的Firefox 还不知道怎样设,试了半天,原来是把链接拖到工具栏的小房子上面,就可以设为首页。

    还有好多浏览器,这时我们要派出终极解决方案。写帮助 -_-b ,好了,不会写的同学可以去抄一下yahoo的帮助
    那除了IE怎样识别是否被设为首页呢?可以写 cookies 或者用服务器来记,而有没有必要就看具体的应用了。

    其实我们不一定要一直显示设置首页,比如用document.referrer发现是从搜索或者XX过来的,也许显示成收藏会更适合,像Yahoo!的首页,看到广告的机会比看到设为首页的机率高多了,大家自己把握吧。

    兼容所有浏览器收藏全攻略:

    • IE用window.external.addFavorite
    • Win的其他浏览器提示使用ctrl+D
    • Mac的浏览器提示使用command+D

    推荐本书:《Web标准设计》

  • Permalink for 'CSS系统颜色'

    CSS系统颜色

    Posted: 01 3rd, 2009, 10:01am PST by aoao

    直接调用系统的颜色显示在网页上本来是件很好玩滴事,但是,也有个缺点,就是可用的色太少 比如Bindows在它的启动画面一点点应用。

    =。= 上次本来想在某个产品上应用,结果给人BS了,原因是色不好看 囧rz

    在css21 中 System Colors也有相关描述,CSS3还加了超链接的色,还有个flavor

    但是,要有浏览器支持才是硬道理,浏览器不支持,虾米都回家睡觉。

    附各色显示效果,如果看不到文字部分就像刮彩票一半刮开看一下,其中,Background 这个在safari 比较神奇,是透明哦。

    CSS2.1中的System Colors

    ActiveBorder Active window border. ActiveCaption Active window caption. AppWorkspace Background color of multiple document interface. Background Desktop background. » test color:Background @aoao ButtonFace Face color for three-dimensional display elements. ButtonHighlight Highlight color for three-dimensional display elements (for edges facing away from the light source). ButtonShadow Shadow color for three-dimensional display elements. ButtonText Text on push buttons. CaptionText Text in caption, size box, and scrollbar arrow box. GrayText Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color. Highlight Item(s) selected in a control. HighlightText Text of item(s) selected in a control. InactiveBorder Inactive window border. InactiveCaption Inactive window caption. InactiveCaptionText Color of text in an inactive caption. InfoBackground Background color for tooltip controls. InfoText Text color for tooltip controls. Menu Menu background. MenuText Text in menus. Scrollbar Scroll bar gray area. ThreeDDarkShadow Dark shadow for three-dimensional display elements. ThreeDFace Face color for three-dimensional display elements. ThreeDHighlight Highlight color for three-dimensional display elements. ThreeDLightShadow Light color for three-dimensional display elements (for edges facing the light source). ThreeDShadow Dark shadow for three-dimensional display elements. Window Window background. WindowFrame Window frame. WindowText Text in windows.

    CSS3 新加的 User preferences for hyperlink colors

    ActiveHyperlink Active hyperlink background. ActiveHyperlinkText Text of an active hyperlink. HoverHyperlink Hover hyperlink background. HoverHyperlinkText Text of a hyperlink in the hover state. Hyperlink Hyperlink background. HyperlinkText Hyperlink text. VisitedHyperlink Visited hyperlink background. VisitedHyperlinkText Text of a visited hyperlink.
    flavor An accent color (typically chosen by the user) to customize the user interface of the user agent itself. User agents may default the ‘flavor’ color to the dominant accent color used on the physical machine/mechanism that the user is interacting with (frequently a mouse, keyboard, monitor and computer case, often just a laptop), if the UA is able to retrieve that information from the platform and machine. It is not expected that this value will make sense on all platforms and machines.

    推荐本书:《Web标准设计》

  • Permalink for '出版了《Web标准设计》!兴奋与失望'

    出版了《Web标准设计》!兴奋与失望

    Posted: 12 14th, 2008, 6:33am PST by aoao

    终于,《Web标准设计》出版了,很高兴,这本书从开始到现在刚好是两年。签合同的时间是2006年12月13号,我去出版社拿样书的时间是2008年12月12号,真TMD的巧。估计很少作者的书要出这样久,月影是我在接书后骗去写《JavaScirpt王者归来》,人家再慢也都已经第二次印刷了,更不敢去对比人家2两个月交稿。

    书的封面最终还是使用这个我不喜欢的封面

    《Web标准设计》封面

    并不是因为创意不好,是制作工艺不好,过重的阴影,让他看起来更山寨。最最让我不能接受的书的排版。从内容开始,除了引用的文字从仿宋改成黑体外,我看不出 跟我交的Word的排版有多少什么区别。早知道这样我就应该坚持我第一次交稿时那样,把图片都排在代码右边,这样看起来又明了,又省纸。价格也不是我能接 受了,虽然说最近纸贵,但是距离我最初想控制在50块下的目标太远了。就算打完折还有5~60块。

    一本书还没正式面市就有勘误表,应该算是比较失败,更失败的是这本书是我写的,我现在最担心的是送印厂的版本不是最终校完的版本。

    也许这本书的收藏意义更大于指导意义。毕竟两年前开始写时对技术的了解远没达到现在的程度。但不管怎样,书是我的,我会努力去维护好它。

    原来计划推出与书同风格的小站主题也因为各种原因拖没了,就连现在的专题页也是拿到样书才发现我什么都没开始做才东拼西凑临时做的。用Webkit的浏览器(Safari,Chrome)会看到一些奇怪的效果哦 ^_^,好了大家来看看《Web标准设计》专题页面

    推荐本书:《Web标准设计》

  • Permalink for '第三届 D2 的“反刍”'

    第三届 D2 的“反刍”

    Posted: 12 1st, 2008, 8:16am PST by 怿飞

    下面是我在此次 D2 中分享主题《前进中的淘宝UED前端》的PPT:

    很歉意分享未能给大家带来更有“营养”的内容。但希望 PPT 能起到抛砖引玉的作用,引发大家对前端未来和自己职业规划的思考。更希望大家能更多的去影响身边的人,当抱怨的时候,不如去引导和改变。

    怿飞	</div>
</li>
</ul>
<ul>

<li>
	<div class=11 29, 2008

  • Permalink for '使用 iframe 获取网页片段的一个好处'

    使用 iframe 获取网页片段的一个好处

    Posted: 11 29th, 2008, 7:30pm PST by realazy

    异步操作数据的方式有两种常见的方式:XM [HttpRequest] iframe. 孰优孰劣在此我们不争论,只是想举一个例子说明在获取网片片段上,使用 iframe 有一个比 XM [HttpRequest] 更易企及的好处。

    Ajax 常见的一种使用方法是加载网页片段填充某个区域。假设我们要在网页 foo.com/index 上请求 foo.com/partial。我们假设 partial 就是 HTML,不涉及 JSON 或 XML 格式。在这种情况下:

    1. 使用 XM [HttpRequest] 直接访问 partial,获取 responseText 后赋予 index 页面上某个元素的 innerHTML 即可完成。partial 必须是一个纯粹的 HTML 片段(基于以上假设)。
    2. 在页面上创建一个隐藏的 iframe, 使用 iframe 的 src 请求 partial, partial 可以作为一个完整的页面,里面包含 JavaScript,由 partial 里的 JS 完成替换 index 上页面片段替换。

    第二种看起来更繁琐,但能给我们更多控制权。例如,假如用户直接访问 foo.com/partial(这种情况很容易发生,假设您是 unobtrusive 的拥护者,机会更大,例如需要点击网页上的链接更新某部分内容时,用户使用新窗口打开了改链接), 你希望她看到的是什么内容呢?

    在第一种情况中,用户看到的是代码片段,绝大部分下没有任何样式,也没有任何额外提示,导致用户体验的下降。因为只是一个 HTML 片段,你什么事都干不了。

    但在第二种情况下,用户看到内容可能也只是 HTML 片段,但这却是一个完整的页面,一个可以执行 JS 的完整页面。我们只需检查这个页面的 parent 对象有没有我们预设的值,就可以判断它是不是在 iframe 之内了,然后我们可以让它跳转到正常的页面。

    Demo: [realazy.org]

  • Permalink for '人肉gzip'

    人肉gzip

    Posted: 11 16th, 2008, 10:15am PST by aoao

    gzip 是什么东东呢?百科跟我们说

    gzip是GNU zip的缩写,它是一个 GNU 自由软件的文件压缩程序。…
    gzip 的基础是 DEFLATE ,DEFLATE 是 LZ77 与哈夫曼编码的一个组合体。…
    “gzip” 也经常用来表示 gzip 文件格式,这种格式:

    • 10 字节的头,包含幻数、版本号以及时间戳
    • 可选的扩展头,如原文件名
    • 文件体,包括 DEFLATE 压缩的数据
    • 8 字节的尾注,包括 CRC-32 校验和以及未压缩的原始数据长度

    简单来说,Gzip就是一种让你的网页显示变快一种神奇的东东。

    那么,什么是人肉Gzip呢?就把本来要服务器动态压的东东人工压,大家可以先看一下这篇:Gzipping CSS and Javascript files。实现方式就人工zip一下,然后服务器再派上头。

    <link rel="stylesheet" href="style.cgz" media="all" type="text/css" />
    # Compressed css files
    AddEncoding x-gzip .cgz
    AddType text/css .cgz

    其实Gzip就是三个选择:CPU、内存、IO,通常都是cpu+内存来做,我并不喜欢这个人工IO的方式,它最大的一个问题就是不能解

  • Permalink for '按钮在 IE 中两边被拉伸的 BUG'

    按钮在 IE 中两边被拉伸的 BUG

    Posted: 11 12th, 2008, 12:07am PST by 怿飞

    大家在写按钮(input、button)的时候会发现在 IE 下:

    1. 随着字数的增多,两边的间距也会越来越大。
    2. 在 WIN 的XP 风格下,当字数很多时,两边还会出现严重的锯齿。

    到底是什么原因呢?

    蓝色理想WEB标准化专栏 的斑竹 zbm2001z 给出了一个答案

    1、IE 在按钮的 value 值每增加 4 个字节(汉字为 2 个)时,就会在按钮的两边产生总共一个字节的内边距宽度。
    2、IE 的按钮(XP 风格)默认样式是一个固定尺寸的圆角矩形图片作背景,所以一旦按钮变宽变高后,这个固定尺寸的圆角矩形图片的边缘自然就会出现“拉毛”的现象了。

  • 对于第二个问题我们暂时没办法去解决,除非 WIN 系统自己修复 BUG ,但对于第一个问题 ,我们还是可以修复的。

    IE 下给按钮元素设置 overflow 属性的 visible 值(注 :padding 值仅在设置了overflow:visible 属性后才有效),这个属性其实在上次写的《background-clip 与 background-origin 的一则运用》一文中已经使用。不过在今天阅读了《The stretched buttons problem in IE》后,发现原来还有一个小 BUG —— 如果将按钮置于表格单元格中,虽然按钮显示正确了,但是原先预留的宽度大小却没有改变,仍然占据着空间,需要在 IE6 中设置按钮的宽度(width)为 0(IE7同样也存在此 BUG,但暂时没有寻找到好的方式解决)。

    最终的修复代码如下(Demo):

    input.button {
        padding: 0 .25em;
        width: auto;
        _width: 0;
        overflow:visible !ie;
    }

  • Permalink for 'Firebox 3 后退后按钮 diasabled 状态不恢复的一个解决方案'

    Firebox 3 后退后按钮 diasabled 状态不恢复的一个解决方案

    Posted: 10 26th, 2008, 11:34pm PDT by realazy

    Firefox 3 有一个很让人讨厌的bug:基于某种目的,在表单提交时 disable 掉提交按钮,通过后退键回到这个页面后,这个提交按钮的状态依旧保持为 disabled 的状态,重新载入(软硬刷新)也无法改变。

    google 良久,从 [https:]] 中发现一个 window.onpageshow 事件,window.onload 事件无法在后退的页面中出发,但这个可以,所以解决方案就是它了。

    
    window.addEventListener('pageshow', function(e){
        // 重置你不需要 disabled 的按钮
    }, false);
    

    更新:网友岁月如歌的解决方案比我的方案简易和正宗多了:给提交按钮加上 autocomplete="off" 的属性。

  • Permalink for 'AppKit 应用程序设计观'

    AppKit 应用程序设计观

    Posted: 10 10th, 2008, 1:50am PDT by realazy

    原文来自 Application Design in AppKit.

    This is a discussion of high-level application design in Cocoa that aims to explain the major class roles in an AppKit application and how they are connected. I’ll show you much more detail than simply “Model-View-Controller” and I also give a specific example of how all the concepts apply to a real application.

    这是一篇关于 Cocoa 高级应用程序设计的讨论,目的在于解释 AppKit 应用程序中的主类的作用,以及它们间的相互联系。我会向你展示比“模型-视图-控制器”更具体的细节,也会给出一个具体例子,展示这些概念是如何应用到一个真实的程序中去的。

    The anecdote 轶事

    The other day, I was showing a friend how to program in Cocoa. She is a very good programmer but has never really programmed a user-application — working almost exclusively on embedded and server applications.

    不久前的一天,我向一位朋友介绍在 Cocoa 中如何编写程序。她是一位不错的程序员,但之前从没实战过用户交互的应用程序——几乎只编写嵌入式或服务器应用程序。

    The experience reminded me that even good programmers can be unaware of basic design traits of user-applications which, while common to user-applications on all platforms, are not common to all programming.

    这种经历告诉我,即使是好的程序员也不一定知道用户交互应用程序的设计的基本特性——虽然它在所有平台上的用户交互应用程序中非常普遍,但不见得在所有类型的编程都是常见的。

    So even though it’s more “novice” than my regular fare, it would appear that even simple topics can be useful to advanced programmers.

    所以,即使这里讲到的比较菜,只是简单的主题,但对高级程序员也会很有帮助。

    A starting point 开门见山

    “Model-View-Controller” is the term normally used to describe the structure of modern applications. Almost every discussion of application design begins with it and I guess I will too. It looks like this:

    “模型-视图-控制器” 是用在描述现代应用程序结构的一个常见术语。几乎所有关于应用程序设计的讨论都会以它开头,我猜我也不能免俗。它大概是这个样子:

    MVC示例图

    Your document data (the “model”) notifies the intermediary (the “controller”) and it tells your user interface elements (“the view”) to update. Going back in the other direction, user actions in the user-interface trigger notifications to the intermediary which modifies the data.

    文档的数据(“模型”)通知中间件(“控制器”)并告诉用户界面元素(“视图”)更新。从反向来说,中间件响应用户界面中的用户动作,并对数据进行修改。

    The rationale may not be immediately obvious. Let me explain it this way:

    这个原理并不是那么浅显易懂。看我这样解释行不行:

    User applications are big and complex — even seemingly simple applications. To manage complexity, everything is compartmentalised. To keep the boundaries between compartments clean, connections between compartments should be simple and generic.

    用户应用程序是大型和复杂的——看起来简单的应用程序也是如此。为了管理复杂性,所有东西都划分管理。为了能保持部件边界的清晰性,它们之间的联系应该是简单和通用的。

    Obviously, you don’t want the model and view to be the same thing because then, there is no separation at all and the application will be a tangled mess.

    显然,你并不想把模型和视图搞成一样的东西,因为完全没有分离的话只会让应用程序一团糟。

    Direct connections between model and view are normally frowned upon because it creates a situation where they must know too much about each other’s internal state to interoperate. Instead, a controller object (which knows about connective state but little else) is used to keep the interaction simple and generic.

    如果模型和视图之间的联系是直接的,要想实现互用,两者必须深入了解对方的内部状态,这是一个纠葛的过程。反之,使用一个控制器对象(它能掌握大部分联系状态,其他的基本不管)来管理模型和视图的交互,就简单和通用多了。

    Better than Model-View-Controller 比模型-视图-控制器更胜一筹

    These traits of Model-View-Controller are all good things but in reality, it says little about how to assemble an application. Real applications have many more traits in common than a separation between model and view.

    模型-视图-控制器的原理非常好,但在实际操作中,它基本上没有指明应用程序该如何组织。真实的应用程序有着比模型和视图的分离更多的通用特性。

    A more complete diagram of a typical application’s design would look like this:

    这是一个更完整的流程图,典型的应用程序设计更应如此:

    App design detail 示例图

    In this diagram, solid black arrows indicate construction and hierarchic ownership. Feint gray arrows indicate communication in response to changes.

    在此图中,黑色的实心箭头表示结构和层级属主;灰色空心箭头表示对变化作出响应的通讯。

    Application instance 应用程序实例

    The application instance incorporates the program entry point and the event loop (which handles all user events like mouse and keyboard actions). As the starting point of the program, the application instance constructs the other top-level objects in the program.

    应用程序实例包含了程序入口的指针和事件循环(它处理所有的用户事件,如鼠标和键盘事件)。作为程序的起点,应用程序实例建立了程序中其他顶层的对象。

    Application controllers 应用程序控制器

    The term “Application User Interface” is used in this part of the diagram to refer to elements of the user-interface that are not part of the document or the main window.

    此图中的术语“应用程序用户界面”用于非文档或主窗口的用户界面元素。

    These objects are constructed by the application at startup. They should only handle things which exist before the main window or main document is open or which fall outside the bounds of these areas. Example behavior here includes the application preferences window and the Mac OS X menu bar.

    这些对象在应用程序启动时就构建。它们应该只处理出现在主窗口之前,开启主文档之前或超出这些范围边界的东西。这种行为的例子包括应用程序偏好设置,以及 Mac OS X 的菜单栏。

    Document instance 文档实例

    This is the first point where a programmer begins to exercise control over the program’s behavior. The document loads or constructs the program’s data and constructs the windows to show it.

    这是程序员控制程序行为的起点,文档载入或构建程序数据,并构建函待显示的窗口。

    A common mistake is to think that your program doesn’t have a “document” so you shouldn’t model a document class. In reality, if a program does anything then it is changing some piece of data (a preference file, a set of objects for rendering in OpenGL, the result of a calculation). You should design your program with this piece of data as the document. Even if your program only has one window, even if it only works with the same piece of data, even if you aren’t writing a “Cocoa Document-based Application”; you should always have a class at the heart of your program which can be called “the current document”.

    认为程序没有“文档”而不应把文档类模型化,这是一个常见的错误。实际上,程序只要干了活就会改变某些数据(比如偏好设置文件、OpenGL 中的渲染对象集合、计算的结果等等)。设计程序时应该跟文档一样考虑这些数据。你的程序即使只有一个窗口,即使以相同的数据运行,甚至你编写的不是“Cocoa 基于文档的应用程序”,心中都应该怀有这样一个类——你可以称之为“当前文档”。

    Window controllers 窗口控制器

    A window controller is the class responsible for loading a window and putting it on screen. The window controller is responsible for giving context to the views and controls within the window, connecting them to data controllers which will provide them with data.

    窗口控制器是负责把窗口载入屏幕的类,负责给窗口内的视图和控制器提供上下文,连接到提供数据的数据控制器上。

    It is common for window controllers to double as data controllers for some functions since the window controller knows the state required to make the connection. This is not a bad thing in itself but should be resisted in the long term since it leads to bloat in the window controller (which often has a lot of work to do already). Generic, data-specific controllers should be used for this task.

    窗口控制器在一些函数上比数据控制器多一倍,这种情况是常见的,因为它需要知道产生连接的状态。这不是什么坏事,但从长远来说应该要抵制,因为它会导致窗口控制器自身的膨胀(它早已排满了各种任务)。一般来说,应该使用特定数据的控制器完成这些任务。

    User Interface Elements 用户界面元素

    Where possible, these should be generic elements: buttons, text display, image display. They end up performing specific actions when connected (through controllers) to their contextually supplied data.

    可能的话,用户界面元素通常包括:按钮、文本显示和图片显示等。(通过控制器)连接相应的数据时,它们最终会执行指定的行为。

    User interface elements are normally hierarchic. The screen contains windows; windows contain views; views contain subviews. One window is normally in front (main window) and one view within this window is normally the focus. The application’s “event loop” will send keyboard actions, mouse events and menu selections to this focus object. Unhandled events get passed up through the hierarchy so that parents can handle events that their children don’t handle.

    用户界面元素通常是分级的。屏幕包含窗口;窗口包含视图;视图包含次级视图。窗口一般在最前(主窗口),这个窗口内视图一般也会聚焦。应用程序的“事件循环”会给这个聚焦的对象发送键盘键盘动作、鼠标事件和菜单选择等行为。未处理的事件可以跨越层级,所以父层能够处理子层未能处理的事件。

    The handling of events should be managed as low in the hierarchy as possible. Again, consolidation in parents leads to bloat. Even “small” applications can become very big.

    事件应该尽可能在低的层级上处理。父层事件的集中会导致应用程序的膨胀。就算是“小”的应用程序也能变得很大。

    Data Controllers 数据控制器

    These should be as generic as possible. Their purpose should be to relay information from a source to a destination about data changes.

    数据控制器应该尽可能通用化。它的目的应是分发从源到目标中关于数据变化的信息。

    The simplest manifestation of a data controller is for a third-party to establish or enable the Observer design pattern between two objects.

    数据控制器最普遍的表现是为第三方建立或启用两个对象之间的观察者设计模式

    The worst approach (sometimes called an anti-pattern) is an all encompassing arbiter object that receives every change request the program makes, performs the change and then updates everything that needs to be updated. This approach is unsustainable on an application-wide scale. Decomposition is key — data controllers should have small, focussed scope.

    最差劲的方法(有时被称为反模式)是使用一个包办一切的对象接收程序产生的一切变化请求,执行变化然后更新所有需要更新的东西。这种方法无法支撑应用程序级别的扩展性。分解才是关键——数据控制器应该是一个小型的、集中的范围。

    An example application 一个实例

    Now we’ll look quickly at what this means in an AppKit-based application. This application is a simple program that creates and edits lists of names. I know that’s a pretty trivial thing for a program to do but the example must be simple so I can describe it here properly.

    现在我们快速浏览一下它们在基于 AppKit 的应用程序中的含义。这是一个简单的应用程序,用以创建和编辑名字清单。我知道对程序来说这是在微不足道,但为了表述清晰才让它必须简单的。

    应用程序流程图

    You can download the project described in this diagram, although it isn’t necessary to understand the discussion.

    你可以下载这张图所描述的应用程序,尽管对理解这个讨论而言不是必要的。

    The application object is an unmodified NSApplication. This will almost always be the case in any Cocoa Application. You can achieve most customisation of the NSApplication object through data (in the Info.plist file) or by attaching an application delegate object (which can intercept control at predetermined points). The application instance handles our startup, event loop and contruction of documents (I have discussed how a Cocoa application loads in a previous post).

    这个应用程序对象是一个未经修改的 NSApplication, 在 Cocoa 应用程序中几乎都是这种情形。你可以通过数据(在 Info.plist 文件中)或附加一个应用程序代理对象(它可以侦听特定情况下的控制)实现 NSApplication 的大部分定制。这个应用程序实例处理我们的启动、事件循环和文档的构建(我在前一篇 blog 中讨论了 Cocoa 应用程序是如何载入的)。

    This application doesn’t have any preferences or significant data outside the scope of the document, so the “Application Controllers” section just has the Main Menu in it.

    这个应用程序没有偏好设置,也没有超出文档的重要数据,所以“应用程序控制器”部分只有一个主菜单。

    Documents in AppKit act as both a data controller for the data (in this case, an array of strings) and the window controller for the main document window. The document handles saving and reading to and from any file on disk. This could be done with basic NSKeyedArchiver methods to turn an array of strings into an NSData object for writing to disk. The window is loaded automatically from the window NIB file (specified in the program’s Info.plist file).

    AppKit 中的文档不仅作为数据(在这个例子中是字符串数组)的数据控制器,还充当主文档窗口的窗口控制器。这个文档还处理磁盘上文件的读写,是通过基本的 NSKeyedArchiver 方法把字符串数组转为可写入磁盘的 NSData 对象实现的,而窗口是从窗口 NIB 文件(在程序的 Info.plist 文件中指定)自动载入的。

    The NIB file for the document contains an NSArrayController which is connected to the list of names from the document via the appropriate keyPath. This allows the NSArrayController to issue key value observing notifications when it changes the array and similarly allows it to update automatically when something else changes the array on the document.

    文档的 NIB 文件含有一个 NSArrayController, 它通过合适的 keyPath 从文档连接到名字的清单。这让 NSArrayController 在改变数组的时候可以发出侦听键值的通知,或类似地在其它东西改变文档数组时也允许它自动更新。

    The NIB file for the document also contains the window, which in turn contains an NSTableView. The NIB file specifies that the NSTableView’s only column (displayed using the NSTextCell) should get its data from the NSArrayController. In this way, the table is updated to display the list of names contained in the document.

    文档的 NIB 文件也含有依次包含 NSTableView 的窗口。NIB 文件规定 NSTableView 唯一的栏(使用 NSTextCell 来显示)应该从 NSArrayController 中获取数据。在这种情况下,文档内的表格作出更新以显示名字清单。

    The NSTextCell displays the name for each row and allows editing. If an entry is changed in this way, notifications are sent back through the NSArrayController to the document.

    NSTextCell 在每一行上显示名字,而且允许编辑。如果某个条目这种方式下改变,NSArrayController 会把通知发送给文档。

    Similarly, the “Add New Name” button can add a new object by communicating with the NSArrayController, asking it to create a new object and insert it in the array, which triggers all relevant change.

    类似地,”Add New Name” 按钮通过跟 NSArrayController 的通讯可以添加一个新对象,要求它创建并插入到数组中,这会触发所有相关变化的传播。

    Conclusion 总结

    All of this may seem like a lot of work — setting up connections and controllers and notifications. When starting a new program, you may think that many of these elements don’t apply to you. Be careful — don’t chase false simplicity.

    所有这些需要的工作量看起来很大——设置连接、控制器和通知。当开始一个新的程序,你可能认为这些元素的大部分都不适用。小心——不要追求失败的简洁性。

    Remember, Cocoa was written to make the approach described in this article easier than the alternatives. Classes like NSArrayController and protocols like NSKeyValueObserving and NSKeyValueBindingCreation make connecting large amounts of data as simple as point and click in Interface Builder. In many cases, it ends up being faster than manually connecting a button or text field directly to a method on your document class.

    记住,对于本文描述的各种方法,Cocoa 是本着更方便而非其他目的而生的。诸如 NSArrayController 等类、诸如 NSKeyValueObserving 和 NSKeyValueBindingCreation 等协议,让大量数据的连接跟 Interface Builder 中的指向和点击一样简单。

    You will always have change behaviors that cannot be connected using these generic objects but following the same structural patterns that they use will keep your application clean and make it work better within Cocoa.

    你总会碰到不能使用这些通用对象连接的变化行为,但遵循这些它们用到的相同结构模式会让你的应用程序更清晰,也能更好地运行在 Cocoa 下。

  • Permalink for 'form 元素内的字段 name 不要跟 form 属性名称一致'

    form 元素内的字段 name 不要跟 form 属性名称一致

    Posted: 10 8th, 2008, 6:35am PDT by realazy

    长话短说,看这个 form 元素:

    <form method="post" action="_some_action_uri_" id="_form_id_">
    <input type="hidden" name="method" value="1" />
    </form>

    试想一下,使用 document.getElementById('_form_id_').getAttribute('method') 会出现什么情况。Firefox 3, Safari 3, Opera 9.5 都会得到预期 “post”, 但是IE 6 和 7 就没有那么幸运了,得到的是一个 object: 其实就是 <input type="hidden" name="method" value="1" /> 这个元素。

    因此,为避免混淆和挽救IE,最好是,as the title.

  • Permalink for 'focus 进 textarea 元素后光标位置的修复'

    focus 进 textarea 元素后光标位置的修复

    Posted: 09 10th, 2008, 12:21am PDT by realazy
    问题

    一个已经有内容的 textarea 元素,在执行该元素的 .focus() 方法后,不同的浏览器有不同表现。我们的预期是能够出现在内容后面,但只有 gecko 浏览器能做到。

    修复

    注意:这个函数不能直接运行,函数内的 isIE, isOpera 和 isWebkit 需要你的库提供或你编写,这并不难,对吧。

    function fixTextareaFocusCursorPosition(elTextarea){
        if (isIE || isOpera){
            var rng = elTextarea.createTextRange();
            rng.text = elTextarea.value;
            rng.collapse(false);
        } else if (isWebkit) {
            elTextarea.select();
            window.getSelection().collapseToEnd();
        }
    }
  • Permalink for '跨浏览器使用剪贴板'

    跨浏览器使用剪贴板

    Posted: 09 1st, 2008, 1:19am PDT by realazy

    一般情况下,访问或设置剪贴板,IE 只需使用 window.clipboardDatagetDatasetData 方法即可。Mozilla 家族的浏览器(如 Firefox)则比较麻烦,不仅开发者需要写一沱代码,用户也需要主动配合(就是需要设置允许访问剪贴板)才可以(参考 Using the Clipboard),以致几不可用。至于 Opera 则根本不提供剪贴板,Safari 可以在 onpaste 等非Dom 事件中访问剪贴板(参考 Using the Pasteboard From JavaScript)。

    中国特色的网站上有一个很中国特色的应用就是,在一个输入框 focus 时自动帮你把内容复制到了剪贴板中。老实说访问剪贴板是个不安全的操作,因此即使是 IE, Windows 在后来的升级中都加入是否允许访问剪贴板的提醒。如果能够做到跨浏览器的“邪恶地悄无声息”地实现中国特色的剪贴板应用,确实是个不小的挑战。

    遗憾的是老外在 2006 年就帮我们做到了:使用 Flash。参考 Clipboard Copy. 原版没有考虑不安转或禁止 Flash 的情况,我做了一个小改进:

    function copy(inElement) {
        var get = function(id){
            return document.getElementById(id);
        },
            elId = 'flashcopier',
            embedId = 'flashembed';
    
        if(!get(elId)) {
            var divholder = Document.createElement('div');
            divholder.setAttribute('id', elId);
            document.body.appendChild(divholder);
        }
    
        var divholder = get(elId);
        divholder.innerHTML = '<embed src="http://static.hainei.com/swf/cp.swf"\
                        FlashVars="clipboard='+encodeURIComponent(inElement.value)+'"\
                        width="0" height="0" type="application/x-shockwave-flash"\
                        id="'+embedId+'"></embed>';
    
        // 检测是否安装了 Flash
        var flashObj = window[embedId] || document[embedId] || {};
        if (!flashObj.SetVariable){// 没有 flash
            try {
                return window.clipboardData.setData("Text", inElement.value);
            }
            catch(ex){
                return false;
            }
        }
    
        return true;
    }

    原版是 GPL 的,这个版本也请爱咋咋用……

  • Permalink for 'Web Forms 2.0'

    Web Forms 2.0

    Posted: 07 22nd, 2008, 1:26am PDT by realazy

    Web Forms 2.0 是一个很有意思的东东,是 HTML 5 的组成部分。它的目标是提升表单的使用性 (usability),基本上就是为 input 元素的 type 属性增加一些值,如 type="email";还有一些新属性,如 required。根据 type 由浏览器实现各种功能。比如,<input type="email" required="required" />,从字面上即可看出,这是一个必须填写,且格式是电子邮件的输入框。如果你用的是 Opera 9+, 猛击这个例子看看效果。

    注意,这不需要任何 JavaScript,是浏览器内部实现的功能。很遗憾的是到目前为止只有 Opera 9+ 有部分实现,作为前端开发者,每天都在为表单验证、自动完成等提升表单用户体验的事情上拼了老命,重复发明轮子。好消息是,基本上这些都可以通过 JavaScript 来模拟实现,项目当然有人在做了:webforms2,不妨下载一试。

  • Permalink for '使用标准的表单字段名'

    使用标准的表单字段名

    Posted: 06 27th, 2008, 9:45pm PDT by realazy

    是不是很烦每次注册网站或填写相关资料时都要重来一遍?其实会有很多自动填写工具能代劳。比如使用 Mac, 在 Safari 的表单中,它可以足够聪明帮你从帐户资料中查找并填写一些相应的字段。Opera 也有相关功能,不过资料设置是在浏览器内。

    当然,它们是根据表单的字段名称进行猜测与匹配的。如何给表单字段起个好名字,以方便自动填写工具的匹配,就变得有必要起来。

    既然说“标准”,那名字肯定不是乱取的。RFC3106 据此而生。比方说,用户 ID 使用 Ecom_User_ID, 密码使用 Ecom_User_Password 等等,你可以访问该页面查看更多的对应实例。虽然商业气息比较浓重,但不妨碍我们的使用,以及给用户带来的便利。

  • Permalink for 'Refactoring HTML 书评'

    Refactoring HTML 书评

    Posted: 05 26th, 2008, 9:02am PDT by realazy

    很多年以前,面对上古时代遗留的 HTML 发出的腐臭,我捂住鼻子唉声叹气。刚练熟 web 标准的我,恨不得寝其尸食其肉,把一切推翻重来。但经理说,没有时间浪费在清理这些垃圾上,快给我把新的页面切了!可想而知,我是郁闷的。你也是,别装了,我知道。我常常处于崩溃边缘,作为一个有深度爱心和追求完美(这应该是所有程序员追求的品质)的 web 前端开发者来说,为何不许我为残障者着想,改善一些无障碍性?为何不许我把这些鸟b(读者最好不要连起来念),鸟i, 还有鸟u送上刑场?看着 W3C 校验器显示出的一串又一串的 erorr, 我灰心丧气,横眉冷对经理指,好像丫欠了我几十万块冥币,哦不,人民币……

    皆因我不知道“重构”(refactoring)。

    或许你第一次看到这个词,会欢呼雀跃,耶,“重构”,网站重构,一切重新再来?很明显,不是。相反,它的过程是逐步的,有时甚至是很微小的。作为一门在编程中的高级技术,重构是指“在不改变代码外在行为的前提下,对代码作出修改,以改进程序的内部结构”(《重构:改善既有代码的设计》,2003,中国电力出版社)。对于 HTML 来说,就是不改变 HTML 所表达意义的基础上,对 HTML 作出修改,以改进 HTML 的内部结构。是不是很简单?

    读者都知道 web 标准的意义所在,很明显,重构的目的是改善既有的 HTML,向 web 标准进军。重点在于改善,而不是取代。在很多情况下,取代的代价远比改善大得多。如果才能做到最大化的投入产出比,很明显,答案是重构。

    啰嗦这么多,到底怎么重构?特别是 HTML, 怎么重构啊,看着堆垃圾我就头痛,是不?很多公司的网站,我们可以看到,比如腾讯,雅虎的新推出的页面,都是基于 web 标准的了,但还是存在大量的旧页面,就算访问量巨大,也是纹丝不动。我相信,这很大程度上不是由于不想改,而是除了全盘推翻重头再来之外,实在找不到好的解决办法。重构是你的解药,宝贝。

    由世界知名的 XML 专家 Elliotte Rusty Harold 所著的 Refactoring HTML: Improving the Design of Existing Web Applications 是你解药的配方。书中大谈特谈的章节不多,就第一章说说who, what, why, when, where,其他章节都是说 how. how, yeah, 就是到底怎么重构的锦囊妙计。书中从编程世界中“窃取”被证明是可行的、成熟的技术,很多可能是 web 标准实践者闻所未闻的技术,结合到 HTML 来,创造行之有效的新技术,解决 HTML 特有的难题。比方说,你关心过 GET 和 POST 是什么吗?知道为何 Google 爬虫爬一爬,很多页面消失殆尽吗?(嗯嗯,老油条们,我知道你知道(绕吧?),我在这里问的是“页面仔”)。关心过自动化测试吗?什么,你还刀耕火种,写一行就在浏览器刷一下看效果?知道怎么样批量校验你的页面吗?不要告诉我你一页又一页的把上千个页面轮番轰炸 W3C Validator…

    不知道?那怎么还浪费时间看什么书评,赶紧的打开书来看啊!噢哦,没有书?google 一下,有得下。英文看不懂?很吃力?噢哦,明年中文版就会上市,嗯嗯,译者就是我……(终于露出狰狞面目)……

    本书是探讨 HTML 领域的重构,是对重构这门技术的又一创造性运行。它从工具入手(我们知道,工具是重构的重要辅助),然后逐一分析良构(well-formness)、合法性、布局、无障碍、web 应用程序和内容等等需要重构的方面,提出问题,讨论理据(motivation)并提供行之有效的解决方案(mechanics)。对于接触 web 标准较少的人来说,提供了一个百科全书式的的参考,而对于我的同行们,sure, 亲爱的页面仔们,不仅可以查漏补缺,亦可学习程序工业中的先进经验,提升自己的“工业”素养。总之,这不是一本束之高阁的书。

    编写 HTML 不仅是一门技术,更是一门艺术。你会了解的,在读了本书之后。在此我很严肃地说。仅以此献给中国所有的页面仔,共勉。

  • Permalink for '富文本编辑器的基本原理与实践'

    富文本编辑器的基本原理与实践

    Posted: 05 1st, 2008, 2:18pm PDT by realazy

    富文本编辑器,Rich Text Editor, 简称 RTE, 它提供类似于 Microsoft Word 的编辑功能,容易被不会编写 HTML 的用户并需要设置各种文本格式的用户所喜爱。它的应用也越来越广泛。最先只有 IE 浏览器支持,其它浏览器相继跟进,在功能的丰富性来说,还是 IE 强些。虽然没有一个统一的标准,但对于最基本的功能,各浏览器提供的 API 基本一致,从而使编写一个跨浏览器的富文本编辑器成为可能。

    在很多开发者看来,富文本编辑器的编写是一件很神秘或者复杂的事情。神秘倒没有,复杂的话,确实如此。但是它的基本原理并不复杂,入门也不难。今天我们的主题是讲述基本原理,并逐步演示一个简单富文本编辑器的产生。这是我在 D2 上的一个分享内容,在台上的演讲效果不佳,固写下来,希望能够对感兴趣的读者有所帮助。

    富文本编辑器的基本原理

    这个原理实在是太简单了!对于支持富文本编辑的浏览器来说,其实就是设置 documentdesignMode 属性为 on 后,再通过执行 document.execCommand('commandName'[, UIFlag[, value]]) 即可。commandNamevalue 可以在 MSDN 上MDC 上找到,它们就是我们创建各种格式的命令,比方说,我们要加粗字体,执行 document.execCommand('bold', false) 即可。很简单是吧?但是值得注意的是,通常是选中了文本后才执行命令,被选中的文本才被格式化。对于未选中的文本进行这个命令,各浏览器有不同的处理方式,比方 IE 可能是对位于光标中的标签内容进行格式化,而其它浏览器不做任何处理,这超出本文的内容,不细述。同时需要注意的是,UIFlag 这个参数设置为 true 表示 display any user interface triggered by the command (if any), 在我们今天的教程中都是 false, 而 value 也只在某些 commandName 中才有,具体参考以上刚给出的两个链接。

    为了不影响当前 document, 通常的做法是在页面中嵌入一个 iframe 元素,然后对这个 iframe 内的 document(通过 iframe.contentWindow.document 获得)进行操作。

    十分简单,是吧?下面我们来动手做一个。

    编写一个简单的富文本编辑器

    这个例子使用了 YUI. 即使你对它不是很熟悉也没有关系,我在这里只使用了它的 DOM 和 Event 的一些跨平台基本方法。

    搭架

    在此强调一下很久未曾提及的 unobtrusive. 我们的编辑器是对 textarea 元素的一个增强(enhencement),就是说,即使 JavaScript 被禁用了,用户还可以通过 textarea 编辑内容。

    在这个例子中,我们将使用 YAHOO.realazy 的命名空间,在之下实现一个 RTE 的类。我们今天的编辑器很简单,因此构造器(constructor) 的参数也只有 textarea 一个。我们使用一个实例变量来保存工具条的各个项目。实例初始化放到一个叫 render 的方法中。这一步的页面和代码见第 1 步

    创建 iframe 并替换 textarea

    搭好架子,正如我在前面所说,建立一个 iframe, 编辑器的所有操作都在 iframedocument 内执行。并且把 textarea 隐藏起来。从第 2 步中可以看到,我们已经有了一个 iframe, 但不能输入任何东西,很正常,我们没有打开它的 designMode 嘛。

    开启 designMode

    这一步涉及的东西挺多,也是关键。我们会创建获取 iframedocument 的方法,并通过程序的方式向 iframe 写入空页而非使用一个外接的 blank.html. 我们使用一个类属性 YAHOO.realazy.RTE.htmlContent 来保存空页的 html. 在准备好一切后,就可以开启 designMode 了。页面和代码详见第 3 步。看,我们已经可以在 iframe 里输入东西了。

    构建工具条

    我们需要操作的工具条!这样才可以控制 iframe 里的内容,才能称之为编辑器。在此我并不打算实现太多的功能,只是选择字形、字号、加粗、斜体、下划线、居左、居中、居右、超链接和插图作为示例。对于跨平台,Mozilla Midas Specification 是不错的参考。ok, 请看第 4 步,我们的工具条出来了,虽然很丑。我同时用 CSS 对 iframe 的宽度做出了一些调整。

    给工具条加上事件

    嗯,工具条出来了,编辑器看起来也“人模狗样”了,你兴奋的点啊点,没什么效果……意料中嘛。我们接着给工具条绑定一些事件,让编辑器内容能够响应工具条。在这一步,我们把 execCommand 再封一层,前面说过,我们用不上 UIFlag,让它永远是 false 好了。好,有代码就有真相,请看第 5 步。如果是正使用 IE, 请先暂时转移到其它浏览器。看到了吧,工具条生效了!

    解决 IE 的问题

    well, 如果你没有听我的劝告,依然使用 IE, 你会发现除了字型和字号其它的都不能用。为什么呢?你观察一下,有没有发现,其它浏览器选择文本后,再点击工具条上的项目,被选中的文本是否依然选中的?而 IE 呢,在点击工具条时,选中的文本马上失去选中的状态,所以它们就失败了。所以,如果我们能够保证点击工具条文本保持选中状态,就可以解决 IE 的问题了。

    Microsoft 给 HTML 标签一个很奇怪的属性 unselectable, 只要设置为 on, 焦点不会转移到点击的元素上,从而保证文本的选中状态。

    请看第 6 步。这也是解决 IE 头痛问题的关键所在。我曾经在这上面费了很大脑筋。

    高级主题展望

    good, 看看我们现在的代码,224 行。相比其它动辄上万行的编辑器,你可能会觉得不可思议。因为我们这个最基本的编辑器,连 selection 都没有用到。很多很酷的效果,比如 Google Doc 里能够动态改变链接文本,使用页内层而非弹出的 prompt 来操作等高级功能,基本上都要用到 TextRange(IE) 或者 Range(W3C). 要命的是这两个东西互补兼容,只是相似而已。入门推荐看PPK 的 Introduction to Range.

    在此我们就不深入了,等我有时间我会总结一些奇技淫巧(呜呼,前端开发需要的奇技淫巧太多了,这不是好事情)出来。

  • Permalink for 'JavaScript Memoization'

    JavaScript Memoization

    Posted: 04 22nd, 2008, 8:57am PDT by realazy

    Memoization 是一种将函数返回值缓存起来的方法,在 Lisp, Ruby, Perl, Python 等语言中使用非常广泛。随着 Ajax 的兴起,客户端对服务器的请求越来越密集(经典如 autocomplete),如果有一个良好的缓存机制,那么客户端 JavaScript 程序的效率的提升是显而易见的。

    Memoization 原理非常简单,就是把函数的每次执行结果都放入一个散列表中,在接下来的执行中,在散列表中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才真正执行函数体的求值部分。很明显,找值,尤其是在散列中找值,比执行函数快多了。现代 JavaScript 的开发也已经大量使用这种技术。

    我通过 Google 寻找了好几种 JavaScript Memoization 的实现,都不太如人愿,有的实现不能缓存递归函数,有的需要修改函数的 prototype,于是自己实现一个:

    /**
     * JavaScript Momoization
     * @param {string} func name of function / method
     * @param {object} [obj] mothed's object or scope correction object
     *
     * MIT / BSD license
     */
    
    function Memoize(func, obj){
        var obj = obj || window,
            func = obj[func],
            cache = {};
        return function(){
            var key = Array.prototype.join.call(arguments, "");
            var key = Array.prototype.join.call(arguments, "_")
            if (!(key in cache))
                cache[key] = func.apply(obj, arguments);
            return cache[key];
        }
    }

    并写了一个测试案例,空口无凭,让大家亲自看看 Memoization 的威力。

    见: [realazy.org]

    另,例子中的 fibonacci 函数有很多更有效率的实现方法,在此我使用最无效率的递归实现,只是为了更直达地演示 memoization.

    又,longwosion 留言所提到的 key 唯一性问题,我略作修正,但应该还有更好的办法,欢迎您留言探讨。

  • Permalink for '测试 Google App Engine'

    测试 Google App Engine

    Posted: 04 11th, 2008, 7:57am PDT by realazy

    一不小心从某 blog 中第一时间发现 Google App Engine 发布,立马注册一个。当天晚上抽空看了看文档,做了做hello world,第二天晚上开始写一个聊天室程序,第三天晚上拿出 Python 技术手册,捣腾到今天才把一个简单的东西弄出来。去年学了一段时间的 Python, 还用 webpy 写了一个不成型的 blog 系统。某段时间认识自己不足,苦读了两个月的 C,后来买了 macbook, 又一头扎进 Objective-C 和 Cocoa, 哈哈哈,总之,一事无成吧。现在 GAE 出来了,突然发现一直寻找的 Python hosting 就这么从天上掉下来了,而且还是馅饼……重新激起 Python 兴趣,却发现又忘得差不多了……

    废话太多了……这个测试 demo 叫 chatlazy, 位于 [chatlazy.appspot.com] . 是一个简易聊天室,后台部分,就是 Python 了,具体一点,是 webpy 0.3 (开发版,未发布)。机制十分简单,就是前端使用 JavaScript 隔 5 秒去提取后台的最新消息。有几个小细节还是值得总结一下的:

    1. 由于 GAE 的数据 ID 使不能用在 Gql 中的,我只能通过时间戳来比对消息状态。把 datetime 和秒数 + 毫秒数的互转,还是比较繁琐的。Python 技术手册帮了我很大忙。解决方案大致如此:
      str(time.mktime(d.timetuple()))[:-1] + str(d.microsecond)

      反过来则是:

      p = str(t).split('.')
      tp = time.localtime(float(p[0]))
      dt = datetime(tp[0], tp[1], tp[2], tp[3], tp[4], tp[5]+1, int(p[1]))
    2. 对于 iterable 的对象, 先要 list 它转成列表,才可以使用 reversed 等相关方法。
    3. 需要取最新的 n 条信息,即数据库末尾的 n 条,但是又要顺序,可以先按逆序取 n 条,再反向排序(由此引发上条启示)。
    4. 对于任何用户输入的东西都要做过滤,一开始我在用户名那块忽略了,结果马上有人 XSS 了。这应该是基本常识,应铭记于心。
    5. 一定要处理异常。

    由于 GAE 这个天上掉的馅饼,我想我近期的精力会放到 Python 上了,有计划地把 blog 迁徙到 GAE 上,并开发一些有趣地程序。GAE rocks. 老实说,这是搜索、Gmail 后,对我而言可以排到第三的 Google 服务了。