(深入JavaScript系列)new背后的原理及实现
(深入JavaScript系列)new背后的原理及实现
面试中遇到关于JavaScript中new关键字背后的实现原理,了解大概的原理,但是表达出来不是很清楚,表示掌握得不够完全,这里查了一些资料,做一下整理。
原理
例如我们做了new的调用操作,
new ConstructorFunction(arg1, arg2);
new操作
背后实际上发生了这些步骤:
- 创建一个新的空对象,对象类型为简单的object
- 设置这个空对象的实例原型(内部的、不可访问的[[prototype]]属性,部分浏览器可通过
__proto__
进行访问,ES5开始可通过Object.getPrototypeOf(obj)
取得)为构造函数的prototype
属性(每个函数对象都拥有一个prototype
属性) - 让
this
变量指向这个新创建的对象 - 以这个新创建的对象为上下文执行构造函数
- 如果构造函数有返回非空的对象,则返回该对象,否则返回第一步中创建的对象。
原型的几个概念
这里涉及到几个概念:
- 构造函数,配合使用new关键字的函数可称为构造函数
- 实例原型对象,在Ecma标准中,通过
[[prototype]]
表示,在部分浏览器中使用__proto__
(非标准的,不建议使用)来表示,ES5开始可使用Object.getPrototypeOf()
读取,ES6开始可使用Object.setPrototypeOf()
方法进行设置(仅支持完全替换对象或者设为null) - 原型对象,构造函数的
prototype
属性
比较难理解的是[[prototype]]
这个属性,每个对象都拥有一个内部的[[prototype]]
属性。这个对象是创建对象的时候设置的,创建包括new
、通过Object.create()
或者用文本字面量,并且只能通过Object.getPrototypeOf()
和Object.setPrototypeOf()
方法进行操作。
原型链和继承
说明
一旦通过new操作实例化一个对象后,如果这个实例上查找某个属性并不存在,脚本会通过[[prototype]]
对象向上一级继续查找,也就是通过原型链的方式进行往上查找。这种方式和在传统的类继承方式是类似的,在JavaScript中通过原型链的形式来继承父类的属性和方法。
函数中,除了拥有隐藏的[[prototype]]
属性,还有一个prototype
属性,这个属性可以访问、修改和添加希望给实例继承的属性和方法。
原型链实例
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之后返回对应的值
继承实例
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构造生成的,因此脚本会自动赋值
实现
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