一般来讲,创建对象的方法有以下四种:
- 对象字面量的方式
- 通过Object.create()
- 通过new关键字创建一个Object构造函数的实例对象
- 通过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的错误 -
properties_object:可选,与传给Object.defineProperties()方法的参数一样,通过配置属性的特性来给被创建的对象添加属性,可配置的属性特性是
value
、writable
、enumerable
、configurable
以及get
、set
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()的效果
从图中可以看出如果传的非对象,则对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__
刚被列入标准,所以建议在现阶段避免直接在业务中使用
三、new一个Object的实例
使用Object创建对象的有两种情况
-
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 复制代码
-
如果不通过new关键字调用,表现跟以构造函数方式调用是一致的
四、new 创建自定义构造函数的实例
通过自定义构造函数创建实例的的好处比较明显,可以封装通用的方法和属性,以创建同一类型的不同实例对象
参考:
- JavaScript instanceof 运算符深入剖析
- javascript高级程序设计第三版