Object.prototype.hasOwnProperty的性能问题

前言

今天下午在阅读Vue3,Component相关源码时,发现了这么一段注释。(源码地址 91行:packages/runtime-core/src/componentProxy.ts

This getter gets called for every property access on the render context during render and is a major hotspot. The most expensive part of this is the multiple hasOwn() calls. It’s much faster to do a simple property access on a plain object, so we use an accessCache object (with null prototype) to memoize what access type a key corresponds to.

大致意思是如下:

在渲染期间,对渲染上下文进行访问时,hasOwn()操作很昂贵。而在普通对象上进行属性访问的操作,速度会快很多。所以在Vue3中使用accessCache对象,对对象进行缓存。

实验

那么Object.prototype.hasOwnProperty操作是有多昂贵,值得尤大进行优化。我使用下面的代码,进行了实验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

const obj = {
name: 'Natalie portman'
}
const accessCache = { ...obj }
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (val, key) => hasOwnProperty.call(val, key)

let start = new Date().getTime()
for (let i = 0; i < 5000000; i++) {
hasOwn(obj, 'name')
}
console.log(`duration: ${new Date().getTime() - start}`)

start = new Date().getTime()
for (let i = 0; i < 5000000; i++) {
accessCache.name
}

console.log(`duration: ${new Date().getTime() - start}`)


// duration: 35
// duration: 4

image

在进行500万次,读取属性操作时,两者的性能相差了大约9倍。

当Vue的组件树很大时,挂载的属性很多时。使用accessCache对象,对Object.prototype.hasOwnProperty操作进行缓存,应该还是很有必要的。

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 Test(){
const obj = {
name: 'Natalie portman'
}
const accessCache = { ...obj }


let start = new Date().getTime()
for (let i = 0; i < 5000000; i++) {
obj.hasOwnProperty('name')
}
console.log(`duration: ${new Date().getTime() - start}`)


start = new Date().getTime()
for (let i = 0; i < 5000000; i++) {
'name' in obj
}
console.log(`duration: ${new Date().getTime() - start}`)


start = new Date().getTime()
for (let i = 0; i < 5000000; i++) {
accessCache.name
}
console.log(`duration: ${new Date().getTime() - start}`)

}

image

原因

查询了相关资料,大致原因如下:

Chrome的V8引擎,会维护一个全局的缓存。一旦高速缓存被填充,它总是会在随后被重复命中。这意味着val[key]属性查找,将始终在编译时处理,而不会进行运行时。

而V8引擎不会对,hasOwnProperty或者key in value进行任何缓存,总是进入运行时。

参考资料