原型链和继承
Posted 小章鱼哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原型链和继承相关的知识,希望对你有一定的参考价值。
1. 原型链
每一个object
对象都有自己[[prototype]]
属性,它指向自己的原型对象(prototype),该原型对象又有自己的[[prototype]]
属性,指向自己的原型对象,层层向上直到一个对象的原型对象是null
。null
没有原型,作为这个原型链的最后一节。
访问一个object
对象的属性的时候,先去查找对象本身有没有同名的属性,如果没有,就去对象的原型上去找,原型的原型,层层向上查询,直到找到同名的属性,或者直到找到原型链的终点:null
。
2. __proto__
vs prototype
2.1 __proto__
每一个object
对象都有一个[[prototype]]
属性,它是一个隐藏属性。它指向对象的原型。在很多浏览器的实现中,把[[prototype]]
实现为__proto__
。
所以,__proto__
指向的原型是什么呢?这就由它的构造方法来决定。
对象的三种构造方法:
字面量构造法
var a =
name: 'lc'
这个a
对象,其__proto__
指向Object.prototype
。
实际上,字面量构造法是一个语法糖,本质上也是依靠构造函数创建的。
var a = Object.create();
a.name = 'lc';
构造函数构造法
function A(name)
this.name = name;
var a = new A();
这个a
对象,其__proto__
指向A.prototype
。
new
内部是这样实现的:
// b = new A()
var obj = ;
obj.__proto__ = A.prototype;
A.call(obj);
return obj;
Object.create
构造法
var b =
name: 'lc'
var a = Object.create(a);
这个a
对象,其__proto__
指向b
。
Object.create
内部是这样实现的:
Object.create = function(b)
function fn();
fn.prototype = b;
return new fn();
实际上,a
也是由new
方法创建,只是,a
的构造函数fn
只是存在了一瞬间。在外部,我们看不到a
的构造函数,只能看到它的原型是b
。
隐式原型
__proto__
的作用:可以在访问一个object
对象的属性的时候,先去查找对象本身有没有同名的属性,如果没有,就去对象的原型上去找,原型的原型,层层向上查询,直到找到同名的属性,或者直到找到原型链的终点:null
。我们称它为隐式原型。
我们可以通过Object.getPrototypeOf
获得。
2.2 prototype
每一个函数在创建之后都有一个prototype
属性,它指向函数的原型对象。
ps: 通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。
显式原型
prototype
的作用:用来实现基于原型的继承与属性的共享。我们把它称之为显式原型。
2.3 prototype
和__proto__
关系
a的构造函数是A,a的隐式原型指向A的prototype。
2.4 constructor
所有对象都会从它的原型对象上继承constructor
属性,它会指向对象的构造函数。
2.5 画个图吧
3. 继承
3.1 构造函数继承
function A(name)
this.name = name;
function B(name)
A.call(this, name);
var a = new A('qq')
var b = new B('lc');
缺点: 子类父类没有公用的方法。函数复用无从谈起。
3.2 原型链继承
function A(name)
this.name = name;
this.age = 3;
A.prototype.getName = function()
return this.name;
function B(name)
this.name = name;
B.prototype = new A();
B.prototype.setName = function(name)
this.name = name;
var b = new B('ls');
b.getName();
b.setName('qq');
缺点: 父类的构造函数的属性变成子类的__proto__
下的属性了。
所以,上述代码
b.age //3
// 实际上,b不应该由age属性
如图,B.prototype
的constructor
属性指向A
而不是B
。
假设,两个子类child1
和child2
,两个子类都继承一个父类parent
。父类的构造函数下的引用类型,就会被两个child
共享。并且共享的是引用值,实例之间的属性会互相干扰。
3.3 组合式继承
function A(name)
this.name = name;
this.age = 3;
A.prototype.getName = function()
return this.name;
function B(name)
A.call(this, name);
B.prototype = new A();
B.prototype.setName = function(name)
this.name = name;
var b = new B('ls');
上面两种方法的组合。目前比较完美了。子类之间的属性既不会互相影响,又可以实现方法的共享。
缺点: 构造函数被执行了两次,new
的时候执行了一次,call
的时候执行了一次。
实际上解决这个问题,有一个可以优化的地方:
function A(name)
this.name = name;
this.age = 3;
A.prototype.getName = function()
return this.name;
function B(name)
A.call(this, name);
// 去掉这行
- B.prototype = new A();
// 添加这行
+ B.prototype = A.prototype;
B.prototype.setName = function(name)
this.name = name;
var b = new B('ls');
这个可以解决构造函数被执行了两次,但是实际上,到组合式继承目前为止,都没有解决子类对象原型constructor
指向的问题。
3.4 原型式继承
function A(name)
this.name = name;
this.age = 3;
A.prototype.getName = function()
return this.name;
var a = new A('ls');
var b = Object.create(a);
对象的浅复制。
b.__proto__ = a;
这个思路好清奇,没有了构造函数,原型,怪怪的。
子类不能创建子类的方法,只能继承父类的方法。
3.5 寄生式继承
function A(name)
this.name = name;
this.age = 3;
A.prototype.getName = function()
return this.name;
var a = new A('ls');
function clone(a)
var obj = Object.create(a);
obj.setName = function(name)
this.name = name;
return obj;
b = clone(a);
寄生式继承在创建函数内部创建的函数(如上例sayName),不能做到函数复用。
3.6 寄生组合式继承
function A(name)
this.name = name;
this.age = 3;
A.prototype.getName = function()
return this.name;
function B(name)
A.call(this, name);
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.setName = function(name)
this.name = name;
var b = new B('ls');
组合式继承的更进一步,解决了组合式继承的子类原型constructor
不指向子类构造函数的问题。
目前是最好的继承方式。
4.参考
《javascript》高级程序设计
https://www.zhihu.com/question/34183746
以上是关于原型链和继承的主要内容,如果未能解决你的问题,请参考以下文章