JavaScript 是一门基于原型的面向对象语言,它是无类(class-free)的。
OOP 是 YUI 的核心代码,代码非常精简:
上面的 7 个方法,加上在 YUI~base 模块里的mix, merge, cached
3 个方法,是 YUI 3 代码组织中的 10 个核心方法。
基础知识点
详细解析代码前,先复习下 JavaScript 的一些基础知识点:
- 在 JavaScript 中,所有对象 o 都拥有一个隐藏的原型对象(在 Firefox 中是 o.__proto__)。该隐藏原型对象拥有一个 constructor 成员,指向该对象的构造函数。当读取对象成员 o.member 时,会顺着原型链往上回溯。因此我们可以得到
o.constructor === o.__proto__.constructor
. 这是最基本的知识点,不多说。 - 在 JavaScript 中,所有函数声明在解析后,都自动拥有一个 prototype 成员。该 prototype 成员拥有一个自动添加的 constructor 成员,指向函数本身。也就是
Fn === Fn.prototype.constructor
. - 函数 Fn 本身也是对象,因此
Fn.constructor === Fn.__proto__.constructor
, 注意Fn.__proto__ !== Fn.prototype
, 千万不要混淆了。 var fn = new Fn()
,在 Firefox 下,不考虑参数传递,可以用下面的代码来表示 new 的过程:var o = {__proto__: Fn.prototype}; Fn.apply(o); fn = o;
- 上面第 4 点是第 1 点的原因,因为任何一个对象都源自函数构造器,比如
var a = {}
可以等价为:var o = {__proto__: Object.prototype}; Object.apply(o); a = o;
因此所有对象都具有 constructor 成员。
上面 5 点可以归结为 2 点:构造函数的实例化过程和对象成员的原型链回溯机制。理解了这两点,市面上 JavaScript 的各种各样 OOP 机制,比如 Dean Edwards 的 Base.extend,MooTools 里的 new Class, 以及《悟透 JavaScript》里的甘露模型等等,就都能轻松轻松理解了。
YUI extend
对于 oop, YUI 让人喜悦的是,仅做了非常简洁的封装:
Y.extend = function(r, s, px, sx) { if (!s || !r) { // @TODO error symbols Y.error("extend failed, verify dependencies"); } var sp = s.prototype, rp = Y.Object(sp); r.prototype = rp; rp.constructor = r; r.superclass = sp; // assign constructor property if (s != Object && sp.constructor == OP.constructor) { sp.constructor = s; } // add prototype overrides if (px) { Y.mix(rp, px, true); } // add object overrides if (sx) { Y.mix(r, sx, true); } return r; };
基本原理不废话了,可以参考 Kevin 的这篇文章。
使用方式很 JavaScript, 很简单:
YUI().use('oop', function(Y) { var Bird = function(name) { this.name = name; }; Bird.prototype.getName = function(){ return this.name; }; var Chicken = function(name) { Chicken.superclass.constructor.call(this, name); }; Y.extend(Chicken, Bird); var chicken = new Chicken('Tom'); Y.log(chicken.getName()); });
supperclass 有两个作用:一是可以用来调用父类的方法,二是可以通过 supperclass.constructor 调用父类的构造函数。一举两得,一旦理解了就非常好用。
extend 方法中,还有一段看起来无用的代码:
// assign constructor property if (s != Object && sp.constructor == OP.constructor) { sp.constructor = s; }
这段代码,正常情况下,的确是不可达的死代码。然而我们会经常不小心地这样组织代码:
YUI().use('oop', function(Y) { var Bird = function(name) { this.name = name; }; Bird.prototype = { getName: function(){ return this.name; } }; var Chicken = function(name) { Chicken.superclass.constructor.call(this, name); }; Y.extend(Chicken, Bird); var chicken = new Chicken('Tom'); Y.log(chicken.getName()); });
注意加粗部分,直接给 prototype 赋值,会使得自动添加的 constructor 成员丢失。为了避免这种很容易犯的错误,在 extend 方法里,很人性化地为我们做了纠正工作。
简洁优美、小巧灵活的 extend, 是我最喜欢的 JavaScript 继承封装。
YUI augment
接下来一个有意思的方法是 augment. 如果说 extend 是实现类继承机制,augment 则是实现了接口。并且这种接口,还不需要自己去写实现代码,直接就借用过来了^o^
augment 源码里最难理解的是为何要对 function 成员做一层封装。其实也很简单,我们看一个例子:
YUI().use('oop', function(Y) { var Bird = function() { this.canFly = true; }; Bird.prototype.canFly = function() { return this.canFly; }; var Pig = function() {}; Y.augment(Pig, Bird); var pig = new Pig(); Y.log(pig.canFly()); });
如果 augment 方法里,没有以下封装代码:
replacements[k] = function() { for (i in sequestered) { if (sequestered.hasOwnProperty(i) && (this[i] === replacements[i])) { this[i] = sequestered[i]; } } // apply the constructor construct.apply(this, a); // apply the original sequestered function return sequestered[k].apply(this, arguments); };
则 pig.canFly() 返回的就是 undefined 了。因为 pig 对象没有 canFly 属性。
有了以上封装代码,才能保证 augment 过来的方法确实可用,从而使得我们能很方便地构造出一头能飞的猪!
其它方法
其它几个方法都很容易理解,不多说。其中 bind 方法是一个返回函数的函数,巧妙利用可以非常灵活地实现非常精妙的代码,请参考这篇文章:JavaScript 中的 arguments
最后,闲话
jQuery, MooTools 等框架,我们在使用时,先要学会它们自身的语法,比如 MooTools 的 new Class 机制等。学习成本高,而且可移植性差(比如学会 jQuery,对学习 MooTools 或原生 JavaScript 没啥很大帮助)。
但 YUI 不同。YUI 的风格是,让我们能像写原生 JavaScript 一样去使用 YUI. 无需掌握过多框架自身引入的语法。这意味着,用 YUI 用得越熟练,对 JavaScript 本身的理解也会越深入。YUI 是一种辅助增强,但不破坏 JavaScript 固有的美好和缺点。这是 YUI 最诱人的地方。
快乐学习,欢迎讨论。