JavaScript巧用Object的get和set方法实现js变量的动态监听

本文介绍如何利用JavaScript的Object.get和Object.set方法实现变量的动态监听,包括基本原理、示例代码及其实现页面数据共享的方法。

javascript 面向对象系列更新:
Javascript面向对象编程之工厂模式、构造函数和ES6的class类
JavaScript 面向对象之Object的getter和setter的使用
更多不定期更新中

JavaScript巧用Object的get和set方法实现js变量的动态监听

咳咳,现在开始打起精神了哈!

  • 话说怎么突然想起写这个了呢?事情是这样的,且听小编给你慢慢道来
  • 前段时间VUE写的有些累了,想换换脑子,就开始学一个js写后端的程序,没有demo,自己瞎玩,玩着玩着就到了从浏览器访问静态页面的环节,一切顺利
  • 但是,我这页面有个按钮:<button onclick="plus()">+</button>,可以实现对下面的数字做+1操作,这一个“+”就导致我的一个变量发生了改变!变成什么了呢?我想实时都知道,用惯了vue的我就想啊,js有没有vue.$watch那样的数据监听呢?

有事找度娘!走一波

  1. switch死循环监听:目的达到了,但性能完全没有了,pass掉
    function applyA(val){
        a = val;
        switch(val){
            case 1: test1(); break;
            case 2: test2(); break;
        }
    }
  1. jQ的fire函数,一开始都没打算学jQ的我,还是放弃吧(也是用的switch+回调)
  2. setInterval 定时去检查该变量的值是否被改变(比死循环好点,但也好不到哪去,pass掉)
  3. 无限多的水贴水回复,简直无力吐槽-_-!,谷歌被墙,还是靠自己吧

第一步:看W3C中JavaScript那一块有没有提到

  • 大多都是比较容易理解的,js各个Dom对象,BoM对象都是几句带过。。。

第二部:查JavaScript标准

  • ES5基本都很熟了,貌似没有吧,直奔ES6
  • 看到对象的get和set方法时,突然想起来Vuex中的getter和setter就是可控的改变和读取变量,果断认真看起来
  • 将需要监听的值放在一个对象中,通过get方法调用,通过set方法改变值,安全又可靠!!!
  • 有戏… …

初步实践

谷歌控制台测试get和set方法,一切顺利

    var c = {
        w: 111,
        get ws(){
            console.log('取值',this.w);
            return this.w;
        },
        set ws(val){
            console.log('存值', this.w , 'val' + val);
            this.w = val;
            console.log('存过后的w',this.w);
        }
    }
    console.log(c)
    // {w: 111}
    // w: 111
    // ws: 111
    // get ws: ƒ ws()
    // set ws: ƒ ws(val)
    // __proto__: Object
    console.log('原生值:',c.w);
    // 111
    console.log('get方法取值:',c.ws);
    // 取值 111
    // 111
    console.log('set方法存值:',c.ws = 222);
    // 存值 111 val222
    // 存过后的w 222
    // 222
    console.log('验证存取:',c.w,c.ws);
    // 取值 222
    // 222 222

用到我的demo中:

    # index.html
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <title>index</title>
        <link rel="stylesheet" href="./css/d02.css">
    </head>

    <body>
        <div>
            <h2>只能用index做首页吗???!!!</h2>
            <a href="./d02_index.html">链接到d02</a><br>
            <button onclick="plus()">&nbsp; + &nbsp;</button>
            <p id="p">0</p>
        </div>
    </body>
    <script type='text/javascript' src='./js/d02.js'></script>
    <script type='text/javascript'>
        function plus() {
            watchVal.val = ++watchVal.value;
            let screen = document.getElementById('p');
            screen.innerHTML = watchVal.value;
            console.log(watchVal.value)
        }
    </script>

    </html>

    # d02.js
    var watchVal = {
        value: 0,
        get val() {
            console.log('取值', this.value);
            return this.value;
        },
        set val(vals) {
            this.value = vals;
            console.log('存过后的值', this.value);
        }
    }

