关于JS中OOP的具体实现,许多大神级的JS专家都给出了本身的方案。javascript
一:Douglas Crockfordhtml
1.1 Douglas Crockford实现的类继承java
/** * 原文地址:http://javascript.crockford.com/inheritance.html */ Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; }; Function.method('inherits', function (parent) { var d = {}, p = (this.prototype = new parent()); this.prototype.constructor = parent; this.method('uber', function uber(name) { if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { while (t) { v = v.constructor.prototype; t -= 1; } f = v[name]; } else { f = p[name]; if (f == this[name]) { f = v[name]; } } d[name] += 1; r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); d[name] -= 1; return r; }); return this; });
DC实现的这个类继承的思路是:正则表达式
1. this.prototype = new parent()经过重写构造函数的原型对象来实现继承浏览器
2. 增长实例方法uber(),经过instance.uber()能够访问父类中的某个方法。固然uber方法有点复杂,分析以前咱们要先明白如下几点:闭包
a. 闭包变量不会被销毁,同时函数的做用域是在函数定义时肯定的。app
b. 每一个函数都有prototype属性,指向一个对象,这个对象会在函数做为构造函数建立对象时,做为建立对象的模板,这个对象咱们称之为原型对象。函数
c. 每一个对象都有一个__proto__指针,指向此对象在建立时所依赖的原型对象。Object.prototype.__proto__ === null;测试
d. 在对一个对象求某个键值时,若是对象自己不存在这个键,则会在对象的原型(链)上面寻找this
Function.method('inherits', function (parent) { //d, p, parent都是一直存在的闭包变量,可供uber()方法使用 var d = {}, //经过 this.prototype = new parent() 实现继承 p = (this.prototype = new parent()); this.prototype.constructor = parent; //经过增长原型方法uber()实现对象父类方法的调用(特别是子父类中的同名方法) //相似Java中的super,调用其直接父类中的方法 this.method('uber', function uber(name) { //d[name]是标识某方法在原型链中的层级,以便在原型链中正确获得相应的父类方法 //使用d[name]这样的map是由于在一个方法中可能调用多个父类的同名方法 if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { //若是层级标识不为0,则要循环在原型链上找到对象的原型对象,再肯定对应的父方法name while (t) { v = v.constructor.prototype; t -= 1; } f = v[name]; } else { f = p[name]; // p === this.prototype === new parent;当前类的原型对象 //若是当前类的原型对象上的方法name与对象实例的name方法相等。 //这里要注意,对于引用类型的相等,比较的是指针。引用类型值a == 引用类型值b 只能说明a,b都指向同一块内存 //原型对象上的方法name与对象实例的方法name相等,只能说明对象自己没有这个方法,这个方法是存放在对象原型对象中的 if (f == this[name]) { f = v[name]; // v = parent.prototype 是父类的原型对象。上面的属性会被父类的实例与子类的实例共享 } } //uber()方法层级+1 d[name] += 1; //使用apply调用父类的方法f,并设置其上下文为this,即子类实例 r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); //层级还原 d[name] -= 1; return r; }); return this; });
//测试 function Animal() {} Animal.prototype.getInfo = function() { return 'Animal'; } Animal.prototype.getAge = function() { return '10'; } function Dog() { } //注意Dog.prototype上新增的方法必定要写在调用了inherits()后面 Dog.inherits(Animal); Dog.prototype.getInfo = function() { var a = 'dogName:' + this.uber('getInfo'); var b = 'dogAge:' + this.getAge(); return a +'|'+ b; } function Collie() {} Collie.inherits(Dog); Collie.prototype.getInfo = function() { return this.uber('getInfo') + '|Collie'; } var c = new Collie(); console.log(c.getInfo()); //dogName:Animal|dogAge:10|Collie console.log(c instanceof Collie);// true console.log(c instanceof Dog); // true console.log(c instanceof Animal); //true console.log(c.constructor === Animal); //false console.log(c.constructor === Dog); //true console.log(c.constructor === Collie); //false //c.constructor 应该等于Collie才对,如今却指向了Dog.错误缘由:this.prototype.constructor = parent;
在玉伯早期的一篇文章中(http://www.iteye.com/topic/248933),有下面一个例子。至于结果,找出完整的调整栈,看一下就完成明白了。结果与原文有点不同,可能DC修改过他的代码吧。
// 例2 function D1() {} D1.prototype.getName = function() { return 'D1' }; // @4 function D2() {} D2.inherits(D1); D2.prototype.getName = function () { return this.uber('getName') + ',D2'; }; // @5 function D3() {} D3.inherits(D2); function D4() {} D4.inherits(D3); function D5() {} D5.inherits(D4); D5.prototype.getName = function () { return this.uber('getName') + ',D5'; }; // @6 function D6() {} D6.inherits(D5); var d6 = new D6(); println(d6.getName()); // => D1,D2,D2,D2,D5,D5 println(d6.uber('getName')); // => D1,D2,D2,D2,D5
发现结果是D1,D2,D2…这样。上面已经说过:在对一个对象求某个键值时,若是对象自己不存在这个键,则会在对象的原型(链)上面寻找。这就是产生多个D2的缘由。但这结果与咱们指望的super效果不同。下面是玉伯加了patch的代码:
// patched by lifesinger@gmail.com 2008/10/4 Function.method('inherits', function (parent) { var d = { }, p = (this.prototype = new parent()); // 还原constructor p.constructor = this; // 添加superclass属性 p.superclass = parent; this.method('uber', function uber(name) { if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { while (t) { // 利用superclass来上溯,避免contructor陷阱。要注意parent没有被修改过,因此v在每次进入uber时的值是同样的。始终指向父类原型对象 v = v.superclass.prototype; // 跳过“断层”的继承点。不会由于“对象自己没有向原型(链)拿”形成重复执行 if(v.hasOwnProperty(name)) { t -= 1; } } f = v[name]; } else { f = p[name]; if (f == this[name]) { f = v[name]; } } d[name] += 1; if(f == this[name]) { // this[name]在父类中的情景 r = this.uber.apply(this, Array.prototype.slice.apply(arguments)); } else { r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); } d[name] -= 1; return r; }); return this; });
// 例3 function F1() { } F1.prototype.getName = function() { return 'F1'; }; function F2() { } F2.inherits(F1); F2.prototype.getName = function() { return this.uber('getName') + ',F2'; }; function F3() { } F3.inherits(F2); F3.prototype.getName = function() { return this.uber('getName') + ',F3'; }; function F4() { } F4.inherits(F3); F4.prototype.getName = function() { return this.uber('getName') + ',F4'; }; document.write('<hr />') var f3 = new F3(); document.write(f3.getName()); // => F1,F2,F3 document.write('<hr />') var f4 = new F4(); document.write(f4.getName()); // => F1,F2,F3,F4 console.log(f3 instanceof F3);//true console.log(f3 instanceof F2);//true console.log(f3 instanceof F1);//true console.log(f3.constructor === F3);//true console.log(f3.constructor === F2);//false console.log(f3.constructor === F1);//false console.log(f4.constructor === F4);//true console.log(f4.constructor === F3);//false console.log(f4.constructor === F2);//false console.log(f4.constructor === F1);//false
至此,能够发现已经实现:
》实现了继承
》修正了实例的constructor属性指向错误;
》instanceof能正常运行
》能使用uber调用父类的方法
固然也会有缺点:
》每一次继承都会建立多个闭包变量,内存占用多
》每一次继承都要先建立一个父类的实例,(new parent())
》子类与父类必须先定义好,为子类增长实例方法也必须放到inherits()方法调用以后。
参考文章:
http://javascript.crockford.com/inheritance.html
http://www.iteye.com/topic/248933
1.2 Douglas Crockford实现的基于原型的继承
if (typeof Object.create !== 'function') { Object.create = function (o) { //定义类 function F() {} //重写原型,实现继承 F.prototype = o; //返回类的实例 return new F(); }; }
这样也能够实现继承。不过没有类,实例相关的概念。固然也不具备什么uber()方法能力。不过DC说, 这才是JavaScript的“本性”。JavaScript自己就是无类的,基于原型的。ES5已经实现这个方法。
二:John Resig
jQuery的做者John Resig的继承实现思路是:实现一个全局对象Class(实际上是一个函数,但在JS中函数也是对象),这个对象具备静态方法extends()。extends()须要传入一个对象做为返回(构造)函数的原型对象依据。同时也实现了与uber()相似的_super()方法。
/* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function(){ var initializing = false, // 摘自http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html // fnTest是一个正则表达式,可能的取值为(/\b_super\b/ 或 /.*/) // - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的状况 // - 不过我对IE7.0,Chrome2.0,FF3.5进行了测试,此测试都返回true。 // - 因此我想这样对fnTest赋值大部分状况下也是对的:fnTest = /\b_super\b/; fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; //这里的this指向window, 至关于window.Class = function(){} this.Class = function(){}; //@1 /** * 向全局对象Class(实际上是一个函数,但在JS中函数也是特殊的对象,能够在上面挂属性或方法)上面增长静态方法extend * extend方法须要一个对象做为返回类的原型对象的模板 * @param {*} prop * @returns {Function} */ Class.extend = function(prop) { //上面说过,Class是一个(函数)对象,函数做为对象的方法调用时,this指向函数所属的对象 //因此this指向Class对象:@1。其实就是一个匿名函数 var _super = this.prototype; initializing = true; //new this()其实就是包含父类原型的一个空对象。这个为继承打了基础。保证了instanceof能获得正确的结果 var prototype = new this(); //@2 initializing = false; /** * 把传进来的prop对象上面的属性复制到@2对象上 * 这时候有三个对象: * prop:用户做为extend方法的参数传进来的对象 * _super:父类的原型对象 * prototype: 要返回的类的原型对象 * 它们之间的关系是:prop用来扩展prototype; prototype用于实现继承; * _super用于实现调用父类的方法 */ for (var name in prop) { /** * 注意:true && true && 1 > 0 ? 1: -1; === (true && true && 1 > 0 ) ? 1: -1; * 因此下面的赋值过程为: * 1. 若是prop[name]不是一个函数: prototype[name] = prop[name] * 2. 若是prop[name]是一个函数,但_super[name]不是一个函数:prototype[name] = prop[name] * 3. 若是prop[name], _super[name]都是一个函数,且fn.Test.test(prop[name])为假:prototype[name] = prop[name] * 4. 其它状况:prototype[name] = 匿名函数自执行的结果 * * 备注:/\b_super\b/.test(function () {}) === > /\b_super\b/.test((function() {}).toString()); * 即要测试的函数代码中包含_super这个字符串都会返回true; * /\b_super\b/.test(function() {var a = '_super'}) === true * /\b_super\b/.test(function() {var a = '_supe'}) === false */ //在prototype上面加入本身的原型属性 prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && //若是传入对象的某个属性中包含'_super',则要作特殊处理 fnTest.test(prop[name]) ? (function(name, fn){ //这个name, fn会成为闭包变量 return function() { var tmp = this._super; //把全部的父类中有'_super'字符串的方法都用闭包变量name保存起来 //同时_super也是一个闭包变量,这样就能够找到在调用this._super()时要调用父类的那个方法 //uber()方法要手动传入一个方法名,但_super()方法却能自动找到父类的同名方法 this._super = _super[name]; //在前面已经准备好this._super的指向,而后调用包含this._super的实例方法, //就会直接转到父类方法 var ret = fn.apply(this, arguments); //将this._super还原。this._super也可能包含其它值 this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } //定义类Klass。Jhon Resig原代码是用的Class,增长了理解难度 function Klass() { //全部的初始化都是在构建函数的init方法里面进行的。 if ( !initializing && this.init ) this.init.apply(this, arguments); } //重写类的原型,实现继承 Klass.prototype = prototype; //修正类原型对象的constructor指向 Klass.prototype.constructor = Klass; //为类增长静态方法extend Klass.extend = arguments.callee; //返回类,实际上是构造方法 return Klass; }; })();
分析:
1. 在Object的上增长了一层:实现了一个Class类,这个类会做为全部使用extend产生类的第二基类,上面有一个extend方法。这个方法会返回一个类(构造函数),返回时已经设置好原型对象。这个原型对象由 extend传入的参数对象与此类父类的原型共同生成
2. 当调用new Constructor()时,会检测对象是否有init方法,若是有,会调用这个init方法对实例进行初始化,不然返回一个空对象
3. 可使用返回的类再产生一个类
var Person = Class.extend({ init: function(isDancing){ this.dancing = isDancing; }, flag:'PersonProp' }); var Ninja = Person.extend({ init: function(){ //能自动找到父类中的init方法 this._super( false ); }, flag:'NinjaProp' }); var p = new Person(true); p.dancing; // => true var n = new Ninja(); n.dancing; // => false p instanceof Object; //=> true p instanceof Class;//=> true p instanceof Person; //=> true p.constructor === Person;//=>true
http://ejohn.org/blog/simple-javascript-inheritance/
http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html