深入聊一聊JS中new的原理与实现

定义

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

使用new [constructor]的方式来创建一个对象实例,但构造函数的差异会导致创建的实例不同。

构造函数体不同

构造函数也是函数,其唯一的区别就是调用方式不同,任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。

因此构造函数也可以带有返回值,但是这会导致new的结果不同。

无返回值

function Person(name) {
  this.name = name;
}

let obj = new Person("Jalenl");
console.log(obj);

显然,打印的是{name:'Jalenl'}

返回对象

function Person(age) {
  this.age = age;
  return { name: "Jalenl" };
}

let obj = new Person(18);
console.log(obj);

打印的是{name:'Jalenl'},也就是说return之前的定义都被覆盖了。这里return的是一个对象,那返回的是个基本类型呢?
返回非对象
function Person(age) {
  this.age = age;
  return 1;
}

let obj = new Person(18);
console.log(obj);
复制代码
返回{age:21},这么说return失效了,跟没有return一样的结果,那如果没有this绑定内部属性,再返回基本数据类型呢?
没有属性绑定+返回非对象
function Person(){
    return 1
}
new Person()
复制代码
返回的是一个空对象{},意料之中。
综上,只有构造函数return返回的是一个对象类型时,才能改变初始结果。
构造函数类型不同
构造函数为普通函数
ECMA-262 3rd. Edition Specification中的说明了对象实例的创建过程:

13.2.2 [[Construct]]
When the [[Construct]] property for a Function object F is called, the following steps are taken:

Create a new native ECMAScript object.
Set the [[Class]] property of Result(1) to "Object".
Get the value of the prototype property of F.
If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in 15.2.3.1.
Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values.
If Type(Result(6)) is Object then return Result(6).
Return Result(1).

总结下来就是:

在内存中创建一个新对象。
这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
执行构造函数内部的代码(给新对象添加属性)。
如果构造函数返回对象,则返回该对象;否则,返回刚创建的新对象(空对象)。

第五步就已经说明了构造函数不同导致new结果不同的原因。
以下摘自MDN的解释:

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

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

构造函数为箭头函数
普通函数创建时,引擎会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。
function Person(){
    this.age = 18;
}
Person.prototype
/**
{
    constructor: ƒ Foo()
    __proto__: Object
}
**/
复制代码
创建箭头函数时,引擎不会为其创建prototype属性,箭头函数没有constructor供new调用,因此使用new调用箭头函数会报错!
const Person = ()=>{}
new Person()//TypeError: Foo is not a constructor
复制代码
手写new
综上,熟悉了new的工作原理后,我们可以自己实现一个低配版的new,实现的关键是:

让实例可以访问到私有属性;
让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性;
构造函数返回的最后结果是引用数据类型。

function _new(constructor, ...args) {
    // 构造函数类型合法判断
    if(typeof constructor !== 'function') {
      throw new Error('constructor must be a function');
    }
    // 新建空对象实例
    let obj = new Object();
    // 将构造函数的原型绑定到新创的对象实例上
    obj.__proto__ = Object.create(constructor.prototype);
    // 调用构造函数并判断返回值
    let res = constructor.apply(obj,  args);
    let isObject = typeof res === 'object' && res !== null;
    let isFunction = typeof res === 'function';
    // 如果有返回值且返回值是对象类型,那么就将它作为返回值,否则就返回之前新建的对象
    return isObject || isFunction ? res : obj;
};
复制代码
这个低配版new实现可以用来创建自定义类的实例,但不支持内置对象,毕竟new属于操作符,底层实现更加复杂。

作者:JaylenL
链接:https://juejin.cn/post/6994000994300330021
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(0)

