JS面向对象之四 【new】 (创建特定对象的语法糖)
个人学习笔记分享
为了讲清楚面向对象,实际上是需要几个前置知识的。
包括前面的几篇文章【原型链】 【this】 和今天要说的【new】
还是先说结论: new只是一个语法糖,这个语法糖被设计出来,使用场景是批量创建对象
从场景说起: 假设这个世界没有new,我们如何批量创建一百个士兵obj?
通篇文章,我们都是在实现这个需求,不断优化,最终实现一个new语法糖。
类似这样的士兵ojb
var 士兵 = {
ID: 1,
兵种: '美国大兵',
攻击力: 5,
生命值: 42,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
版本v1.0: 最直觉的方法当然是循环100次
var 士兵
var 士兵们 = []
for (var i = 0 ; i < 100 ; i++){
士兵 = {
ID: 1,
兵种: '美国大兵',
攻击力: 5,
生命值: 42,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
士兵们.push(士兵)
}
最终我们得到了array士兵们
根据第二篇文章 JS面向对象之二【原型链】(对象和对象的关系).md),我们知道这里的攻击、防御、死亡都是匿名函数,在内存空间都会占据空间。
这些函数的内容完全相同,300个匿名函数却要占用300个内存空间,十分浪费。
自然地,我们就想到JS的原型链就是专门解决这个问题的,我们把共同的属性放到__proto__里,所有士兵的__proto__都指向相同的内存空间。
版本v1.1: 优化内存空间,使用__proto__保存共同的属性
var 士兵
var 士兵们 = []
for (var i=0 ; i<100 ; i++){
士兵 = {
ID: 1,
生命值: 42
}
士兵.__proto__ = 士兵共
士兵们.push(士兵)
}
var 士兵共 = {
兵种: '美国大兵', // 如果兵种和攻击力也是一样的话,那么也需要放到__proto__里去共享
攻击力: 5,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
最终依旧得到了array士兵们
(以上代码可粘贴运行)
现在我们省掉了之前297个匿名函数所占用的空间。让他们的__proto__都指向'士兵共'这个内容空间就OK了
版本v1.2: 变量名称优化成英文
var soldier
var soldiers= []
for (var i=0 ; i<100 ; i++){
soldier = {
ID: 1,
生命值: 42
}
soldier.__proto__ = soldierCommon
soldiers.push(soldier)
}
var soldierCommon = {
兵种: '美国大兵', // 如果兵种和攻击力也是一样的话,那么也需要放到__proto__里去共享
攻击力: 5,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
版本v2.0: 封装成createSoldier函数,避免意大利式面条,让代码更加集中。
var soldierCommon = {
兵种: '美国大兵', // 如果兵种和攻击力也是一样的话,那么也需要放到__proto__里去共享
攻击力: 5,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
function createSoldier(){
var obj = {
ID: 1,
生命值: 42
}
obj.__proto__ = soldierCommon
return obj
}
------分割线以上是创建构造函数, 分割线以下,是使用构造函数------
var soldiers = []
for(var i = 0 ;i<100 ; i++){
soldiers.push(createSoldier())
}
版本v2.1: 让开发者一眼就知道soldierCommon和createSoldier是有关系的,让代码进一步集中
// 让soldierCommon成为构造函数的属性(因为函数也是obj,当然可以有属性)
// soldierCommon可以改叫xxx,只是一个名字,只是为了让这个对象和createSoldier产生联系
createSoldier.xxx = {
兵种: '美国大兵',
攻击力: 5,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
function createSoldier(){
var obj = {
ID: 1,
生命值: 42
}
obj.__proto__ = createSoldier.xxx
return obj
}
------分割线以上是创建构造函数, 分割线以下,是使用构造函数------
var soldiers = []
for(var i = 0 ;i<100 ; i++){
soldiers.push(createSoldier())
}
那么现在还可以优化吗? 上面这段代码,唯一的缺点就是xxx不太容易理解。
所以,JS之父为了容易理解将这个xxx,也就是共有属性的引用,统一叫做prototype,虽然prototype对于中国人来说依旧不好理解。
版本v2.2: 完美的代码,将xxx改为prototype (只是一个名字,但是JS之父将它命名成了prototype)
createSoldier.prototype = {
兵种: '美国大兵',
攻击力: 5,
攻击: function(){console.log('攻击')},
防御: function(){console.log('防御')},
死亡: function(){console.log('死亡')},
}
function createSoldier(){
var obj = {
ID: 1,
生命值: 42
}
obj.__proto__ = createSoldier.prototype
return obj
}
------分割线以上是创建构造函数, 分割线以下,是使用构造函数------
var soldiers = []
for(var i = 0 ;i<100 ; i++){
soldiers.push(createSoldier())
}
好了,仔细看看上面的代码,还有什么可优化的地方吗?
内存、变量名、代码集中都做了优化,也就是范例式的代码。
那么,JS之父也认为这个就是最好的代码,他希望,当JS开发者们需要批量创建特定对象时,都这样写。
于是,JS之父创造了new这个语法糖。
版本v3.0 让我们看看JS之父创造的new语法糖
function Soldier(){
this.ID = 1
this.生命值 = 42
}
Soldier.prototype.兵种 = '美国大兵'
Soldier.prototype.攻击力 = '65
Soldier.prototype.攻击 = function(){console.log('攻击')}
Soldier.prototype.防御 = function(){console.log('防御')}
Soldier.prototype.死亡 = function(){console.log('死亡')}
------分割线以上是创建构造函数, 分割线以下,是使用构造函数------
var soldiers = []
for(var i = 0 ;i<100 ; i++){
soldiers.push( new Soldier())
}
对比v2.2 和 v3.0 这两个版本,就是new帮我们做的事情。
v2.2 没有使用new语法的函数createSoldier
function createSoldier(){
var obj = {
ID: 1,
生命值: 42
}
obj.__proto__ = createSoldier.prototype
return obj
}
v3.0 使用了new语法的函数Soldier
function Soldier(){
// this = {} JS偷偷做的第1个事情
// this.__proto__ = Soldier.prototype JS偷偷做的第2个事情
this.ID = 1
this.生命值 = 42
// return this JS偷偷做的第3个事情
}
Soldier.prototype = { JS偷偷给Soldier.prototype加了个constructor属性
constructor : Soldier
}
对比一下,当你使用new语法,JS之父帮你做了什么事情
- 第一 , JS帮你在函数里创建了一个this空对象,并且这个this指向是构造函数的实例
- 第二 , JS帮你让this的__proto__ 指向 构造函数的原型,也就是prototype
- 第三 , JS帮你偷偷return了 this
- 第四 , JS偷偷给Soldier.prototype加了个constructor属性
其他注意事项
- 构造函数首字母大写,习惯写法
- __proto__不能在生产环境出现,因为会严重影响性能
- 用new调用的时候,也没法用call了,因为JS之父帮你call了,this的指向就像你见到的指向实例
所以使用了new,就可以让代码没有废话。
你只需要设置对象的自有属性和共有属性。
JS帮你创建空对象,帮你搞定this指向,帮你改变this的__proto__,帮你return this
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。