这一篇博客不做读书笔记了,结合之前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
是函数的属性,它是一个对象。叫做函数的原型(对象)。__proto__
是对象的属性,它指向一个对象。 叫做对象的原型链。我们可以把它理解为一个”指针”
1 | function Person(name, age, job) { |
当我们创建一个函数时,同时会为这个函数创建一个原型对象,并且这个原型对象的constructor
属性指向该函数。
当我们用上述函数创建(new)一个实例对象时,我们会将该实例对象的 __proto__
指向上述函数的原型对象,即prototype
属性
这个__proto__
的作用就是继承。为什么我们所有的对象都能使用一些共有的方法,比如isOwnProperty
。其实这些方法都定义在Object.prototype
中,它也是所有对象的原型链的终点(这个对象的原型链指向null)。我们对象的属性就包括了自己的属性和原型链上的属性。这个原型链就通过__proto__
不断向上连接,最终连接到终点。
3、构造器
我们可以使用一些内置的构造器来创建对象,诸如: Object()
、Function()
、Array()
、Date()
等等
1 | var b = new Array(); |
其实这些构造器本身就是个函数而已,只不过我们通常使用new来调用它,进而创建对象。关于js的new,我们在上一篇笔记中有所介绍。
4、 函数对象
我们知道 “JavaScript中万物都是对象”(当然这句话是错的,我们当然有基本类型,不过它们可以转化为相应的对象)
那么函数它也是一个对象。
1 | function fun() { |
这个例子可以看到,我们的函数对象,是通过构造器Function
来构造的。也就是说,每当我们声明一个function,它其实就是调用了Function()
函数new
了一个对象。
那么我们之前知道prototype是一个对象。Function的prototype是什么呢? 它是一个空函数,如下图所示.
因此typeof Function.prototype
的结果是function,这也是唯一一个原型是函数的函数。
所有函数对象的__proto__都指向Function.prototype,包括那些构造器。也包括Function()
自己,毕竟它自己也是一个函数对象。
1 | Number.__proto__ === Function.prototype // true |
所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind
所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。
[[ProtoType]]链
这个原型链就是我们之前提过的 __proto__
,它通过链的形式,一层一层网上指,使得所有的对象都继承了Object.prototype
的属性
同样它也使得所有的数组继承了 Array.prototype
的属性。
等等等
因此,js中的继承靠的就是原型链(而不是原型prototype)。
到这里建议你回到第一章,好好看看那张图,和我们的说过的话。然后再读一遍本文。