一、认识构造函数
我们先理解什么是构造函数?
- 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
- 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
- 但是JavaScript中的构造函数有点不太一样;
JavaScript中的构造函数是怎么样的?
- 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
- 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
那么被new调用有什么特殊的呢?
二、new操作符调用的作用
如果一个函数被使用new操作符调用了,那么它会执行如下操作:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);
- 构造函数内部的this,会指向创建出来的新对象;
- 执行函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;
三、认识对象的原型
JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]]
,这个特殊的对象可以指向另外一个对象。
那么这个对象有什么用呢?
- 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
- 这个操作会首先检查该对象是否有对应的属性,如果有的话就使用它;
如果对象中没有该属性,那么会访问对象[[prototype]]内置属性
指向的对象上的属性;
那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
- 答案是有的,只要是对象都会有这样的一个内置属性;
获取的方式有两种:
- 方式一:通过对象的
__proto__ 属性
可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题); - 方式二:通过 Object.getPrototypeOf() 方法可以获取到;
四、函数的原型 prototype
那么我们知道上面的东西对于我们的构造函数创建对象来说有什么用呢?
- 它的意义是非常重大的,接下来我们继续来探讨;
这里我们又要引入一个新的概念:所有的函数都有一个prototype的属性
:
你可能会问题,是不是因为函数是一个对象,所以它有prototype的属性呢?
- 不是的,因为它是一个函数,才有了这个特殊的属性;
- 而不是它是一个对象,所以有这个特殊的属性;
我们前面讲过new关键字的步骤如下:
1.在内存中创建一个新的对象(空对象);
2.这个对象内部的[[prototype]]属性
会被赋值为该构造函数的prototype属性
;
那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向构造函数的prototype(Person.prototype)
:
五、创建对象的内存表现
六、constructor属性
事实上原型对象上面是有一个属性的:constructor
- 默认情况下
原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象;
七、重写原型对象
如果我们需要在原型上添加过多的属性,通常我们会重新整个原型对象:
前面我们说过, 每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性;
- 而我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函数, 而不是Person构造函数了。
如果希望constructor指向Person,那么可以手动添加:
- 上面的方式虽然可以, 但是也会造成constructor的[[Enumerable]]特性被设置了true.
- 默认情况下,
原生的constructor属性是不可枚举的
.
如果希望解决这个问题, 就可以使用我们前面介绍的Object.defineProperty()函数:
八、构造函数和原型组合
我们在上一个构造函数的方式创建对象时,有一个弊端:我们需要为每个对象的函数去创建一个函数对象实例
,会创建出重复的函数,比如running、eating这些函数。
那么有没有办法让所有的对象去共享这些函数呢?
可以,将这些函数放到Person.prototype的对象上
即可;