前言
前几天面试了几家公司的实习岗位,发现自己原型链的相关知识比较薄弱,借此机会找了许多资料学习了一番,现在就原型链这块的知识谈谈自己的理解
原型
对于使用过基于类的语言的开发者来说,JavaScript 实在是有些令人困惑------JavaScript 是动态的且没有静态类型,它也并不是一个完全的面向对象语言。
那么js是如何实现继承的呢?我们知道对于js的复杂对象,本质上都是一种结构:object。ES6前的js没有class
这个关键字,于是设计者采用构造函数
的方式来实现继承。
ini
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 生成实例
const person = new Person('ccat', 20);
当我们创建了一个实例化对象后,新的问题出现了,我们实例对象上的this绑定的是实例对象本身,无法像基于类的语言那样实现extends关键字的继承。于是原型对象诞生了,挂载在每一个实例对象上,为它们提供共享的属性和方法。
我们在浏览器控制台中new一个实例对象看看:

可以看到,在person这个对象上挂载着一个Prototype的属性,这就是这个实例对象的原型 。其中就可以看到这个实例对象是由哪个构造函数创建出来的。也就是说,我们可以在实例对象上找到他的构造函数。
原型链
知道了原型的概念以后,我们怎么在这个实例对象上调用所谓的共享方法呢?
我们再来一段示例代码:
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function (word) {
console.log(`${this.name}说:${word}`);
}
const person = new Person('ccat', 20);
person.hasOwnProperty('say');
person.say('hi');
在控制台中输入代码:

可以看到实例对象本身是没有say这个函数的,但是我们却可以在person上调用say这个函数。这就是原型链的作用,让我们能够调用对象原型上的函数。

每个函数都有一个prototype属性,这个prototype属性就是我们的原型对象,我们拿这个函数通过new构造函数创建出来的实例对象,这个实例对象自己会有一个指针( _ proto _ )指向他的构造函数的原型对象,他们的关系如下:

最后借用MDN官方文档的一句话总结一下:
当谈到继承时,JavaScript 只有一种结构:对象。每个对象(object)都有一个私有属性指向另一个名为原型 (prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为
null
。根据定义,null
没有原型,并作为这个原型链(prototype chain)中的最后一个环节。
我们可以new一个Object的实例对象,沿着原型链去查找它的尽头,就可以找到这个null了
另外还找到一个对于_proto_
的补充
__proto__
并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性
,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。(摘自阮一峰的ES6入门)
这段话提醒我们在修改实例对象原型时不要直接修改_proto_上的属性方法,这会改变其构造函数
的原型对象
,从而被所有实例所共享
结语
本篇文章就原型和原型链的知识做了一定程度上的讲解,其中的许多内容我都从其他原型链相关好文、以及各个官方文档中获得了灵感,如果有不准确的地方,烦请各位大佬指正