PS:
本文参考了司徒正美的《JavaScript框架设计》,以及许多其它的书籍和网络文章,感谢作者们分享了这么好的学习资源!
关于JS的类和继承的原理,见:JavaScript里的类和继承
文中提到的库和测试文件戳这里:https://github.com/hellobugme/jsclass/
1、基于拷贝继承
Prototype(http://prototypejs.org/)和 jQuery(https://jquery.com/)都是风靡一时的库,功能大而全,并不是纯粹的类工厂,这里只是简单说下里面的继承。
它们的 extend 都是基于拷贝继承,其中 jQuery 支持 深度拷贝(deep copy)。
拷贝继承的缺点在上篇文章中有提过:子类实例无法通过父类的 instanceof 验证。
Prototype 中类和继承相关的代码如下:
var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } }; Object.extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; };
是吧,够简单的… 只不过是使用 Class.create() 创建出来的类,在实例化时会主动帮你执行下初始化函数 initialize 而已。使用方法大致如下:
var Person = Class.create(); Object.extend(Person.prototype, { initialize: function(name) { this.name = name; }, getName: function() { return this.name; } }); var User = Class.create(); User.prototype = Object.extend(new Person(), { initialize: function(name, password) { this.name = name; this.password = password; }, getPassword: function() { return this.password; } });
jQuery 在 extend 的实现中加入递归,对数组和对象等引用类型的属性值进行深度拷贝:
(... 表示一大波的代码,下同)
jQuery.extend = jQuery.fn.extend = function() { //... for (name in options) { src = target[name]; copy = options[name]; //... if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // 通过递归对数组和对象进行深度拷贝 target[name] = jQuery.extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; } } //... };
deep 是可选参数,指定是否进行深度拷贝。
通过 jQuery.isPlainObject() 和 jQuery.isArray() 判断属性值是否为纯粹对象或数组,然后决定是递归操作还是直接赋值。
2、Sugar
道爷(Douglas Crockford)在自己的网站上提出了一套简单的方法来模拟类式继承—— Sugar(http://javascript.crockford.com/inheritance.html)。
1 // 辅助函数,可以将新函数绑定到对象的 prototype 上 2 Function.prototype.method = function(name, func) { 3 this.prototype[name] = func; 4 return this; 5 }; 6 7 // 从其它对象继承函数,同时仍然可以调用数据父对象的那些函数 8 Function.method('inherits', function(parent) { 9 // 继承父对象的方法 10 this.prototype = new parent(); 11 this.prototype.constructor = parent; 12 13 var d = {}, 14 p = this.prototype; 15 // 创建一个新的特权函数'uber',调用它时会执行所有在继承时被重写的函数 16 this.method('uber', function uber(name) { 17 if (!(name in d)) { 18 d[name] = 0; 19 } 20 var f, // 要执行的函数 21 r, // 函数的返回值 22 t = d[name], // 记录当前所在的父层次的级数 23 v = parent.prototype; // 父对象的prototype 24 25 // 如果已经在某个'uber'函数之内 26 if (t) { 27 // 上溯必要的t,找到原始的prototype 28 while (t) { 29 v = v.constructor.prototype; 30 t -= 1; 31 } 32 // 从该prototype中获得函数 33 f = v[name]; 34 // 否则这就是'uber'函数的第一次调用 35 } else { 36 // 从prototype获得要执行的函数 37 f = p[name]; 38 // 如果此函数属于当前的prototype 39 if (f == this[name]) { 40 // 则改为调用父对象的prototype 41 f = v[name]; 42 } 43 } 44 45 // 记录在继承堆栈中所在位置的级数 46 d[name] += 1; 47 48 // 使用除第一个以外所有的arguments调用此函数 49 r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); 50 // 恢复继承堆栈 51 d[name] -= 1; 52 53 // 返回执行过的函数的返回值 54 return r; 55 }); 56 return this; 57 }); 58 59 // 只继承父对象特定函数的函数(非使用new parent()继承所有的函数) 60 Function.method('swiss', function(parent) { 61 // 遍历所有要继承的方法 62 for (var i = 1; i < arguments.length; i += 1) { 63 // 需要导入的方法名 64 var name = arguments[i]; 65 // 将此方法导入this对象的prototype中 66 this.prototype[name] = parent.prototype[name]; 67 } 68 return this; 69 });