标签存档: YUI

YUI学习笔记(4)

YUI学习笔记(4)

by xujiwei (http://www.xujiwei.com/)

YAHOO.util.Subscriber 与 YAHOO.util.CustomEvent。

1. YAHOO.util.Subscriber (event.js)

这 应该算是设计模式中的观察者模式了,Subscriber 订阅一个事件,在 Publisher 触发那个事件后,会逐个通知 Subscriber。

对 于一般开发者来说,并不需要去关心 Subscriber 的实现,因为 Subscriber 主要是 CustomEvent 用来分发动作以及删 除 Subscriber 的。

Subscriber 类只定义了 3 个属性:fn、obj 以及 override,3 个方 法:getScope、contains、toString,其中 fn 为订阅者的回调函数,obj 为要传递给回调函数的一个额外参 数,override 如果是布尔型的 true 值,那么表示使用 obj 属性为回调函数执行时的上下文,或者直接使用一个对象来作为回调函数执行的 上下文。

Subscriber 的 3 个方法中比较有用的是 getScope 和 contains,toString 只是简单的 将 Subscriber 对象转换成一个字符串。getScope 会根据 Subscriber 对象的 override 属性来获取回调函数执行 的上下文,contains 用来判断 Subscriber 对象与指定的回调函数和 obj 是否一致。

2. YAHOO.util.CustomEvent (event.js)

CustomEvent 的 作用相当在观察者模式中发布者的身份,可以通过它来实现一个自己的事件发布者。

CustomEvent 构造函数的定义如下:

CustomEvent = function(type, oScope, silent, signature)

在 创建 CustomEvent 对象时,几个参数的用途如下:

type 是自定义事件的名称,在使用回调函数的参数格式 为 YAHOO.util.Event.LIST 时,回调函数的第一个参数就是 CustomEvent 对象的名称;

oScope 是 执行回调函数时的上下文对象,也就是在回调函数中可以用 this 来引用这个对象;

silent 参数是用指示是否 在 YUI 为 debug 版本时禁用调试信息;

signature 用来指示回调函数参数的格式,可以为 YAHOO.util.Event.FLAT 或 YAHOO.util.Event.LIST, 默认是 YAHOO.util.Event.LIST。

在使用 CustomEvent 之前,先要了解一下 CustomEvent 中 回调函数参数的格式,CustomEvent 的回调函数可以有两种格式,一种为 YAHOO.util.Event.LIST,这种格式的回调函数具有 三个参数,分别是事件名称、参数数组和附加对象参数;另外一种回调函数参数格式为 YAHOO.util.Event.FLAT,这个时候回调函数只有两 个参数,一个为 CustomEvent 对象调用 fire 方法时的第一个参数,另外一个是订阅时的额外对象参数。

在创 建 CustomEvent 对象时,CustomEvent 构造函数还会首先创建一个内部的自定义事件,用来处理该自定义事件被订阅的事件,这 在 EventProvider 中用到,这里暂且不提。

CustomEvent 对象使用一个名为 subscribers 的数组来保 存所有订阅者的列表,并且通过维护这个列表来维护该自定义事件的订阅者。

CustomEvent 对象提供了 subscribe、 unsubscribe、unsubscribeAll、fire 这几个方法来处理自定义事件的订阅、退订以及触发等动作,而这几个就是观察者模式中的 主要动作了。

subscribe 的签名为 subscribe: function(fn, obj, override), 三个参数分别对应了 Subscriber 类构造函数的三个参数,分别对应了回调函数、额外对象参数以及是否使用额外对象参数作为执行上下文。 subscribe 只是简单的判断参数 fn 是否有定义,然后会触发自定义事件订阅事件,最后使用这三个参数创建一个 Subscriber 对象添 加到 CustomEvent 对象的 subscribers 属性中。

unsubscribe 方法用来取消事件的订阅,它的函数签名 为 unsubscribe: function(fn, obj),两个参数分别是回调函数和额外对象参数,如果使用无参数调 用 unsubscribe 方法,那么会直接调用 unsubscribeAll 来删除所有订阅者,否则会逐一判断 subscribers 中的每 个对象,通过使用 Subscriber 对象的 contains 方法来判断给定的 fn 和 obj 与其是否一致,如果一致,就使用一个私有方 法 _delete 来删除这个 Subscriber。

unsubscribeAll 方法没有参数,它只是简单的直接删除自定义事件的 所有订阅者,最后直接给 subscribers 赋值一个空数组来避免有可能出现漏删订阅者的情况。

内部方法 _delete 的参数 是 Subscriber 对象在 subscribers 数组中的索引,它会先删除 Subscriber 对象的 fn 和 obj 属性,最后使 用 splice 方法将 Subscriber 对象从数组中删除。

var s = this.subscribers[index];
if (s) {
delete s.fn;
delete s.obj;
}
this.subscribers.splice(index, 1);

使用 delete 删除 Subscriber 对象的 fn 和 obj 属性是为了去除 回调函数及额外对象参数的引用,以免引起不必要的内 存泄露。

CustomEvent 对象最重要的方法就是 fire 了,就是通过这个方法来通知所有了订阅者这个自定义事件被触发了。 fire 方法先使用 Array 的 slice 方法将调用 fire 方法时的参数转化成数组,这样就可以在调用 Subscriber 的回调函 数时可以传递参数给它们。

在遍历 subscribers 中的 Subscriber 前,fire 方法先使用 了 subscribers 的 slice 方法来创建一个 subscribers 的副本,这样避免在执行 fire 的过程中 有 Subscirber 取消订阅了这个自定义事件会导致错误。

在执行 Subscriber 的回调函数前,先使 用 Subscriber 对象的 getScope 方法来获取执行回调函数时的上下文对象,再根据 CustomEvent 对象 的 signature 属性来决定怎么去调用 Subscriber 的回调函数。

如 果 signature 为 YAHOO.util.CustomEvent.FLAT,那么就把调用 fire 方法时的第一个参数做为回调函数的第一 个参数,再把 Subscriber 对象的 obj 属性做为第二个参数:

s.fn.call(scope, param, s.obj)

如 果 signature 为 YAHOO.util.CustomEvent.LIST,那么就除了把整个 fire 方法的参数列表传递给回调函数外, 还要传递当前 CustomEvent 的名称给回调函数:

s.fn.call(scope, this.type, args, s.obj)

Subscriber 的 回调函数如果在执行过程中出现了错误,那么 CustomEvent 的 lastError 属性就是指向错误对象的引用,另外,如 果 YAOO.util.Event.throwErrors 为 true,那么会把这个错误再次抛出。

另外,Subscriber 对 象也可以控制事件通知是否继续,如果 Subscriber 对象的回调函数执行后的返回一个 false,那么在 fire 方法中就会停止通知剩下 的 Subscriber 对象,通常情况下,先订阅自定义事件的 Subscriber 可以阻止后订阅的 Subscriber 接收到通知。

使 用 YUI 的自定义事件(CustomEvent)可以很方便地实现观察者模式,更好地组织 JavaScript 程序的结构。

YUI学习笔记(3)

YUI学习笔记(3)

by xujiwei (http://www.xujiwei.com/)

YAHOO.lang.later,YAHOO.lang.trim,YAHOO.lang.isXXX 以及 YAHOO.lang.hasOwnProperty。

1. YAHOO.lang.later(yahoo/yahoo.js)

later 方法用来延迟执行方法,是对 setInterval 和 setTimeout 的封装,并且可以传递参数到延迟执行的函数或者使用参数数组批量执行指定的函数。

later 方法的签名为:

later: function(when, o, fn, data, periodic)

when 是用来指定在多长时间后执行指定的函数,以毫秒计算;

o 是上下文对象,即在要执行的函数里使用 this 是会引用这个 o 对象;

fn 就是要延迟执行的函数了,可以传递一个函数引用,也可以传递一个字符串,later 方法会在 o 对象中查找对应名称的属性来做为要执行的方法;

data 就是传递给延迟执行的函数的参数了,可以为一个参数,或者是一个参数数组,那么如果我们要传递的参数本身就是一个数组的话,就要自己先把这个参数数组包装成一个数组;

peridoic 参数是一个布尔值,用来表示延迟执行的函数是否需要周期执行而不是只执行一次。

later 方法执行后会返回一个对象,包含了一个名 interval 的属性用来表示函数是以 setInterval 来执行的还是以 setTimeout 来执行的,以及一个方法 cancel 用来取消执行被延迟执行的函数。嗯,不过只有在 peridoic 为 true 时这个 cancel 方法比较有用,毕竟如果 peridoic 为 false 时函数执行一次就不会再执行了,cancel 也没有什么意义。

{
interval: periodic,
cancel: function() {
if (this.interval) {
clearInterval(r);
} else {
clearTimeout(r);
}
}
};

    2. YAHOO.lang.trim (yahoo/yahoo.js)

    trim 方法用来去除字符串两边的空白字符,其实也就用了一个正则来匹配字符串两端的空白字符并替换成空白字符串。

    不过在 YAHOO.lang.trim 中,它使用了一个 try … catch 来在调用的参数不为字符串时直接返回原来的对象。

    3. 对象类型判断 YAHOO.lang.isXXX (yahoo/yahoo.js)

    YAHOO.lang 中包含了一堆用于判断对象是否为某个类型的方法,例如 isObject、isString、isNumber 等。

    YAHOO.lang.isArray 用来判断一个对象是否为数组,YUI 中并不是使用的 obj instanceof Array 或者 obj.constructor == Array,而是判断指定的对象是否有两个数组应该具有的经典属性和方法 length 和 splice,这是因为如果要判断的对象是属于另外一个 frame 中时,除非你能获得另外一个 frame 中 Array 定义的费用,否则 instanceof 和 constructor == Array 都是返回 false 的,我们可以使用以下代码来测试一下:

    test.html

    <iframe id=”frame” src=”iframe.html”></iframe>
    <script type=”text/javascript”>
    <!–
    var f = document.getElementById(‘frame’);
    setTimeout(function() {
    alert(f.contentWindow.arr.constructor == Array);
    }, 1000);
    //–>
    </script>

      iframe.html

      <script type=”text/javascript”>
      <!–
      var arr = [1, 2, 3];
      //–>
      </script>

        而如果把 Array 换成 arr 所在 frame 中 Array 的引用,结果就是为 true 了:

        test.html

        <iframe id=”frame” src=”iframe.html”></iframe>
        <script type=”text/javascript”>
        <!–
        var f = document.getElementById(‘frame’);
        setTimeout(function() {
        // 这里将 Array 换成 f.contentWindow.Array
        alert(f.contentWindow.arr.constructor == f.contentWindow.Array);
        }, 1000);
        //–>
        </script>

          但是因为 isArray 是一个通用的方法,在使用时不可能要求调用者同时也传递一个目标 frame 的引用过来,所以只好通过判断两个经典的属性来代替了。

          YUI 的注释中提到是 Safari 不支持跨框架通过 instanceof 的检测,但是我测试的结果是几个主流浏览器都不支持跨框架的 instanceof 检测。

          YAHOO.lang.isBooleanYAHOO.lang.isFunctionYAHOO.lang.isStringYAHOO.lang.isUndefined 这 4 个方法是直接通过使用 typeof 来判断对象类型是否为 boolean、function、string 或 undefined 的。

          YAHOO.lang.isNumber 也是通过 typeof 来判断的,不过附加了一个检测条件,使用 isFinite 来检测数字是否为有限的,即不是 NaN、负无穷或正无穷。

          YAHOO.lang.isObject 通过 typeof 来判断对象类型是否 object 以及使用 isFunction 方法来检测对象是否为一个 Function,在 YAHOO.lang.isObject 方法的眼中,Object 与 Function 都是 Object。

          YAHOO.lang.isNull 直接将判断对象与 null 进行严格相等比较。

          YAHOO.lang.isValue 用来判断传递给它的参数是否为一个有值的对象,它是将 isObject、isString、isNumber 以及 isBoolean 四个方法对参数进行判断的结果进行或操作来作为结果的,对 null/undefined/NaN 这三个值进行 isValue 判断时就会返回 false,因为 0、false、’ 这三个值在分别在 isNumber、isBoolean、isString 时判断为 true,所有 isValue 的结果也为 true。

          4. YAHOO.lang.hasOwnProperty (yahoo/yahoo.js)

          嗯,这个方法也是对 Object.prototype.hasOwnProperty 的封装,前提是当前上下文的 Object.prototype 有 hasOwnProperty 方法,如果 Object.prototype.hasOwnProperty 不存在,那么 YAHOO.lang.hasOwnProperty 就实现了自己的 hasOwnProperty 方法,它是通过判断对象的属性与对象 prototype 的同名属性是否为同一个对象的判断的。如果对象的属性不是从原型继承过来的,那么两个属性值就有可能不一致:

          o.constructor.prototype[prop] !== o[prop]

          不过这里也有个问题,在 YAHOO.lang.hasOwnProperty 的注释中也提到了,就是 YUI 自己实现的 hasOwnProperty 是无法分辨在属性值一样时,这个属性是从原型继承过来的还是对象自身的:

          var YAHOO = {
          lang : {
          isUndefined : function(o) {
          return typeof o === “undefined”;
          },
          hasOwnProperty : function(o, prop) {
          return !this.isUndefined(o[prop]) &&
          o.constructor.prototype[prop] !== o[prop];
          }
          }
          }
          var A = function() {};
          A.prototype.foo = ‘foo’;
          var a = new A();
          a.foo = ‘foo’;
          alert(a.hasOwnProperty(‘foo’)); // true
          alert(YAHOO.lang.hasOwnProperty(a, ‘foo’)); // 这里返回 false,事实上应该是 true

            但是现在 Object.prototype 中没有 hasOwnProperty 的浏览器应该基本没有了,所以这个问题也不是很大。

            5. YAHOO.lang.augmentProto 以及 YAHOO.lang.extend 的别名 (yahoo/yahoo.js)

            在 yahoo.js 中,将 YAHOO.lang.augment 以及 YAHOO.augment 指向了 YAHOO.lang.augmentProto,YAHOO.extend 指向了 YAHOO.lang.extend,可以一定程度上减少代码的编写。

            YUI学习笔记(2)

            YUI学习笔记(2)

            by xujiwei (http://www.xujiwei.com/)

            YAHOO.lang.dump 与 YAHOO.lang.substitute。

            1. YAHOO.lang.dump(yahoo.js)

            dump 方法用来将一个对象转储为一个字符串,并且可以指定转储的深度。

            在 dump 过程中,对于基础类型例如 Number、String、Boolean,是直接返回字符串的,对 HTMLElement 对象是返回 HTMLElement 本身,也就是不做处理,对于函数 Function 则是返回字符串“f(){…}”。

            对于数组,dump 返回的格式就如我们定义时一样“[item1, item2 item3, …]”,对于对象 Object,则是使用键值对的形式“key => value”,与 PHP 里面的数组定义方式相似。

            例如一个对象定义如下:

            var obj = {
            num: 1,
            str: “string”,
            bool: true,
            date: new Date(),
            obj: {
            obj_num: 1,
            obj_str: “obj_string”
            },
            foo: function() {
            }
            }

              dump 之后的字符串如下:

              {num => 1, str => string, bool => true, date => Wed Jan 7 15:57:52 UTC+0800 2009,obj => {obj_num => 1, obj_str => obj_string}, foo => f(){…}}

              字符串没有被引号引起来,这个方法只适合用来展示对象的结构,与 JSON 序列化差得有点远了。

              2. YAHOO.lang.substitute(yahoo.js)

              substitute 实现的功能与 C# 中的 String.Format 方法类似,用来格式化一个字符串,但是它是一个字符串来代表一个占位符,而不像 C# 中是使用数字,或许这个用法和 python 中的字符串格式化更像。

              substitute 调用的格式为:substitute(formatString, valueObject [,formatCallback]),其中 formatString 是作为格式化字符串的字符串,其中包含着一些格式为 {key} 这样的占位符,valueObject 是包含要取代占位符的值的对象,它的结构为 { key1 : value1, key2 : value2 },最后的 formatCallback 参数则是额外的格式化处理函数,用来在格式化字符串进行一些额外的处理。

              substitute 格式化字符串的占位符格式为“{key [meta]}”,其中 meta 是可选的,用来表示额外的格式化属性,而要替换的数据则是 valueObject 中名称为占位符 key 的属性,例如,使用 { name : “xujiwei” } 做为数据,那么“{name}”将被替换为“xujiwei”。

              基本数据类型例如 String、Number 在 substitute 中是直接替换的,但是如果要替换的值为一个数组,那么 subtitute 会先使用 YAHOO.lang.dump 方法将对象转储为一个字符串再进行替换,深度为 10,而在替换的值为一个对象时,则会先检测 meta 数据中是否包含了 dump 关键字,或者这个对象的 toString 方法是否与 Object 对象原型的 toString 方法一样,如果是的话就使用 YAHOO.lang.dump 方法来将对象转储为字符串进行替换,否则就调用对象的 toString 方法得到字符串进行格式化。

              如果替换的数据类型不是 Object、Array、String、Number 中的一种的话,那么这个占位符就不进行替换。

              一个覆盖了所有类型数据的例子如下:

              YAHOO.lang.substitute(
              “String : {key1}\nNumber : {key2}\nObject : {key3}\nArray : {key4}”,
              {
              key1 : “xujiwei”,       // String
              key2 : 123456,          // Number
              key3 : {
              firstName : “Jiwei”,
              lastName  : “Xu”
              },                      // Object
              key4 : [1, 2, 3]        // Array
              });

                它的输出如下:

                String : xujiwei

                Number : 123456

                Object : {firstName => Jiwei, lastName => Xu}

                Array : [1, 2, 3]

                继续阅读 »

                YUI学习笔记(1)

                YUI学习笔记(1)

                by xujiwei (http://www.xujiwei.com/)

                今天开始学习 YUI,加强一下对 JavaScript 的理解。

                1. 命名空间 YAHOO.namespace(yahoo.js)

                YUI 中使用了命名空间的概念,在 JS 中使用命名空间是为了模块以及代码组织清晰的需要,通过使用命名空间可以将功能相似或同一模块中的函数、变量等放到同一个命名空间下。

                其实 JS 中的命名空间就是一个嵌套的对象而已,即子命名空间相当于父命名空间中的一个属性,它本身也是一个对象,这样子命名空间也可以有自己的子命名空间。

                在 YUI 中,命名空间的格式与 C# 中类似,是以点号分隔的字符串,可以使用 YAHOO 对象的静态方法 namespace 来创建命名空间,需要注意的是,以 namespace 方法创建命名空间时,所有的对象都是附加在 YAHOO 这个对象上的,如果调用 namespace 方法创建一个“com.xujiwei.ajax”这样的命名空间,其中 ajax 的完整路径将是 YAHOO.com.xujiwei.ajax,而不是 com.xujiwei.ajax,也就是说 namespace 不会产生新的顶层对象,一切以 YAHOO 对象为基础。

                namespace 方法接受一个或多个字符串来生成命名空间,但是它只返回最后一个参数所代表的命名空间的最后一个域的对象,例如使用 namespace(“com.xujiwei.js”, “com.xujiwei.ajax”) 时它的返回值是代表 js 这个模块的的对象:

                YAHOO.namespace(“com.xujiwei.js”, “com.xujiwei.ajax”).get = function() {
                alert(“get method of com.xujiwei.ajax”);
                }
                YAHOO.com.xujiwei.ajax.get();

                2. YAHOO.lang.extend(yahoo.js)

                YAHOO.lang.extend 负责扩展一个现在的函数对象,相当于面向对象中的继承,并且可以附加一个参数重写继承的方法,它的函数声明是:

                extend: function(subc, superc, overrides)

                其中 subc 为要扩展的函数对象,superc 是要继续的函数对象,overrides 中包含着要重写父类的方法。

                YAHOO.lang.extend 是通过原型链的方式来扩展对象,即创建一个父类的实例做为子类的原型对象,在 extend 方法中,首先构造了一个空函数,将空函数的 prototype 指向父类的 prototype,以免在父类的构造函数需要参数时创建父类实例时会出错,然后创建这个空函数的实例赋值给子类的 prototype 对象,并且给子类添加一个 superclass 属性指定它的父类,另外还指定了子类 prototype 的 constructor 属性为子类构造函数,以免创建子类实例时会丢失 constructor 属性,这个问题我在《慎用 somefunction.prototype》有提到。

                var F = function() {};
                F.prototype = superc.prototype;
                subc.prototype = new F();
                subc.prototype.constructor = subc;
                subc.superclass = superc.prototype;

                接着处理的是要重写的函数,首先遍历 overrides 参数,并且使用 hasOwnProperty 方法判断属性是否为 overrides 自身的属性,而不是从 Object 对象的 prototype 继承过来的,如果是 overrides 自身的属性,那么就覆盖子类 prototype 的相同成员。

                在处理 overrides 的最后,还修复了 IE 中不枚举与内部函数同名的函数的 bug,即例如 overrides 中包含了 toString 或 valueOf 的定义,但是这两个函数也是 Object 对象所具有的,在 IE 中使用 for(p in overrides) 就不会得到这两个函数,因此需要修复,这是 YAHOO.lang 中 _IEEnumFix 函数所做的工作。

                if (overrides) {
                for (var i in overrides) {
                if (L.hasOwnProperty(overrides, i)) {
                subc.prototype[ i ]=overrides[ i ];
                }
                }
                L._IEEnumFix(subc.prototype, overrides);
                }

                _IEEnumFix 只是简单的判断当前浏览器是否为 IE,如果为 IE 则从一个已经定义的列表中获取在 IE 中枚举不到的函数名列表,逐一判断是否为源对象自身的属性并且与 Object 原型中的同名函数不一样,如果是则添加到目标对象。如果浏览器不是 IE 则 _IEEnumFix 直接就是一个空函数,直接不处理。

                _IEEnumFix: (YAHOO.env.ua.ie) ? function(r, s) {
                for (var i=0;i<ADD.length;i=i+1) {
                var fname=ADD[ i ],f=s[fname];
                if (L.isFunction(f) && f!=Object.prototype[fname]) {
                r[fname]=f;
                }
                }
                } : function(){},

                3. YAHOO.lang.augmentObject(yahoo.js)

                augmentObject 方法用来引入目标对象的属性,并且处理重写,通常用于合并配置对象与默认配置对象。

                augmentObject 有三种调用方法,分别是:

                augmentObject(result, source),一般情况下用些方法,从 source 引入 result 中没有的属性;

                augmentObject(result, source, override),override 设置是否覆盖,如果是 true 的话,那么 source 中所有属性会覆盖 result 中已有的属性;

                augmentObject(result, source, ovrride_name, …),指定要覆盖属性的列表,source 参数后为要覆盖属性名称的列表,例如覆盖 result 中的 toString 和 valueOf,那么可以调用 augmentObject(result, source, “toString”, “valueOf”)来实现。

                合并对象的最后同样调用了 _IEEnumFix 方法来修正 IE 中不枚举与内部属性同名属性的 bug。

                augmentProto 与 augmentObject 功能基本相同,只不过后者是处理对象,而前者是处理函数的 prototype,它是通过使用两个参数的 prototype 来构造一个参数列表,传递给 augmentObject 对象来实现 augmentProto 的功能。

                var a=[r.prototype,s.prototype];
                for (var i=2;i<arguments.length;i=i+1) {
                a.push(arguments[ i ]);
                }
                L.augmentObject.apply(this, a);

                因为 augmentProto 是调用 augmentObject 来实现的,因此它的调用格式也是 augmentObject 一样,具体可以看看上面 augmentObject 的三种调用方法。

                另外,YAHOO.lang.merge 方法也是通过调用 augmentObject 方法来实现对象的合并。