一文读懂JavaScript 中的延迟加载属性模式

传统上,开发人员在 JavaScript 类中为实例中可能需要的任何数据创建属性。对于在构造函数中随时可用的小块数据来说,这不是问题。但是,如果在实例中可用之前需要计算某些数据,您可能不想预先支付该费用。例如,考虑这个类:

class MyClass {
    constructor() {
        this.data = someExpensiveComputation();
    }
}

在这里,data属性是作为执行一些昂贵计算的结果而创建的。如果您不确定是否会使用该属性,则预先执行该计算可能效率不高。幸运的是,有几种方法可以将这些操作推迟到以后。

按需属性模式

优化执行昂贵操作的最简单方法是等到需要数据后再进行计算。例如,您可以使用带有 getter 的访问器属性来按需进行计算,如下所示:

class MyClass {
    get data() {
        return someExpensiveComputation();
    }
}

在这种情况下,直到有人第一次读取该data属性时,您的昂贵计算才会发生,这是一种改进。但是,每次data读取属性时都会执行相同的昂贵计算,这比之前的示例更糟糕,其中至少只执行了一次计算。这不是一个好的解决方案,但您可以在此基础上创建一个更好的解决方案。

凌乱的延迟加载属性模式

只有在访问属性时才执行计算是一个好的开始。您真正需要的是在该点之后缓存信息并仅使用缓存版本。但是您将这些信息缓存在哪里以便于访问?最简单的方法是定义一个具有相同名称的属性并将其值设置为计算数据,如下所示:

class MyClass {
    get data() {
        const actualData = someExpensiveComputation();

        Object.defineProperty(this, "data", {
            value: actualData,
            writable: false,
            configurable: false,
            enumerable: false
        });

        return actualData;
    }
}

在这里,该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;

实际上,所有计算仅在第一次data读取属性时完成。对该data属性的每次后续读取都返回缓存的版本。

这种模式的一个缺点是data属性开始是不可枚举的原型属性,最终是不可枚举的自己的属性:

const object = new MyClass();
console.log(object.hasOwnProperty("data"));     // false

const data = object.data;
console.log(object.hasOwnProperty("data"));     // true

虽然这种区别在很多情况下并不重要,但理解这种模式很重要,因为它在传递对象时可能会导致微妙的问题。幸运的是,使用更新的模式很容易解决这个问题。

类的唯一自己的延迟加载属性模式

如果您有一个用例,其中延迟加载的属性始终存在于实例中很重要,那么您可以使用Object.defineProperty()在类构造函数中创建属性。它比前面的例子有点混乱,但它会确保该属性只存在于实例上。下面是一个例子:

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使用Object.defineProperty().该属性是在实例上创建的(通过使用this)并定义一个 getter 并指定该属性为可枚举和可配置的(典型的自己的属性)。将data属性设置为可配置特别重要,以便您可以Object.defineProperty()再次调用它。

然后 getter 函数进行计算并再次调用Object.defineProperty()。该data属性现在被重新定义为具有特定值的数据属性,并且不可写和不可配置以保护最终数据。然后,计算数据从 getter 返回。下次data读取属性时,它将从存储的值中读取。作为奖励,该data财产现在仅作为自己的财产存在,并且在第一次阅读之前和之后的行为都相同:

const object = new MyClass();
console.log(object.hasOwnProperty("data"));     // true

const data = object.data;
console.log(object.hasOwnProperty("data"));     // true

对于类,这很可能是您要使用的模式;另一方面,对象文字可以使用更简单的方法。

对象字面量的延迟加载属性模式

如果您使用对象字面量而不是类,则过程要简单得多,因为在对象字面量上定义的 getter 被定义为可枚举的自身属性(而不是原型属性),就像数据属性一样。这意味着您可以对类使用凌乱的延迟加载属性模式而对于对象来说不会凌乱:

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

结论

在 JavaScript 中重新定义对象属性的能力提供了一个独特的机会来缓存可能计算成本很高的信息。通过从重新定义为数据属性的访问器属性开始,您可以将计算推迟到第一次读取属性时,然后缓存结果以供以后使用。这种方法既适用于类,也适用于对象字面量,并且在对象字面量中更简单一些,因为您不必担心您的 getter 会在原型上结束。