相关推荐

  • 浅析JS中NEW的实现原理及重写

    提到new,肯定会和类和实例联系起来,如: function Func() { let x = 100; this.num = x + } let f = new Func(); 上面的代码,我们首先创建了一个函数,如果是用面向对象的说法就是创建了一个Function类的实例,如果直接执行这个函数,那它就是一个普通的函数,如果用new执行,则这个函数被称为一个自定义的类. 如果是一个普通函数执行,他会如下做几件事: ·形成一个全新的执行上下文EC(Execution Context 执行环境)

  • 浅谈javascript中new操作符的原理

    javascript中的new是一个语法糖,对于学过c++,java 和c#等面向对象语言的人来说,以为js里面是有类和对象的区别的,实现上js并没有类,一切皆对象,比java还来的彻底 new的过程实际上是创建一个新对象,把新象的原型设置为构造器函数的原型,在使用new的过程中,一共有3个对象参与了协作,构造器函数是第一个对象,原型对象是二个,新生成了一个空对象是第三个对象,最终返回的是一个空对象,但这个空对象不是真空的,而是已经含有原型的引用(__proto__) 步骤如下: (1) 创建一

  • Javascript模拟实现new原理解析

    new是JS中的一个关键字,用来将构造函数实例化的一个运算符.例子: function Animal(name) { this.name = name; } Animal.prototype.sayName = function() { console.log("I'm " + this.name); } var cat = new Animal('Tom'); console.log(cat.name); // Tom console.log(cat.__proto__ === An

  • 深入聊一聊JS中new的原理与实现

    定义 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例. 使用new [constructor]的方式来创建一个对象实例,但构造函数的差异会导致创建的实例不同. 构造函数体不同 构造函数也是函数,其唯一的区别就是调用方式不同,任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数. 因此构造函数也可以带有返回值,但是这会导致new的结果不同. 无返回值 function Person(name) { this.name = n

  • JS中bridge的原理与封装

    目录 一.hybird背景介绍 1.借助原生可以实现以下能力 二. 我们可以看一下纯H5和 app应用之间的区别 三.JsBridge 原理以及实现方式 1. JavaScript调用Native-注入API方式 2.JavaScript调用Native-拦截URLSCHEME 3.两种方式优缺点 四. 现有开源解决方案 五.我司使用的方案 一.hybird背景介绍 一般原生app发版周期长,而web版的app 开发速度快,周期短,所以hybird-H5 就是,web页面嵌入到app 的webv

  • 聊一聊JS中this的指向问题

    JS中的this指向一直是个让人头疼的问题,想当初我学的是天昏地暗,查了好多资料,看的头都大了,跟他大战了那么多回合,终于把它搞定个七八分,其实往往都是我们复杂化了,现在就让大家轻松看懂this的指向,我会分以下几种情况来说. this的指向: 1.this 指的是调用当前方法(函数)的那个对象,也就是说函数在谁那被调用,this就指的是谁. 来看两个栗子: oBtn.onclick = function(){ alert(this); //oBtn } oBtn[i].onclick = fn

  • 聊一聊JS中的prototype

    什么是prototype: function定义的对象有一个prototype属性,prototype属性又指向了一个prototype对象,注意prototype属性与prototype对象是两个不同的东西,要注意区别.在prototype对象中又有一个constructor属性,这个constructor属性同样指向一个constructor对象,而这个constructor对象恰恰就是这个function函数本身. //判断是否是数组 function isArray(obj) { ret

  • 关于AOP在JS中的实现与应用详解

    1.AOP介绍 简介 AOP (面向切面编程),缩写为Aspect Oriented Programming,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是JAVA 中Spring框架的一个重要内容,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 主要功能 日志记录 性能统计 安全控制 事务处理 异常处

  • 浅谈js中几种实用的跨域方法原理详解

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被当作是不同的域. 下表给出了相对http://store.company.com/dir/page.html同源检测的结果: 要解决跨域的问题,我们可以使用以下几种方法: 一.通过jsonp跨域 在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的.但是,在页面上引入不同

  • js中数组排序sort方法的原理分析

    本文实例分析了js中数组排序sort方法的原理.分享给大家供大家参考.具体分析如下: 最近在百度的项目中要用到对数组进行排序,当然一开始自然想到了数组的sort方法,这方法应用非常简单,大致如下: 复制代码 代码如下: window.onload=function(){         var arr=[2,55,55,1,75,3,9,35,70,166,432,678,32,98];         var arr2=["George","John","

  • Vue.js中的computed工作原理

    JS属性: JavaScript有一个特性是 Object.defineProperty ,它能做很多事,但我在这篇文章只专注于这个方法中的一个: var person = {}; Object.defineProperty (person, 'age', { get: function () { console.log ("Getting the age"); return 25; } }); console.log ("The age is ", person.

  • Node.Js中实现端口重用原理详解

    本文介绍了Node.Js中实现端口重用原理详解,分享给大家,具体如下: 起源,从官方实例中看多进程共用端口 const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); for (let i =

  • node.js中Buffer缓冲器的原理与使用方法分析

    本文实例讲述了node.js中Buffer缓冲器的原理与使用方法.分享给大家供大家参考,具体如下: 一.什么是Buffer Buffer缓冲器是用来存储输入和输出数据的一段内存.js语言没有二进制数据类型,在处理TCP和文件流的时候,就不是很方便了. 所以node.js提供了Buffer类来处理二进制数据,Buffer类是一个全局变量,Buffer在创建的时候大小就固定了,无法改变. Buffer类的实例类似于由字节元素组成的数组,可以有效的表示二进制数据. 二.什么是字节 字节是计算机存储时的

随机推荐