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

你可能感兴趣的:(mootools)