进一步了解对象创建

一般来讲,创建对象的方法有以下四种:

  1. 对象字面量的方式
  2. 通过Object.create()
  3. 通过new关键字创建一个Object构造函数的实例对象
  4. 通过new关键字创建一个自定义构造函数的实例对象

在日常开发中,除了字面量方式,其他方式可能会偶尔用到,但毕竟少,了解得也未必够深刻,这里针对其中的一两种做下浅谈

一、字面量方式

const o = {
    key_one: 'value',
    key_two: 1234
}
复制代码

字面量方式是实际开发中使用最多的创建对象的方式,想比其他方式,优点在于简便、灵活 值得注意的是,通过字面量创建的对象并没有隐式的调用Object构造函数(见《javascript高级程序设计》,5.1 Object类型,p81),另一方面

const o = {}
o instanceof Object // true
复制代码

并没有通过构造函数创建,但通过instanceof来判断确为Object的实例,是什么原因?

instanceof操作运算原理可以归结为以下方法,虽然在ES2015对instanceof判断的方式做了一些调整,但大体逻辑上还是可以沿用的

// L 表示左表达式,R 表示右表达式
function instance_of(L, R) {
    // 取 R 的显示原型
    var O = R.prototype;
    // 取 L 的隐式原型
    L = L.__proto__;
    while (true) {
        if (L === null)
           return false
        // 这里重点:当 O 严格等于 L 时,返回 true
        if (O === L)
            return true
        L = L.__proto__
     }
}
复制代码

总结为一句话就是,通过比较L instanceof R中L原型链中的原型对象是否与R的prototype对象严格相等,来判断L是否为R的实例。(想更深入了解,请点击这里

因此,虽然字面量形式创建的对象并没有隐式调用Object构造函数,但在创建的时候,o.__proto__会默认指向Object.prototype,除非手动修改,否则是成立的

二、Object.create()方法创建对象

Object.create()接受两个参数:

Object.create(prototype_object[, properties_object])
复制代码
  • prototype_object: 只能是一个对象或者null,将作为返回的新建对象的原型(即可通过新对象的**proto**访问),如果传入其他值或不传值,则会报TypeError的错误

    prototype_object 参数

  • properties_object:可选,与传给Object.defineProperties()方法的参数一样,通过配置属性的特性来给被创建的对象添加属性,可配置的属性特性是valuewritableenumerableconfigurable以及getset

    const prototype_object = {
        color: 'red',
        showColor () {
            return this.color
        }
    }
    const properties_object = {
        age: {
            value: 27,
            writable: false,
            enumerable: false,
            configurable: false
        },
        name: {
            value: 'lan',
            writable: true,
            enumerable: true,
            configurable: true
        }
    }
    const o = Object.create(prototype_object, properties_object)
    o.hasOwnProperty('color') // false
    o.hasOwnProperty('age') // true
    prototype_object.isPrototypeOf(o)  // true
    o.__proto__ === prototype_object // true
    o instanceof Object // true
    复制代码

❗️如果第一个参数为null,第二个参数不传,则会创建一个没有属性、没有原型的空对象

空对象

const o = Object.create(null)
o.__proto__ // undefined
o instanceof Object // false
复制代码

o.__proto__并不是指向null,而是undefined,如此一来,也就不再是Object的实例。 这样的创建对象,通过字面量或者Object构造函数是不能很好实现的。

Object.create()的模拟实现

object()方法实现

在《javascript高级程序设计 第三版》,6.3.4 原型式继承[p169],中提到道格拉斯·克劳克福德在2006年一篇题为Prototypal Inheritance in Javascript文章中,提出了一种使用原型实现继承的方法。

function object(o) {
    function F () {}
    F.prototype = o
    return new F()
}
复制代码

这个方法所达到的效果与Object.create()没传第二个参数时的功能类似,把传入的对象作为新对象的原型,后来被标准化实现,就是现在的Object.create()。当传人的值为非对象(包括null)时,会返回一个普通对象,所以在这点上,还是无法达到Object.create()的效果

通过object创建对象

从图中可以看出如果传的非对象,则对prototype的赋值无效

修改__proto__

__proto__,在开始并没有被标准支持,由于各现代浏览器都实现了(IE10及以下不支持)通过__proto__属性访问对象的[[Prototype]]即原型,为了统一兼容性,__proto__方式在ES2015中被加入了标准,(详请请查看MDN中关于Object.prototype.__proto__部分),在标准中有关__proto__的赋值操作可点击这里了解。

The proto property can also be used in an object literal definition to set the object [[Prototype]] on creation, as an alternative to Object.create()

在MDN关于__proto__的介绍中,说明__proto__是可以用来作为Object.create()一个可选实现方案的。

其实它的操作与Object.getPrototypeOf和Object.setPrototypeOf效果是一样的。都是通过修改[[Prototype]]来改变对象的原型链。通过__proto__还可以使用更为快捷的方式

// 以下方法是有效的
const o = {
    __proto__: {
        color: 'red'
    }
}
o.__proto__ // {color: 'red'}
Object.getPrototypeOf(o)  // {color: 'red'}

// 与Object.create() 只传第一个参数时表现一致
// 第二个参数接受对象和null,不传或为其他值会报错
Object.setPrototypeOf(o, null)
o.__proto__ // null
复制代码

但在MDN中也明确警告,修改[[Prototype]]在任何一个js引擎中都是一个耗时的操作,所以并不建议通过__proto__或Object.setPrototypeOf()的方式去修改[[Prototype]],而是建议使用Object.create(),同时,由于__proto__刚被列入标准,所以建议在现阶段避免直接在业务中使用

warning

三、new一个Object的实例

使用Object创建对象的有两种情况

  1. Object作为构造函数,通过new关键字调用,Object([value]),value为可选,根据参数的类型,可以分为三种情况:

    • 没有传参或者为null、undefined,返回一个空的普通对象
    • 如果是对象,则会直接返回这个对象
    • 如果是其他类型,则会通过相应类型的构造函数去创建相对应的类型实例,并返回
    new Object(undefined) // {}
    let o = new Object(1) // Number{1}
    o instanceof Number // true
    Number.prototype.isPrototypeOf(o) // true
    o instanceof Object // true
    复制代码
  2. 如果不通过new关键字调用,表现跟以构造函数方式调用是一致的

四、new 创建自定义构造函数的实例

通过自定义构造函数创建实例的的好处比较明显,可以封装通用的方法和属性,以创建同一类型的不同实例对象

参考:

猜你喜欢

转载自juejin.im/post/5b4870776fb9a04fe370a6d2