日志更新

学习 YUI3 中的沙箱机制

简化了一下 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 < l; i = i+1) {

                name = r[i];
                m    = mods[name];

                if (!attached[name] && m) {

                    attached[name] = true;
                    
                    fn  = m.fn;
                    d   = m.details;
                    req = d.requires;
                    use = d.use;

                    if (req) {
                        self._attach(req);
                    }

                    if (fn) {
                        fn(self);
                    }

                    if (use) {
                        self._attach(use);
                    }
                }
            }

        },

        /**
         * 绑定模块至 Sandbox 实例
         * @param modules* {string} 1-n 个模块 (uses arguments array)
         * @param *callback {function} callback function  如果包括,必须是最后一个参数。
         *
         * Sandbox().use('planabc.net')
         * Sandbox().use('planabc.net',function(){})
         * Sandbox().use('planabc.net','planabc.com')
         * Sandbox().use('planabc.net','planabc.com',function(){})
         * Sandbox().use('*'); // use all available modules
         *
         */
        use: function() {

            var self = this,
                a = SLICE.call(arguments, 0),
                mods = Sandbox.Env.mods,
                used = self.Env._used,
                loader,
                firstArg = a[0],
                callback = a[a.length-1],
                k, i, l,
                r = [],
                process = function(name) {

                    // 添加模块至附加的模块列表
                    r.push(name);

                    // 一个模块仅附加一次
                    if (used[name]) {
                        return;
                    }

                    var m = mods[name], req, use, j, jl, t, tl,
                        d = m.details;

                    if (m) {
                        used[name] = true;

                        req = d.requires;
                        use = d.use;
                    }

                    // 附加上 requires 模块
                    if (req) {
                        for (j = 0,jl = req.length ; j < jl; j = j + 1) {
                            process(req[j]);
                        }
                    }

                    // 附加上 use 模块
                    if (use) {
                        for (t = 0, tl = use.length; t < tl; t = t + 1) {
                            process(use[t]);
                        }
                    }

                },

                onComplete;

            if (typeof callback === 'function') {
                a.pop();
            } else {
                callback = null;
            }

            onComplete = function() {
                if (callback) {
                    callback(self);
                }
            };

            // Sandbox().use('*');
            if (firstArg === "*") {
                a = [];
                for (k in mods) {
                    if (mods.hasOwnProperty(k)) {
                        a.push(k);
                    }
                }

                if (callback) {
                    a.push(callback);
                }

                return self.use.apply(self, a);
            }

            l = a.length;

            // 处理所有必须和附加的模块
            for (i = 0; i < l; i = i + 1) {
                process(a[i]);
            }

            self._attach(r);
            onComplete();

            return self; // chain support
        }
    };

})();

测试页面:http://www.planabc.net/lab/yui/sandbox.html

前端 JavaScript 相关的小Tips

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

如何获取当前 select 元素的值

  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;
}

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

什么是事件代理(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    },