提高性能的最佳方法之一是避免重复执行相同的工作,因此任何时候您可以缓存结果以供以后使用,都可以加快程序的运行速度。延迟加载属性模式等技术允许任何属性成为缓存层以提高性能。

以上就是JavaScript 中的延迟加载属性模式的详细内容,更多关于js延迟加载属性的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS实现非首屏图片延迟加载的示例

    目标 减少资源加载可以明显的优化页面加载的速度,所以可以减少页面载入时立即下载的图片的数量,以提高页面加载速度,其他的图片在需要的时候再进行加载. 思路 想要实现以上的目标,有几个地方需要思考. 1.如何判断哪些图片需要立即加载,哪些可以晚些再加载? 2.如何控制图片在指定的时候加载? 对于第一个问题,页面打开就会被用户看到的图片肯定需要立即加载,其他的可以延后.即在视窗中的图片需要立即加载.那么如何判断图片是否在视窗内呢?getBoundingClientRect可以返回元素的大小及其相对于视

  • js实现多张图片延迟加载效果

    本文实例为大家分享了js实现多张图片延迟加载效果的具体代码,供大家参考,具体内容如下 具体代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <!--做移动端响应式必须加的样式--> <meta content="width=device-width, initial-scale=1.0, maximum-

  • js实现延迟加载的几种方法

    js的延迟加载有助与提高页面的加载速度,以下是延迟加载的几种方法: 1.使用setTimeout延迟方法的加载时间 延迟加载js代码,给网页加载留出更多时间 <script type="text/javascript" > function A(){ $.post("/lord/login",{name:username,pwd:password},function(){ alert("Hello"); }); } $(functio

  • js实现延迟加载的几种方法详解

    这是一个面试经常问到的问题:js的延迟加载方法 (js的延迟加载有助于提高页面的加载速度) 主要考察对程序的性能方面是否有研究,程序的性能是一个项目不断地追求的,通常也是项目完成后需要长期做的一件事情,像腾讯QQ依然对程序的性能不断地做优化,让用户的体验更好,性能优化的核心思想就是快,可以预先准备数据(如缓存的使用),可以按需获取,可以分段获取等都是常见的优化手段. 解题思路 : 1.defer属性 <script src="file.js" defer> </scr

  • JS同步、异步、延迟加载的方法

    本文讲述了JS同步.异步.延迟加载的方法.分享给大家供大家参考,具体如下: 一:同步加载 我们平时使用的最多的一种方式. <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作.所以默认同步执行才是安全的.但这样如果js中有输出document内容.修改dom.重定向等行为,就会造成页面堵塞.所以一般建议

  • 一文读懂JavaScript 中的延迟加载属性模式

    传统上,开发人员在 JavaScript 类中为实例中可能需要的任何数据创建属性.对于在构造函数中随时可用的小块数据来说,这不是问题.但是,如果在实例中可用之前需要计算某些数据,您可能不想预先支付该费用.例如,考虑这个类: class MyClass { constructor() { this.data = someExpensiveComputation(); } } 在这里,data属性是作为执行一些昂贵计算的结果而创建的.如果您不确定是否会使用该属性,则预先执行该计算可能效率不高.幸运的

  • 浅谈JavaScript 中的延迟加载属性模式

    目录 一.前言 二.按需属性模式 三.凌乱的延迟加载属性模式 四.类的唯一自己的延迟加载属性模式 五.对象字面量的延迟加载属性模式 六.结论 一.前言 传统上,开发人员在 JavaScript 类中为实例中可能需要的任何数据创建属性.对于在构造函数中随时可用的小块数据来说,这不是问题.但是,如果在实例中可用之前需要计算某些数据,您可能不想预先支付该费用.例如,考虑这个类: class MyClass { constructor() { this.data = someExpensiveCompu

  • 一文搞懂JavaScript中的内存泄露

    目录 什么是内存泄漏 怎么检测内存泄漏 Performance Memory 内存泄漏的场景 垃圾回收算法 引用计数 循环引用 标记清除 闭包是内存泄漏吗 总结 以前我们说的内存泄漏,通常发生在后端,但是不代表前端就不会有内存泄漏.特别是当前端项目变得越来越复杂后,前端也逐渐称为内存泄漏的高发区.本文就带你认识一下Javascript的内存泄漏. 什么是内存泄漏 什么是内存?内存其实就是程序在运行时,系统为其分配的一块存储空间.每一块内存都有对应的生命周期: 内存分配:在声明变量.函数时,系统分

  • 一文读懂ava中的Volatile关键字使用

    在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是易挥发的,不稳定的.那么在java中使用是什么意思呢? 我们知道,在java中,每个线程都会有个自己的内存空间,我们称之为working memory.这个空间会缓存一些变量的信息,从而提升程序的性能.当执行完某个操作之后,thread会将更新后的变量更新到主缓存中,以供其他线程读写. 因为变量存在working memory和main memory两个地方,那么就有可能出现不一致的情况. 那么我们就可以使

  • 一文读懂JAVA中HttpURLConnection的用法

    针对JDK中的URLConnection连接Servlet的问题,网上有虽然有所涉及,但是只是说明了某一个或几个问题,是以FAQ的方式来解决的,而且比较零散,现在对这个类的使用就本人在项目中的使用经验做如下总结: 1:> URL请求的类别: 分为二类,GET与POST请求.二者的区别在于: a:) get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet, b:) post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内. 2:>

  • 一文搞懂JavaScript中bind,apply,call的实现

    目录 bind.call和apply的用法 bind call&apply 实现bind 实现call和apply 总结 bind.call和apply都是Function原型链上面的方法,因此不管是使用function声明的函数,还是箭头函数都可以直接调用.这三个函数在使用时都可以改变this指向,本文就带你看看如何实现bind.call和apply. bind.call和apply的用法 bind bind()方法可以被函数对象调用,并返回一个新创建的函数. 语法: function.bin

  • 一文搞懂JavaScript中原型与原型链

    目录 1.构造函数原型prototype 2.对象原型__proto__ 3.constructor构造函数 4.原型链 5.原型对象中的this指向 6.扩展内置对象(原型对象的应用) 在ES6之前,我们面向对象是通过构造函数实现的.我们把对象的公共属性和方法放在构造函数里 像这样: function student(uname,age) { this.uname = uname; this.age = age; this.school = function() { console.log('

  • 一文读懂ES7中的javascript修饰器

    什么是修饰器 修饰器(Decorator)是ES7的一个提案,它的出现能解决两个问题: 不同类间共享方法 编译期对类和方法的行为进行改变 用法也很简单,就是在类或方法的上面加一个@符,在vue in typescript中经常用到 以上的两个用处可能不太明白,没关系,我们开始第一个例子 例子1:修饰类 @setProp class User {} function setProp(target) { target.age = 30 } console.log(User.age) 这个例子要表达的

  • 一文读懂go中semaphore(信号量)源码

    运行时信号量机制 semaphore 前言 最近在看源码,发现好多地方用到了这个semaphore. 本文是在go version go1.13.15 darwin/amd64上进行的 作用是什么 下面是官方的描述 // Semaphore implementation exposed to Go. // Intended use is provide a sleep and wakeup // primitive that can be used in the contended case /

  • 一文读懂C++中的继承之菱形继承(案例分析)

    前言 我们上一篇说了世间万物都有一个继承体制,或多或少子类继承了父类的某些特征,但大多都是单向继承,但是就有些特例他就是多继承,比如: 我们从图片中就可以看到,两栖动物它既继承了水生动物的一部分特性,也继承了陆地动物的一些特性,那么我们的代码,会不会也会有这种多继承现象呢,我们一起来看一下. 提示:以下是本篇文章正文内容,下面案例可供参考 一.什么是多继承? 1.单继承 我们来看一个图先了解一下单继承,再看有什莫区别 也就是说,一个子类只有一个直接父类时称这个继承关系为单继承 2.多继承 我们把

随机推荐