Libx

如何监听js中变量的变化

字数统计: 1,529阅读时长: 6 min
2017/08/30 Share

原生JS监听变量的变化

今天在刷知乎的时候遇到了这样的一个问题:如何侦听JS中变量的变化首先想到的是在Vue中的数据绑定,但是上车时间不长,是直接拿来用的。。并不知道实现原理,想了又想,没有好的解决方案。看到有大佬的答案后依然很迷,遂google一番,总结一下。

几种解决方案:

ES5的getter与setter

主要使用的是:Object.defineProperty(obj, prop, descriptor)

参数

  • obj 需要被操作的目标对象
  • prop 目标对象需要定义或修改的属性的名称。
  • descriptor 将被定义或修改的属性的描述符。

返回值 :

  • 被传递给函数的对象

以下是MDN的相关定义:

该方法允许精确添加或修改对象的属性。一般情况下,我们为对象添加属性是通过赋值来创建并显示在属性枚举中(for…in 或 Object.keys 方法), 但这种方式添加的属性值可以被改变,也可以被删除。而使用 Object.defineProperty() 则允许改变这些额外细节的默认设置。例如,默认情况下,使用 Object.defineProperty() 增加的属性值是不可改变的。

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。描述符必须是两种形式之一;不能同时是两者。

数据描述符和存取描述符均具有以下可选键值:

  • configurable
    当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
  • enumerable
    当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。

数据描述符同时具有以下可选键值:

value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writable
当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。

存取描述符同时具有以下可选键值:

  • get:
    一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。
  • set
    一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。

定义看起来感觉有点复杂,而如果深入的讨论的话更加复杂,完全当手册用了,我们在此只关注基本的setter和getter

下面的例子说明了如何实现自我存档的对象。当 temperature 属性设置时,archive 数组会得到一个 log

function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

另一个例子:

var pattern = {
get: function () {
return 'I alway return this string,whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
};
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);

这两个例子很好。

另外:IE8及更低版本IE是无法使用的,而且这个特性是没有polyfill的,无法在不支持的平台实现。

Object.observe()

Object.observe() 方法用于异步地监视一个对象的修改。当对象属性被修改时,方法的回调函数会提供一个有序的修改流。然而,这个接口已经被废弃并从各浏览器中移除。但是在我看了这几种解决方案之后,反而觉得这是种很好的解决方案。。

简单的介绍一下

Object.observe(obj, callback[, acceptList])

  • obj 被监控的对象.
  • callback 当对象被修改时触发的回调函数,其参数为:

    • changes 一个数组,其中包含的每一个对象代表一个修改行为。每个修改行为的对象包含:

      • name: 被修改的属性名称。
      • object: 修改后该对象的值。
      • type: 表示对该对象做了何种类型的修改,可能的值为”add”, “update”, or “delete”。
      • oldValue: 对象修改前的值。该值只在”update”与”delete”有效。
    • acceptList 在给定对象上给定回调中要监视的变化类型列表。如果省略, [“add”, “update”, “delete”, “reconfigure”, “setPrototype”, “preventExtensions”] 将会被使用。

数据绑定的示例:

// 一个数据模型
var user = {
id: 0,
name: 'Brendan Eich',
title: 'Mr.'
};
// 创建用户的greeting
function updateGreeting() {
user.greeting = 'Hello, ' + user.title + ' ' + user.name + '!';
}
updateGreeting();
Object.observe(user, function(changes) {
changes.forEach(function(change) {
// 当name或title属性改变时, 更新greeting
if (change.name === 'name' || change.name === 'title') {
updateGreeting();
}
});
});

使用ES6中的proxy

proxy可以说是一个比较庞大的东西了,他可以做的事情有很多

let p = new Proxy(target, handler);

参数:

  • target 一个目标对象(可以是任何类型的对象,包括本机数组,函数,甚至另一个代理)用Proxy来包装。
  • handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数。
    通过代理,你可以轻松地验证向一个对象的传值。
    以下例子使用了 set 处理器(set handler)。
    let validator = {
    set: function(obj, prop, value) {
    if (prop === 'age') {
    if (!Number.isInteger(value)) {
    throw new TypeError('The age is not an integer');
    }
    if (value > 200) {
    throw new RangeError('The age seems invalid');
    }
    }
    // The default behavior to store the value
    obj[prop] = value;
    }
    };
    let person = new Proxy({}, validator);
    person.age = 100;
    console.log(person.age);
    // 100
    person.age = 'young';
    // 抛出异常: Uncaught TypeError: The age is not an integer
    person.age = 300;
    // 抛出异常: Uncaught RangeError: The age seems invalid

这是一个在官方文档上的实例:它使用了set handler,监听了数据的改变。

暂时就这些吧

参考文章:

CATALOG
  1. 1. 原生JS监听变量的变化
  2. 2. 几种解决方案:
    1. 2.1. ES5的getter与setter
    2. 2.2. Object.observe()
    3. 2.3. 使用ES6中的proxy