关于原理:JS-原生方法原理探究三从规范解读如何实现-new-操作符

43次阅读

共计 2646 个字符,预计需要花费 7 分钟才能阅读完成。

这是 JS 原生办法原理探索系列的第三篇文章。本文会介绍如何模仿实现 new 操作符。对于 new 的具体用法,MDN 曾经形容得很分明了,这里咱们不说废话,间接讲如何模仿实现。

new 操作符的标准

留神:上面展现的所有标准都是 ES5 版本的,与当初最新的标准有些区别

首先看一下依据标准的形容,new 操作符做了什么事:

全是英文,不过没关系,我简略翻译一下:

我在应用 new 操作符的时候,前面跟着的构造函数可能带参数,也可能不带参数,如果不带参数的话,比如说 new Fn(),那么这里这个 Fn 就是一个 NewExpression;如果带参数,比如说 new Fn(name,age),那么这里的 Fn 就是一个 MemberExpression

这两种状况下应用 new 操作符所进行的操作有点点不同,这里拿带参数的状况阐明一下:

  1. 首先会对 Fn 这个 MemberExpression 求值,其后果是指向理论函数对象的一个援用,咱们把这个援用作为 ref
  2. 接着调用 GetValue(ref) 进行求值,失去理论的函数对象,把这个对象作为 constructor
  3. Arguments 也就是传进来的参数求值,失去一个参数列表,作为 argList
  4. 如果 constructor 不是对象,则抛出类型谬误
  5. 如果 constructor 没有实现外部的 [[Constructor]] 办法,也抛出类型谬误
  6. 调用 constructor[[Constructor]] 办法,并将 argList 传入作为参数,返回调用后果

从这些形容能够看出,更多的实现细节放在函数的 [[Constructor]] 办法里。那么这个办法具体是做什么用的呢?

[[Constructor]] 的标准

在 JS 中,函数有两种调用形式,一种是失常调用,这将调用函数的外部办法 [[Call]],还有一种是通过 new 调用,此时的函数作为一个构造函数,这将调用函数的另一个外部办法 [[Consturct]]。所以,要实现 new 操作的话,咱们得先搞懂 [[Construct]] 外部办法做了什么事。

这里持续看标准是怎么说的:

简略翻译一下:

当通过可能为空的参数列表调用函数 F 的外部办法 [[Construct]] 的时候,会执行如下步骤:

  1. obj 作为一个新创建的原生对象
  2. 依照标准指定的,为 obj 设置所有外部办法
  3. obj 的外部属性 [[Class]] 设置为 Object
  4. 传参 prototype 调用函数 F 的外部办法 [[Get]],获取函数的原型对象,作为 proto
  5. 如果 proto 是对象,则将 obj 的外部属性 [[Prototype]] 设置为 proto
  6. 如果 proto 不是对象,则将 obj 的外部属性 [[Prototype]] 设置为规范内建的 Object 的原型对象
  7. 调用函数 F 的外部办法 Callobj 作为调用时的 this 值,此前传给 [[Construct]] 的参数列表作为调用时的参数。将调用后失去的后果作为 result
  8. 如果 result 是对象,则将其返回
  9. 否则,返回 obj

能够说,标准曾经讲得很分明了,简略地说,在 new 一个构造函数的时候,具体会做上面的事件:

  • 外部创立一个实例对象,并指定实例对象的原型:

    • 如果构造函数的原型是对象,则让实例的 __proto__ 等于构造函数的 prototype
    • 如果构造函数的原型不是对象,则让实例的 __proto__ 等于 Objectprototype
  • 将实例对象绑定为构造函数中的 this,此前传递进来的参数作为参数,并执行一遍构造函数
  • 如果构造函数返回了对象,则将其作为返回值,否则将实例对象作为返回值

代码实现

ES3 版本的实现如下:

function myNew(Fn){if(typeof Fn != 'function'){throw new TypeError(Fn + 'is not a constructor')
    }
    myNew.target = Fn
    var instance = {}
    // 检测构造函数原型是不是对象
    instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype 
    const returnValue = Fn.apply(instance,Array.prototype.slice.call(arguments,1))
    if(typeof returnValue === 'object' && returnValue !== null || typeof returnValue === 'function'){return returnValue} else {return instance}
}

ES6 版本的实现如下:

function myNew(Fn,...args){if(typeof Fn != 'function'){throw new TypeError(Fn + 'is not a constructor')
    }
    myNew.target = Fn
    const instance = {}
    // 检测构造函数原型是不是对象
    instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype 
    const returnValue = Fn.call(instance,...args)
    return returnValue instanceof Object ? returnValue : instance
}

留神几个要点:

1)当函数是通过 new 调用的时候,new.target 会指向函数本身,这个“指向”的操作在代码里就是通过 myNew.target = Fn 体现的

2)为什么不间接应用 const instance = Object.create(Fn.prototype) 创立实例呢?依据标准,咱们在实现 new 的时候,须要检测构造函数的原型是不是对象,如果不是对象,比如说是 null,那么实例的 __proto__ 会从新链接到 Object.prototype,而这里如果应用了 Object.create,则会导致实例的 __proto__ 依然指向 null。网上很多 new 的模仿实现间接应用了 Object.create,或者基本没有对构造函数的原型进行类型查看,这是不够谨严的(留神,我没有说这是谬误的,只是不够贴近原生 [[Consturct]] 的实现细节)。具体能够浏览我之前写的另一篇文章。

3)如果无奈应用 instanceof,咱们也能够改用 typeof Fn.prototype === 'Object' && Fn.prototype !== null 进行判断

正文完
 0