You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 木易杨functionFoo(){return'foo';}Foo.prototype.method=function(){return'method';}functionBar(){return'bar';}Bar.prototype=Foo;// Bar.prototype 指向到函数letbar=newBar();console.dir(bar);bar.method();// Uncaught TypeError: bar.method is not a function
引言
上篇文章介绍了构造函数、原型和原型链的关系,并且说明了
prototype
、[[Prototype]]
和__proto__
之间的区别,今天这篇文章用图解的方式向大家介绍原型链及其继承方案,在介绍原型链继承的过程中讲解原型链运作机制以及属性遮蔽等知识。建议阅读上篇文章后再来阅读本文,链接:【进阶5-1期】重新认识构造函数、原型和原型链
有什么想法或者意见都可以在评论区留言。下图是本文的思维导图,高清思维导图和更多文章请看我的 Github。
原型链
上篇文章中我们介绍了原型链的概念,即每个对象拥有一个原型对象,通过
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
,这种关系被称为原型链(prototype chain)。根据规范不建议直接使用
__proto__
,推荐使用Object.getPrototypeOf()
,不过为了行文方便逻辑清晰,下面都以__proto__
代替。注意上面的说法,原型上的方法和属性被 继承 到新对象中,并不是被复制到新对象,我们看下面这个例子。
原型上的属性和方法定义在
prototype
对象上,而非对象实例本身。当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null
)。比如调用
foo.valueOf()
会发生什么?foo
对象是否具有可用的valueOf()
方法。foo
对象的原型对象(即Foo.prototype
)是否具有可用的valueof()
方法。Foo.prototype
所指向的对象的原型对象(即Object.prototype
)是否具有可用的valueOf()
方法。这里有这个方法,于是该方法被调用。prototype
和__proto__
上篇文章介绍了
prototype
和__proto__
的区别,其中原型对象prototype
是构造函数的属性,__proto__
是每个实例上都有的属性,这两个并不一样,但foo.__proto__
和Foo.prototype
指向同一个对象。这次我们再深入一点,原型链的构建是依赖于
prototype
还是__proto__
呢?https://kenneth-kin-lum.blogspot.com/2012/10/javascripts-pseudo-classical.html
Foo.prototype
中的prototype
并没有构建成一条原型链,其只是指向原型链中的某一处。原型链的构建依赖于__proto__
,如上图通过foo.__proto__
指向Foo.prototype
,foo.__proto__.__proto__
指向Bichon.prototype
,如此一层一层最终链接到null
。不要使用
Bar.prototype = Foo
,因为这不会执行Foo
的原型,而是指向函数Foo
。 因此原型链将会回溯到Function.prototype
而不是Foo.prototype
,因此method
方法将不会在 Bar 的原型链上。instanceof 原理及实现
instanceof
运算符用来检测constructor.prototype
是否存在于参数object
的原型链上。instanceof 原理就是一层一层查找
__proto__
,如果和constructor.prototype
相等则返回 true,如果一直没有查找成功则返回 false。知道了原理后我们来实现 instanceof,代码如下。
原型链继承
原型链继承的本质是重写原型对象,代之以一个新类型的实例。如下代码,新原型
Cat
不仅有new Animal()
实例上的全部属性和方法,并且由于指向了Animal
原型,所以还继承了Animal
原型上的属性和方法。原型链继承方案有以下缺点:
问题 1
原型链继承方案中,原型实际上会变成另一个类型的实例,如下代码,
Cat.prototype
变成了Animal
的一个实例,所以Animal
的实例属性names
就变成了Cat.prototype
的属性。而原型属性上的引用类型值会被所有实例共享,所以多个实例对引用类型的操作会被篡改。如下代码,改变了
instance1.names
后影响了instance2
。问题 2
子类型原型上的 constructor 属性被重写了,执行
Cat.prototype = new Animal()
后原型被覆盖,Cat.prototype
上丢失了 constructor 属性,Cat.prototype
指向了Animal.prototype
,而Animal.prototype.constructor
指向了Animal
,所以Cat.prototype.constructor
指向了Animal
。解决办法就是重写
Cat.prototype.constructor
属性,指向自己的构造函数Cat
。问题 3
给子类型原型添加属性和方法必须在替换原型之后,原因在第二点已经解释过了,因为子类型的原型会被覆盖。
属性遮蔽
改造上面的代码,在
Cat.prototype
上添加run
方法,但是Animal.prototype
上也有一个run
方法,不过它不会被访问到,这种情况称为属性遮蔽 (property shadowing)。那如何访问被遮蔽的属性呢?通过
__proto__
调用原型链上的属性即可。其他继承方案
原型链继承方案有很多问题,实践中很少会单独使用,日常工作中使用 ES6 Class extends(模拟原型)继承方案即可,更多更详细的继承方案可以阅读我之前写的一篇文章,欢迎拍砖。
点击阅读:JavaScript 常用八种继承方案
扩展题
有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣
参考答案:点击查看
小结
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
,这种关系被称为**原型链 **null
)。__proto__
,一层一层最终链接到null
。__proto__
,如果和constructor.prototype
相等则返回 true,如果一直没有查找成功则返回 false。参考
MDN 之对象原型
MDN 之继承与原型链
JavaScript Prototype Explained By Examples
JavaScript's Pseudo Classical Inheritance diagram
进阶系列目录
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,公众号「高级前端进阶」作者,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: