Thinking系列,旨在利用10分钟的时间传达一种可落地的编程思想。
Vue 遍历对象所有的 property,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter。getter/setter 对用户来说是不可见的,但是在内部 Vue 能够追踪依赖,在 property 被访问和修改时通知变更,依此做到了数据的响应式。
vue 通过 Object.defineProperty
的实现思路,值得我们思考。下面提到的属性延迟加载就是其中一个引发点。
常规写法示例
async function getData() {
let data = await fetch(new Request('./data.json')).then(res => res.json())
return data
}
let res = { data: [] }
res.data = await getData()
// res.data 用于页面渲染
render(res.data)
上述写法,在 javascript 编码中可能出现比较少,但在 vue 等开发中,却会经常看到类似的写法:
<script>
data () {
return {
res: {data: []}
}
},
methods: {
async function getData() {
this.res.data = await fetch(new Request('./data.json')).then(res => res.json())
}
},
created () {
this.getData()
}
script>
res.data
在页面渲染时,直接首屏呈现,上述写法没有问题;res.data
是通过某些操作触发才呈现,那上述写法需要优化。关于第2种假设,优化点在于**「延迟加载」**。你有可能会说,可以监听触发动作(如click,scroll),然后在相应事件中触发。
document.querySelector('#btn').addEventListener('click', async () => render(await getData()))
上述处理没有问题,但这里想要提到的是属性自动触发的方式 – 借助 Object.defineProperty
。
let res = {
get data() {
return fetch(new Request('./data.json')).then(res => res.json())
}
}
// 注意上述是异步
await res.data
这样可以做到在调用 res.data
时,才会执行相关获取数据操作。
延迟加载(将计算推迟到第一次读取属性时),然后缓存结果以供后续使用。避免重复执行相同的工作是提高性能的最佳方式之一,直接利用缓存结果可以加快运行速度。
let res = {
get data() {
let _data = fetch(new Request('./data.json')).then(res => res.json())
Object.defineProperty(this, 'data', {
value: _data,
writable: false,
configurable: false,
enumerable: false
})
return _data
}
}
console.log(object.hasOwnProperty("data")) // true
const data = await object.data
console.log(object.hasOwnProperty("data")) // true
「延迟加载、缓存结果」这个和 vue computed 实现的效果一样!计算属性是通过 getter 函数延迟加载,基于它们的响应式依赖进行缓存的。
Vue 中对于 computed 实现也是借助 defineProperty - https://github1s.com/vuejs/vue/blob/2.6/dist/vue.js#L3580-L3581
function defineComputed (
target,
key,
userDef
) {
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
class 可以同样实现延迟加载,并对结果做相应的缓存处理。
class MyClass {
constructor() {
Object.defineProperty(this, "data", {
get() {
// 开销大的操作(如数据请求)
const actualData = someExpensiveComputation()
Object.defineProperty(this, "data", {
value: actualData,
writable: false,
configurable: false
})
return actualData
},
// ①
configurable: true,
enumerable: true
})
}
}
这里①将 data 设置成可配置尤为重要,因为我们需要对 data
再次调用 Object.defineProperty()
const obj = new MyClass();
console.log(obj.hasOwnProperty("data")) // true
const data = obj.data;
console.log(obj.hasOwnProperty("data")) // true