Object.create()

Object.create()

Object.create()E5 的一个新特性,其实可以理解为继承一个对象,官方的定义为 Object.create() 方法可以创建一个拥有指定原型和若干个指定属性的对象

它的基本语法为

1
Object.create(proto, [ propertiesObject ])

参数

  • proto,一个对象,作为新创建对象的原型
  • propertiesObject,可选,该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符

需要注意的是,该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的

下面我们就来看看如何使用 Object.create() 当中的 propertyObject 参数

参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建一个原型为 null 的空对象
var o = Object.create(null)

// 以字面量方式创建的空对象就相当于
var o = Object.create(Object.prototype, {

// foo 会成为所创建对象的数据属性
foo: { writable: true, configurable: true, value: 'hello' },

// bar 会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function () { return 10 },
set: function (value) { console.log(value) }
}
})

另外一个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Constructor() { }

o = new Constructor()

// 上面的一句就相当于 ==>

// 当然,如果在 Constructor 函数中有一些初始化代码,Object.create 不能执行那些代码
o = Object.create(Constructor.prototype)

// 创建一个以另一个空对象为原型,且拥有一个属性 p 的对象
o = Object.create({}, { p: { value: 42 } })

// 省略了的属性特性默认为 false,所以属性 p 是不可写,不可枚举,不可配置的
o.p = 24
o.p
// 42

o.q = 12
for (var prop in o) {
console.log(prop)
}
// 'q'

delete o.p
// false

// 创建一个可写的,可枚举的,可配置的属性 p
o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } })

不过有几个需要注意的地方,第一种情况

1
2
3
4
5
var a = { x: 1 }
var b = Object.create(a)

console.log(b) // 输出 { }
console.log(b.__proto__) // 输出 { x: 1 }

第二种情况,注意区分 __proto__prototype

1
2
3
4
5
// 如果用的是 
b = new object(a)

connsole.log(b) // 输出 { x: 1 }
congsole.log(b.__proto__) // 输出 { }

实例

一般来说,Object.create() 的使用还是在继承当中使用较多,下面我们就来看一个使用它来实现类式继承的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Foo(name) {
this.name = name
}

Foo.prototype.myName = function () {
return this.name
}

function Bar(name, label) {
Foo.call(this, name)
this.label = label
}

// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
Bar.prototype = Object.create(Foo.prototype)

// 现在没有 Bar.prototype.constructor 了,如果你需要这个属性的话可能需要手动修复一下它
// Bar.prototype.constructor = Bar

Bar.prototype.myLabel = function () {
return this.label
}

var a = new Bar('a', 'obj a')

a.myName() // 'a'
a.myLabel() // 'obj a'

这段代码的核心部分就是语句 Bar.prototype = Object.create(Foo.prototype),调用 Object.create() 会凭空创建一个新对象并把新对象内部的 [[Prototype]] 关联到你指定的对象(本例中是 Foo.prototype),换句话说,这条语句的意思是,创建一个新的 Bar.prototype 对象并把它关联到 Foo.prototype

声明函数 Bar() 的时候,和其他函数一样,Bar 会有一个 .prototype 关联到默认的对象,但是这个对象并不是我们想要的 Foo.prototype,因此我们创建了一个新对象并把它关联到我们希望的对象上,直接把原始的关联对象抛弃掉,注意下面这两种方式是常见的错误做法,实际上它们都存在一些问题

1
2
3
4
5
// 和你想要的机制不一样
Bar.prototype = Foo.prototype

// 基本上满足你的需求,但是可能会产生一些副作用
Bar.prototype = new Foo()

第一种情况的 Bar.prototype = Foo.prototype 并不会创建一个关联到 Bar.prototype 的新对象,它只是让 Bar.prototype 直接引用 Foo.prototype 对象,因此当你执行类似 Bar.prototype.myLabel = ... 的赋值语句时会直接修改 Foo.prototype 对象本身

而第二种情况的 Bar.prototype = new Foo() 的确会创建一个关联到 Bar.prototype 的新对象,但是它使用了 new 操作符来进行调用,如果函数 Foo 有一些副作用(比如写日志、修改状态、注册到其他对象、给 this 添加数据属性,等等)的话,就会影响到 Bar() 的后代

因此,要创建一个合适的关联对象,我们必须使用 Object.create() 而不是使用具有副作用的 new 操作,这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象

ES6 中的 Object.setPrototypeOf()

如果能有一个标准并且可靠的方法来修改对象的 [[Prototype]] 关联就好了,在 ES6 之前,我们只能通过设置 .__proto__ 属性来实现,但是这个方法并不是标准并且无法兼容所有浏览器,ES6 添加了辅助函数 Object.setPrototypeOf(),可以用标准并且可靠的方法来修改关联,我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法

1
2
3
4
5
// ES6 之前需要抛弃默认的 Bar.prototype
Bar.ptototype = Object.create(Foo.prototype)

// ES6 开始可以直接修改现有的 Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype)

如果忽略掉 Object.create() 方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收),它实际上比 ES6 及其之后的方法更短而且可读性更高,不过无论如何,这是两种完全不同的语法

最后我们再来看一种特殊的使用方式,它就是 Object.create(null)

Object.create(null)

通过 Object.create(null) 创建出来的对象,就没有 Object.prototype 上的一些方法(需要注意与 {} 进行区分),我们可以在控制台进行简单的测试

1
2
3
{}                   // {} __proto__: Object

Object.create(null) // {} No properties

所以并不是所有的对象都继承有 Object.prototype 上的一些方法,看下面代码

1
2
3
4
5
var obj = Object.create(null)

obj.__proto__ // undefined

obj.toString() // obj.toString is not a function
  • Object.create(null) 会创建一个拥有空(或者说 null[[Prototype]] 链接的对象,这个对象无法进行委托
  • 由于这个对象没有原型链,所以 instanceof 操作符无法进行判断,因此总是会返回 false

这些特殊的空 [[Prototype]] 对象通常被称作字典,它们完全不会受到原型链的干扰,因此非常适合用来存储数据,另外一个需要注意的地方,那就是并不是所有的函数对象都会有 prototype 属性,代码如下

1
2
3
4
5
6
7
8
9
function abc() {}

abc.prototype // constructor: ƒ abc()

var binded = abc.bind(null)

typeof binded // 'function'

binded.prototype // undefined ==> 需要注意,binded 没有 prototype 属性

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×