手动实现 new 关键字

new 做了哪些事

在实现 new 关键字之前我们需要了解 new 到底做了什么工作?

我们从一段简单的代码入手来看

javascript
1function fn(name){
2  this.name=name
3}
4fn.prototype.say=function (){
5  alert(this.name)
6}
7const boy=new fn('qiuyanxi')

这段代码非常简单,它定义了一个构造函数,然后给了这个构造函数的原型一个名叫 say 的函数。

毫无疑问,当我调用 boy.say的时候会打印qiuyanxi,那么我们推断 new 做了什么?

  • 首先一定会返回一个对象。
  • 该对象内肯定有个 name 属性,值为 qiuyanxi。
  • 既然这个对象可以调用 say 方法,说明也继承了 say 方法。

为了证明我们的猜想,我们从打印台查看一下boy 这个对象 image

推断正确,有一点值得我们注意,say 方法是在 boy.__poroto__上的,而这个属性有个constructor属性指向 fn,那么我们就知道这是 fn 函数的原型。

javascript
1fn.prototype===boy.__proto__
2// true

手写 new 功能

知道了原理,那我们可以开始动手了

1.我们需要先定义一个构造函数

javascript
1function create(name){
2  this.name=name
3}
4create.prototype.say=function (){
5  alert(this.name)
6}

2.创建一个 new 函数,接收构造函数为参数

javascript
1function lineNew(func){
2  const x=Object.create(func.prototype)  //创建一个空对象,原型连接到传入的构造函数身上
3  return x //返回一个对象
4}

3.既然内部需要有 name 属性,那我们就需要把 name 传进去,而且需要调用构造函数

javascript
1function likeNew(func,...args){
2   const x=Object.create(func.prototype)
3   func.call(x,...args)
4  return x
5}

实验一下:

javascript
1const boy=likeNew(create,'qiuyanxi')

image

看起来没问题。

细节

虽然功能实现了,但是有些细节问题需要处理。

比如说箭头函数是没有 原型的,所以我们可以手动帮它创建一个。

比如说会不会有些人不传函数进去呀?所以我们需要做一点 Polyfill

优化代码

javascript
1function likeNew(func,...args){
2  if(typeof func !=='function'){
3    throw new Error('first arg is not function')
4  }
5  let x={}
6  if(func.prototype){
7    x=Object.create(func.prototype)
8  }else{
9    x.__proto__={constructor:func} //手动创建一个原型接上
10  }
11   func.call(x,...args)
12  return x
13}

构造函数返回对象问题

有时候面试题会问你构造函数返回一个对象,那么 new 会怎么做。

我们直接实验出真知

javascript
1function foo(name){
2  this.name=name;
3  return {age:18}
4}
5const boy=new foo('qiuyanxi')
6boy // {age: 18}

结论:如果构造函数返回了对象,那 new 就返回这个对象,否则就返回 new 自己创建的对象。

这一点 MDN 也有介绍

当代码 new Foo(...) 执行时,会发生以下事情:

1.一个继承自 Foo.prototype 的新对象被创建。 2.使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。 3.由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤 1 创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

那么我们继续改一下代码

javascript
1function likeNew(func,...args){
2  if(typeof func !=='function'){
3    throw new Error('first arg is not function')
4  }
5  let x={}
6  if(func.prototype){
7    x=Object.create(func.prototype)
8  }else{
9    x.__proto__={constructor:func} //手动创建一个原型接上
10  }
11  return typeof func.call(x,...args)==='object'?func.call(x,...args):x
12}

一般面试不需要做 Polyfill,这里附上简单的几行代码

javascript
1function _new(initFunc, ...rest) {
2  // let xx.__proto__ = initFunc.prototype
3  let cache = Object.create(initFunc.prototype)
4  let result = initFunc.call(cache, ...rest)
5  return typeof result === 'object' ? result : cache
6}

结束~

enjoy!