2007-07-29

prototype.js解读

关键字: prototype.js,解读,注释

/*
 * 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号
 */
var Prototype = {
    Version: '1.5.0_rc0',
    ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',

    emptyFunction: function()
    {
    },
    K: function(x)
    {
        return x
    }
}

/*
 * 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。
 * 一般使用如下 
 *     var X = Class.create();  返回一个类型,类似于 java 的一个Class实例。
 * 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的 Class.newInstance()方法。
 *
 * 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。
 * 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。
 *
 * 如果一定要从java上去理解。你可以理解为用Class.create()创建一个继承java.lang.Class类的类。当然java不允许这样做,因为Class类是final的
 *
 */
var Class = {
    create: function()
    {
        return function()
        {
            this.initialize.apply(this, arguments);
        }
    }
}

/*
 * 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都 extend 它。
 * 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。
 * 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。
 *
 * 从java去理解,就是动态给一个对象创建内部类。
 */
var Abstract = new Object();

/*
 * 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。
 * 如:
 *     var a = new ObjectA(), b = new ObjectB();
 *     var c = a.extend(b);
 * 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,c instanceof ObjectB 将返回false。
 */
Object.extend = function(destination, source)
{
    for (var property in source)
    {
        destination[property] = source[property];
    }
    return destination;
}

Object.inspect = function(object)
{
    try
    {
        if (object == undefined) return 'undefined';
        if (object == null) return 'null';
        return object.inspect ? object.inspect() : object.toString();
    }
    catch (e)
    {
        if (e instanceof RangeError) return '...';
        throw e;
    }
}

/*
 * 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。
 * 也就是说新函数中的 this 引用被改变为参数提供的对象。
 * 比如:
 *     <input type="text" id="aaa" value="aaa">
 *     <input type="text" id="bbb" value="bbb">
 *     .................
 *     <script>
 *         var aaa = document.getElementById("aaa");
 *         var bbb = document.getElementById("bbb");
 *         aaa.showValue = function() {alert(this.value);}
 *         aaa.showValue2 = aaa.showValue.bind(bbb);
 *     /script>
 *  那么,调用aaa.showValue 将返回"aaa", 但调用aaa.showValue2 将返回"bbb"。
 *
 * apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。
 * 该方法更多的资料参考MSDN http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp
 * 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。
 */
Function.prototype.bind = function()
{
    var __method = this, args = $A(arguments), object = args.shift();
    return function()
    {
        return __method.apply(object, args.concat($A(arguments)));
    }
}

/*
 * 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象
 * 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参数形式的定义。
 * 如同 java 两个过载的方法。
 */
Function.prototype.bindAsEventListener = function(object)
{
    var __method = this;
    return function(event)
    {
        return __method.call(object, event || window.event);
    }
}

/*
 * 将整数形式RGB颜色值转换为HEX形式
 */
Object.extend(Number.prototype, {
    toColorPart: function()
    {
        var digits = this.toString(16);
        if (this < 16) return '0' + digits;
        return digits;
    },

    succ: function()
    {
        return this + 1;
    },

    times: function(iterator)
    {
        $R(0, this, true).each(iterator);
        return this;
    }
});

/*
 * 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值
 */
var Try = {
    these: function()
    {
        var returnValue;

        for (var i = 0; i < arguments.length; i++)
        {
            var lambda = arguments[i];
            try
            {
                returnValue = lambda();
                break;
            }
            catch (e)
            {
            }
        }

        return returnValue;
    }
}

/*--------------------------------------------------------------------------*/

/*
 * 一个设计精巧的定时执行器
 * 首先由 Class.create() 创建一个 PeriodicalExecuter 类型,
 * 然后用对象直接量的语法形式设置原型。
 *
 * 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind, 并传递自己为参数。
 * 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,如果 registerCallback 方法定义如下的话:
 *     registerCallback: function() {
 *         setTimeout(this.onTimerEvent, this.frequency * 1000);
 *     }
 * 那么,this.onTimeoutEvent 方法执行失败,因为它无法访问 this.currentlyExecuting 属性。
 * 而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。
 */
