- new是什么?
new是js中的关键字,用来通过构造函数新建一个实例对象,如:
const obj = new Object() // {}
function People(name, age){
// 设置当前实例的属性,作为构造函数时this指向当前要创建的实例
// 作为普通函数使用时,this指向当前调用者,未指明调用者时,默认指向window(非严格模式下)
this.name = name
this.age = age
}
const p = new People('李逍遥', 24) // p {name:李逍遥, age:24}
- new创建实例时内部做了哪些事?
① 新建一个空对象
② 通过Object.setPrototypeOf(source, target)或__proto__,设置空对象的隐式原型为构造函数的原型对象prototype
③ 改变构造函数的this指向为当前空对象(这样函数内this才能指向空对象,执行时能对空对象属性赋值),并将传参(设置实例的属性值一系列参数,该演示里指传入的name、age值)传入构造函数中执行
④ 若上一步构造函数执行结果有返回对象,则返回该对象,否则返回当前自己创建的对象。
注: 必须要知道一件事就是: 如果构造函数有返回值且是对象(包括数组),就会将当前对象作为 new 出来的对象,无返回就会是正常新建的实例(即取自己手动创建的对象)。如下演示:
function People(name, age) {
this.name = name
this.age = age
return {
name: '上官耀',
age: 29,
hobbit: 'eat'
}
}
undefined
var a = new People('叶少伟', 24) // {name: '上官耀', age: 29, hobbit: 'eat'}
代码实现:
// 构造函数
function People(name, age) {
this.name = name
this.age = age
return this // 可不用返回当前实例对象
}
// 手写new关键字
function myNew(fn) {
const obj = {} // 创建空对象
Object.setPrototypeOf(obj, fn.prototype) // 设置空对象隐式原型为构造函数的prototype
const args = Array.prototype.slice.call(arguments, 1) // myNew函数的传参获取及处理
const newObj = fn.apply(obj, args) // 执行构造函数为obj设置属性值
return newObj ?? obj // 返回创建的实例对象,完成通过函数实现new关键字功能
}
const mine = myNew(People, '李逍遥', 24) // 等价于 const mine = new People('李逍遥', 24)
console.log(mine) // People {name: '李逍遥', age: 24}
手写 new 简化版:
function myNew(fn, ...arr) {
let newObj = {} // 创建空对象
Object.setPrototypeOf(newObj, fn.prototype) // 设置空对象隐式原型为构造函数的prototype
const res = fn.apply(newObj, arr) // 执行构造函数为 newObj 设置对象成员属性
return res instanceof Object? res:newObj // 如果构造函数本身有返回对象,就将该对象作为新建对象,没有就返回自己创建的对象 newObj
}
现在对上面代码可能疑惑的点进行解读:
① arguments
:这个是ES6新增的一种获取函数传参的方式,它是类数组对象,可自行在控制台打印出来查看,会看到是一个数组展现形式,但是别被误导,展开看是有callee等属性的。(见下图)
② Array.prototype.slice.call(arguments, 1)
:上面说了arguments是类数组,所以需要借助②转换成数组类型,也可以通过ES6新增的数组方法Array.from(arguments)转为数组类型(②是ES6未出现前常用的将类数组转为数组的方法)。②为什么要写成这种形式,还多传了一个1?因为slice是数组原型上的方法,而arguments不是真正意义上的数组,所以没有该方法,不能arguments.slice()处理,得从Array调取slice方法并且通过call改变slice函数内部this指向为当前arguments对其处理,传1是因为arguments[0]第一个元素是传入的构造函数fn也就是People,所以只截取arguments第二个往后的参数,就是要传入的一系列属性值。
③ return newObj ?? obj
:??
ES6的null/undefined判断运算符,前者是null或undefined就会取后面的值。因为我在构造函数里返回了当前实例,所以这里取得就是newObj,没有返回newObj就是undefined,这时会取obj。很多人看不懂其他人手撕new时,为什么在这里要用newObj instanceOf Object? newObj:obj(和③写法等价),这里就是一种代码强壮性表现,如果你在构造函数时有返回实例,就会取newObj,没有的话就取刚创建的对象obj,有返回时newObj和obj都是同一个实例。