JavaScript中延迟加载属性模式
共 9358字,需浏览 19分钟
·
2021-04-28 11:56
(给前端大学加星标,提升前端技能.)
译者:飘飘 作者:Nicholas C. Zakas
https://humanwhocodes.com/blog/2021/04/lazy-loading-property-pattern-javascript/
You can defer computationally-expensive operations until needed using an accessor property.
你可以使用访问器属性将计算量大的操作推迟到需要时进行。
Traditionally, developers have created properties inside of JavaScript classes for any data that might be needed within an instance. This isn’t a problem for small pieces of data that are readily available inside of the constructor. However, if some data needs to be calculated before becoming available in the instance, you may not want to pay that cost upfront. For example, consider this class:
传统意义上,开发人员在JavaScript类内部为实例可能需要的任何数据创建属性。对于在构造函数内部随时可用的小数据而言,这不是问题。但是,如果在实例可用之前需要计算一些数据,你可能不希望预先支付这个成本。例如,考虑这个类。
class MyClass {
constructor() {
this.data = someExpensiveComputation();
}
}
Here, the data property is created as the result of performing some expensive computation. It may not be efficient to perform that calculation upfront if you aren’t sure the property will be used. Fortunately, there are several ways to defer these operations until later.
这里,由于执行一些昂贵的计算而创建了数据属性。如果你不确定该属性会被使用,则提前执行该计算可能并不高效。幸运的是,有几种方法可以将这些操作推迟到以后。
按需属性模式 The on-demand property pattern
The easiest way to optimize performing an expensive operation is to wait until the data is needed before doing the computation. For example, you could use an accessor property with a getter to do the computation on demand, like this:
最优化执行昂贵操作的最简单方法是,等到需要数据时再进行计算。例如,你可以使用一个带有getter的accessor属性来按需进行计算,如下所示。
class MyClass {
get data() {
return someExpensiveComputation();
}
}
In this case, your expensive computation isn’t happening until the first time someone reads the data property, which is an improvement. However, that same expensive computation is performed every time the data property is read, which is worse than previous example where at least the computation was performed just once. This isn’t a good solution, but you can build upon it to create a better one.
在这种情况下,只有当有人第一次读取data属性时,昂贵的计算才会发生,这是一个改进。但是,每次读取data属性时都执行相同昂贵的计算,这比之前的例子(其中该计算至少执行一次)的效果差。这不是一个好的解决方案,但你可以在此基础上创建一个更好的解决方案。
混乱的延迟加载属性模式 The messy lazy-loading property pattern
Only performing the computation when the property is accessed is a good start. What you really need is to cache the information after that point and just use the cached version. But where do you cache that information for easy access? The easiest approach is to define a property with the same name and set its value to the computed data, like this:
只在在访问该属性时才执行计算是一个好的开始。你真正需要的是在那之后对信息进行缓存,然后仅使用缓存的版本。但是你将信息缓存在哪里方便访问呢?最简单的方法是定义一个同名的属性,并将其值设置为计算的数据,像这样。
class MyClass {
get data() {
const actualData = someExpensiveComputation();
Object.defineProperty(this, "data", {
value: actualData,
writable: false,
configurable: false,
enumerable: false
});
return actualData;
}
}
Here, the data property is once again defined as a getter on the class, but this time it caches the result. The call to Object.defineProperty() creates a new property called data that has a fixed value of actualData, and is set to not be writable, configurable, and enumerable (to match the getter). After that, the value itself is returned. The next time the data property is accessed, it will be reading from the newly created property rather than calling the getter:
在这里,data属性再次被定义为类的getter,但这一次它缓存了结果。对Object.defineProperty()的调用会创建了一个名为data的新属性,该属性的固定值为actualData,并设置为不可写、可配置和可枚举(以匹配getter)之后,将返回值本身。下次访问data属性时,它将从新创建的属性读取,而不是调用getter。
const object = new MyClass();
// calls the getter
const data1 = object.data;
// reads from the data property
const data2 = object.data;
Effectively, all of the computation is done only the first time the data property is read. Each subsequent read of the data property is returning the cached the version.
实际上,所有的计算都是在第一次读取数据属性时才完成的。之后每次读取数据属性都会返回缓存的版本。
The one downside to this pattern is that the data property starts out as a non-enumerable prototype property and ends up as a non-enumerable own property:
这种模式的一个缺点是,data属性开始时是不可枚举的原型属性,最后是一个不可枚举的自有属性。
const object = new MyClass();
console.log(object.hasOwnProperty("data")); // false
const data = object.data;
console.log(object.hasOwnProperty("data")); // true
While this distinction isn’t important in many cases, it is an important thing to understand about this pattern as it can cause subtle issues when the object is passed around. Fortunately, it’s easy to address this with an updated pattern.
虽然这种区别在很多情况下并不重要,但了解这种模式很重要,因为当对象传递时,这种模式可能会引起细微的问题。幸运的是,用一个更新的模式可以轻松解决此问题。
类的唯一的延迟加载属性模式 The only-own lazy-loading property pattern for classes
If you have a use case where it’s important for the lazy-loaded property to always exist on the instance, then you can using Object.defineProperty() to create the property inside of the class constructor. It’s a little bit messier than the previous example, but it will ensure that the property only ever exists on the instance. Here’s an example:
如果你有一个实例,对于该实例始终存在延迟加载的属性很重要,那么你可以使用Object.defineProperty()在类的构造函数内部创建该属性。这比前面的例子要混乱一些,但它能确保该属性只存在于实例上。下面是一个例子。
class MyClass {
constructor() {
const instance = this;
Object.defineProperty(this, "data", {
get() {
const actualData = someExpensiveComputation();
Object.defineProperty(instance, "data", {
value: actualData,
writable: false,
configurable: false
});
return actualData;
},
configurable: true,
enumerable: true
});
}
}
Here, the constructor creates the data accessor property using Object.defineProperty(). The property is created on the instance (by using this) and defines a getter as well as specifying the property to be enumerable and configurable (typical of own properties). It’s particularly important to set the data property as configurable so you can call Object.defineProperty() on it again.
在这里,构造函数使用Object.defineProperty()创建数据访问器属性。该属性是在实例上创建的(通过使用此属性),并定义了一个getter,并指定该属性为可枚举和可配置(典型的自有属性)。将data属性设置为可配置尤其重要的,这样你就可以再次调用Object.defineProperty()了。
The getter function then does the computation and calls Object.defineProperty() a second time. The data property is now redefined as a data property with a specific value and is made non-writable and non-configurable to protect the final data. Then, the computed data is returned from the getter. The next time the data property is read, it will read from the stored value. As a bonus, the data property now only ever exists as an own property and acts the same both before and after the first read:
然后getter函数进行计算并再次调用Object.defineProperty()。注意,第一个参数是instance,因为它在getter函数内部有着不同的含义--它引用申明了getter函数的对象,而不是MyClass的实例。现在将data属性重新定义为具有特定值的data属性,并且将其变成不可写和不可配置的,以保护最终数据。然后,计算好的数据从getter返回。下次读取数据属性时,它将从存储的值中读取。另外,data属性现在只作为一个自己的属性存在,在第一次读取之前和之后的行为都是一样的。
const object = new MyClass();
console.log(object.hasOwnProperty("data")); // true
const data = object.data;
console.log(object.hasOwnProperty("data")); // true
For classes, this is most likely the pattern you want to use; object literals, on the other hand, can use a simpler approach.
对于类来说,这很可能是你想使用的模式;另一方面,对象字面量可以使用更简单的方法。
对象字面量的延迟加载属性模式 The lazy-loading property pattern for object literals
If you are using an object literal instead of a class, the process is must simpler because getters defined on object literals are defined as enumerable own properties (not prototype properties) just like data properties. That means you can use the messy lazy-loading property pattern for classes without being messy:
如果你使用的是一个对象字面量,而不是一个类,那么这个过程肯定更简单,因为在对象字面量上定义的getters与data属性一样被定义为可枚举的自有属性(而不是原型属性)。这意味着你可以对类使用混乱的延迟加载属性模式,而不会造成混乱。
const object = {
get data() {
const actualData = someExpensiveComputation();
Object.defineProperty(this, "data", {
value: actualData,
writable: false,
configurable: false,
enumerable: false
});
return actualData;
}
};
console.log(object.hasOwnProperty("data")); // true
const data = object.data;
console.log(object.hasOwnProperty("data")); // true
结束语 Conclusion
The ability to redefine object properties in JavaScript allows a unique opportunity to cache information that may be expensive to compute. By starting out with an accessor property that is redefined as a data property, you can defer computation until the first time a property is read and then cache the result for later use. This approach works both for classes and for object literals, and is a bit simpler in object literals because you don’t have to worry about your getter ending up on the prototype.
在JavaScript中重新定义对象属性的能力为缓存信息(计算起来可能会很贵)提供了独特的机会。通过从重新定义为data属性的访问器属性开始,你可以将计算推迟到第一次读取该属性时,然后将结果缓存起来以备后用。这种方法既适用于类,也适用于对象字面量,而且在对象字面量中更简单一些,因为你不必担心你的getter最终会出现在原型上。
One of the best ways to improve performance is to avoid doing the same work twice, so any time you can cache a result for use later, you’ll speed up your program. Techniques like the lazy-loading property pattern allow any property to become a caching layer to improve performance.
改善性能的最好方法之一是避免两次做同样的工作,所以任何时候你可以缓存结果供以后使用,就可以加快程序运行速度。诸如延迟加载属性模式之类的技术使任何属性都可以成为缓存层以提高性能。
点赞和在看就是最大的支持❤️