var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
    initialize: function(callback, frequency)
    {
        this.callback = callback;
        this.frequency = frequency;
        this.currentlyExecuting = false;

        this.registerCallback();
    },

    registerCallback: function()
    {
        setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
    },

    onTimerEvent: function()
    {
        if (!this.currentlyExecuting)
        {
            try
            {
                this.currentlyExecuting = true;
                this.callback();
            }
            finally
            {
                this.currentlyExecuting = false;
            }
        }
    }
}
Object.extend(String.prototype, {
    gsub: function(pattern, replacement)
    {
        var result = '', source = this, match;
        replacement = arguments.callee.prepareReplacement(replacement);

        while (source.length > 0)
        {
            if (match = source.match(pattern))
            {
                result += source.slice(0, match.index);
                result += (replacement(match) || '').toString();
                source = source.slice(match.index + match[0].length);
            }
            else
            {
                result += source,source = '';
            }
        }
        return result;
    },

    sub: function(pattern, replacement, count)
    {
        replacement = this.gsub.prepareReplacement(replacement);
        count = count === undefined ? 1 : count;

        return this.gsub(pattern, function(match)
        {
            if (--count < 0) return match[0];
            return replacement(match);
        });
    },

    scan: function(pattern, iterator)
    {
        this.gsub(pattern, iterator);
        return this;
    },

    truncate: function(length, truncation)
    {
        length = length || 30;
        truncation = truncation === undefined ? '...' : truncation;
        return this.length > length ?
               this.slice(0, length - truncation.length) + truncation : this;
    },

    strip: function()
    {
        return this.replace(/^\s+/, '').replace(/\s+$/, '');
    },

    stripTags: function()
    {
        return this.replace(/<\/?[^>]+>/gi, '');
    },

    stripScripts: function()
    {
        return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
    },

    extractScripts: function()
    {
        var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
        var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
        return (this.match(matchAll) || []).map(function(scriptTag)
        {
            return (scriptTag.match(matchOne) || ['', ''])[1];
        });
    },

    evalScripts: function()
    {
        return this.extractScripts().map(function(script)
        {
            return eval(script)
        });
    },

    escapeHTML: function()
    {
        var div = document.createElement('div');
        var text = document.createTextNode(this);
        div.appendChild(text);
        return div.innerHTML;
    },

    unescapeHTML: function()
    {
        var div = document.createElement('div');
        div.innerHTML = this.stripTags();
        return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
    },

    toQueryParams: function()
    {
        var pairs = this.match(/^\??(.*)$/)[1].split('&');
        return pairs.inject({}, function(params, pairString)
        {
            var pair = pairString.split('=');
            params[pair[0]] = pair[1];
            return params;
        });
    },

    toArray: function()
    {
        return this.split('');
    },

    camelize: function()
    {
        var oStringList = this.split('-');
        if (oStringList.length == 1) return oStringList[0];

        var camelizedString = this.indexOf('-') == 0
                ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
                : oStringList[0];

        for (var i = 1, len = oStringList.length; i < len; i++)
        {
            var s = oStringList[i];
            camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
        }

        return camelizedString;
    },

    inspect: function()
    {
        return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'";
    }
});

String.prototype.gsub.prepareReplacement = function(replacement)
{
    if (typeof replacement == 'function') return replacement;
    var template = new Template(replacement);
    return function(match)
    {
        return template.evaluate(match)
    };
}

String.prototype.parseQuery = String.prototype.toQueryParams;

var Template = Class.create();
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.prototype = {
    initialize: function(template, pattern)
    {
        this.template = template.toString();
        this.pattern = pattern || Template.Pattern;
    },

    evaluate: function(object)
    {
        return this.template.gsub(this.pattern, function(match)
        {
            var before = match[1];

            if (before == '\\') return match[2];
            return before + (object[match[3]] || '').toString();
        });
    }
}

var $break = new Object();
var $continue = new Object();