测试效果:

点击页面的 “+”,p标签显示数值+1,控制台打印:存过后的值 1
大功告成,现在你就可以在watchVal对象中的set方法里写任意你想要的监听回调函数了,简单又安全

最后,通过js变量监听实现页面数据共享

  • 目标:index页面和test页面各有一个“+”按钮和显示当前变量值的p标签,通过点击按钮使p标签的值改变,切换页面后,p的值还是最后的值,可继续增加
  • 思路:首先把p标签的值保存在一个对象变量中,通过对象的set和get方法来存储值得改变和读取新的值,监听每次值发生改变时,都往localStorage中存一次,页面初始化时,初始p标签的值为localStorage中存的值即可
  • 代码如下:(示例很简单,但我学到了东西,那么,聪明的你你学会了吗)
    // index.html
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <title>index</title>
        <link rel="stylesheet" href="./css/d02.css">
    </head>

    <body>
        <div>
            <h2>只能用index做首页吗???!!!</h2>
            <a href="./test.html">链接到test</a><br>
            <button onclick="plus()">&nbsp; + &nbsp;</button>
            <p id="p">0</p>
        </div>
    </body>
    <script type='text/javascript' src='./js/d02.js'></script>
    <script type='text/javascript'>
        window.onload = function () {
            let screen = document.getElementById('p');
            screen.innerHTML = watchVal.value;
        }
        function plus() {
            watchVal.val = ++watchVal.value;
            let screen = document.getElementById('p');
            screen.innerHTML = watchVal.value;
            console.log(watchVal.value)
            storage();
        }
    </script>
    </html>

// test.html

    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <title>test</title>
        <link rel="stylesheet" href="./css/d02.css">
    </head>

    <body>
        <div>
            <h2>这是test的内容</h2>
            <button onclick="plus()"> + </button>
            <p id="p">0</p>
        </div>
    </body>
    <script type='text/javascript' src='./js/d02.js'></script>
    <script type='text/javascript'>
        window.onload = function () {
            let screen = document.getElementById('p');
            screen.innerHTML = watchVal.value;
        }
        function plus() {
            watchVal.val = ++watchVal.value;
            let screen = document.getElementById('p');
            screen.innerHTML = watchVal.value;
            console.log(watchVal.value);
            storage();
        }
    </script>
    </html>

// d02.js

    var stor = window.localStorage;
    var item = stor.getItem('wv');

    // 监听value的动态变化
    var watchVal = {
        value: item > 0 ? item : 0,
        get val() {
            console.log('取值', this.value);
            return this.value;
        },
        set val(vals) {
            this.value = vals;
            console.log('存过后的值', this.value);
        }
    }

    // 存储到本地
    function storage() {
        let localStorage = window.localStorage;
        localStorage.setItem('wv',watchVal.value);
    }
