JS 的 new 做了什么?

178 阅读3分钟

最近在准备面试,经典题目之一,想写篇博客记录清楚,JS 的 new 做了什么?参考了方应杭的这篇文章

new 做了五件事

  1. 创建了一个临时对象
  2. 绑定原型
  3. 指定 this = 临时对象
  4. 执行构造函数
  5. 返回该临时对象

使用 js 原生代码实现new

function myNew () {
	//创建一个新对象
	var obj = new Object ();
	//取出参数中的第一个参数,获得构造函数
	var constructor = Array.prototype.shift.call(arguments);
	//连接原型,新对象可以访问原型中的属性
	obj._proto_ = constructor.prototype;
	// 执行构造函数,即绑定 this,并且为这个新对象添加属性
	var result = constructor.apply(obj,arguments);
	return typeof result === "object" ? result :obj ;
}

更简单的写法就是

function myNew(fn) {
	const obj = Object.create(fn.prototype);
	result = fn.apply(obj, [...arguments].slice(1));
	return typeof result === "object" ? result : obj;
}

// var obj = Object.create(fn.prototype) 等价于:

// var obj = new Object ();
// obj._proto_ = constructor.prototype;

底层逻辑

那里面的底层逻辑是什么呢,方方的文章说的很清楚。

若我们要制造一个士兵,如下图:

代码其实很简单

var 士兵 = {
  ID: 1, // 用于区分每个士兵
  兵种:"美国大兵",
  攻击力:5,
  生命值:42, 
  行走:function(){ /*走俩步的代码*/},
  奔跑:function(){ /*狂奔的代码*/  },
  死亡:function(){ /*Go die*/    },
  攻击:function(){ /*糊他熊脸*/   },
  防御:function(){ /*护脸*/       }
}

兵营.制造(士兵)

那要批量制造的话,我们就会想到循环

var 士兵们 = []
var 士兵
for(var i=0; i<100; i++){
  士兵 = {
    ID: i, // ID 不能重复
    兵种:"美国大兵",
    攻击力:5,
    生命值:42, 
    行走:function(){ /*走俩步的代码*/},
    奔跑:function(){ /*狂奔的代码*/  },
    死亡:function(){ /*Go die*/    },
    攻击:function(){ /*糊他熊脸*/   },
    防御:function(){ /*护脸*/       }
  }
  士兵们.push(士兵)
}

兵营.批量制造(士兵们)

但是,这就会浪费很多内存。一样的操作我们重复创建了很多次,这里面只有 ID 和生命值不一样。

简言之就是利用原型,那就直接把共有属性放在原型上面就好了,用原型链可以解决重复创建的问题:我们先创建一个「士兵原型」,然后让「士兵」的__proto__指向「士兵原型」

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 关键字的诞生

JS 之父创建了 new 关键字,可以让我们少写几行代码:

只要你在士兵前面使用 new 关键字,那么可以少做四件事情:

  1. 不用创建临时对象,因为 new 会帮你做(你使用「this」就可以访问到临时对象);

  2. 不用绑定原型,因为 new 会帮你做(new 为了知道原型在哪,所以指定原型的名字为 prototype);

  3. 不用 return 临时对象,因为 new 会帮你做;

  4. 不要给原型想名字了,因为 new 指定名字为 prototype。

这一次我们用 new 来写

function 士兵(ID){
  this.ID = ID
  this.生命值 = 42
}

士兵.prototype = {
  兵种:"美国大兵",
  攻击力:5,
  行走:function(){ /*走俩步的代码*/},
  奔跑:function(){ /*狂奔的代码*/  },
  死亡:function(){ /*Go die*/    },
  攻击:function(){ /*糊他熊脸*/   },
  防御:function(){ /*护脸*/       }
}

// 保存为文件:士兵.js

然后是创建士兵(加了一个 new 关键字):

var 士兵们 = []
for(var i=0; i<100; i++){
  士兵们.push(new 士兵(i))
}

兵营.批量制造(士兵们)

new 的作用就是省几行代码——所谓的语法糖