(深入JavaScript系列)new背后的原理及实现

面试中遇到关于JavaScript中new关键字背后的实现原理,了解大概的原理,但是表达出来不是很清楚,表示掌握得不够完全,这里查了一些资料,做一下整理。

原理

例如我们做了new的调用操作,

1
new ConstructorFunction(arg1, arg2);

new操作

背后实际上发生了这些步骤:

  1. 创建一个新的空对象,对象类型为简单的object
  2. 设置这个空对象的实例原型(内部的、不可访问的[[prototype]]属性,部分浏览器可通过__proto__进行访问,ES5开始可通过Object.getPrototypeOf(obj)取得)为构造函数的prototype属性(每个函数对象都拥有一个prototype属性)
  3. this变量指向这个新创建的对象
  4. 以这个新创建的对象为上下文执行构造函数
  5. 如果构造函数有返回非空的对象,则返回该对象,否则返回第一步中创建的对象。

原型的几个概念

这里涉及到几个概念:

  1. 构造函数,配合使用new关键字的函数可称为构造函数
  2. 实例原型对象,在Ecma标准中,通过[[prototype]]表示,在部分浏览器中使用__proto__(非标准的,不建议使用)来表示,ES5开始可使用Object.getPrototypeOf()读取,ES6开始可使用Object.setPrototypeOf()方法进行设置(仅支持完全替换对象或者设为null)
  3. 原型对象,构造函数的prototype属性

比较难理解的是[[prototype]]这个属性,每个对象都拥有一个内部的[[prototype]]属性。这个对象是创建对象的时候设置的,创建包括new、通过Object.create()或者用文本字面量,并且只能通过Object.getPrototypeOf()Object.setPrototypeOf()方法进行操作。

原型链和继承

说明

一旦通过new操作实例化一个对象后,如果这个实例上查找某个属性并不存在,脚本会通过[[prototype]]对象向上一级继续查找,也就是通过原型链的方式进行往上查找。这种方式和在传统的类继承方式是类似的,在JavaScript中通过原型链的形式来继承父类的属性和方法。

函数中,除了拥有隐藏的[[prototype]]属性,还有一个prototype属性,这个属性可以访问、修改和添加希望给实例继承的属性和方法。

原型链实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ObjMaker = function() { this.a = 'first'; }
// ObjMaker是一个普通函数,并且可以作为一个构造函数使用

ObjMaker.prototype.b = 'second';
// 像其他函数一样,ObjMaker拥有一个可访问的属性prototype可以被修改,这里增加了一个属性b。
// objMaker还有一个内部的属性[[prototype]],可以通过上述的两个方法进行访问和修改,
// 修改的话仅直接替换为其他对象或者设置为null

obj1 = new ObjMaker();
// 这里会发生前面所说的几件事情
// 首先创建一个空的对象obj1
// 然后将obj1的内部实例对象[[prototype]]设置为ObjMaker的prototype值,
// 并且设置this上下文为obj1,执行构造函数。因此obj1.a可以拿到first值

console.log(obj1.a);
// 输出first

console.log(obj1.b);
// 输出second,解释: obj1并没有属性b,因此会去obj1的内部实例对象属性[[prototype]]上查找,也就是ObjMaker的prototype对象,找到b之后返回对应的值

继承实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SubObjMaker = function() {};
SubObjMaker.prototype = new ObjMaker(); // 这种继承模式已经废弃
// 因为使用了new,SubObjMaker.prototype的内部实例对象属性[[prototype]]
// 会被设置为ObjMaker.prototype属性。
// 现在通常会使用ES5中的Object.create()方法来实现。
// SubObjMaker.prototype = Object.create(ObjMaker.prototype);

SubObjMaker.prototype.c = 'third';
obj2 = new SubObjectMaker();
// obj2的实例原型对象[[prototype]]被设置为SubObjectMaker.prototype属性,
// SubObjectMaker.prototype的[[prototype]]属性为ObjMaker.prototype
// 形成了一条如下的原型链
// ojb2 -> SubObjMaker.prototype -> ObjMaker.prototype

console.log(obj2.c);
// 输出third,通过原型链查找,在SubObjMaker.prototype上找到

console.log(obj2.b);
// 输出second, 通过原型链查找,在ObjMaker.prototype上找到

console.log(obj2.a);
// 输出first, 通过原型链查找,在SubObjMaker.prototype上找到,
// 因为SubObjMaker.prototype是由ObjMaker构造生成的,因此脚本会自动赋值

实现

1
2
3
4
5
6
7
8
9
10
11
function newOperator(ConStr, args) {
var thisValue = Object.create(ConStr.prototype);
// 构建一个空对象并实现继承

var result = ConStr.apply(thisValue, args);
if (typeof result === 'object' && result != null) {
// 构造函数可能会返回对象,这里要增加判断,并且要排除null,因为null的类型检测为object
return ret;
}
return thisValue;
}

欢迎访问我的博客 https://blog.bookcell.org

参考

您的支持将鼓励我继续创作!
0%