JavaScript 深入之 new 的模拟实现
JavaScript 深入系列第十二篇,通过 new 的模拟实现,带大家揭开使用 new 获得构造函数实例的真相
new
一句话介绍 new:
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一
也许有点难懂,我们在模拟 new 之前,先看看 new 实现了哪些功能。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Otaku(name, age) { this.name = name; this.age = age;
this.habit = 'Games'; }
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name); };
var person = new Otaku('Kevin', '18');
console.log(person.name); console.log(person.habit); console.log(person.strength);
person.sayYourName();
|
从这个例子中,我们可以看到,实例 person 可以:
- 访问到 Otaku 构造函数里的属性
- 访问到 Otaku.prototype 中的属性
接下来,我们可以尝试着模拟一下了。
因为 new 是关键字,所以无法像 bind 函数一样直接覆盖,所以我们写一个函数,命名为 objectFactory,来模拟 new 的效果。用的时候是这样的:
1 2 3 4 5 6 7 8
| function Otaku () { …… }
var person = new Otaku(……);
var person = objectFactory(Otaku, ……)
|
初步实现
分析:
因为 new 的结果是一个新对象,所以在模拟实现的时候,我们也要建立一个新对象,假设这个对象叫 obj,因为 obj 会具有 Otaku 构造函数里的属性,想想经典继承的例子,我们可以使用 Otaku.apply(obj, arguments)来给 obj 添加新的属性。
在 JavaScript 深入系列第一篇中,我们便讲了原型与原型链,我们知道实例的 __proto__ 属性会指向构造函数的 prototype,也正是因为建立起这样的关系,实例可以访问原型上的属性。
现在,我们可以尝试着写第一版了:
1 2 3 4 5 6 7 8 9 10 11
| function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj; }
|
在这一版中,我们:
- 用 new Object() 的方式新建了一个对象 obj
- 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
- 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
- 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
- 返回 obj
更多关于:
原型与原型链,可以看《JavaScript 深入之从原型到原型链》
apply,可以看《JavaScript 深入之 call 和 apply 的模拟实现》
经典继承,可以看《JavaScript 深入之继承》
复制以下的代码,到浏览器中,我们可以做一下测试:
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
| function Otaku(name, age) { this.name = name; this.age = age;
this.habit = 'Games'; }
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name); };
function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments); return obj; }
var person = objectFactory(Otaku, 'Kevin', '18');
console.log(person.name); console.log(person.habit); console.log(person.strength);
person.sayYourName();
|
[]~( ̄ ▽  ̄)~**
返回值效果实现
接下来我们再来看一种情况,假如构造函数有返回值,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Otaku(name, age) { this.strength = 60; this.age = age;
return { name: name, habit: 'Games', }; }
var person = new Otaku('Kevin', '18');
console.log(person.name); console.log(person.habit); console.log(person.strength); console.log(person.age);
|
在这个例子中,构造函数返回了一个对象,在实例 person 中只能访问返回的对象中的属性。
而且还要注意一点,在这里我们是返回了一个对象,假如我们只是返回一个基本类型的值呢?
再举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Otaku(name, age) { this.strength = 60; this.age = age;
return 'handsome boy'; }
var person = new Otaku('Kevin', '18');
console.log(person.name); console.log(person.habit); console.log(person.strength); console.log(person.age);
|
结果完全颠倒过来,这次尽管有返回值,但是相当于没有返回值进行处理。
所以我们还需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么。
再来看第二版的代码,也是最后一版的代码:
1 2 3 4 5 6 7 8 9 10 11
| function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj; }
|
下一篇文章
JavaScript 深入之类数组对象与 arguments
相关链接
《JavaScript 深入之从原型到原型链》
《JavaScript 深入之 call 和 apply 的模拟实现》
《JavaScript 深入之继承》
深入系列
JavaScript 深入系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript 深入系列预计写十五篇左右,旨在帮大家捋顺 JavaScript 底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。