var Enumerable = {
    each: function(iterator)
    {
        var index = 0;
        try
        {
            this._each(function(value)
            {
                try
                {
                    iterator(value, index++);
                }
                catch (e)
                {
                    if (e != $continue) throw e;
                }
            });
        }
        catch (e)
        {
            if (e != $break) throw e;
        }
    },

    all: function(iterator)
    {
        var result = true;
        this.each(function(value, index)
        {
            result = result && !!(iterator || Prototype.K)(value, index);
            if (!result) throw $break;
        });
        return result;
    },

    any: function(iterator)
    {
        var result = true;
        this.each(function(value, index)
        {
            if (result = !!(iterator || Prototype.K)(value, index))
                throw $break;
        });
        return result;
    },

    collect: function(iterator)
    {
        var results = [];
        this.each(function(value, index)
        {
            results.push(iterator(value, index));
        });
        return results;
    },

    detect: function (iterator)
    {
        var result;
        this.each(function(value, index)
        {
            if (iterator(value, index))
            {
                result = value;
                throw $break;
            }
        });
        return result;
    },

    findAll: function(iterator)
    {
        var results = [];
        this.each(function(value, index)
        {
            if (iterator(value, index))
                results.push(value);
        });
        return results;
    },

    grep: function(pattern, iterator)
    {
        var results = [];
        this.each(function(value, index)
        {
            var stringValue = value.toString();
            if (stringValue.match(pattern))
                results.push((iterator || Prototype.K)(value, index));
        })
        return results;
    },

    include: function(object)
    {
        var found = false;
        this.each(function(value)
        {
            if (value == object)
            {
                found = true;
                throw $break;
            }
        });
        return found;
    },

    inject: function(memo, iterator)
    {
        this.each(function(value, index)
        {
            memo = iterator(memo, value, index);
        });
        return memo;
    },

    invoke: function(method)
    {
        var args = $A(arguments).slice(1);
        return this.collect(function(value)
        {
            return value[method].apply(value, args);
        });
    },

    max: function(iterator)
    {
        var result;
        this.each(function(value, index)
        {
            value = (iterator || Prototype.K)(value, index);
            if (result == undefined || value >= result)
                result = value;
        });
        return result;
    },

    min: function(iterator)
    {
        var result;
        this.each(function(value, index)
        {
            value = (iterator || Prototype.K)(value, index);
            if (result == undefined || value < result)
                result = value;
        });
        return result;
    },

    partition: function(iterator)
    {
        var trues = [], falses = [];
        this.each(function(value, index)
        {
            ((iterator || Prototype.K)(value, index) ?
             trues : falses).push(value);
        });
        return [trues, falses];
    },

    pluck: function(property)
    {
        var results = [];
        this.each(function(value, index)
        {
            results.push(value[property]);
        });
        return results;
    },

    reject: function(iterator)
    {
        var results = [];
        this.each(function(value, index)
        {
            if (!iterator(value, index))
                results.push(value);
        });
        return results;
    },

    sortBy: function(iterator)
    {
        return this.collect(function(value, index)
        {
            return {value: value, criteria: iterator(value, index)};
        }).sort(function(left, right)
        {
            var a = left.criteria, b = right.criteria;
            return a < b ? -1 : a > b ? 1 : 0;
        }).pluck('value');
    },

    toArray: function()
    {
        return this.collect(Prototype.K);
    },
    zip: function()
    {
        var iterator = Prototype.K, args = $A(arguments);
        if (typeof args.last() == 'function')
            iterator = args.pop();

        var collections = [this].concat(args).map($A);
        return this.map(function(value, index)
        {
            return iterator(collections.pluck(index));
        });
    },

    inspect: function()
    {
        return '#<Enumerable:' + this.toArray().inspect() + '>';
    }
}

