在日常的开发中经常会用到new操作符,那么它到底做了哪些事情呢?让我们一起来学习一下new具体干了什么。
new做了什么?
假设我们开发一款打怪兽游戏,玩家可以打一堆怪兽,那么我们应该制造一堆怪兽。 一只怪兽有以下属性:
攻击力
生命值
行走
奔跑
死亡
攻击
防御
那么,我们可以这样制造一只怪兽
var 怪兽 = {
ID: 1, // 用于区分每只怪兽
攻击力: 10,
生命值: 99,
行走: function () {},
奔跑: function () {},
死亡: function () {},
攻击: function () {},
防御: function () {}
}
怪兽制造局.制造(怪兽)
那么,如何制造100只怪兽呢?可以这样子批量制造:
var 怪兽们 = []
for (let i = 0; i < 100; i++) {
var 怪兽 = {
ID: i + 1, // 用于区分每只怪兽
攻击力: 10,
生命值: 99,
行走: function () {},
奔跑: function () {},
死亡: function () {},
攻击: function () {},
防御: function () {}
}
怪兽们.push(怪兽)
}
怪兽制造局.批量制造(怪兽们)
但是这样子批量制造存在一个缺点:浪费内存。攻击力、行走、奔跑等每个怪兽都是一样的,没必要创建100次。只有ID和生命值每只怪兽是不一样的,才需要创造100次。 有人想到了用原型去解决这个问题:
怪兽.原型 = {
攻击力: 10,
行走: function () {},
奔跑: function () {},
死亡: function () {},
攻击: function () {},
防御: function () {}
}
function 怪兽(ID) {
var 临时对象 = {} // JS帮我们创建临时对象
临时对象.__proto__ = 怪兽.原型 // JS帮我们绑定原型
临时对象.ID = ID
临时对象.生命值 = 42
return 临时对象 // JS帮我们返回临时对象
}
// 然后就可以愉快地制造怪兽了
var 怪兽们 = []
for(var i = 0; i<100; i++) {
怪兽们.push(怪兽(i+1))
}
怪兽制造局.批量制造(怪兽们)
看到这里有人会质疑了,讲了这么一大堆,这跟new有啥关系? 其实new关键字就是替我们实现了这个(怪兽函数)过程:
- 创建一个临时对象
- 绑定原型:将临时对象的__proto__(实际名字不一定叫这个,不同浏览器不一样)指向构造函数的prototype属性,这一步其实是为了绑定共有属性,比如:行走
- 改变this指向,将this指向临时对象
- 执行构造函数,这一步其实是为了绑定私有属性,比如:怪兽的ID
- 返回临时对象
- 一般是返回临时对象;
- 但是当 构造函数有返回值时 则需要做判断再返回对应的值,是 对象类型则返回该对象,是 原始类型则返回第一步创建的空对象。
用new重写:
怪兽.原型 = {
攻击力: 10,
行走: function () {},
奔跑: function () {},
死亡: function () {},
攻击: function () {},
防御: function () {}
}
function 怪兽(ID) {
this.ID = ID
this.生命值 = 42
}
// 然后就可以愉快地制造怪兽了
var 怪兽们 = []
for(var i = 0; i<100; i++) {
怪兽们.push(new 怪兽(i+1))
}
怪兽制造局.批量制造(怪兽们)
new其实是帮我们省了代码,也就是所谓的语法糖。
实现一个new
掌握了new的原理,那么实现起来也就很简单了:
function myNew(Con, ...args) {
// 创建一个临时对象
let obj = {};
// 将这个空对象的__proto__指向构造函数的原型
// obj.__proto__ = Con.prototype;
Object.setPrototypeOf(obj, Con.prototype);
// 将this指向空对象,并且执行了构造函数
let res = Con.apply(obj, args);
// 对构造函数返回值做判断,然后返回对应的值
return res instanceof Object ? res : obj;
}
完。