想象我们在制作一个策略类战争游戏,玩家可以操作一堆士兵攻击敌方。
我们着重来研究一下这个游戏里面的「制造士兵」环节。
制造1个士兵
一个士兵的在计算机里就是一堆属性,如下图:
我们只需要这样就可以制造一个士兵:
var 士兵 = {
ID: 1, // 用于区分每个士兵
兵种:"美国大兵",
攻击力:5,
生命值:42,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}
兵营.制造(士兵)
制造100个士兵
如果需要制造 100 个士兵怎么办呢?
循环 100 次吧:
var 士兵们 = []
var 士兵
for(var i=0; i<100; i++){
士兵 = {
ID: i, // ID 不能重复
兵种:"美国大兵",
攻击力:5,
生命值:42,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}
士兵们.push(士兵)
}
兵营.批量制造(士兵们)
质疑
上面的代码存在一个问题:浪费了很多内存。
- 行走、奔跑、死亡、攻击、防御这五个动作对于每个士兵其实是一样的,只需要各自引用同一个函数就可以了,没必要重复创建 100
个行走、100个奔跑…… - 这些士兵的兵种和攻击力都是一样的,没必要创建 100 次。
- 只有 ID 和生命值需要创建 100 次,因为每个士兵有自己的 ID 和生命值。
改进
用原型链可以解决重复创建的问题:
- 我们先创建一个「士兵原型」
- 然后让「士兵」的
__proto__
指向「士兵原型」
var 士兵原型 = {
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}
var 士兵们 = []
var 士兵
for(var i=0; i<100; i++){
士兵 = {
ID: i, // ID 不能重复
生命值:42
}
/*实际工作中不要这样写,因为 __proto__ 不是标准属性*/
士兵.__proto__ = 士兵原型
士兵们.push(士兵)
}
兵营.批量制造(士兵们)
优雅?
有人指出创建一个士兵的代码分散在两个地方很不优雅,于是我们用一个函数把这两部分联系起来:
function 士兵(ID){
var 临时对象 = {}
临时对象.__proto__ = 士兵.原型
临时对象.ID = ID
临时对象.生命值 = 42
return 临时对象
}
士兵.原型 = {
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}
// 保存为文件:士兵.js
然后就可以愉快地引用「士兵」来创建士兵了:
var 士兵们 = []
for(var i=0; i<100; i++){
士兵们.push(士兵(i))
}
兵营.批量制造(士兵们)
new的出现
new 的 作用是通过构造函数来创建一个实例对象。
用new来写
function 士兵(ID){
this.ID = ID
this.生命值 = 42
}
士兵.prototype = {
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}
// 保存为文件:士兵.js
var 士兵们 = []
for(var i=0; i<100; i++){
士兵们.push(new 士兵(i))
}
兵营.批量制造(士兵们)
function Foo(name) {
this.name = name;
}
// object
console.log("new Foo('mm')的类型:", typeof new Foo('mm'));
// function
console.log("Foo的类型:",typeof Foo);
new干了什么
Foo明明只是一个函数,可是为什么new Foo()执行后会突然返回一个对象呢?
我们从结果出发可以推断出,既然返回了一个对象,那么这事肯定和对象有关系。
实际上new干了这几件事情:
- 创建了一个空对象,eg:obj
- 将空对象原型的内存地址
__proto__
指向函数的原型对象 - 利用函数的call方法,将原本指向window的绑定对象this指向obj。
- 利用函数返回对象obj
new过程分析
function Foo(name) {
this.name = name;
return this;
}
var obj = {};
obj.__proto__ = Foo.prototype;
var foo = Foo.call(obj,'mm');
console.log(foo);
分析:
- obj指向空对象
- obj的原型地址指向构造函数Foo的原型对象
- 执行
Foo.call(obj,'mm')
;this.name = name
; 通过函数的call方法将this绑定到obj(也就是说this就是obj),实参mm传入构造函数Foo中,这样this.name = ‘mm’,那么obj.name = ‘mm’,也就是说name属性被挂载到obj对象上。return this
; 就是return obj,这样obj对象就被返回出来了。
- 将结果赋值给变量foo
- 打印结果。
简而言之,new操作符帮我们做了四件事:
- 创建空对象;
- 空对象的原型指针指向构造函数的原型对象;
- 利用函数的call方法改变this指向,在空对象上挂载属性或方法;
- 返回对象。
参考博客:
士兵问题