<think>我们有一个明确的问题:在JavaScript的class类中,如何实现对某个变量有值时进行监听,并在值变化时执行对应的方法? 根据用户的问题,我们可以考虑以下几种方法: 1. 使用对象gettersetter(如引用[1]所提到的) 2. 使用Proxy代理(更现代的方式) 3. 使用第三方库(如Vue,但这里要求是纯JavaScript的class) 由于用户提到了“class类”,我们可以采用ES6的class语法,并在其中使用gettersetter来实现对特定属性的监听。 步骤: 1. 在class中,将需要监听的属性用一个内部属性(通常用下划线开头)存储。 2. 为该属性定义一个gettersetter。 3. 在setter中,我们可以在值被设置时执行我们想要的方法(回调)。 另外,引用[3]提到了Vue中的watch,但我们这里不使用Vue,而是原生JavaScript。 示例: 假设我们有一个类MyClass,其中有一个属性`count`,我们希望在`count`发生变化时执行一个方法(例如打印新值旧值)。 我们可以这样实现: ```javascript class MyClass { constructor() { this._count = 0; // 内部属性,存储实际值 } // 对外暴露的count属性,通过gettersetter get count() { return this._count; } set count(value) { const oldValue = this._count; this._count = value; // 当count变化时,执行回调 this.onCountChanged(oldValue, value); } // 定义一个方法,当count变化时执行 onCountChanged(oldValue, newValue) { console.log(`count changed from ${oldValue} to ${newValue}`); // 这里可以执行任意方法 } } // 使用 const myInstance = new MyClass(); myInstance.count = 10; // 输出: count changed from 0 to 10 ``` 但是,这种方式需要在setter中硬编码回调函数名(如`onCountChanged`),不够灵活。我们可以允许外部传入回调函数,或者使用事件机制。 改进:允许在实例化时传入回调,或者使用事件发布订阅模式。这里我们采用在实例化时传入回调的方式: ```javascript class MyClass { constructor(onChange) { this._count = 0; // 将回调函数保存 this.onChange = onChange; } get count() { return this._count; } set count(value) { const oldValue = this._count; this._count = value; // 如果有回调函数,则调用 if (this.onChange) { this.onChange(oldValue, value); } } } // 使用 const myInstance = new MyClass((oldValue, newValue) => { console.log(`count changed from ${oldValue} to ${newValue}`); }); myInstance.count = 5; // 输出: count changed from 0 to 5 ``` 另一种更通用的方式是使用Proxy代理整个对象,这样可以监听多个属性,且不需要为每个属性单独写getter/setter。但注意Proxy不能直接用于class实例,我们可以将class改造成返回Proxy的形式,或者将实例用Proxy包裹。 使用Proxy的示例: ```javascript class MyClass { constructor() { this.count = 0; } } // 创建代理 const createProxy = (instance, callback) => { return new Proxy(instance, { set(target, property, value) { const oldValue = target[property]; target[property] = value; // 调用回调,传入属性名、旧值新值 callback(property, oldValue, value); return true; // 表示设置成功 } }); }; const myInstance = new MyClass(); const proxiedInstance = createProxy(myInstance, (property, oldValue, newValue) => { console.log(`属性 ${property} 从 ${oldValue} 变为 ${newValue}`); }); proxiedInstance.count = 10; // 输出: 属性 count 从 0 变为 10 ``` 但是,Proxy不能直接定义在class内部,它是对实例的包装。如果我们希望class本身具有这种能力,可以在构造函数中返回Proxy: ```javascript class MyClass { constructor() { this.count = 0; // 返回一个代理,替代默认的实例 return new Proxy(this, { set(target, property, value) { const oldValue = target[property]; target[property] = value; console.log(`属性 ${property} 从 ${oldValue} 变为 ${newValue}`); // 这里可以调用某个回调,或者触发事件 return true; } }); } } const myInstance = new MyClass(); myInstance.count = 10; // 输出: 属性 count 从 0 变为 10 ``` 注意:在构造函数中返回Proxy会覆盖原本的this,这样实例就是Proxy代理后的对象。 根据问题,我们总结两种方法方法一:使用getter/setter,适合对特定属性进行监听方法二:使用Proxy,可以监听对象上任意属性的变化。 引用[1]中提到了使用Objectgetset方法,其实也就是getter/setter,但注意Object.defineProperty也可以实现,不过class中的getter/setter语法更简洁。 另外,引用[3]中Vue的监听方式在底层也是基于Object.defineProperty(Vue2)Proxy(Vue3)的。 因此,在class内部,我们可以根据需求选择这两种方式之一。 最后,我们给出一个使用getter/setter的完整示例(因为问题中指定了“某个变量”,通常我们明确知道属性名): </think>在 JavaScript 的 class 类中监听变量变化并执行对应方法,可以通过以下三种方式实现,它们都利用了 JavaScript 的特性来实现响应式更新: ### 1. 使用 Getter/Setter 方法(推荐) 在类内部定义带有自定义逻辑的 getter setter,当变量被访问或修改时自动触发回调函数: ```javascript class DataModel { constructor() { this._value = null; // 内部存储的实际值 this.callbacks = []; // 存储回调函数的数组 } // 定义 value 属性的 getter get value() { return this._value; } // 定义 value 属性的 setter set value(newVal) { const oldVal = this._value; this._value = newVal; // 值变化时执行所有回调 this.callbacks.forEach(callback => { callback(newVal, oldVal); }); } // 添加监听回调的方法 watch(callback) { this.callbacks.push(callback); } } // 使用示例 const model = new DataModel(); // 添加监听回调 model.watch((newVal, oldVal) => { console.log(`值从 ${oldVal} 变为 ${newVal}`); }); model.value = "Hello"; // 输出: 值从 null 变为 Hello model.value = "World"; // 输出: 值从 Hello 变为 World ``` **原理说明**: - 使用 `_value` 作为实际存储的私有变量 - 通过 `get value()` `set value()` 定义访问器属性 - 当设置值时会触发 setter 中的回调逻辑 - 使用 `watch()` 方法添加监听回调[^1] ### 2. 使用 Proxy 代理 创建一个代理对象包裹实际实例,拦截所有属性操作: ```javascript class DataModel { constructor() { this.value = null; } } // 创建带监听的工厂函数 function createObservable(instance) { const callbacks = new Map(); // 存储属性对应的回调函数 return new Proxy(instance, { set(target, property, value) { const oldValue = target[property]; target[property] = value; // 触发该属性的所有回调 if (callbacks.has(property)) { callbacks.get(property).forEach(cb => cb(value, oldValue)); } return true; }, // 添加监听方法 get(target, prop) { if (prop === 'watch') { return (property, callback) => { if (!callbacks.has(property)) { callbacks.set(property, []); } callbacks.get(property).push(callback); }; } return target[prop]; } }); } // 使用示例 const model = createObservable(new DataModel()); model.watch('value', (newVal, oldVal) => { console.log(`Proxy监听: ${oldVal} → ${newVal}`); }); model.value = "Proxy"; // 输出: Proxy监听: null → Proxy ``` **优势**: - 无需修改原始类结构 - 可以监听任意属性变化 - 更细粒度的控制(按属性监听) ### 3. 使用 EventEmitter 模式 实现自定义事件系统,通过发布订阅模式通知变化: ```javascript class ObservableClass { constructor() { this.value = null; this.listeners = {}; } // 触发事件 emit(event, ...args) { if (this.listeners[event]) { this.listeners[event].forEach(cb => cb(...args)); } } // 添加监听 on(event, callback) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); } // 设置值的方法 setValue(newVal) { const oldVal = this.value; this.value = newVal; this.emit('valueChange', newVal, oldVal); } } // 使用示例 const instance = new ObservableClass(); instance.on('valueChange', (newVal, oldVal) => { console.log(`事件触发: ${oldVal} → ${newVal}`); }); instance.setValue("Event"); // 输出: 事件触发: null → Event ``` **适用场景**: - 需要监听多个不同事件时 - 复杂状态管理场景 - 需要实现自定义事件系统时 ### 总结对比 | 方法 | 优点 | 缺点 | 适用场景 | |----------------|-------------------------------|-------------------------------|----------------------| | **Getter/Setter** | 原生支持,简单直接 | 需为每个属性单独实现 | 单个关键属性监听 | | **Proxy** | 无需修改类,监听任意属性 | 兼容性要求(IE不支持) | 需要灵活监听的场景 | | **EventEmitter** | 支持多事件,结构清晰 | 需要手动触发事件 | 复杂状态管理 | 实际项目中,对于简单场景推荐使用 Getter/Setter 方法,对于需要监听多个属性或多个实例的情况,Proxy 是更灵活的选择[^1]。Vue 等框架底层也使用了类似 Proxy 的机制实现响应式更新[^3][^4]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值