对js原型和继承的理解一直处于“不懂-懂-不懂-懂-不懂。。。”的无限循环之中,原本打算只是简单总结下js继承方式,可看了些网上的资料后,发现又不懂继承了。。。这篇文章只是一个阅读笔记,总结了我所看到的js文章,参考见文末。javascript
Js全部的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,咱们讲的更可能是构造函数的原型,可是也不可否定普通函数也有原型。譬如普通函数:html
function F(){}; alert(F.prototype instanceof Object);//true
默认状况下,原型对象也会得到一个constructor属性,该属性包含一个指针,指向prototype属性所在的函数java
Person.prototype.constructor === Person
在面向对象的语言中,咱们使用类来建立一个自定义对象。然而js中全部事物都是对象,那么用什么办法来建立自定义对象呢?这就须要用到js的原型:
咱们能够简单的把prototype看作是一个模版,新建立的自定义对象都是这个模版(prototype)的一个拷贝 (实际上不是拷贝而是连接,只不过这种连接是不可见,新实例化的对象内部有一个看不见的__Proto__
指针,指向原型对象)。es6
_proto_
是对[[propertyName]]
属性的实现(是只能对象能够拥有的属性,而且是不可访问的内部属性),它指向对象的构造函数的原型对象。以下:segmentfault
function Person(name) { this.name = name; } var p1 = new Person(); p1._proto_ === Person.prototype;//true
__proto__
只是浏览器的私有实现,目前ECMAScript标准实现方法是Object.getPrototypeOf(object),以下:浏览器
function Person(name) { this.name = name; } var p1 = new Person(); Object.getPrototypeOf(p1) === Person.prototype;//true
构造函数,也即构造对象。首先了解下经过构造函数实例化对象的过程app
function A(x){ this.x=x; } var obj = new A(1);
实例化obj对象有三步:函数
(1).建立obj对象:obj=new Object();
(2).将obj的内部__proto__指向构造他的函数A的prototype,同时obj.constructor===A.prototype.constructor(这个是永远成立的,即便A.prototype再也不指向原来的A原型,也就是说:类的实例对象的constructor属性永远指向"构造函数"的prototype.constructor),从而使得obj.constructor.prototype指向A.prototype(obj.constructor.prototype===A.prototype,当A.prototype改变时则不成立,下文有遇到)obj.constructor.prototype与的内部_proto_是两码事,实例化对象时用的是_proto_,obj是没有prototype属性的,可是有内部的__proto__,经过__proto__来取得原型链上的原型属性和原型方法,FireFox公开了__proto__,能够在FireFox中alert(obj.__proto__);
(3).将obj做为this去调用构造函数A,从而设置成员(即对象属性和对象方法)并初始化。this
当这3步完成,这个obj对象就与构造函数A再无联系,这个时候即便构造函数A再加任何成员,都再也不影响已经实例化的obj对象了。此时,obj对象具备了x属性,同时具备了构造函数A的原型对象的全部成员,固然,此时该原型对象是没有成员的。prototype
原型对象初始是空的,也就是没有一个成员(即原型属性和原型方法)。能够经过以下方法验证原型对象具备多少成员。
function A(x){ this.x=x; } var num = 0; for(o in A.prototype){ alert(o);// alert 原型属性名字 num++; } alert(num);//0
可是,一旦定义了原型属性或原型方法,则全部经过该构造函数实例化出来的全部对象,都继承了这些原型属性和原型方法,这是经过内部的_proto_链来实现的。
例如:
A.prototype.say=function(){alert('haha');}
那全部的A的对象都具备了say方法,这个原型对象的say方法是惟一的副本给你们共享的,而不是每个对象都有关于say方法的一个副本。
因为js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念。因此,要想实现继承,能够经过构造函数和原型的方式模拟实现类的功能。
js里经常使用的以下两种继承方式:
原型链继承(对象间的继承) 类式继承(构造函数间的继承)
如下经过实例详细介绍这两种继承方式的原理
类式继承是在子类型构造函数的内部调用超类型的构造函数。
1 function A(x){ 2 this.x = x; 3 } 4 function B(x,y){ 5 this.tempObj = A; 6 this.tempObj(x); 7 delete this.tempObj; 8 this.y=y; 9 }
解释:
第五、六、7行:建立临时属性tmpObj引用构造函数A,而后在B内部执行(注意,这里执行函数的时候并无用new),执行完后删除。当在B内部执行了this.x=x后(这里的this是B的对象),B固然就拥有了x属性,固然B的x属性和A的x属性二者是独立,因此并不能算严格的继承。第五、六、7行有更简单的实现,就是经过call(apply)方法:A.call(this,x);
这两种方法都有将this传递到A的执行里,this指向的是B的对象,这就是为何不直接A(x)的缘由。这种继承方式便是类继承(js没有类,这里只是指构造函数),虽然继承了A构造对象的全部属性方法,可是不能继承A的原型对象的成员。而要实现这个目的,就是在此基础上再添加原型继承。
原型式继承是借助已有的对象建立新的对象,将子类的原型指向父类,就至关于加入了父类这条原型链。
1 function A(x){ 2 this.x = x; 3 } 4 A.prototype.a = 'a'; 5 function B(x,y){ 6 A.call(this,x); 7 this.y = y; 8 } 9 B.prototype.b1 = function(){ 10 alert('b1'); 11 } 12 B.prototype = new A(); 13 B.prototype.b2 = function(){ 14 alert('b2'); 15 } 16 B.prototype.constructor = B; 17 var obj = new B(1,3);
解释:
这个例子讲的就是B继承A。第7行类继承:A.call(this.x);上面已讲过。实现原型继承的是第12行:B.prototype = new A();
就是说把B的原型指向了A的1个实例对象,这个实例对象具备x属性,为undefined,还具备a属性,值为"a"。因此B原型也具备了这2个属性(或者说,B和A创建了原型链,B是A的下级)。而由于方才的类继承,B的实例对象也具备了x属性,也就是说obj对象有2个同名的x属性,此时原型属性x要让位于实例对象属性x,因此obj.x是1,而非undefined。第13行又定义了原型方法b2,因此B原型也具备了b2。虽然第9~11行设置了原型方法b1,可是你会发现第12行执行后,B原型再也不具备b1方法,也就是obj.b1是undefined。由于第12行使得B原型指向改变,原来具备b1的原型对象被抛弃,天然就没有b1了。
第12行执行完后,B原型(B.prototype)指向了A的实例对象,而A的实例对象的构造器是构造函数A,因此B.prototype.constructor就是构造对象A了(换句话说,A构造了B的原型)。alert(B.prototype.constructor)出来后就是"function A(x){...}" 。一样地,obj.constructor也是A构造对象,alert(obj.constructor)出来后就是"function A(x){...}" ,也就是说B.prototype.constructor===obj.constructor(true),可是B.prototype===obj.constructor.prototype(false),由于前者是B的原型,具备成员:x,a,b2,后者是A的原型,具备成员:a。如何修正这个问题呢,就在第16行,将B原型的构造器从新指向了B构造函数,那么B.prototype===obj.constructor.prototype(true),都具备成员:x,a,b2。
若是没有第16行,那是否是obj = new B(1,3)会去调用A构造函数实例化呢?答案是否认的,你会发现obj.y=3,因此仍然是调用的B构造函数实例化的。虽然obj.constructor===A(true),可是对于new B()的行为来讲,执行了上面所说的经过构造函数建立实例对象的3个步骤,第一步,建立空对象;第二步,obj.__proto__ === B.prototype,B.prototype是具备x,a,b2成员的,obj.constructor指向了B.prototype.constructor,即构造函数A;第三步,调用的构造函数B去设置和初始化成员,具备了属性x,y。虽然不加16行不影响obj的属性,但如上一段说,却影响obj.constructor和obj.constructor.prototype。因此在使用了原型继承后,要进行修正的操做。
关于第十二、16行,总言之,第12行使得B原型继承了A的原型对象的全部成员,可是也使得B的实例对象的构造器的原型指向了A原型,因此要经过第16行修正这个缺陷。
为了让子类继承父类的属性(也包括方法),首先须要定义一个构造函数。而后,将父类的新实例赋值给构造函数的原型。
function Parent(){ this.name = 'mike'; } function Child(){ this.age = 12; } Child.prototype = new Parent();//Child继承Parent,经过原型,造成链条 var test = new Child(); alert(test.age); alert(test.name);//获得被继承的属性 //继续原型链继承 function Brother(){ //brother构造 this.weight = 60; } Brother.prototype = new Child();//继续原型链继承 var brother = new Brother(); alert(brother.name);//继承了Parent和Child,弹出mike alert(brother.age);//弹出12
以上原型链继承还缺乏一环,那就是Object,全部的构造函数都继承自Object。而继承Object是自动完成的,并不须要咱们本身手动继承,那么他们的从属关系可使用操做符instanceof
和函数isPrototypeOf()
判断,以下:
alert(test instanceof Parent);//true alert(test instanceof Child);//true alert(brother instanceof Parent);//true alert(brother instanceof Child);//true
alert(Parent.prototype.isPrototypeOf(test));//true alert(Child.prototype.isPrototypeOf(test));//true alert(Parent.prototype.isPrototypeOf(brother));//true alert(Child.prototype.isPrototypeof(brother));//true
问题字面量重写原型会中断关系,使用引用类型的原型,而且子类型还没法给超类型传递参数。
伪类解决引用共享和超类型没法传参的问题,咱们能够采用“借用构造函数”技术
示例1
function Parent(firstname) { this.fname=firstname; this.age=40; this.sayAge=function() { console.log(this.age); } } function Child(firstname) { this.parent=Parent; this.parent(firstname); delete this.parent;//以上三行也能够用call和apply函数改写 this.saySomeThing=function() { console.log(this.fname); this.sayAge(); } } var mychild=new Child("李"); mychild.saySomeThing();
示例2:用call函数实现
function Parent(firstname) { this.fname=firstname; this.age=40; this.sayAge=function() { console.log(this.age); } } function Child(firstname) { this.saySomeThing=function() { console.log(this.fname); this.sayAge(); } this.getName=function() { return firstname; } } var child=new Child("张"); Parent.call(child,child.getName()); child.saySomeThing();
示例3:用apply函数实现
function Parent(firstname) { this.fname=firstname; this.age=40; this.sayAge=function() { console.log(this.age); } } function Child(firstname) { this.saySomeThing=function() { console.log(this.fname); this.sayAge(); } this.getName=function() { return firstname; } } var child=new Child("张"); Parent.apply(child,[child.getName()]); child.saySomeThing();
问题:类式继承没有原型,子类型只是继承了父类型构造对象的属性和方法,没有继承父类型原型对象的成员。
举例说明:
function Father(x){ this.x=x; } Father.prototype.say = function(){ alert(this.x); } function Child(x,y){ this.y=y; this.tempObj=Father; this.tempObj(x); delete this.tempObj; } var obj = new Child(1,2); alert(obj.x);//1 alert(obj.y);//2 alert(obj.say);//undefined
组合继承利用原型链+借用构造函数的模式解决了原型链继承和类式继承的问题。
示例
function Parent(age){ this.name = ['mike','jack','smith']; this.age = age; } Parent.prototype.say = function(){ return this.name + 'are both' + this.age; } function Child(age){ Parent.call(this,age); } Child.prototype = new Parent(); var test = new Child(1); alert(test.say());
组合式继承是比较经常使用的一种继承方法,其背后的思路是 使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承。这样,既经过在原型上定义方法实现了函数复用,又保证每一个实例都有它本身的属性。
问题:组合继承的父类型在使用过程当中会被调用两次;一次是建立子类型的时候,另外一次是在子类型构造函数的内部。
举例说明以下:
function Parent(name){ this.name = name; this.arr = ['哥哥','妹妹','父母']; } Parent.prototype.run = function () { return this.name; }; function Child(name,age){ Parent.call(this,age);//第二次调用 this.age = age; } Child.prototype = new Parent();//第一次调用
这两次调用一次是经过类式继承实现的,另外一次是经过原型链继承实现的,在上面原型链继承详解中有介绍。能够用下面介绍的寄生组合继承解决该问题。
这种继承借助原型并基于已有的对象建立新对象。
1 function obj(o){ 2 function F(){} 3 F.prototype = o; 4 return new F(); 5 } 6 var person = { 7 name : 'ss', 8 arr : ['hh','kk','ll'] 9 } 10 var b1 = obj(person); 11 alert(b1.name);//ss 12 b1.name = 'join'; 13 alert(b1.name);//join 14 alert(b1.arr);//hh,kk,ll 15 b1.arr.push('gg'); 16 alert(b1.arr);//hh,kk,ll,gg 17 var b2 = obj(person); 18 alert(b2.name);//ss 19 alert(b2.arr);//hh,kk,ll,gg
疑问:(求解答)
这里的b1
和b2
会共享原型对象person
的属性,那么在12
行经过b1.name='join'
修改了原型对象person
的name
属性后,为何并无影响到b2.name
,在18
行仍然输出ss
???
而在15
行经过b1.arr.push('gg')
修改了原型对象person
的arr
属性后,却影响到了b2.arr
,在19
行的输出多了gg
这种继承方式是把原型式+工厂模式结合起来,目的是为了封装建立的过程。
function obj(o){ function F(){} F.prototype = o; return new F() } function create(o){ var f = obj(o); f.say = function() { return this.arr } return f }
寄生组合继承解决了组合继承中父类型两次调用问题
function obj(o){ function F(){} F.prototype = o; return new F() } function create(parent,child){//经过这个函数,实现了原型链继承,但child只含有parent原型链中的属性 var f = obj(parent.prototype); f.constructor = child; child.prototype = f; } function Parent(name){ this.name = name; this.arr = ['heheh','guagua','jiji']; } Parent.prototype.say = function(){ return this.name } function Child(name,age){ Parent.call(this,name);//类式继承,这里继承parent构造函数中定义的属性 this.age = age; } create(Parent,Child); var test = new Child('trigkit4',21); test.arr.push('nephew'); alert(test.arr);// alert(test.run());//只共享了方法 var test2 = new Child('jack',22); alert(test2.arr);//引用问题解决
1.js实现继承的5中方式
2.ES6 Class
3.js继承方式详解
4.前段开发必须知道的js(一)原型和继承
5.细说 Javascript 对象篇(二) : 原型对象
6.javascript原型概念(一)
7.Javascript基于 ‘__proto__’ 的原型链