likes
comments
collection
share

一文帮你彻底搞懂JS原型和原型链!

作者站长头像
站长
· 阅读数 13

JavaScript中,原型(prototype)原型链(prototype chain)是非常重要的概念,但是很多人对它们的掌握都有点不太清楚,本文带大家彻底搞懂它们!

非常重要的基本概念

首先我们需要搞清楚几个非常重要的基本概念:

所有的对象都是通过new 函数创建的,且所有的对象都是引用类型

var obj1 = {};
var obj2 = new Object();

console.log(obj1, obj2)

如上,字面量{}只是创建对象的一种语法糖,其实上面的两种写法是完全等价的。而Object其实就是一个js内置的构造函数。

所有的函数也都是对象,都是通过new Function创建的(可以称为函数对象)

function fn1() { };

var fn2 = new Function();

console.log(fn1, fn2)

既然函数也都是对象,又称为函数对象,而且所有的对象都是通过new 函数创建的,那么函数又是怎么创建的呢?其实函数都是通过new Function创建的。

如上,同理可得,上面的两种写法也是完全等价的。而Object既然是一个函数,所以它也不例外,它其实就是相当于var Object = new Function()

思考:其实Function也是一个函数对象,那它又是怎么创建的呢?

结论:Function函数是JS内置的,不需要创建!

综上所述,可以用一张图来形象的展示出它们的关系:

Function
Object
Array
Fn1
Fn2
obj1
obj2
obj3
obj4
obj5
obj6
obj7
obj8

原型prototype

什么是原型?

所有的函数都有一个属性叫prototype,称之为函数原型

function Fn() {
  
};

console.log(Fn.prototype)
console.log(Object.prototype)
console.log(Array.prototype)

可以运行如上代码看看效果,ObjectArray其实都是js内置的构造函数。

默认情况下,prototype是一个普通的object对象

你应该可以在上面的打印中看到它是一个对象,包含了若干的属性和方法。什么意思呢?既然它是一个对象,那么其实就是通过new Object()创建的。

默认情况下,prototype中有一个属性叫constructor,它也是一个对象,它指向构造函数本身

function Fn() {
  
};

console.log(Fn.prototype.constructor === Fn) // true
console.log(Object.prototype.constructor === Object) // true
console.log(Array.prototype.constructor === Array) // true

通过这个特点,我们可以在原型链上查找实例化后的对象所对应的构造函数,这个后面会讲到。

隐式原型__proto__

什么是隐式原型?

所有的对象都有一个属性叫__proto__,称之为隐式原型

function Fn() {

};

var fn1 = new Fn();

console.log(fn1.__proto__)

var obj1 = {};

console.log(obj1.__proto__)

可以运行如上代码看看效果,所有的对象在被创建的时候就会有一个叫__proto__的属性。

默认情况下,__proto__指向创建该对象的函数的原型,即prototype

function Fn() {

};

var fn1 = new Fn();

console.log(fn1.__proto__ === Fn.prototype) // true

var obj1 = {};

console.log(obj1.__proto__ === Object.prototype) // true

运行如上代码,可以验证结论。在这里其实就已经初步形成了一种三角关系了。

prototypeFunctionobjprototypeprotonew

原型链prototype chain

到重点了,上面讲了原型和隐式原型,估计有的人会问:搞这么麻烦,它们有什么用呢?

function User(name) {
  this.name = name;
  this.sayHello = function () {
    console.log(`hello,${this.name}`);
  };
};

var user1 = new User('zhangsan');
var user2 = new User('lisi');

user1.sayHello(); // hello,zhangsan
user2.sayHello(); // hello,lisi

console.log(user1.sayHello === user2.sayHello) // false

如上代码,我通过构造函数User实例化了两个对象user1user2,因为它们都有各自属性和方法,所以user1user2sayHello方法是不相等的。

但是我们可以发现,它们的方法其实是一样的,完全没必要创建多次。可以假设一下,如果我们创建了很多个示例,那么这样就会浪费内存空间。

现在让我们来改下代码

function User(name) {
  this.name = name;
};

User.prototype.sayHello = function () {
  console.log(`hello,${this.name}`);
};

var user1 = new User('zhangsan');
var user2 = new User('lisi');

user1.sayHello(); // hello,zhangsan
user2.sayHello(); // hello,lisi

console.log(user1.sayHello === user2.sayHello) // true

先看结果,这时候我们会发现,代码依然能够如期运行,而且user1user2sayHello方法是完全相等的!

那为什么user1user2中并没有sayHello这个属性,但是却能够运行呢?其实这就是原型链的强大之处。

那这个到底是怎么实现的呢?原型链它到底是什么呢?

function User(name) {
  this.name = name;
};

User.prototype.sayHello = function () {
  console.log(`hello,${this.name}`);
};

var user1 = new User('zhangsan');

console.log(user1.__proto__)
console.log(user1.__proto__.__proto__)
console.log(user1.__proto__.__proto__.__proto__) // null

运行如上代码,需要理解以下几点:

首先根据上面所说的,user1是个实例化的对象,那么user1.proto一定是指向User.prototype

User.prototype其实也是个对象,既然是对象就有proto属性,那么User.prototype.proto一定是指向Object.prototype,也可以通过user1.proto.proto来访问,即原型对象的原型对象

同理,那user1.proto.proto也是个对象,也有proto属性,不过这里就指向null

因为每个一对象都有隐式原型,隐式原型的指向就形成了一个链条,称之为原型链

所以上面的示例就会按照原型链继续依次查找

当我们访问一个对象的成员时:

首先看该对象自身是否拥有该成员,如果有直接使用;

如果没有再看该对象的隐式原型是否拥有该成员,如果有直接使用;

如果还没有就会继续查找原型对象的原型对象(该对象的隐式原型所指向的原型对象),直到找到为止;

这样形成的链条就被称为原型链(prototype chain)

读到这里,再看上面的问题,为什么实例对象user1user2中并没有sayHello这个方法,但是却能够运行?答案就是可以在原型链上查找到sayHello这个方法。

通过这种方法,我们就可以实现多个实例对象共享一个原型对象,这样就可以极大的减少对内存空间的消耗且极大的提升了代码的可复用性。这就是原型链的作用。(其实这也是js实现继承的基本原理,这里就不展开说了)

核心:原型链的全貌

下面就是js原型链的全貌了

一文帮你彻底搞懂JS原型和原型链!

这里有两个比较特殊的点需要注意一下:

Objectprototype__protp__指向null

Function__protp__指向自身的prototype

仔细对照这张图,你是否能够理解如下问题:

为什么任意一个对象都能调用toStringhasOwnProperty方法了吗?

为什么任意一个函数都能调用calltoString方法了吗?

为什么任意一个数组都能调用自身没有的api了吗,如filterpush等方法了吗?

其实答案都是因为原型上有!

如果你能够看到这里并且完全理解了,那么其实你已经对原型和原型链已经有了一个比较全面的认知了。

不过关于原型和原型链还有很多其它方面的知识,比如说,js中的继承,多态以及扩展具体是怎么实现的?怎么通过js去操作原型和原型链?原型和原型链具体有什么应用场景呢?

如果大家还想笔者继续分享原型和原型链的知识,或者笔者觉得文章写的还不错的话,可以动动小手点个👍,这样我才有更新的动力!