美文网首页
new的过程

new的过程

作者: 达文西_Huong | 来源:发表于2020-06-17 11:10 被阅读0次

new的过程

借用 MDN 对 new 的说明

new 运算符创建一个用户定义的对象类型(当构造函数有返回值时)的实例或具有构造函数的内置对象(当前函数可用来作为构造函数,那么返回内部创建的新对象)的实例

new 一个对象的过程分为四步

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象 (this指向这个新对象)
  3. 执行构造函数中的代码
  4. 返回新对象

一个普通的构造函数

    function Person(name) {
        this.name = name 
    }
    var p = new Person('小明')

    console.log(p.name)                 // 小明
    console.log( p instanceof Person )  // true

用js 模拟一个new 的函数

    function _new () {
        // 1. 创建一个对象
        let target = {}
        let [constructor,...args] = [...arguments];     // 第一个参数是构造函数

        // 2. 原型链连接
        target.__proto__ = constructor.prototype

        // 3. 将构造函数的属性和方法添加到这个新的空对象上
        let result = constructor.apply(target,args)
        if(result && (typeof result == 'object' || typeof result == 'function')){
            // 如果构造函数返回的结果是一个对象,就返回这个对象
            return result
        }
        // 如果构造函数返回的不是一个对象,就返回创建的新对象。
        return target
    }
    let p2 = _new(Person, "小花")
    console.log(p2.name)               // 小花
    console.log(p2 instanceof Person ) // true

上半部分的原文连接:https://www.jianshu.com/p/9bf81eea03b2


关于如何判断函数是否可以作为构造函数

我们通过function定义的普通函数都可以结合 new 来作为构造函数使用,那么到底如何判断一个函数能否作为构造函数呢

根据网上的文章描述

每个函数都有一些内部的属性,例如Construct 表示可以作为构造函数使用,Call表示可以用来作为普通函数
所以当一个函数没有Construct 内部属性时,它就不能用来作为构造函数

1. 经验积累方式
  • 箭头函数不能作为构造函数使用
  • Generator函数不能作为构造函数使用
  • 对象的简写方法不能作为构造函数使用
  • 内置方法不能作为构造函数使用(如Math.min)

除非特别说明,es6+ 实现的特定函数都没有实现[constructor] 内置方法

除了依靠经验,还有另类思路,如下

  • 通过构造函数是否有该属性判断 Fn.prototype.constructor, 但有局限性,无法处理手动修改的场景
  • 通过抛出异常,局限性是依赖原有new操作符,而且会导致构造函数逻辑被先行处理
  • 通过 Reflect.construct , 加上 Symbol 的特殊处理后,就没有局限性。(推荐)

前两种方式网上都有文章,我们就单独说说最后一种

通过Reflect.construct()来判断一个函数是否能够作为构造函数

    function is_constructor(f){
        // 特殊判断,Symbol 能通过检测
        if(f === Symbol ) return false
        try {
            Reflect.construct(String,[],f)
        }catch (e) {
            return false
        }
        return true;
    }

其实本质上也是用抛出异常方式来判断,但与直接 new A() 的抛异常方式不同的是,它不会触发构造函数的执行。这就得来看看Reflect.construct

Reflect.construct 方法等同于 new target(...args), 提供了一种不使用new来调用构造函数的方法

    function A() {
        this.a = 1
    }
    new A(); {a:1}
    // 等于
    Reflect.construct(A,[]); // {a:1}

Reflect.construct 可以接收一个可选的第三个参数

Reflect.construct(target, argumentsList[, newTarget])

  • target: 被调用的构造函数
  • argumentsList: 参数列表,类数组类型数据
  • new Target: 可选,当传入时,使用newTarget.prototype 来作为实例对象的 prototype。否则使用 target.prototype
  • 当 target 或者 newTarget 不能作为构造函数时,抛出 TypeError 异常

那么,我们可以怎么利用这些特性呢,先看使用原始 new 的方式

    function A () {
        console.log(1);
    }
    B = () =>{
        console.log(2);
    }

    new A(); // 输出1
    new B(); // TypeError, 抛出异常

    // 使用抛异常方式来判断某个函数能否作为构造函数时,如果可以,那么构造函数就会先执行一遍
    // 如果刚好在构造函数内处理一些业务代码,那么可能就会有副作用影响
    function isConstructor(Fn){
        try{
            new A();   // 能够判断出 A 可以作为构造函数,但 A 会被先执行一次
            // new B(); // 能够判断出 B 不能作为构造函数
        } catch(e) {
            return false
        }
        return true
    }