Object.extend(Enumerable, {
    map:     Enumerable.collect,
    find:    Enumerable.detect,
    select:  Enumerable.findAll,
    member:  Enumerable.include,
    entries: Enumerable.toArray
});
var $A = Array.from = function(iterable)
{
    if (!iterable) return [];
    if (iterable.toArray)
    {
        return iterable.toArray();
    }
    else
    {
        var results = [];
        for (var i = 0; i < iterable.length; i++)
            results.push(iterable[i]);
        return results;
    }
}

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse)
    Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
    _each: function(iterator)
    {
        for (var i = 0; i < this.length; i++)
            iterator(this[i]);
    },

    clear: function()
    {
        this.length = 0;
        return this;
    },

    first: function()
    {
        return this[0];
    },

    last: function()
    {
        return this[this.length - 1];
    },

    compact: function()
    {
        return this.select(function(value)
        {
            return value != undefined || value != null;
        });
    },

    flatten: function()
    {
        return this.inject([], function(array, value)
        {
            return array.concat(value && value.constructor == Array ?
                                value.flatten() : [value]);
        });
    },

    without: function()
    {
        var values = $A(arguments);
        return this.select(function(value)
        {
            return !values.include(value);
        });
    },

    indexOf: function(object)
    {
        for (var i = 0; i < this.length; i++)
            if (this[i] == object) return i;
        return -1;
    },

    reverse: function(inline)
    {
        return (inline !== false ? this : this.toArray())._reverse();
    },

    inspect: function()
    {
        return '[' + this.map(Object.inspect).join(', ') + ']';
    }
});
var Hash = {
    _each: function(iterator)
    {
        for (var key in this)
        {
            var value = this[key];

            if (typeof value == 'function') continue;

            var pair = [key, value];
            pair.key = key;
            pair.value = value;
            iterator(pair);
        }
    },

    keys: function()
    {
        return this.pluck('key');
    },

    values: function()
    {
        return this.pluck('value');
    },

    merge: function(hash)
    {
        return $H(hash).inject($H(this), function(mergedHash, pair)
        {
            mergedHash[pair.key] = pair.value;
            return mergedHash;
        });
    },

    toQueryString: function()
    {
        return this.map(function(pair)
        {
            return pair.map(encodeURIComponent).join('=');
        }).join('&');
    },

    inspect: function()
    {
        return '#<Hash:{' + this.map(function(pair)
        {
            return pair.map(Object.inspect).join(': ');
        }).join(', ') + '}>';
    }
}

function $H(object)
{
    var hash = Object.extend({}, object || {});
    Object.extend(hash, Enumerable);
    Object.extend(hash, Hash);
    return hash;
}
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
    initialize: function(start, end, exclusive)
    {
        this.start = start;
        this.end = end;
        this.exclusive = exclusive;
    },

    _each: function(iterator)
    {
        var value = this.start;
        do {
            iterator(value);
            value = value.succ();
        } while (this.include(value));
    },

    include: function(value)
    {
        if (value < this.start)
            return false;
        if (this.exclusive)
            return value < this.end;
        return value <= this.end;
    }
});

var $R = function(start, end, exclusive)
{
    return new ObjectRange(start, end, exclusive);
}

/*
 * 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象
 */
var Ajax = {
    getTransport: function()
    {
        return Try.these(
                function()
                {
                    return new XMLHttpRequest()
                },
                function()
                {
                    return new ActiveXObject('Msxml2.XMLHTTP')
                },
                function()
                {
                    return new ActiveXObject('Microsoft.XMLHTTP')
                }
                ) || false;
    },

    activeRequestCount: 0
}

Ajax.Responders = {
    responders: [],

    _each: function(iterator)
    {
        this.responders._each(iterator);
    },

    register: function(responderToAdd)
    {
        if (!this.include(responderToAdd))
            this.responders.push(responderToAdd);
    },

    unregister: function(responderToRemove)
    {
        this.responders = this.responders.without(responderToRemove);
    },

    dispatch: function(callback, request, transport, json)
    {
        this.each(function(responder)
        {
            if (responder[callback] && typeof responder[callback] == 'function')
            {
                try
                {
                    responder[callback].apply(responder, [request, transport, json]);
                }
                catch (e)
                {
                }
            }
        });
    }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
    onCreate: function()
    {
        Ajax.activeRequestCount++;
    },

    onComplete: function()
    {
        Ajax.activeRequestCount--;
    }
});

/*
 * 我以为此时的Ajax对象起到命名空间的作用。
 * Ajax.Base 声明为一个基础对象类型
 * 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不希望 Ajax.Base 被库使用者实例化。
 * 作者在其他对象类型的声明中,将会继承于它。
 * 就好像 java 中的私有抽象类
 */
