Thinking--JavaScript延迟加载属性数据(性能提升)

Posted 奋飛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thinking--JavaScript延迟加载属性数据(性能提升)相关的知识,希望对你有一定的参考价值。

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 commputed

「延迟加载、缓存结果」这个和 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 可以同样实现延迟加载,并对结果做相应的缓存处理。

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

以上是关于Thinking--JavaScript延迟加载属性数据(性能提升)的主要内容,如果未能解决你的问题,请参考以下文章

EF的延迟加载LazyLoad

关于延迟加载(lazy)和强制加载(Hibernate.initialize(Object proxy) )

Thinking--javascript 多类目创建(npm源码解读)

Thinking--javascript 多类目创建(npm源码解读)

Thinking--javascript 多类目创建(npm源码解读)

Thinking--javascript 多类目创建(npm源码解读)