web前端开发必懂之一:JS继承和继承基础总结
首先,推荐一篇博客豪情的博客JS提高: http://www.cnblogs.com/jikey/p/3604459.html ,里面的链接全是精华, 一般人我不告诉他;
我们会先从JS的基本的设计模式开始,由浅入深, 会描述prototype,__proto__,consturctor等基础知识和JS的常见继承方式, 以及四个类工厂的推荐和使用(包括JS.Class,prototype的类工厂,john resig写的一个简洁类工厂库,以及Pjs一个很飘逸的继承库,很飘逸-_-),最后有3个参考资料链接:,最后有我个人的视频,欢迎拍砖哇, ///(o)_(o)////。
工厂模式:因为使用用一个接口创建很多对象会产生大量的重复代码,为了解决这个问题,人们就开始使用工厂模式:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> //工厂模式:因为使用用一个接口创建很多对象会产生大量的重复代码,为了解决这个问题,人们就开始使用工厂模式: function Person(hairs,face, eye) { var o = new Object(); o.hairs = hairs; o.face = face; o.eye = eye; o.say = function(){ console.log("say someting to me!"); }; return o; }; //我们通过 Person(0,1,2)就可以创建一个包含特定信息的Person对象, 以后要生成对象直接执行Person然后给他传参数就好了; //比如我们要生成10个Person, 很方便很快; var c = 10; while( c-- ) { console.log(Person(c,c,c)) }; </script> </body> </html>
构造函数模式:使用构造函数模式我们能少些更多代码,比如上面的工厂模式我们可以改写成:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Person(hairs, face, eye) { this.hairs = hairs; this.face = face; this.eye = eye; }; //同样, 我们再生成10个小朋友 var c = 10; while( c-- ) { console.log( new Person(c,c,c) ); }; </script> </body> </html>
//知识点1: 那么工厂模式和构造函数模式有什么区别呢:
/*
* 没有显式地创建对象
* 直接把属性和方法赋值给了this
* 没有return语句
* 构造函数的前面有一个new;
* */
// 知识点2:
/*
* 通过new的构造函数会经过四个阶段:
* 1:创建一个新对象;
* 2:把this赋值给这个新对象
* 3:执行构造函数中的代码
* 4:返回新对象
* */
原型是神马? 原型就是公用的方法或者属性,这么理解最简单, 当然:
1、prototype本质上还是一个JavaScript对象;
2、每个函数都有一个默认的prototype属性;
3、通过prototype我们可以扩展Javascript的内建对象
一些代码弄懂原型的本质:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Memo() {}; Memo.prototype.hehe = 1; Memo.prototype.shut = function() { console.log("miaomiao") }; var m0 = new Memo(); var m1 = new Memo(); console.log(m0.shut === m1.shut); //ture, 因为m0的shut和m1的shut就是指向了Memo.prototype.shut这个方法,所以他们相等; //Object.getPrototypeOf会返回实例的原型; console.log(Object.getPrototypeOf(m0)) //输出了:Memo {hehe: 1, shut: function} console.log( Memo.prototype.isPrototypeOf(m0) )// 输出: true; //原型对象有一个很大的问题要非常注意 ==》》 原型的属性和方法是被共享的, 比如: Memo.prototype.shut = "W_W"; l(m0.shut) //输出:W_W, 悲剧了吧, 本来原型上的shut是个方法, 被改成字符串以后, 实例上的shut也发生了改变; l(m1.shut) //输出:W_W; m0.__proto__.shut = 1111; //m0的__proto__指向了Memo.prototype.shut,__proto__在标准浏览器下面是不能枚举到的,但确实是存在的一个属性; l(m1.shut) //输出了1111 //只要原型上的属性或者方法被改了, 实例上的也会发生改变; </script> </body>
</html>
知识点:Object.getPrototypeOf;
Fn.prototype.isPrototypeOf( instance );
好的, 现在再来了解一下constructor, 如果你已经知道constructor是什么的话, 这段略过, constructor是默认指向创建当前对象的构造函数, 但是这里面有一些坑要注意, 比如你的原型prototype被改了, 实例的constructor就变了 ,
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var Fn = function(){ //我是构造函数Fn; }; l("Fn的constructor"); l(Fn.prototype.constructor); /*输出function (){ //我是构造函数Fn; } */ var f = new Fn; l("f的constructor"); l(f.constructor); /*输出; * function (){ //我是构造函数Fn; }*/ //当函数创建的时候默认就为prototype新建一个constructor指向自己; l(Fn.constructor === Function); //输出true, Function这个构造函数是Function; l(Fn.constructor) // 输出function Function() { [native code] } ,意思是Function这个构造函数 ; l(Fn.constructor === Fn.__proto__.constructor) //输出true; Fn的constructor实际上是指向Fn.__proto__的; //好的, 现在重新定义一个Fn; var Fn = function(){ //我是构造函数Fn; }; Fn.prototype = {}; l(Fn.prototype.constructor) /*打印出了内部的代码, 为什么呢? 因为我们改变了Fn的原型, Fn的constructor指向了{}空对象的Contructor; * function Object() { [native code] } */ </script> </body> </html>
大家一定要懂的是实例上的__proto__就是指向原型上的prototype, 这样会少走一些弯路,可以节约更多的时间用来看片, 你懂的;
每一个函数新建的时候都有一个默认的prototype, prototype这个对象上面默认有一个指向自己的constructor;
prototype和__proto__的区别:__proto__是实例和Person.prototype之间的关系,而constructor是实例和Person之间的关系
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var Fn = function() {}; Fn.prototype.hehe = 1; Fn.prototype.lala = 2; var f = new Fn; l(f) //输出了Fn {hehe: 1, lala: 2} ; l(f.__proto__) //输出了Fn {hehe: 1, lala: 2} ; l(Fn.prototype === f.__proto__) //输出了true, 这里要懂的东西是实例上的__proto__这个属性指向了构造函数的prototype; l(f === f.__proto__) //输出false; 这里要懂的是f是new出来的实例; //因为f上面的hehe和lala都是继承的属性, 所以这里面的log并没有被输出; for(var p in f){ l("输出所以属性:" + p); //输出所以属性:hehe demo.html:11; 输出所以属性:lala //过滤非私有属性; if( f.hasOwnProperty(p) ) l("输出私有属性" + p); //因为f没有私有属性,所以这边没有log出来; }; </script> </body> </html>
有了上面的基础, 我们开始说说JS的面向对象和继承吧;
1:组合使用构造器和原型模式, 这种模式是构造函数和原型混合的模式, 使用最广泛, 认同度也最高的一种模式, 也是最基础的模式;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Duck( name ,word) { this.name = name; this.word = word; }; Duck.prototype.say = function() { console.log( this.name+" say : " + this.word ) }; var duck = new Duck("nono","hehe"); duck.say(); </script> </body> </html>
寄生构造模式; 听名字真的很玄乎..其实跟工厂模式一模一样的, 其实就是自定义模型的封装;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Foxy(name , word) { var result = new Object(); result.name = name; result.word = word; return result; }; l( new Foxy("nono","say someting") ); //输出:Object {name: "nono", word: "say someting"} ; function Monkey( ) { var aResult = []; //技巧:通过apply把arguments保存到数组; aResult.push.apply(aResult, arguments); return aResult; }; l( new Monkey("nono","dino","kite","sam") ); //打印出了这个:["nono", "dino", "kite", "sam"]; //要注意的是使用寄生模式的返回的对象和构造函数一点关系都没有; </script> </body> </html>
JS的原型继承, 继承是依赖于原型链的;那么JS原型链是什么呢:
/* 这段话慢慢读, 从搞基程序设计三抄过来的,很重要, 实体书最好自己看一看哇;
* ECMAScript中描述了原型链的概念, 并将原型链作为实现继承的主要方法, 基本思想是利用引用类型继承另一个引用类型的属性和方法。
* 简单回顾一下构造函数,原型和实例的关系:每一个函数都有一个原型对象, 每一个原型对象都有一个指向构造函数的指针,
* 而实例包含了一个指向原型对象的内部(不可见的)指针。 那么我们让原型对象等于另一个类型的实例, 那么这个原型对象将会包含指向
* 另一个原型对象的指针,如果另一个原型对象又是指向了别的原型的一个实例, 这样层层嵌套, 就形成了原型链;
* */
那么我们利用原型的原型链相互继承来写一个基本的例子:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var Fn = function() { this.property = true; } Fn.prototype.getFnProperty = function() { console.log( this.property ); }; var SubFn = function() { this.subProperty = false; }; //SubFn继承了Fn的实例 SubFn.prototype = new Fn(); //为实例添加额外的实例方法; SubFn.prototype.getSubProperty = function(){ console.log(this.subProperty); }; var subFn = new SubFn(); subFn.getFnProperty(); //输出了true subFn.getSubProperty(); //输出了false /*现在subFn的constructor 是 function () { this.property = true; }; 所以要修正SubFn.prototype.constructor = SubFn */ </script> </body> </html>
原型式继承第二个例子:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> // 首先, 准备一个方法; var inherit = function(o) { if(!typeof o === "object")return; function F () {} F.prototype = o; F.prototype.constructor = F; return new F(); }; var Fn = function() { this.property = true; } Fn.prototype.getFnProperty = function() { console.log( this.property ); }; var SubFn = function() { this.subProperty = false; }; //SubFn继承了Fn的实例 SubFn.prototype = new Fn(); //为实例添加额外的实例方法; SubFn.prototype.getSubProperty = function(){ console.log(this.subProperty); }; var subFn = new SubFn(); //这个方法的内部, 临时创建了一个构造函数, 然后将传入的对象作为这个构造函数的原型, 最后返回一个临时的新实例; //ECMASscript 5 有新增了一个Object.create 效果和inherit一模一样, 它可以接收第二个参数, // 第二个参数要通过defineProperties的方式设置,会覆盖原型的属性, 比如: Object.create(subFn, { getFnProperty: { value:1 } }); var Fn = function() {}; //如果我们inherit传对象; Fn.prototype = inherit( {0:0,1:1,2:2,3:3,4:4} ); l( new Fn ) //==>Fn {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, constructor: function(){....} 继承了哦 //如果我们给inherit传一个构造函数的实例; Fn.prototype = inherit( new SubFn ); l(new Fn); </script> </body> </html>
有时候看到原型的各种引用会尿失禁, 引来引去的,坑爹啊, 不说了去洗裤子了....
寄生组合式继承
组合继承是JS的常用继承模式, 但是也有自己的不足, 组合继承最大的问题的无论是什么情况下, 都会两次调用超类的构造函数;
一个是在创建子类原型的时候, 另一个是在子类构造函数的内部, 那么子类的原型会包含所有超类实例的全部属性,
寄生组合式继承就是为了解决子类原型包含所有超类实例全部属性这个问题而存在的;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var inherit = function(o) { if(!typeof o === "object")return; function F () {} F.prototype = o; F.prototype.constructor = F; return new F(); }; //首先要准备inheritPrototype方法; var util = util || {}; util.inherit = inherit; util.inheritPrototype = function(subType, superType) { var _prototype = this.inherit( superType.prototype ); _prototype.constructor = subType; subType.prototype = _prototype; }; function F( name ) { this.name = name; this.type = "human"; this.habits = ["dance","code"]; }; F.prototype.laugh = function() { console.log("heha!"); }; var InheritF = function() { F.apply( this, arguments ); }; util.inheritPrototype(InheritF, F); InheritF.prototype.letsGo = function() { l("1,2,3,4") }; var nono = new InheritF("nono"); nono.habits.push("read books"); l(nono.habits) var nonono = new InheritF("nono"); l( nonono.habits ); //继承的方法千万种,万变不离其宗; </script> </body> </html>
JS各种使用了继承库的类库推荐, 可以加深印象:
首先, JS.Class 是一个mootools式的类工厂 基于 lunereaper<![[dawid.kraczkowski[at]gmail[dot]com]]>的项目进行修改, 让子类的实现更简洁;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> JS = {}; // 这个是库的源代码; JS.Class = function(classDefinition) { //返回目标类的真正构造器 function getClassBase() { return function() { //它在里面执行用户传入的构造器construct //preventJSBaseConstructorCall是为了防止在createClassDefinition辅助方法中执行父类的construct if (typeof this['construct'] === 'function' && preventJSBaseConstructorCall === false) { this.construct.apply(this, arguments); } }; } //为目标类添加类成员与原型成员 function createClassDefinition(classDefinition) { //此对象用于保存父类的同名方法 var parent = this.prototype["parent"] || (this.prototype["parent"] = {}); for (var prop in classDefinition) { if (prop === 'statics') { for (var sprop in classDefinition.statics) { this[sprop] = classDefinition.statics[sprop]; } } else { //为目标类添加原型成员,如果是函数,那么检测它还没有同名的超类方法,如果有 if (typeof this.prototype[prop] === 'function') { var parentMethod = this.prototype[prop]; parent[prop] = parentMethod; } this.prototype[prop] = classDefinition[prop]; } } } //其实就是这样的Base = function() {};别想太多了; var preventJSBaseConstructorCall = true; var Base = getClassBase(); preventJSBaseConstructorCall = false; createClassDefinition.call(Base, classDefinition); //用于创建当前类的子类 Base.extend = function(classDefinition) { //其实就是这样的Base = function() {};别想太多了; preventJSBaseConstructorCall = true; var SonClass = getClassBase(); SonClass.prototype = new this();//将一个父类的实例当作子类的原型 preventJSBaseConstructorCall = false; createClassDefinition.call(SonClass, classDefinition); SonClass.extend = this.extend; return SonClass; }; return Base; }; </script> <script> //这是实际案例; var Animal = JS.Class({ construct: function(name) { this.name = name; }, shout: function(s) { console.log(s); } }); var animal = new Animal(); animal.shout('animal'); // animal var Dog = Animal.extend({ construct: function(name, age) { //调用父类构造器 this.parent.construct.apply(this, arguments); this.age = age; }, run: function(s) { console.log(s); } }); var dog = new Dog("dog", 4); console.log(dog.name); dog.shout("dog"); // dog dog.run("run"); // run console.log(dog.constructor + "") var Shepherd = Dog.extend({ statics: {//静态成员 TYPE: "Shepherd" }, run: function() {//方法链,调用超类同名方法 this.parent.run.call(this, "fast"); } }); console.log(Shepherd.TYPE);//Shepherd var shepherd = new Shepherd("shepherd", 5); shepherd.run();//fast var a = new Animal("xx"); console.log(a.run); </script> </body> </html>
prototype这个( ▼-▼ )库以前牛逼着呢, 但是一百年河东一百年河西, prototype当前的版本是最新版本的1.7稳定版,prototype里面的类工厂创建的主代码也被我单独裁出来了, 源码也差不多, 源码有我加的注释,自己个人方便阅读, HTML代码的最后自己写的几种 创建类工厂的方式, 可以借鉴(我没看api, 源码就是api....)
运行下面代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>prototype Demo</title> </head> <body> <script> var Class = (function() { var IS_DONTENUM_BUGGY = (function(){ for (var p in { toString: 1 }) { if (p === 'toString') return false; } return true; })(); Object.keys = Object.keys || (function() { if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } var results = []; for (var property in object) { if (object.hasOwnProperty(property)) { results.push(property); } } return results; })(); Object.isFunction = function(fn) { return typeof fn === "function"; }; function argumentNames() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }; function $A(iterable) { if (!iterable) return []; if ('toArray' in Object(iterable)) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; }; function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; }; //wrapper表示的是现在的方法; function wrap(wrapper) { //this指向的返回的闭包; var __method = this; return function() { //a是指把orginalFu和当前的参数混合成一个数组; var a = update([__method.bind(this)], arguments); return wrapper.apply(this, a); }; }; Object.extend = extend; Function.prototype.argumentNames = argumentNames; Function.prototype.wrap = wrap; //把第二个参数的属性继承到第一个参数上; function extend(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; function subclass() {}; function create() { //相当于对参数进行slice; var parent = null, properties = $A(arguments); //如果第一个是函数就把这个函数作为超类; if (Object.isFunction(properties[0])) parent = properties.shift(); //这个是返回的构造函数; function klass() { this.initialize.apply(this, arguments); }; //为klass添加addMethods方法; Object.extend(klass, Class.Methods); //保存超类; klass.superclass = parent; klass.subclasses = []; if (parent) { subclass.prototype = parent.prototype; klass.prototype = new subclass; //把当前的类保存到父级的超类, 有什么意义呢; parent.subclasses.push(klass); }; for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); //避免没有initialize而报错; if (!klass.prototype.initialize) klass.prototype.initialize = function() {}; //这个避免constructor被properties覆盖了, 重新赋值; klass.prototype.constructor = klass; return klass; } function addMethods(source) { var ancestor = this.superclass && this.superclass.prototype, properties = Object.keys(source); //IE8以下可以遍历到toString和valueOf; if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf"); } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; //没有父级就没必要走下去了 , 如果有父级, 而且value不是function那么就没必要走了; //如果你传:Class.create(Fn, {0:function($super){alert(1)},1:1,2:2,3:3,4:4}) //那么$super这个方法是指向ancestor的闭包, 只要执行$super那么超类的同名方法会被执行; if (ancestor && Object.isFunction(value) && value.argumentNames()[0] == "$super") { var method = value; //返回一个闭包; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); /* 如果不用简写的话,写成这样子也是ok的,他那么写, 一下子高端起来了,我C, 就是两个闭包哇; * var closureFn = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; })(property); value = function() { var args = Array.prototype.slice.call(arguments); args.unshift( closureFn ); //是谁执行了value,当前的this就是谁,刚好是实例对象; method.apply(this, args) }; * */ //修正这个闭包的valueOf为method这个方法.... value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); }; //Class.create({0:0,1:1,2:2,3:3,4:4})会走这边; this.prototype[property] = value; }; return this; }; return { create: create, Methods: { addMethods: addMethods } }; })(); </script> <script> //__________________________________________ //创建一个类工厂 var Fn = Class.create({ method0 : function() { console.log("method0"); console.log(arguments); }, method1 : function() { console.log("method1"); console.log(arguments); } }); //实例化该类工厂; var fn = new Fn(); fn.method0(); //输出 : method0 [] fn.method1(0,1,2,3,4); //输出 : method1 , [0, 1, 2, 3, 4]; //SubFn , 继承了Fn; var SubFn = Class.create(Fn, { method2 : function() { console.log(2) } }); (new SubFn).method2() //输出 :2 //__________________________________________ //__________________________________________ //子类调用超类的同名方法的使用; //子类; var Fn = Class.create({ 0:function(){ console.log(arguments); return "hehe"; }, 1:1, 2:2, 3:3, 4:4 }); //超类; //Fn0继承了Fn; var Fn0 = Class.create(Fn, { 0:function($super,arg0,arg1,arg2,arg3){ console.log( $super( arguments ) ); console.log(arg0+" "+arg1+" "+arg2+ " " +arg3); } }); //实例化Fn0,执行0的方法传进去几个参数, 都会传到Fn的0方法里面, Fn0如果叫做$super的参数, $super代表超类的同名方法; (new Fn0)[0](10,9,8,7); //输出 三个字段,包含超类的的arguments和超类的返回,以及子类的输出: arguments5 , hehe , 10,9,8,7 console.log(new Fn0); //输出一个实例; //__________________________________________ //为Fn0的原型新加一个“nono”方法; Fn0.addMethods({"nono" : function() { console.log("you are super man"); }}); //实例化原型执行nono方法: var instance = new Fn0; instance.nono() //输出了:you are super man; //__________________________________________ //我们现在的类方法和属性都是原型上继承下来的,只要超类的方法或者属性发生了改变, 那么子类的方法也发生了改变; //比如 Fn0.prototype.nono = "someting wrong!!"; instance.nono // 输出了== >"someting wrong!!" ...卧槽,这样可不行啊; //我们需要创建私有属性 var FnBar = Class.create(Fn,{ initialize : function() { this.hehe = "haha", this.say = function( words ) { console.log( words ||this.hehe ); }; } }); var instanceBar = new FnBar(); var FnFoo = Class.create(FnBar,{ initialize : function() { this.say = function() { console.log("fuck! you duck!!!"); }; } }); //prototype是基于原型继承原型的库, 而不是基于原型继承实例, 所以用起来也挺方便的; (new FnFoo).say(); </script> </body> </html>
这款继承创建的作者是jQ的作者,你懂的, 不绕;
https://github.com/html5crew/simple-inheritance 《《== 这个是代码的源地址:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> /* source: https://gist.github.com/shakyShane/5944153 * * Simple JavaScript Inheritance for ES 5.1 ( includes polyfill for IE < 9 ) * based on http://ejohn.org/blog/simple-javascript-inheritance/ * (inspired by base2 and Prototype) * MIT Licensed. */ (function (global) { "use strict"; if (!Object.create) { Object.create = (function () { function F() { } return function (o) { if (arguments.length !== 1) { throw new Error("Object.create implementation only accepts one parameter."); } F.prototype = o; return new F(); }; })(); } var fnTest = /xyz/.test(function () { /* jshint ignore:start */ xyz; /* jshint ignore:end */ }) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) function BaseClass() { } // Create a new Class that inherits from this class BaseClass.extend = function (props) { var _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) var proto = Object.create(_super); // Copy the properties over onto the new prototype for (var name in props) { // Check if we're overwriting an existing function proto[name] = typeof props[name] === "function" && typeof _super[name] === "function" && fnTest.test(props[name]) ? (function (name, fn) { return function () { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, props[name]) : props[name]; } // The new constructor var newClass = function () { if (typeof this.init === "function") { this.init.apply(this, arguments); } }; // Populate our constructed prototype object newClass.prototype = proto; // Enforce the constructor to be what we expect proto.constructor = newClass; // And make this class extendable newClass.extend = BaseClass.extend; return newClass; }; // export global.Class = BaseClass; })(this); </script> <script> var Fn = function() {}; //对继承的插件进行引用; Fn.extend = window.Class.extend; Fn.prototype.lala = 1; Fn.prototype.say = function() { alert( this.lala ); }; var Foo = Fn.extend({ dudu:2, init:function(){ this.name="nono" }, dayDudu:function(){ alert(this.dudu); } }) </script> </body> </html>
执行 new Foo打印出来对象结构如下:
pjs这个类工厂的github地址是:git://github.com/jayferd/pjs ,挺有名的js类工厂库, 写了源码分析, 关于代码我就不吐槽了,你看了就懂了, 不过真心挺方便的;
运行下面代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>无标题文档</title> </head> <body> <script> var P = (function(prototype, ownProperty, undefined) { // helper functions that also help minification function isObject(o) { return typeof o === 'object'; } function isFunction(f) { return typeof f === 'function'; } // a function that gets reused to make uninitialized objects function BareConstructor() {} function P(_superclass /* = Object */, definition) { // handle the case where no superclass is given //如果没有超类,那么超类就设置为Object,definition为对象或者是fn都行 //是对象的话会把对象的所有属性和方法复制到返回的类原型, //是函数会传给函数有关返回类的原型(引用)等参数; if (definition === undefined) { definition = _superclass; _superclass = Object; }; // C is the class to be returned. // // It delegates to instantiating an instance of `Bare`, so that it // will always return a new instance regardless of the calling // context. // // TODO: the Chrome inspector shows all created objects as `C` // rather than `Object`. Setting the .name property seems to // have no effect. Is there a way to override this behavior? //无论你是使用 new还是直接运行都会返回C的实例; function C() { var self = new Bare; //如果创建的实例有init会执行init方法, init这个函数里面放的是私有的属性; if (isFunction(self.init)) self.init.apply(self, arguments); return self; } // C.Bare is a class with a noop constructor. Its prototype is the // same as C, so that instances of C.Bare are also instances of C. // New objects can be allocated without initialization by calling // `new MyClass.Bare`. function Bare() {} C.Bare = Bare; // Set up the prototype of the new class. //指定超类的原型 到BareConstructor, 公用一个函数到BareConstructor,专门用来实例化超类; var _super = BareConstructor[prototype] = _superclass[prototype]; //proto为Bare原型的引用; //实例化的超类的实例指向了C和Bare的Prototype,主要是Bare的prototype,因为类工厂返回的实例就是Bare的实例; var proto = Bare[prototype] = C[prototype] = new BareConstructor; // other variables, as a minifier optimization var extensions; // set the constructor property on the prototype, for convenience proto.constructor = C; //这个超类的mixin会重新调用P返回原型重新赋值给Bare和C,返回C, 方便无new实例化; C.mixin = function(def) { Bare[prototype] = C[prototype] = P(C, def)[prototype]; return C; }; //C.open 这个方法可以用来为这个类添加原型 return (C.open = function(def) { //重新定义extensions为一个空对象, C.open打开或者第一次打开的时候重新定义, 写成var extensions = {}; 也行,个人感觉没有问题; extensions = {}; if (isFunction(def)) { // call the defining function with all the arguments you need // extensions captures the return value. //proto指向了C和Bare的原型,要添加原型就往这个对象extend方法即可; //也可以为这个函数返回对象作为原型的属性; extensions = def.call(C, proto, _super, C, _superclass); } else if (isObject(def)) { // if you passed an object instead, we'll take it extensions = def; }; //继承属性到超类; // ...and extend it if (isObject(extensions)) { for (var ext in extensions) { if (ownProperty.call(extensions, ext)) { proto[ext] = extensions[ext]; }; }; }; //没有init就把init设置为超类; // if there's no init, we assume we're inheriting a non-pjs class, so // we default to applying the superclass's constructor. if (!isFunction(proto.init)) { proto.init = _superclass; } return C; })(definition); } // ship it return P; // as a minifier optimization, we've closured in a few helper functions // and the string 'prototype' (C[p] is much shorter than C.prototype) })('prototype', ({}).hasOwnProperty); </script> <script> //直接创建一个类 var Fn = P({ 0:0, 1:1 }); console.log( new Fn() ); //输出: Bare {0: 0, 1: 1, constructor: function, init: function} console.log( (new Fn()).init() ) //如果没有init, 那么init出来默认为 Object {} //让FnBar继承Fn var FnBar = P(Fn,{ 2:2, 3:3, 4:4 }); console.log( new FnBar ); //输出: Bare {2: 2, 3: 3, 4: 4, constructor: function, 0: 0, 1: 1, init: function} //为FnFoo添加额外的方法和属性; var FnFoo = FnBar.open({ 5:5, 6:6 }); console.log( new FnFoo ); //输出: Bare {2: 2, 3: 3, 4: 4, 5: 5, 6: 6, constructor: function, 0: 0, 1: 1, init: function} //各种各样的继承方式 你都可以用 var FnFoo0 = FnFoo.mixin({ 7:7, 8:8 }); console.log( new FnFoo0); //输出: Bare {7: 7, 8: 8, constructor: function, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 0: 0, 1: 1, init: function} //____________________________________-_-_______________ //直接创建一个类 var Fn = P({ 0:0, 1:1, init : function() { this.name = "nono"; console.log(this); } }); (new Fn).init(); // 输出: Bare {name: "nono", 0: 0, 1: 1, constructor: function, init: function}; //通过C.open的方式为Fn添加超类 var FnBar = Fn.open(function( proto, _super, C, _superclass ) { proto.style = "gangnanStyle"; proto.job = "superWebDevoloper"; proto.say = function() { console.log( this.job + " __ " + this.job); }; }); (new FnBar).say(); //输出: superWebDevoloper __ superWebDevoloper //通过传入function的方式为构造函数添加超类; // 该方法不但继承了FnBar, 而且在第二个Fn的参数中为构造函数添加了sayWhat方法, 该函数的返回也会作为原型被添加到类原型上。。。。; var FF = P(FnBar, function( proto, _super, C, _superclass ) { proto.sayWhat = function() { console.log( "funky world!" ); }; return { sayHi : function() { console.log( "monkey world!" ); } }; }); console.log( new FF );//输出: Bare {name: "nono", constructor: function, sayWhat: function, 0: 0, 1: 1, init: function, style: "gangnanStyle"…}; (new FF).sayWhat(); //输出: funky world! (new FF).sayHi(); //输出:monkey world! </script> </body> </html>
每一个人都要有一片属于自己的宁静天空, 在那里没有压力, 只有自己 , 和自己身体的对话;
最后提供一些参考的链接:
javascript 类属性、类方法、类实例、实例属性、实例方法、prototype、__proto__ 测试与小结:
http://www.cnblogs.com/mrsunny/archive/2011/05/09/2041185.html
JS的构造函数:
http://www.cnblogs.com/jikey/archive/2011/05/13/2045005.html
浅析Javascript原型继承: