Code前端首页关于Code前端联系我们

JavaScript的new和Object.create()详解——每天进步一点

terry 2年前 (2023-09-08) 阅读数 158 #Vue

前言

这里详细介绍了new算子和Object.create(),以及它们是如何实现的。最后,我们还简要介绍了 Object.create()、new Object() 和 {}

之间的区别

新干员

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

新角色

我们看前三个栗子:

  1. new

    的常见用途
    
    function Test (name) {
      this.name = name
    }
    Test.prototype.sayName = function () {
        console.log(this.name)
    }
    
    const t = new Test('xx')
    console.log(t.name) // 'xx'
    t.sayName() // 'xx'
    
     

    使用构造函数创建对象的过程大家都知道,无需过多赘述。如果构造函数的值为 return 怎么办?

  2. return 对象类型数据

    
    function Test (name) {
      this.name = name
    
      return {a: '啊啊啊'}
    }
    
    const t = new Test('xx')
    console.log(t) // '{a: '啊啊啊'}'
    
     

    可见return之前的工作白费了,最终返回了return后面的对象。

  3. return 基本类型数据

    
    function Test (name) {
      this.name = name
    
      return 1
    }
    
    const t = new Test('xx')
    console.log(t) // '{name: "xx"}'
    
     

    与没有 return 的效果相同。

我们可以得出一个结论:如果构造函数的返回值是一个对象,那么正常使用该返回值,否则默认返回一个新对象

这个例子告诉我们一件事:构造函数应该尽量不返回值。由于返回的原始值不生效,因此返回的对象是new操作符不起作用。

  1. 创建一个新的空对象。

  2. 允许构造函数的作用域为新对象(因此this指向新对象)。

  3. 在构造函数中添加代码(为这个新对象添加属性)。

  4. 如果这个函数有返回值,并且返回值是一个对象,则返回它;否则,默认返回一个新对象。

箭头函数中没有 [[Construct]] 方法。不能用new调用,会报错。

新认识

根据以上四个功能,分为四个步骤,实现一个new

在这个函数中,第一个参数是构造函数,第二个参数是构造函数中的参数。

第一步

创建一个新的空对象


function myNew () {
  let obj = {}
}

 
第二步

将构造函数的作用域分配给一个新对象是一个原型链来构造这个新对象并将其连接到构造函数的原型对象上,这样新对象就可以访问到构造函数中的属性和方法构造函数。

  1. 构造函数是我们传递的第一个参数。由于 arguments 是一个数组类,因此我们不能直接使用 shift 方法。我们可以使用callArray上调用shift方法来得到constr

    
    let constr = Array.prototype.shift.call(arguments)
    
     
  2. 将对象的原型指向构造函数的原型对象:

    
    obj.__proto__ = constr.prototype
    
     

    或者,您可以使用 setPrototypeOf 方法(将指定对象的原型(即内部 [[prototype]] 属性)设置为另一个对象或 null

    
    Object.setPrototypeOf(obj, constr.prototype)
    
     
  3. 简化:使用Object.create()来简化上述步骤:

    
    let obj = obj = Object.create(constr.prototype)
    
     

    使用Object.create(object, objectProps)这个方法,第一个参数是要创建的对象的原型

第三步

执行构造函数中的代码(向这个新对象添加属性)。使用apply到达:


constr.apply(obj, arguments)

 

第 4 步

如果这个函数有返回值,并且返回值是一个对象,则返回;否则默认返回一个新对象

  1. 首先你需要得到这个返回值:

    
    let res = constr.apply(obj, arguments)
    
     
  2. 由上可知:new关键字,如果将undefined,null返回到基类型,则返回一个新对象;并且只有当返回一个对象时,才返回构造函数的返回值。

    因此:判断res是否属于object类型。如果类型为object,则返回res,否则返回obj

    
    return res instanceof Object ? res : obj
    
     

    使用 res instanceof Object 确定 res 是否为对象类型。
    使用愚蠢的方式typeOf()target !== null && (typeof target === 'object' || typeof target === 'function')

最终代码

测试:myNew(Test, 'xuxu') 与上述一致。

Object.create()

方法

Object.create() 创建一个新对象,使用现有对象作为新创建的对象 __proto__

语法


Object.create(proto, [propertiesObject])

 
  1. proto是必填参数,它是新对象的原型对象。

    注意,如果该参数为null,则新对象完全为空,不会继承Object.prototype上的任何属性和方法,如hasOwnProperty()、toString()等。

  2. propertiesObject 是可选参数。定义了可枚举属性或修改的属性描述符的对象。对象中存在两种主要类型的属性描述符:数据描述符访问器描述符

    了解更多请访问上:关于Object.defineProperty()和Object.defineProperties()

    
    let xx = Object.create({a: 1}, {
      b: {
          value: 2,
          writable: false,
          configurable: true
      }
    })
    
    console.log(xx) // {b: 2}
    console.log(xx.__proto__) // {a: 1}  新对象xx的__proto__指向{a: 1}
    
    xx.b = 77;
    // throws an error in strict mode
    
    console.log(xx.b);
    // expected output: 2
    
     

应用

最大的用处就是实现js的继承:原型继承寄生组合继承♼❀❝

了解更多信息,请访问转上一篇:JavaScript继承详解(ES5和ES6)——每天一个小进步

已达到

的思路很简单:创建一个新对象,使用输入的第一个参数作为这个对象的原型,当找到第二个参数时,通过Object.definePropertieskey、value设置为创建的对象,最后返回可以使用创建的对象。

  1. 我自己的实现,我认为没有问题:(todo:更正)

    
    Object.myCreate = function (proto, propertyObject = undefined) {
      let obj = {}
      obj.__proto__ = proto
      if (propertyObject !== undefined) {
        Object.defineProperties(obj, propertyObject)
      }
      return obj
    }
    
     
  2. 源代码:定义一个空的构造函数,然后指定构造函数的原型对象,并使用new运算符创建一个空对象。

    
    Object.myCreate = function (proto, propertyObject = undefined) {
      if (propertyObject === null) {
        // 这里没有判断propertyObject是否是原始包装对象
        throw 'TypeError'
      } else {
        function F() {}
        F.prototype = proto
        const obj = new F()
        if (propertyObject !== undefined) {
          Object.defineProperties(obj, propertyObject)
        }
        if (proto === null) {
          // 创建一个没有原型对象的对象,Object.create(null)
          obj.__proto__ = null
        }
        return obj
      }
    }
    
     

    个人理解:if (proto === null)这个没有必要,因为当proto = null时,上面这句F.prototype = proto,的原型对象已经是❙

Object.create()、new Object() 和 {}

的区别
  1. new Object(){} 之间没有区别,并且 __proto__ 指向创建的新对象的 Object.prototype

  2. Object.create(proto, [propertiesObject])创建的对象原型依赖于protoprotonull,新对象是一个空对象,没有原型,不继承任何对象; proto为指定对象,新对象的原型指向指定对象,继承指定对象

参考文献

  1. 前端面试题-重新实现自己
  2. 详情JS中新增call函数原理讲解
  3. Object.create()、new Object() 和 {}
  4. 的区别
  5. Object.create实现原理

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门