【 js手写系列】实现自己的new

307 阅读3分钟

努力让学习成为一种习惯,自信来源于充分的准备

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

new

引用MDN对new的定义

new运算符允许开发人员创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例

这里有个重要信息:通过new操作符调用函数返回的是一个对象,如果函数没有显示返回一个对象则返回构造函数内置的对象

// 没有返回值
function Foo() {}
const f = new Foo()
console.log(f) // Foo {}
// 返回了基本类型,非对象
function Foo() {
   return 1
}
const f = new Foo()
console.log(f) // Foo {}
// 返回了对象
function Foo() {
   return {a: 1}
}
const f = new Foo()
console.log(f) // {a: 1}
// 返回了对象
function Foo() {
   return []
}
const f = new Foo()
console.log(f) // []
// 返回了null
function Foo() {
   return null
}
const f = new Foo()
console.log(f) // Foo {}

第一版

基于上面的现象,我们实现第一版代码

需要注意的是:内置默认返回的对象是一个普通对象,其[[prototype]]指向 Object.prototype,所以不能使用 Object.create(null)创建对象

image.png

function myNew(constructor) {
  return constructor() instanceof Object ? constructor() : {}
}

function func() {}

const f = myNew(func)
console.log(f); // {}

function func2() {
  return 1
}
const f2 = myNew(func2)
console.log(f2); // {}

function func3() {
  return {
    a: 1
  }
}
const f3 = myNew(func3)
console.log(f3); // {a: 1}

function func4() {
  return []
}
const f4 = myNew(func4)
console.log(f4); // []

function func5() {
  return null
}
const f5 = myNew(func5)
console.log(f5); // {}

第二版

我们可以给构造函数传参,且构造函数内的this引用内置对象

function Foo(a, b) {
    this.a = a
    this.b = b
}
const f = new Foo(1,2)
console.log(f) // Foo {a: 1, b: 2}

我们基于上面的现象实现第二版代码

function myNew(constructor, ...args) {
  const o = {}
  const funcReturnValue = constructor.apply(o, args)
  return funcReturnValue instanceof Object ? funcReturnValue : o
}

function Func(a, b) {
  this.a = a
  this.b = b
}

const f = myNew(Func, 1, 2)
console.log(f); // {a: 1, b: 2}

第三版

返回的对象可以访问构造函数原型上的方法

function Foo(a, b) {
    this.a = a
    this.b = b
}
Foo.prototype.say = function () {
  console.log(this.a)
}
const f = new Foo(1,2)
console.log(f) // Foo {a: 1, b: 2}
f.say() // 1

这里我们只需要设置内置对象的[[prototype]]属性指向构造函数的原型即可

function myNew(constructor, ...args) {
  const o = {}
  Object.setPrototypeOf(o, constructor.prototype)
  const funcReturnValue = constructor.apply(o, args)
  return funcReturnValue instanceof Object ? funcReturnValue : o
}

function Func(a, b) {
  this.a = a
  this.b = b
}

Func.prototype.say = function () {
  console.log(this.a);
}

const f = myNew(Func, 1, 2)
f.say() // 1

好了,到这里。new的主要核心功能我们已经实现了。但有一些细节需要完善

image.png

如果调用的不是函数或者是箭头函数,则抛出异常

我们可以依据是否有prototype属性来判断是否是箭头函数

function myNew(constructor, ...args) {
  if (typeof constructor === 'function' && !!constructor.prototype) {
      const o = {}
      Object.setPrototypeOf(o, constructor.prototype)
      const funcReturnValue = constructor.apply(o, args)
      return funcReturnValue instanceof Object ? funcReturnValue : o
  } else {
     throw new TypeError('is not a constructor')
  }
}

引入mdn的话

由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]] 在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。此外,修改继承的影响是微妙和广泛的,并不仅限于在 Object.setPrototypeOf(...) 语句上的时间花费,而是可能扩展到任何访问已更改 [[Prototype]] 属性的对象的代码

不管怎么样都不建议修改对象的原型,这可能会极大的影响性能

因此原型赋值这块代码可以优化下,直接使用Object.create,在创建的时候就设置好原型

function myNew(constructor, ...args) {
  if (typeof constructor === 'function' && !!constructor.prototype) {
      const o = Object.create(constructor.prototype)
      const funcReturnValue = constructor.apply(o, args)
      return funcReturnValue instanceof Object ? funcReturnValue : o
  } else {
     throw new TypeError('is not a constructor')
  }
}

最终版终于完成!!🎉🎉🎉

最后

到这里,就是本篇文章的全部内容了

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

如果你有疑问或者出入,评论区告诉我,我们一起讨论

参考文章

MDN new