那么,该如何来使用 Reflect.construct 呢

关键在于它的第三个参数,是用来指定构造函数生成的对象的 prototype,并不会去执行它,但却会跟第一个参数的构造函数一起经过能否作为构造函数的检查,来看看用法

    function A () {
        console.log(1)
    }
    A.prototype.a = 1;
    
    function B () {
        console.log(2)
    }
    B.prototype.a = 2

    var a = Reflect.construct(A,[]);    // 输出 1
    a.a; // 1,继承自 A.prototype

    var b = Rflect.construct(A,[], B);  // 输出 1
    b.a; // 2,继承自 B.prototype; 

我们来大概写一下 Reflect.construct 传入三个参数时的伪代码

    Reflect.construct = function (target,args, newTarget){
        check target has [[Construct]]
        check newTarget has [[Construct]]
        var obj = Object.create(newTarget ? newTarget.prototype : target.prototype)
        var result = target.call(obj, ...args);
        return result instanceof Object ? result : obj
    }

第一个参数 target 和第三个参数 newTarget 都会进行是否能作为构造函数使用的检查,虽然 target 会被作为构造函数调用,但我们可以把待检查的函数传给第三个参数,而第一个参数随便传入一个无关但可用来作为构造函数使用不久好了,所以代码如下

    function is_contructor(f) {
        // 特殊处理,因为 symbol 能通过 Reflect.construct 对参数的检测
        if(f === Symbol ) return false
        try {
            // 第一个 target 参数传入一个无关的构造函数,第三个参数传入一个待检测的函数
            Reflect.construct(String,[],f)
        } catch(e) {
            return false
        }
        return true
    }
    // 当 f 可作为构造函数使用,Reflect.construct 就会正常执行,那么此时:
    // Reflect.construct(String, [], f) 其实相当于执行了
    // a.__proto__ = f.prototype
    // 既不会让被检测函数先执行一遍,又可以达到利用引擎层面检测函数是否能作为构造函数的目的

最终,模拟new的实现代码

    function _new(Fn,...args) {
        function is_constructor(f) {
            if(f === Symbol ) return false
            try {
                Reflect.construct(String, [], f)
            } catch(e) {
                return false
            }
            return true
        }

        // 1. 参数判断检测
        let isFunction = typeof Fn === 'function';
        if(!isFunction || !is_constructor(Fn)) {
            throw new TypeError(`${Fn.name || Fn} is not a constructor`)
        }

        // 2. 创建一个继承构造函数 .prototype 的空对象
        var obj = Object.create(Fn.prototype);

        // 3. 让空对象作为函数 A 的上下文,并调用 A 同时获取它的返回值
        let result = Fn.call(obj,...args)

        // 4. 如果构造函数返回一个对象,那么直接return 它,否则返回内部创建的新对象
        return result instanceof Object ? result : obj;
    }

总结

关键的一些点

  • 如何判断某个函数能否作为构造函数
  • 构造函数又返回值时的处理
  • 构造函数生成的对象的原型处理

参考文章:https://www.jianshu.com/p/5541477481bd

相关文章

  • new的过程

  • new的过程

    new的过程 借用 MDN 对 new 的说明 new 运算符创建一个用户定义的对象类型(当构造函数有返回值时)的...

  • new 过程

    【如果你对js的this还不了解,请先阅读:JS作用域和this关键字】【如果你不了解JS原型链,请先阅读:JS原...

  • new的执行过程

  • New 实现过程

    1.新生成一个对象2.链接到原型3.绑定this4.返回新对象

  • 对象的创建过程(new 的过程)

    在Java程序当中每时每刻都有对象被创建出来。在语言层面上,创建对象通常仅仅是使用一个new关键字而已,而在虚拟机...

  • 对象的创建过程(new 的过程)

    虚拟机中对象创建的过程: 1、遇到new指令,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并...

  • Javascript new 对象的过程

    首先看一下下面的代码 想想这两种获取对象的方式有什么不同? 再看看下面的代码 看到这里是不是感觉好像明白了什么 n...

  • 深拷贝和浅拷贝

    clone的过程new 一个对象的过程和 clone 一个对象的过程区别new 操作符的本意是分配内存。程序执行到...

  • js中的原型链,prototype与__proto__的关系

    首先说一下new的过程先看一段代码: 很简单的一段代码,我们看看这个new到底做了什么?我们可以把new的过程拆分...

网友评论

      本文标题:new的过程

      本文链接:https://www.haomeiwen.com/subject/wudatktx.html