JS原型到原型链
简单点用一句话描述,理清楚实例、构造函数、原型对象(也称实例原型) 三者的关系即可。他们之间关系如图:

下面我们来具体聊一聊
原型
构造函数
通俗一点理解,生成对象的基础,并描述对象的基本结构的函数。
与普通函数典型区别
需要使用
new
关键字生成对象实例才可以使用。构造函数内
this
指向生成的对象实例。命令上一般使用首字母大写。
示例:
var Person = function() {
this.name = 'aoao';
};
//两种写法相同。
function Person() {
this.name = 'aoao';
}
我们用到的Array
,String
等内置对象都是构造函数。
原型对象(实例原型)
构造函数的 prototype
属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也称为原型对象。关系如下:
同时,每个原型都有一个 constructor
属性指向关联的构造函数。

为了验证这一点,我们可以尝试:
function Person() {
}
console.log(Person === Person.prototype.constructor); // true
实例
实例是通过 new
一个构造函数创建的,称之为实例对象。
实例有一个有一个私有属性,叫__proto__
,这个属性会指向该对象的原型对象(prototype)。关系图如下:
为了证明这一点,我们测试:
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
最终我们得到关于篇首关于这三者的关系图:

既然实例对象和构造函数都可以指向原型,那么原型对象是否有属性指向构造函数或者实例呢?
原型链
上面我们知道,实例对象有一个私有属性__proto__
指向它的构造函数的原型对象prototype
,那么原型对象也有一个私有属性__proto__
指向自己的原型对象,即该原型的原型,如此下去,就是所谓的原型链(prototype chain)。

上面图中看到Person原型对象的原型是Object.prototype
,这是因为所有的引用类型默认都继承了Object
,这个继承也是通过原型链实现的,因此默认原型都会包含一个内部指针,指向Object.prototype
。
而Object.prototype
的原型是原型链的结尾,指向了null
。
我们可以验证一下
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(person.__proto__.__proto__ === Object.prototype); // true
console.log(person.__proto__.__proto__.__proto__); // null
原型链查找
我们在读取实例对象属性的时候,要注意搜索顺序,
归纳一下查找顺利就是:
先在实例对象上查看
再在原型对象上查找
沿着原型链上一直往上查找,没有的话返回
undefined
.
我们可以看个例子
function Person() {
}
Person.prototype.name = 'Nicholas';
var person1 = new Person();
var person2 = new Person();
person1.name='Mack';
console.log(person1.name) // Mack
//此时执行两次搜索
console.log(person2.name) // Nicholas
原型操作
查看原型
Object.getPrototypeOf
Object.getPrototypeOf
该方法返回指定对象的原型(也就是该对象内部属性[Prototype]
的值)。
console.log(Object.getPrototypeOf({}))
//Object.prototype
Object.prototype.__proto__
Object.prototype.__proto__
一个对象的__proto__
属性和自己的内部属性[Prototype]
指向一个相同的值 (通常称这个值为原型),原型的值可以是一个对象值也可以是null(比如说Object.prototype.__proto__
的值就是null)。
此方法已经从 Web 标准中删除, 尽量少用, 推荐使用
Object.getProtoTypeOf
({}).__proto__
Object.prototype
// 这两者的结果是一样的,都是实例原型
修改原型
直接在prototype增删改
修改构造函数的prototype属性从而达到操作对象原型的目的。
Person上原来并没有name和add
function Person () {}
Person.prototype.name = 'Nike';
Person.prototype.add = function(name) {
this.name = name;
}
const person = new Person();
console.log(person.name); // Nike;
这个方法是目前主流的方法,在查看setPrototypeOf
时发现MDN官网已经将直接修改prototype
标记为警告。
警告: 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = ... 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。
Object.setPrototypeOf
目前推荐使用
var a = {};
var b = {};
Object.setPrototypeOf(a, b);
Object.getPrototypeOf(a) === b;
// true
// a ---> b
__proto__
__proto__
ES6还带来了一个属性,通过这个属性也可以直接操作原型
var a = {};
var b = {};
a.__proto__ = b;
Object.getPrototypeOf(a) === b;
// true
// a ---> b
注意这个属性在ES6规范的附录中,也就意味着不是所有的环境都会有这个属性。
class的extends
ES6引入了以class语法糖,通过extends关键字我们也可以实现继承,但是无法直接操作对象的原型,而是要借助“类”,其实就是构造函数和函数的prototype属性。
class B {}
class A extends B {}
var a = new A();
Object.getPrototypeOf(a) === A.prototype;
// true
// a ---> A.prototype === B的实例
参考
《高程》
Last updated