原型与原型链

定义

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法

———— MDN

__proto__prototype

__proto__是每个实例上都有的属性,prototype是构造函数的属性。

举个例子

// 定义一个简单的构造函数
function aFn () {}
复制代码

然后我们研究这个构造函数的prototype属性,在控制台输出下面的代码

Object.prototype.toString.call(aFn.prototype) 
// '[object Object]'

aFn.prototype
// {
//   constructor: ƒ aFn()
//   [[Prototype]]: Object
// }

aFn.prototype.constructor === aFn
// true
复制代码

可以得知,构造函数aFnprototype属性是一个对象类型,准确地说,构造函数aFnprototype属性指向了该构造函数的原型对象。
原型对象有一个constructor属性,又指回了构造函数aFn。(但在后面的作图中,我们都用aFn.prototype来表示aFn构造函数的原型对象。)

画图简单表示

图片[1]-原型与原型链-一一网

继续对aFn进行操作

// 给原型对象添加name属性
aFn.prototype.name = 'aFn'
// 并new一个实例
const a1 = new aFn();
复制代码

new运算符会生成一个新的空对象a1,然后为a1添加一个__proto__属性,指向构造函数的原型对象aFn.prototype,可以验证:

a1.__proto__ === aFn.prototype
// true
复制代码

图片[2]-原型与原型链-一一网

对象通过__proto__属性访问构造函数的原型对象。比如a1是一个空对象,但是a1.name输出了aFn,是因为a1先在自身属性中查找name属性,如果没有,则通过__proto__访问原型对象,在原型对象上查找name属性,如果再没有,就继续往原型对象的__proto__上找,如果都没有,就返回undefined__proto__将原型对象连接在一起,就形成了原型链。

aFn.prototype
// {
//   constructor: ƒ aFn()
//   [[Prototype]]: Object
// }
复制代码

上面我们打印的构造函数aFn的原型对象,里面有一个[[Prototype]],在控制台上将其展开,可以看到一个构造函数属性constructor: ƒ Object(),是不是和aFn的原型对象很像?其实[[Prototype]]等同于__proto__[1]

图片[3]-原型与原型链-一一网

这里的[[Prototype]]指向的就是Object.prototype,如图所示,我们绘制出aFn在内的所有原型链

图片[4]-原型与原型链-一一网

Object.prototypeconstructor属性指向Object构造函数,这是js的内置方法,Object.prototype作为对象,其__proto__指向的是null,这是所有原型链的尽头。

在JavaScript中,几乎所有的对象都是Object类型的实例,它们都会从Object.prototype继承属性和方法,虽然大部分属性都会被覆盖(shadowed)或者说被重写了(overridden)[2]

Object构造函数我们似乎很少用,平时我们新建一个对象都是用字面量语法:

var obj = {} // 字面量语法
obj.__proto__ === Object.prototype // true

// 其实等同于
var o = new Object()
o.__proto__ === Object.prototype // true
复制代码

内置对象中的原型与原型链

JavaScript 有一个内置对象的标准库,JavaScript标准内置对象中有我们常见的类型:Object类型、Number类型,Array类型、String类型、Function类型等等。

通过实验得知:

Symbol.prototype.__proto__ === Object.prototype // true
Number.prototype.__proto__ === Object.prototype // true
String.prototype.__proto__ === Object.prototype // true
Array.prototype.__proto__ === Object.prototype  // true
Date.prototype.__proto__ === Object.prototype   // true
RegExp.prototype.__proto__ === Object.prototype // true
Map.prototype.__proto__ === Object.prototype    // true
Set.prototype.__proto__ === Object.prototype    // true
Promsie.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
复制代码

其实可以理解,prototype是对象类型,对象都是Object.prototype的实例。

图片[5]-原型与原型链-一一网

我们再看Function类型[3]

比如,我们用Function构造函数生成一个函数

var bFn = new Function('console.log(1234)');
bFn() // 1234
Object.prototype.toString.call(bFn) // [object Function]
bFn.prototype.__proto__ === Object.prototype // true
复制代码

new运算符会生成一个对象,所以这个bFn也是一个对象类型。所以

bFn.__proto__ === Function.prototype // true
复制代码

而通过函数声明、函数表达式等也同理

functoin a() {}
a.__proto__ === Function.prototype // true

var b= function () {}
b.__proto__ === Function.prototype // true

var c = () => {}
c.__proto__ === Function.prototype // true
复制代码

那么内置对象对应的构造函数也同理:

Array.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
Number.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype   // true
Symbol.__proto__ === Function.prototype // true
// Object 构造函数同理
Object.__proto__ === Function.prototype // true
复制代码

每个 JavaScript 函数实际上都是一个 Function 对象[3]

内置函数都可以理解为是Function构造函数的实例对象,但有一个特殊的

Function.__proto__ === Function.prototype // true
复制代码

Function__proto__ 也指向Function.prototype,而Function构造函数的prototype属性也指向Function.prototype

图片[6]-原型与原型链-一一网

这里用红线标注指向Function.prototype

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享