给KISSY源码写注释——kissy

有这么一个情况,大部分时候框架的API常常让人犹豫和不知所措。

除了jQuery,其他的类库大都是API看起来比源码要复杂,让人头疼。

kissy在一定程度上借鉴了jQuery的接口写法,但API Docs目前看起来更偏向YUI的风格(事实上,大部分类库的风格都类似,真正的差别还是在API设计上)。

所以准备要好好看看kissy的源码,并在此做个记录。

 

还是老毛病,嘴笨——同样的,文字也笨。所以直接在读源码的时候写自己的想法和疑问。

喜欢读源码的同学,看本文会觉得很直观。

 

这是第一篇,是关于kissy 核心部分的内容。边看边查资料、做些测试来验证自己的想法,同时还要关注工作,效率相当的低。

菜鸟希望各位高手能批评指正!

wgk1987#gmail.com

今天 Getting Started with KISSY发布了,有兴趣的同学可以参观一下

==========================================

文件下载:kissy-core.js

/*
/*
Copyright 2010, KISSY UI Library v1.1.4
MIT Licensed
build time: Sep 13 17:31
*/
/**
 * @module kissy
 * @author lifesinger@gmail.com
 */
// 在变量没有赋予初始值的时候,js预编译的时候默认设置为window.undefined的引用。
// 当我们将变量和undefined比较时,实际上是从window对象上取得undefined属性的值,再与变量相比较。而变量的查找总是先从局部作用域开始,所以定义一个局部的undefined变量能解决一点性能上的问题。同时,也能加大压缩力度。
//
// 关于 undefined 和 null
// 1、window.ake == undefined   => true
// 2、ake == undefined          => throw error "ake is not defined"
//      规范的情况下,我们总是会申明变量,倒是不会出这个错误。
//      话说 2 不合理。已经声明了但没有赋值的变量的默认值是undefined。ake还没有声明。可以忽略。
//
// 1、document.getElementById("nothisid") == null => true
// 2、null == undefined     => true
// 3、nothisname == null    => throw error "nothisname is not defined"
//      按理解,nothisname是未声明的变量,是不存在的,难道不是null类型的?
// 4、typeof nothisname == null         => false
// 5、typeof nothisname == undefined    => false
// 6、typeof nothisname == 'undefined'  => true
//      类型不应该是null么?为什么是undefined?
//
// ECMA-262的定义:
// - Undefined Value : The undefined value is a primitive value used when a variable has not been assigned a value.
// - Undefined Type: The type Undefined has exactly one value, called undefined.
// - Null Value: The null value is a primitive value that represents the null, empty, or non-existent reference.
// - Null Type: The type Null has exactly one value, called null.
//
(function(win, S, undefined) {

    // If KISSY is already defined, the existing KISSY object will not
    // be overwritten so that defined namespaces are preserved.
    if (win[S] === undefined) win[S] = {};
    S = win[S]; // shortcut

    var doc = win['document'], loc = location,
        EMPTY = '',

        // Copies all the properties of s to r
        // mix方法是kissy用来组织代码最基本的方法。
        // 使用该方式来扩展对象或者函数。
        mix = function(r, s, ov, wl) {
            if (!s || !r) return r;
            if (ov === undefined) ov = true;
            var i, p, l;

            // 添加的方法/属性列表。
            if (wl && (l = wl.length)) {
                for (i = 0; i < l; i++) {
                    p = wl[i];
                    if (p in s) {
                        if (ov || !(p in r)) {
                            r[p] = s[p];
                        }
                    }
                }
            } else {
                for (p in s) {
                    if (ov || !(p in r)) {
                        r[p] = s[p];
                    }
                }
            }
            return r;
        },

        // Is the DOM ready to be used? Set to true once it occurs.
        isReady = false,

        // The functions to execute on DOM ready.
        readyList = [],

        // Has the ready events already been bound?
        // 如果执行了_bindReady,就设置为true,更像是判断是否已经执行过的一个标识。
        readyBound = false,

        // The number of poll times.
        POLL_RETRYS = 500,

        // The poll interval in milliseconds.
        POLL_INTERVAL = 40,

        // #id or id
        RE_IDSTR = /^#?([\w-]+)$/,

        // global unique id
        guid = 0;

    mix(S, {

        /**
         * The version of the library.
         * @type {String}
         */
        version: '1.1.4',

        /**
         * Initializes KISSY object.
         */
        __init: function() {
            // 环境信息
            this.Env = {
                mods: { }, // 所有模块列表
                _loadQueue: { } // 加载的模块信息
            };

            // 从当前引用文件路径中提取 base
            var scripts = doc.getElementsByTagName('script'),
                currentScript = scripts[scripts.length - 1],
                // 没有加全局标识。匹配到第一个项。
                base = currentScript.src.replace(/^(.*)(seed|kissy).*$/i, '$1');
            
            // 配置信息
            this.Config = {
                debug: '@DEBUG@', // build 时,会将 @DEBUG@ 替换为空
                base: base,
                timeout: 10   // getScript 的默认 timeout 时间
            };
        },

        /**
         * Specify a function to execute when the DOM is fully loaded.
         * @param fn {Function} A function to execute after the DOM is ready
         * 
         * KISSY.ready(function(S){ });
         * 
         * @return {KISSY}
         */
        ready: function(fn) {
            var self = this;

            // Attach the listeners
            if (!readyBound) self._bindReady();

            // If the DOM is already ready
            if (isReady) {
                // Execute the function immediately
                fn.call(win, self);
            } else {
                // Remember the function for later
                readyList.push(fn);
            }

            return self;
        },

        /**
         * Binds ready events.
         */
        _bindReady: function() {
            var self = this,
                doScroll = doc.documentElement.doScroll,
                // 这里的判断使得后续的调用保持一致。
                eventType = doScroll ? 'onreadystatechange' : 'DOMContentLoaded',

                // 一直比较好奇,这种定义是为了最大化压缩比率和所谓的常量定义外还有其它妙处么?
                COMPLETE = 'complete',
                fire = function() {
                    self._fireReady();
                };

            // Set to true once it runs
            readyBound = true;

            // Catch cases where ready() is called after the
            // browser event has already occurred.
            if (doc.readyState === COMPLETE) {
                return fire();
            }

            // w3c mode
            if (doc.addEventListener) {
                function domReady() {
                    doc.removeEventListener(eventType, domReady, false);
                    fire();
                }

                doc.addEventListener(eventType, domReady, false);

                // A fallback to window.onload, that will always work
                // 在页面加载完成以后,load新的事件是不会被触发的。
                // 而在_fireReady事件中,有状态判断,也不用担心重复执行产生冲突。
                win.addEventListener('load', fire, false);
            }
            // IE event model is used
            else {
                // 在判断if..else的时候,如果判断不同条件中又定义了同一个方法,
                // 在IE中总是后一个覆盖之前定义的。
                // 所以这里定义的是另一个方法名称。
                function stateChange() {
                    if (doc.readyState === COMPLETE) {
                        doc.detachEvent(eventType, stateChange);
                        fire();
                    }
                }

                // ensure firing before onload, maybe late but safe also for iframes
                doc.attachEvent(eventType, stateChange);

                // A fallback to window.onload, that will always work.
                win.attachEvent('onload', fire);

                if (win == win.top) { // not an iframe
                    function readyScroll() {
                        try {
                            // Ref: http://javascript.nwbox.com/IEContentLoaded/
                            doScroll('left');
                            fire();
                        } catch(ex) {
                            setTimeout(readyScroll, 1);
                        }
                    }

                    readyScroll();
                }
            }
        },

        /**
         * Executes functions bound to ready event.
         */
        _fireReady: function() {
            if (isReady) return;

            // Remember that the DOM is ready
            isReady = true;

            // If there are functions bound, to execute
            if (readyList) {
                // Execute all of them
                var fn, i = 0;
                while (fn = readyList[i++]) {
                    fn.call(win, this);
                }

                // Reset the list of functions
                readyList = null;
            }
        },

        /**
         * Executes the supplied callback when the item with the supplied id is found.
         * @param id  The id of the element, or an array of ids to look for.
         * @param fn  What to execute when the element is found.
         */
        available: function(id, fn) {
            // 如果不匹配会返回 null。
            id = (id + EMPTY).match(RE_IDSTR)[1];
            if (!id || !S.isFunction(fn)) return;

            var retryCount = 1,

                timer = S.later(function() {
                    // 判断由左到右进行。
                    // “&&” 的技巧,有时可以减少代码量,同“||”的原理一样。
                    // “fn() || 1”保证表达式返回的都是“true(1)”。不影响后续的代码执行。
                    if (doc.getElementById(id) && (fn() || 1) || ++retryCount > POLL_RETRYS) {
                        timer.cancel();
                    }

                }, POLL_INTERVAL, true);
        },

        /**
         * Copies all the properties of s to r.
         * @return {Object} the augmented object
         */
        mix: mix,

        /**
         * Returns a new object containing all of the properties of
         * all the supplied objects. The properties from later objects
         * will overwrite those in earlier objects. Passing in a
         * single object will create a shallow copy of it.
         * @return {Object} the new merged object
         */
        merge: function() {
            var o = {}, i, l = arguments.length;
            for (i = 0; i < l; ++i) {
                mix(o, arguments[i]);
            }
            return o;
        },

        /**
         * Applies prototype properties from the supplier to the receiver.
         * @return {Object} the augmented object
         */
        augment: function(/*r, s1, s2, ..., ov, wl*/) {
            var args = arguments, len = args.length - 2,
                r = args[0], ov = args[len], wl = args[len + 1],
                i = 1;

            // 如果在指定的位置不存在指定的参数,则设置指定参数并扩大范围。
            // 使得在最终调用的时候变量ov和wl都是确定的。
            if (!S.isArray(wl)) {
                ov = wl;
                wl = undefined;
                len++;
            }

            if (!S.isBoolean(ov)) {
                ov = undefined;
                len++;
            }

            for (; i < len; i++) {
                mix(r.prototype, args[i].prototype || args[i], ov, wl);
            }

            return r;
        },

        /**
         * Utility to set up the prototype, constructor and superclass properties to
         * support an inheritance strategy that can chain constructors and methods.
         * Static members will not be inherited.
         * @param r {Function} the object to modify
         * @param s {Function} the object to inherit
         * @param px {Object} prototype properties to add/override
         * @param sx {Object} static properties to add/override
         * @return r {Object}
         */
        extend: function(r, s, px, sx) {
            if (!s || !r) return r;

            var OP = Object.prototype,
                O = function (o) {
                    function F() {
                    }

                    F.prototype = o;
                    return new F();
                },
                sp = s.prototype,
                // 不直接使用new s()(这等于执行了s的构造方法),
                // 而重新定义一个空的Function对象是因为
                // 在此时父类的构造函数的参数无法给予(如果有参数的话),
                // 这会导致在构造函数中根据参数进行赋值的属性的值为undefined,会有出错的可能。
                // 是否还有其他的原因?
                rp = O(sp);

            r.prototype = rp;
            rp.constructor = r;
            r.superclass = sp;

            // assign constructor property
            if (s !== Object && sp.constructor === OP.constructor) {
                sp.constructor = s;
            }

            // 我比较不理解的是,为什么不设置一个方法以便执行被继承函数的构造方法?
            // 在mootools的继承机制中,有一个方法来执行被继承方法的构造函数。
            // 如果没有,就只能以 r.superclass.constructor.call(this, name);的方式来完成构造。
            // 如过添加类似 px["super"] = function() { s.apply(this, arguments);};的代码
            // 这样就可以直接在新方法中执行this.super(/*arguments*/);来完成构造了。
            // 这样更方便啊~~~

            // add prototype overrides
            if (px) {
                mix(rp, px);
            }

            // add object overrides
            if (sx) {
                mix(r, sx);
            }

            return r;
        },

        /**
         * Returns the namespace specified and creates it if it doesn't exist. Be careful
         * when naming packages. Reserved words may work in some browsers and not others.
         * 
         * S.namespace('KISSY.app'); // returns KISSY.app
         * S.namespace('app.Shop'); // returns KISSY.app.Shop
         * 
         * @return {Object}  A reference to the last namespace object created
         */
        // 为什么要限制在kissy命名空间下,而不能自定义?
        namespace: function() {
            var l = arguments.length, o = null, i, j, p;

            for (i = 0; i < l; ++i) {
                p = (EMPTY + arguments[i]).split('.');
                o = this;
                // 为什么这里又不缓存p.length了?是因为没必要还是有其他原因?
                for (j = (win[p[0]] === o) ? 1 : 0; j < p.length; ++j) {
                    // 确保o[p[j]]是有效的(原值或者{}),
                    // 然后赋给返回的变量。
                    o = o[p[j]] = o[p[j]] || {};
                }
            }
            return o;
        },

        /**
         * create app based on KISSY.
         * @param name {String} the app name
         * @param sx {Object} static properties to add/override
         * 
         * S.app('TB');
         * TB.namespace('app'); // returns TB.app
         * 
         * @return {Object}  A reference to the app global object
         */
        // 以应用的方式来隔离。能说明namespace基于“this”来创建的原因么?
        app: function(name, sx) {
            var isStr = S.isString(name),
                O = isStr ? win[name] || { } : name;

            // 覆盖方式来拷贝
            mix(O, this, true, S.__APP_MEMBERS);
            O.__init();

            mix(O, S.isFunction(sx) ? sx() : sx);
            isStr && (win[name] = O);

            return O;
        },

        /**
         * Prints debug info.
         * @param msg {String} the message to log.
         * @param cat {String} the log category for the message. Default
         *        categories are "info", "warn", "error", "time" etc.
         * @param src {String} the source of the the message (opt)
         */
        log: function(msg, cat, src) {
            if (S.Config.debug) {
                if (src) {
                    msg = src + ': ' + msg;
                }
                if (win['console'] !== undefined && console.log) {
                    console[cat && console[cat] ? cat : 'log'](msg);
                }
            }
        },

        /**
         * Throws error message.
         */
        error: function(msg) {
            if (S.Config.debug) {
                throw msg;
            }
        },

        /*
         * Generate a global unique id.
         * @param pre {String} optional guid prefix
         * @return {String} the guid
         */
        guid: function(pre) {
            var id = guid++ + EMPTY;
            return pre ? pre + id : id;
        }
    });

    S.__init();

    // S.app() 时,需要动态复制的成员列表
    S.__APP_MEMBERS = ['__init', 'namespace'];

    // 可以通过在 url 上加 ?ks-debug 参数来强制开启 debug 模式
    if (loc && (loc.search || EMPTY).indexOf('ks-debug') !== -1) {
        S.Config.debug = true;
    }

})(window, 'KISSY');

/**
 * NOTES:
 *
 * 2010/08
 *  - 将 loader 功能独立到 loader.js 中
 *
 * 2010/07
 *  - 增加 available 和 guid 方法
 *
 * 2010/04
 *  - 移除掉 weave 方法,鸡肋
 *
 * 2010/01
 *  - add 方法决定内部代码的基本组织方式(用 module 和 submodule 来组织代码)
 *  - ready, available 方法决定外部代码的基本调用方式,提供了一个简单的弱沙箱
 *  - mix, merge, augment, extend 方法,决定了类库代码的基本实现方式,充分利用 mixin 特性和 prototype 方式来实现代码
 *  - namespace, app 方法,决定子库的实现和代码的整体组织
 *  - log, error 方法,简单的调试工具和报错机制
 *  - guid 方法,全局辅助方法
 *  - 考虑简单够用和 2/8 原则,去掉对 YUI3 沙箱的模拟。(archives/2009 r402)
 *
 */

posted on 2010-09-19 10:59  Akecn  阅读(3549)  评论(0编辑  收藏  举报