Ajax.Base = function()
{
};
Ajax.Base.prototype = {
    setOptions: function(options)
    {
        this.options = {
            method:       'post',
            asynchronous: true,
            contentType:  'application/x-www-form-urlencoded',
            parameters:   ''
        }
        Object.extend(this.options, options || {});
    },

    responseIsSuccess: function()
    {
        return this.transport.status == undefined
                || this.transport.status == 0
                || (this.transport.status >= 200 && this.transport.status < 300);
    },

    responseIsFailure: function()
    {
        return !this.responseIsSuccess();
    }
}

/*
 * Ajax.Request 封装 XmlHttp
 */
Ajax.Request = Class.create();

/*
 * 定义四种事件(状态), 参考http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readystate_1.asp
 */
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
    initialize: function(url, options)
    {
        this.transport = Ajax.getTransport();
        this.setOptions(options);
        this.request(url);
    },

    request: function(url)
    {
        var parameters = this.options.parameters || '';
        if (parameters.length > 0) parameters += '&_=';

        try
        {
            this.url = url;
            if (this.options.method == 'get' && parameters.length > 0)

                this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

            Ajax.Responders.dispatch('onCreate', this, this.transport);

            /*
             * 此处好像强制使用了异步方式,而不是依照 this.options.asynchronous 的值
             */
            this.transport.open(this.options.method, this.url,
                    this.options.asynchronous);

            /*
             * 这里提供了 XmlHttp 传输过程中每个步骤的回调函数
             */
            if (this.options.asynchronous)
            {
                this.transport.onreadystatechange = this.onStateChange.bind(this);
                setTimeout((function()
                {
                    this.respondToReadyState(1)
                }).bind(this), 10);
            }

            this.setRequestHeaders();

            var body = this.options.postBody ? this.options.postBody : parameters;
            this.transport.send(this.options.method == 'post' ? body : null);

        }
        catch (e)
        {
            this.dispatchException(e);
        }
    },

    setRequestHeaders: function()
    {
        var requestHeaders =
                ['X-Requested-With', 'XMLHttpRequest',
                        'X-Prototype-Version', Prototype.Version,
                        'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];

        if (this.options.method == 'post')
        {
            requestHeaders.push('Content-type', this.options.contentType);

            if (this.transport.overrideMimeType)
                requestHeaders.push('Connection', 'close');
        }

        if (this.options.requestHeaders)
            requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);

        for (var i = 0; i < requestHeaders.length; i += 2)
            this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i + 1]);
    },

    onStateChange: function()
    {
        var readyState = this.transport.readyState;
        /*
         * 如果不是 Loading 状态,就调用回调函数
         */
        if (readyState != 1)
            this.respondToReadyState(this.transport.readyState);
    },

    header: function(name)
    {
        try
        {
            return this.transport.getResponseHeader(name);
        }
        catch (e)
        {
        }
    },

    evalJSON: function()
    {
        try
        {
            return eval('(' + this.header('X-JSON') + ')');
        }
        catch (e)
        {
        }
    },
    evalResponse: function()
    {
        try
        {
            return eval(this.transport.responseText);
        }
        catch (e)
        {
            this.dispatchException(e);
        }
    },

/*
 * 回调函数定义在 this.options 属性中,比如:
 var option = {
 onLoaded : function(req) {...};
 ......
 }
 new Ajax.Request(url, option);
 */
    respondToReadyState: function(readyState)
    {
        var event = Ajax.Request.Events[readyState];
        var transport = this.transport, json = this.evalJSON();

        if (event == 'Complete')
        {
            try
            {
                (this.options['on' + this.transport.status]
                        || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
                        || Prototype.emptyFunction)(transport, json);
            }
            catch (e)
            {
                this.dispatchException(e);
            }

            if ((this.header('Content-type') || '').match(/^text\/javascript/i))
                this.evalResponse();
        }

        try
        {
            (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
            Ajax.Responders.dispatch('on' + event, this, transport, json);
        }
        catch (e)
        {
            this.dispatchException(e);
        }

        if (event == 'Complete')

评论
liubing1985820 2007-08-08
是否可以更详细些,我指的是每个函数都可以解释一下
发表评论

您还没有登录,请登录后发表评论

ecchanger
搜索本博客
最近加入圈子
存档
最新评论