《你不知道的JavaScript(上)》阅读笔记(三)- prototype

这一篇博客不做读书笔记了,结合之前qc做的内训,直接谈谈我对于prototype的理解。直到今天我才真正地理解了js的原型、原型链。

比较嘲讽的是qc的内训时间是2018年的12月15日,现在是2020年,过去了几乎两年的时间。还记得当时内训的时候,我对于C++的面向对象模式都还是一知半解。那个时候听不懂也可以理解。

1、面向对象和面向类

借用《你不知道的JavaScript》作者对于面向对象的说法。js才是真正的面向对象,而Java和C++则是面向类的。

怎样来理解这句话呢。首先js没有类,或者说没有真正的类(es6有了class的语法糖)

其实确实是这样的,C++的面向对象模式中,所谓的面向对象3个设计理念:封装、继承、多态的实现都是基于类的。我们在C++中写面向对象的代码都是在想法设法地设计类。它的对象只是对类进行实例化。

而js中,我们的封装、继承、多态的实现则都是基于对象的。我们直接面向对象去进行设计,而不是面向类。

2、prototype和__proto__

声明一下: __proto__就是书中的 [[prototype]]链,也就是原型链

先放个图,这个图里p表示prototype[p] 表示 __proto__

prototype

prototype是函数的属性,它是一个对象。叫做函数的原型(对象)。
__proto__ 是对象的属性,它指向一个对象。 叫做对象的原型链。我们可以把它理解为一个”指针”

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) }
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');

person1.constructor == Person
Person.prototype.constructor == Person

prototype

当我们创建一个函数时,同时会为这个函数创建一个原型对象,并且这个原型对象的constructor属性指向该函数。

当我们用上述函数创建(new)一个实例对象时,我们会将该实例对象的 __proto__指向上述函数的原型对象,即prototype属性

这个__proto__的作用就是继承。为什么我们所有的对象都能使用一些共有的方法,比如isOwnProperty。其实这些方法都定义在Object.prototype中,它也是所有对象的原型链的终点(这个对象的原型链指向null)。我们对象的属性就包括了自己的属性和原型链上的属性。这个原型链就通过__proto__不断向上连接,最终连接到终点。

3、构造器

我们可以使用一些内置的构造器来创建对象,诸如: Object()Function()Array()Date()等等

1
2
3
4
5
6
7
8
9
10
11
var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;

var c = new Date();
c.constructor === Date;
c.__proto__ === Date.prototype;

var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;

其实这些构造器本身就是个函数而已,只不过我们通常使用new来调用它,进而创建对象。关于js的new,我们在上一篇笔记中有所介绍。

4、 函数对象

我们知道 “JavaScript中万物都是对象”(当然这句话是错的,我们当然有基本类型,不过它们可以转化为相应的对象)

那么函数它也是一个对象。

1
2
3
4
function fun() {

}
fun.__proto__ === Function.prototype

这个例子可以看到,我们的函数对象,是通过构造器Function来构造的。也就是说,每当我们声明一个function,它其实就是调用了Function()函数new了一个对象。

那么我们之前知道prototype是一个对象。Function的prototype是什么呢? 它是一个空函数,如下图所示.

因此typeof Function.prototype的结果是function,这也是唯一一个原型是函数的函数。

所有函数对象的__proto__都指向Function.prototype,包括那些构造器。也包括Function()自己,毕竟它自己也是一个函数对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true

String.__proto__ === Function.prototype // true
String.constructor == Function //true

// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true

// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

Array.__proto__ === Function.prototype // true
Array.constructor == Function //true

RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype // true
Date.constructor == Function //true

所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind

所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。

[[ProtoType]]链

这个原型链就是我们之前提过的 __proto__,它通过链的形式,一层一层网上指,使得所有的对象都继承了Object.prototype的属性

同样它也使得所有的数组继承了 Array.prototype 的属性。

等等等

因此,js中的继承靠的就是原型链(而不是原型prototype)。

到这里建议你回到第一章,好好看看那张图,和我们的说过的话。然后再读一遍本文。