前言:JavaScript中原型和原型链是一个非常重要的知识点,对于后续JavaScript中继承以及ES6中引入的类相关的知识点有着非常重要的作用,同时笔者也想通过这篇文章整理下这块的知识点方便大家查阅。
从新建对象说起
要了解这方面的知识点,首先要从JavaScript中新建对象的方式说起,常见的创建对象的方式有下面几种:
-
对象字面量
1
let a = {name: 'test'};
-
new关键字
1
let a = new Object();
-
工厂模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); }; return o; } var person1 = createPerson('Walter White', 50, 'Chemist'); var person2 = createPerson('Jesse Pinkman', 20, 'Student'); console.log(person1); //{ name: 'zzx', age: 20, job: 'Programmer', sayName: [Function] }
-
构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { console.log(this.name); }; } let person1 = new Person("Walter White", 50, "Chemist"); let person2 = new Person("Jesse Pinkman", 20, "Student"); person1.sayName(); // Nicholas person2.sayName(); // Greg console.log(person1.sayName == person2.sayName); // false
❕关于new的原理可以参考笔者写的这篇文章: JavaScript | new原理
但是使用构造函数创建的对象有个非常不好的地方,就是每当新建对象的时候构造函数内的方法都会重新执行,造成内存的浪费(如下图):
所以就有了下面的这种方式。
-
原型模式与构造函数模式组合创建对象
- 原型模式
1 2 3 4 5 6 7 8 9 10 11 12
function Person() {} Person.prototype.name = "Walter White"; Person.prototype.age = 50; Person.prototype.job = "Chemist"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); // "Walter White" let person2 = new Person(); person2.sayName(); // "Walter White" console.log(person1.sayName == person2.sayName); // true
在JavaScript中每个函数都会有一个
prototype
属性,这个属性指向一个对象(此对象一般称为:原型对象),所以可以将共用的属性与方法设置到prototype
(原型对象)上,在原型对象对象上属性与方法是所有实例共享的。所以就可以通过将原型模式与构造函数组合使用的方式解决构造函数创建创建对象时造成的内存浪费的问题,具体做法如下:
- 私有属性定义在构造函数里面。
- 公共的方法定义在原型对象(
prototype
)上(防止内存空间的浪费)。
- 原型模式
|
|
显式原型与隐式原型
- 显示原型(
prototype
):每个对象(除了null
)都具有的属性,该属性指向该对象的原型。 - 隐式原型(
__proto__
):只有函数(实例)对象才有的属性,该属性指向该函数的原型对象。
在JavaScript中每次调用构造函数创建一个新的实例,都会在该实例的内部创建一个__proto__
属性(有些浏览器中叫([[prototype]]
)),此属性指向构造函数的 prototype
原型对象 上,具体可以参考下面的代码和示例图:
|
|
constructor
构造函数属性
正常情况下prototype
(原型对象)会自动获得一个constructor
属性,constructor
的意思是构造函数,该属性指回构造函数本身。
constructor
属性的作用:
-
用于在原型对象中记录对象引用自哪一个构造函数。
-
在有些情况下可以将原型对象中的
constructor
重新指向回原本的构造函数。比如刚才提到的 原型模式与构造函数模式组合 的情况:
1 2 3 4 5 6 7 8 9 10
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; } Person.prototype.sayName = function (){ console.log('Say my name!'); console.log(this.name + '!'); }
如果现在要在
prototype
上添加非常多方法,就可以采用另一种写法:重写prototype
(原型对象)。此时就需要手动的利用constructor
属性将prototype
(原型对象)中的constructor
重新指向回原本的构造函数。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; } // 重写prototype Person.prototype = { // 重新添加constructor重新回原来的构造函数 constructor: Person, sayName: function(){ console.log('Say my name!'); console.log(this.name + '!'); }, sayAge: function(){ console.log(this.name + " is " + this.age + " years old!"); }, sayJob: function(){ console.log(this.name + " is a " + this.job); } }
原型链
-
prototype
(原型对象)是一个对象 -
在新建对象的时候会在该对象的内部创建一个
__proto__
属性,__proto__
属性指向该对象的原型对象。 -
Person.prototype
原型对象 作为一个对象,也有__proto__
属性,此属性指向的是Object.prototype
。(PS:可以理解成原型的原型)默认情况下,所有引用类型都继承自 Object,这也是通过原型链实现的。任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默认方法的原因。——《JavaScript高级程序设计》(第四版)
个人理解:所有的对象都是Object构造函数的实例。
-
Object.prototype
原型对象 作为一个对象,也有__proto__
属性,此时__proto__
指向为null
(原型的终点)。 -
原型连成一条链,称为原型链,为查找对象的成员提供了一条链,通过这条链一次进行查找。
|
|
JS对象成员查找机制
JS中查找对象是根据原型链的机制去查找的,具体规则如下:
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 若没有则查找它的原型(
__proto__
指向的prototype
原型对象)。 - 如果还没有则就查找原型对象的原型(
Object
的原型对象Object.prototype
)。 - 依此类推直到找到
Object
的__proto__
(null
)为止。 __proto__
对象原型的意义就是为对象成员查找机制提供一个方向(可以理解为原型链中的 链 )。
|
|
原型对象this指向
原则:
- 只有调用函数的时候才能确认
this
指向谁。 - 一般情况下
this
指向的是函数的调用者。
|
|
结论:
- 在构造函数中,里面
this
指向的时对象实例person1
。 - 原型对象方法里面
this
指向的是 实例对象person1
。
利用原型对象扩展内置对象方法
比如给String
的prototype
上添加一个startWith
方法:
|
|
参考文献
- 《JavaScript高级程序设计》(第四版)
- 说说原型(prototype)、原型链和原型继承
- 如何回答面试中的JavaScript原型链问题
- JS三座大山之原型和原型链(彻底搞清楚原型、原型链)
- 原型与原型链详解