Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JS 原生方法原理探究(三):如何实现 new 操作符?

JS 原生方法原理探究(三):如何实现 new 操作符?

作者头像
Chor
发布于 2021-06-08 13:26:27
发布于 2021-06-08 13:26:27
1.1K00
代码可运行
举报
文章被收录于专栏:前端之旅前端之旅
运行总次数:0
代码可运行

这是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 版本的实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 版本的实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
}

注意几个要点:

  • 当函数是通过 new 调用的时候,new.target 会指向函数自身,这个“指向”的操作在代码里就是通过 myNew.target = Fn 体现的
  • 为什么不直接使用 const instance = Object.create(Fn.prototype) 创建实例呢?根据规范,我们在实现 new 的时候,需要检测构造函数的原型是不是对象,如果不是对象,比如说是 null,那么实例的 __proto__ 会指向 Object 的原型,而这里如果使用了 Object.create,则会导致实例的 __proto__ 仍然指向 null。网上很多 new 的模拟实现直接使用了 Object.create,或者根本没有对构造函数的原型进行类型检查,这是不够严谨的
  • 如果无法使用 instanceof,我们也可以改用 typeof Fn.prototype === 'Object' && Fn.prototype !== null 进行判断
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
你的手写 new 实现足够严谨吗?
在开始阅读这篇文章之前,你可以对比下面这两段代码的输出结果是否一致(假设 myNew 是你自己实现的 new 操作):
Chor
2021/06/08
5390
你的手写 new 实现足够严谨吗?
模拟实现 new 操作符(js)
首先需要理解,JavaScript 中的构造函数跟 Java 中的构造函数性质是不一样的。js 不是基于 class 这种静态类模式,而是基于原型对象的模式。
请叫我大苏
2019/10/24
3.6K0
JS 原生方法原理探究(二):如何实现 Object.create?
这是JS 原生方法原理探究系列的第二篇文章。本文会介绍如何实现 Object.create() 方法。关于这个方法的具体用法,MDN 已经描述得很清楚了,这里我们只做简单的介绍,具体的重点在于如何模拟实现。
Chor
2021/06/08
1.9K0
JS 原生方法原理探究(二):如何实现 Object.create?
JS原生方法原理探究(六)从 Babel 转译过程浅谈 ES6 实现继承的原理
都说 ES6 的 Class 是 ES5 的语法糖,那么 ES6 的 Class 是如何实现的呢?其实现继承的原理又是什么呢?不妨我们通过 Babel 转译代码的方式,看看其中有什么门道。
Chor
2021/06/08
1.2K0
557 原型prototype和原型链__proto__:原理,函数的三种角色,for in,手写new
Object.create(xxx):创建一个空对象,并且让把xxx作为创建对象的原型(空对象.__proto __ = xxx),xxx必须是对象或者null,如果xxx是null,则创建一个没有任何原型指向的空对象
全栈程序员站长
2022/09/07
2200
557 原型prototype和原型链__proto__:原理,函数的三种角色,for in,手写new
前端进阶高薪必看-手写源码
此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈
青梅煮码
2023/03/13
7530
js手写题汇总(面试前必刷)
event bus既是node中各个模块的基石,又是前端组件通信的依赖手段之一,同时涉及了订阅-发布设计模式,是非常重要的基础。
helloworld1024
2022/11/09
1.1K0
JS 原生方法原理探究(四):如何实现继承的几种方式?
这是JS 原生方法原理探究系列的第四篇文章。本文会介绍如何实现 JS 中常见的几种继承方式,同时简要它们的优缺点。
Chor
2021/06/08
1.5K0
JS 原生方法原理探究(四):如何实现继承的几种方式?
前端手写代码原理实现
现在的前端门槛越来越高,不再是只会写写页面那么简单。模块化、自动化、跨端开发等逐渐成为要求,但是这些都需要建立在我们牢固的基础之上。不管框架和模式怎么变,把基础原理打牢才能快速适应市场的变化。下面介绍一些常用的源码实现:
WahFung
2020/08/24
4590
New 操作符的原理
完整高频题库仓库地址:https://github.com/hzfe/awesome-interview
HZFEStudio
2021/09/26
7870
面试官问:能否模拟实现JS的new操作符
从这里例子中,我们可以看出:一个函数用new操作符来调用后,生成了一个全新的对象。而且Student和Object都是函数,只不过Student是我们自定义的,Object是JS本身就内置的。 再来看下控制台输出图,感兴趣的读者可以在控制台试试。
苏南
2020/12/16
4010
面试官问:能否模拟实现JS的new操作符
面试官问:能否模拟实现JS的new操作符(高频考点)
这是面试官问系列的第一篇,旨在帮助读者提升JS基础知识,包含new、call、apply、this、继承相关知识。 面试官问系列文章如下:感兴趣的读者可以点击阅读。 1.面试官问:能否模拟实现JS的new操作符 2.面试官问:能否模拟实现JS的bind方法 3.面试官问:能否模拟实现JS的call和apply方法 4.面试官问:JS的this指向 5.面试官问:JS的继承
若川
2020/12/31
4720
面试官问:能否模拟实现JS的new操作符(高频考点)
JS 原生方法原理探究(五):如何实现 instanceof?
这是JS 原生方法原理探究系列的第五篇文章。本文会介绍如何实现 instanceof 方法。
Chor
2021/06/08
1.9K1
彻底搞懂JS原型与原型链
说到JavaScript的原型和原型链,相关文章已有不少,但是大都晦涩难懂。本文将换一个角度出发,先理解原型和原型链是什么,有什么作用,再去分析那些令人头疼的关系。
hellocoder2029
2022/10/17
3.1K1
JS原型链与继承别再被问倒了
继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承 和 实现继承 .接口继承只继承方法签名,而实现继承则继承实际的方法.由于js中方法没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的.
全栈程序员站长
2021/06/10
6330
手把手教你剖析,并手写十五个重要 API 的实现
在面试中,常常会遇到一些手写XXX之类的面试题,因此好好总结一下,对于巩固我们的原生js的基础是非常必要的。
苏南
2020/12/16
3740
手把手教你剖析,并手写十五个重要 API 的实现
再谈构造函数、原型、原型链之间的关系
构造函数、原型、原型链作为ES5的内容,已经是老生常谈的问题了。首先说说为什么要再次拿起这个话题去说呢?这几天有空我会看一些源码,这些源码的底层实现考虑到兼容性还是来源于ES5,很多方法的封装以及实现(不管是按照模块封装还是统一实现)都是面向对象的思想,而且webpack以及rollup打包之后解析出来的代码利用@babel/core和@babel/preset-env转化之后也都是ES5的代码,所以有想再次谈起这个话题,回顾回顾旧知识,温故而知新。
小丑同学
2020/09/21
7190
JS 继承
用过 React的读者知道,经常用 extends继承 React.Component:
grain先森
2019/03/28
3K0
JS 继承
前端-如何继承 Date 对象?由一道题彻底弄懂 JS 继承
于是,随手用JS中经典的组合寄生法写了一个继承,然后,刚准备完美收工,一运行,却出现了以下的情景:
grain先森
2019/03/29
1.1K0
前端-如何继承 Date 对象?由一道题彻底弄懂 JS 继承
深入理解New操作符
当我们对函数进行实例化时,需要用new操作符来实现。那么,对于它的底层实现原理你是否清楚呢?本文就跟大家分享下它的原理并用一个函数来模拟实现它,欢迎各位感兴趣的开发者阅读本文。
神奇的程序员
2022/10/30
3070
深入理解New操作符
相关推荐
你的手写 new 实现足够严谨吗?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验