mootools 源码分析之 Core.js

本早就想写一篇这样的博客,也许是mootools的一个系列吧,不过估计最近不会有充足的时间,所以说系列就太挖坑了。感兴趣的同学可以继续,我觉得从mootools的源码出发,可以写出一本不错的Javascript的中级的教材,如果我有时间我会就会发出系列的口号的。

这次我分析的是mootools的核心,Core.js。我看的版本是1.2.4dev,是从github上clone下来的。git clone git://github.com/mootools/mootools-core.git。Core.js最核心的就是Native函数了,然后还有一些常用的工具,以及对于Object和Array的简单实用扩展,其中Object的扩展类名为Hash。我把分析都详细的写到了代码里,所以大家可以耐心的好好读一下,我相信一定有不少收获。

var MooTools = {
    'version': '1.2.4dev',
    'build': '%build%'
};
// Native是mootools的绝对核心,所有的类的构造都会由Native函数完成
var Native = function(options){
    // 如果没有任何配置,就是一个初始的空的对象
    options = options || {};
    // 从配置指定类名
    var name = options.name;
    // 用于对内置对象包装时,保留的原内置对象
    var legacy = options.legacy;
    // 是否防止原对象的prototype中的方法被覆盖
    var protect = options.protect;
    // 类需要添加的属性或方法,如果generics不是false就在prototype里添加,同时静态化
    // 这里methods表明作者希望options.implement传入的都是方法
    var methods = options.implement;
    // 是否把对象的所有属性和方法静态化,如果不想静态化,只能设置为false值本身
    var generics = options.generics;
    // 初始化方法
    var initialize = options.initialize;
    // 用于add方法的后续操作
    var afterImplement = options.afterImplement || function(){};
    // 如果没有提供initialize方法就使用内置对象作为原型
    var object = initialize || legacy;
    // 只用generics恒等为false时取false,其它情况都作true处理
    generics = generics !== false;
    // 设置类的构造器
    object.constructor = Native;
    // 统一类对象的静态属性$family对象的name值为native,$type函数不会使用这个值
    object.$family = {name: 'native'};
    // 继承legacy的原型
    if (legacy && initialize) object.prototype = legacy.prototype;
    object.prototype.constructor = object;
    if (name){
        // 类型名统一为小写
        var family = name.toLowerCase();
        // 写入原型链,这样修改原型就可以让所有实例的$family变化,这在调整父类名称时很有用
        // 这个原型链的$family是$type函数真正要用到的
        object.prototype.$family = {name: family};
        // 使object.type(someObject)能判断object与someObject是否为同一类型
        Native.typize(object, family);
    }
    // 为对象添加属性或方法的函数(在prototype,以及根据generics同时为object添加成静态属性或方法 )
    // 参数名method,表明作者希望add的都是方法
    var add = function(obj, name, method, force){
        // 仅当不受保护或者强制覆盖或者对象prototype中不存在该属性或方法时添加
        if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
        // 静态化
        if (generics) Native.genericize(obj, name, protect);
        // 添加方法后的操作
        afterImplement.call(obj, name, method);
        return obj;
    };
    // 给类对象中的属性或方法取别名
    object.alias = function(a1, a2, a3){
        // 如果为object.alias('forEach', 'each')
        if (typeof a1 == 'string'){
            var pa1 = this.prototype[a1];
            // 如果pa1有值,也就是a1是prototype中的一个属性或者方法
            if ((a1 = pa1)) return add(this, a2, a1, a3);
        }
        // 如果a1是一个集合,那就批处理
        // 如:object.alias({func1: 'functionOne', func2: 'functionTwo'})
        for (var a in a1) this.alias(a, a1[a], a2);
        // 支持链式操作
        return this;
    };
    // 对类对象进行扩展(增加属性和方法)
    object.implement = function(a1, a2, a3){
        if (typeof a1 == 'string') return add(this, a1, a2, a3);
        for (var p in a1) add(this, p, a1[p], a2);
        // 支持链式操作
        return this;
    };
    // 添加配置中指定的扩展实现(增加属性和方法)
    if (methods) object.implement(methods);
    // 返回类对象
    return object;
};
// 静态化类对象的属性或方法
// check为true则不会覆盖已有的静态同名属性或方法
Native.genericize = function(object, property, check){
    if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
        var args = Array.prototype.slice.call(arguments);
        // 参数列表的第一个参数做为原实例方法的this引用传入
        return object.prototype[property].apply(args.shift(), args);
    };
};
// 同时对多个对象进行扩展(增加属性和方法)
Native.implement = function(objects, properties){
    for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
};
// 让类实例增加type类型判断方法,利用$type判断是否为同一类或者继承自同一父类
Native.typize = function(object, family){
    if (!object.type) object.type = function(item){
        return ($type(item) === family);
    };
};
(function(){
    // 让Javascript的核心内置对象Native化,即加入一些方便以后扩展的属性和方法,已存在的不会被 覆盖
    var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
    for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
    // 让Boolean、Native和Object对象实例具有type方法,可以做类型判断
    var types = {'boolean': Boolean, 'native': Native, 'object': Object};
    for (var t in types) Native.typize(types[t], t);
    // 将Array和String的实例方法静态化到Array和String本身
    var generics = {
        'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
        'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
    };
    for (var g in generics){
        for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
    }
})();
var Hash = new Native({
    // 指定类名,用于$type方法的类型判断
    name: 'Hash',
    initialize: function(object){
        // 如果object是Hash对象,则复制一个干净的副本,
        // 先使用getClean方法得到object对象本身的属性和方法(不包括其prototype的),
        // 再使用$unlink方法解除属性中值为对象的引用
        if ($type(object) == 'hash') object = $unlink(object.getClean());
        // 开始复制
        for (var key in object) this[key] = object[key];
        return this;
    }
});
Hash.implement({
    // 遍历Hash实例的属性和方法,执行fn函数,第一参数是属性或者方法的值,第二个参数是key名称
    forEach: function(fn, bind){
        for (var key in this){
            if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
        }
    },
    // 得到一个干净的拷贝,不会拷贝继承的属性或方法
    getClean: function(){
        var clean = {};
        for (var key in this){
            if (this.hasOwnProperty(key)) clean[key] = this[key];
        }
        return clean;
    },
    // 得到Hash实例的属性和方法的总数量,不包括继承的属性和方法
    getLength: function(){
        var length = 0;
        for (var key in this){
            if (this.hasOwnProperty(key)) length++;
        }
        return length;
    }
});
// 取Hash类的forEach方法别名为each
Hash.alias('forEach', 'each');
Array.implement({
    // 遍历Array实例,执行fn函数,第一参数是值,第二个参数是数组下标
    forEach: function(fn, bind){
        for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
    }
});
// 取Array类的forEach方法别名为each
Array.alias('forEach', 'each');
// 把传入的迭代对象转换为数组Array
function $A(iterable){
    // 如果是DOM集合
    if (iterable.item){
        var l = iterable.length, array = new Array(l);
        while (l--) array[l] = iterable[l];
        return array;
    }
    return Array.prototype.slice.call(iterable);
};
// 返回一个函数,这个函数返回它接受的参数列表的第i项
function $arguments(i){
    return function(){
        return arguments[i];
    };
};
// 检查对象的属性或方法是否已定义或者变量是否已赋值或者是否为0
function $chk(obj){
    return !!(obj || obj === 0);
};
// 通用计时器清除方法,如果timer不存在也不会报错
function $clear(timer){
    clearTimeout(timer);
    clearInterval(timer);
    return null;
};
// 检查对象的属性是否已定义或者变量是否已赋值
// 即对象不是undefined
function $defined(obj){
    return (obj != undefined);
};
// 迭代对象,对里面的所有属性或在方法执行fn(value, key)
function $each(iterable, fn, bind){
    // 获取迭代对象的类型名称
    var type = $type(iterable);
    // 如果为arguments、collection或者array中的一种,则执行Array的each方法,
    // 其它情况则执行Hash的each方法
    ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
};
// 空函数
function $empty(){};
// 通过浅拷贝实现original的扩展,注意不会解除引用
function $extend(original, extended){
    for (var key in (extended || {})) original[key] = extended[key];
    return original;
};
// 实例化Hash对象的快捷方式
function $H(object){
    return new Hash(object);
};
// 如果传入一个函数,就返还这个函数,如果传入的是一个其它值,则生成一个返回这个值的函数
function $lambda(value){
    return ($type(value) == 'function') ? value : function(){
        return value;
    };
};
// 递归合并参数列表中的所有对象,后出现的属性或者方法会覆盖前面出现的,
// 为深拷贝,会利用$unlink解除引用
function $merge(){
    // 转换arguments为真的Array对象,从而可以使用unshift等Array的方法
    var args = Array.slice(arguments);
    // 在数组最前面插入一个空对象
    args.unshift({});
    return $mixin.apply(null, args);
};
// 递归合并所有参数列表中的对象的属性和方法,后面的会覆盖前面的
// 这里的mix就是上面插入的{}空的对象,也是参数列表的第一个参数
function $mixin(mix){
    for (var i = 1, l = arguments.length; i < l; i++){
        var object = arguments[i];
        // 只对object对象才处理,$merge的参数必须要对象才会启作用
        if ($type(object) != 'object') continue;
        for (var key in object){
            var op = object[key], mp = mix[key];
            mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
        }
    }
    return mix;
};
// 获取传入参数列表中从左至右第一个定义赋值过的对象属性或方法,
// 或者第一个赋值过的变量的值
// 即第一个不为undefined的值
function $pick(){
    for (var i = 0, l = arguments.length; i < l; i++){
        if (arguments[i] != undefined) return arguments[i];
    }
    return null;
};
// 在min和max之间取一个伪随机数
function $random(min, max){
    return Math.floor(Math.random() * (max - min + 1) + min);
};
// 把传入的对象转换为一个数组包裹,如果本身就是数组则直接返回该数组,undefined的则返回空数组
function $splat(obj){
    var type = $type(obj);
    return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
};
// 返回当前的时间戳,相当于new Date().getTime()
var $time = Date.now || function(){
    // 如果没有Date.now函数,则直接获取new Date对象的valueOf值,这里的+号就起了这个转换作用
    return +new Date;
};
// 在简单的try-catch中依次执行参数列表中的所有函数调用
function $try(){
    for (var i = 0, l = arguments.length; i < l; i++){
        try {
            return arguments[i]();
        } catch(e){}
    }
    return null;
};
// 判断实例对象是什么类型
function $type(obj){
    if (obj == undefined) return false;
    // NaN或正无穷或负无穷也会返回false
    if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
    // 如果是DOM单个节点
    if (obj.nodeName){
        switch (obj.nodeType){
            //Element元素
            case 1: return 'element';
            //文本节点
            case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
        }
    } else if (typeof obj.length == 'number'){
        if (obj.callee) return 'arguments';
        // DOM集合(HTMLElements collection)
        else if (obj.item) return 'collection';
    }
    return typeof obj;
};
// 解除对象的引用,使其独立,返回的对象被修改而不会影响原来的对象
function $unlink(object){
    var unlinked;
    switch ($type(object)){
        case 'object':
            unlinked = {};
            for (var p in object) unlinked[p] = $unlink(object[p]);
        break;
        case 'hash':
            // Hash里本身又会调用$unlink
            unlinked = new Hash(object);
        break;
        case 'array':
            unlinked = [];
            for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
        break;
        default: return object;
    }
    return unlinked;
};

从我的角度出发,我觉得核心应该写得更加精简,当然这个度是需要数据去说话的,mootools目前这个Core我觉得Hash和Array的扩展是没有必要放到Core.js里的,这样相关的$H也可以去掉,还觉得$chk和$defined写得有点部份重复,浪费代码空间。微核心的好处是能让上面的扩展能更加灵活,从服务于产品的角度就是可以更小,因为可以按需build更小的js,昂贵的流量也就节约了,用户的等待也会减少。同时如果framework服务的业务异常复杂,也可以更灵活的产生少数合理的分枝。这就可以解决framework维护成本和本生性能的矛盾。

转自:http://blog.csdn.net/siren0203/archive/2010/12/29/6105084.aspx

posted @ 2011-05-10 11:41  oneroundseven  阅读(2114)  评论(0编辑  收藏  举报