热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何监听JS变量的变化

如何监听JS变量的变化如何监听js中变量的变化?我现在有这样一个需求,需要监控js的某个变量的改变,如果该变量发生变化,则触发一些事件,不能使用timeinterval之类的

如何监听JS变量的变化

如何监听 js 中变量的变化?

我现在有这样一个需求,需要监控js的某个变量的改变, 如果该变量发生变化,则触发一些事件, 不能使用timeinterval之类的定时去监控的方法, 不知道有比较好的解决方案么?

这个问题问的很好。

流行的MVVMJS库/框架都有共同的特点就是数据绑定, 在数据变更后响应式的自动进行相关计算并变更DOM展现。 所以这个问题也可以理解为如何实现MVVM库/框架的数据绑定。

常见的数据绑定的实现有脏值检测, 基于ES5gettersetter,以及ES已被废弃的Object.observe, 和ES6中添加的Proxy

脏值检测

angular使用的就是脏值检测,原理是比较新值和旧值, 当值真的发生改变时再去更改DOM,所以angular中有一个$digest。 那么为什么在像ng-click这样的内置指令在触发后会自动变更呢? 原理也很简单,在ng-click这样的内置指令中最后追加了$digest

简易的实现一个脏值检测:


<html>
    <head>
        <meta charset="utf-8" />
        <title>two-way bindingtitle>
    head>
    <body onload="init()">
        <button ng-click="inc">
            Increase
        button>
        <button ng-click="reset">
            Reset
        button>
        <span style="color:red" ng-bind="counter">span>
        <span style="color:blue" ng-bind="counter">span>
        <span style="color:green" ng-bind="counter">span>
        <script type="text/Javascript"> /* 数据模型区开始 */ var counter = 0; function inc() { counter++; } function reset() { counter = 0; } /* 数据模型区结束 */ /* 绑定关系区开始 */ function init() { bind(); } function bind() { var list = document.querySelectorAll("[ng-click]"); for (var i=0; ifunction(index) { return function() { window[list[index].getAttribute("ng-click")](); apply(); }; })(i); } } function apply() { var list = document.querySelectorAll("[ng-bind='counter']"); for (var i=0; iif (list[i].innerHTML != counter) { list[i].innerHTML = counter; } } } /* 绑定关系区结束 */ script>
    body>
html>

这样做的坏处是自己变更数据后,是无法自动改变DOM的, 必须要想办法触发apply(),所以只能借助ng-click的包装, 在ng-click中包含真实的click事件监听并追加脏值检测以判断是否要更新DOM

另外一个坏处是如果不注意,每次脏值检测会检测大量的数据, 而很多数据是没有检测的必要的,容易影响性能。

关于如何实现一个和angular一样的脏值检测,知道原理后还有很多工作要去做, 以及如何优化等等。如果有兴趣可以看看民工叔曾经推荐的《Build Your Own Angular.js》, 第一章Scope便讲了如何实现angular的作用域和脏值检测。 对了,上面的例子也是从民工叔的博客稍加修改来的,建议最后去看下原文,链接在参考资料中。

ES5的gettersetter

ES5中新增了一个Object.defineProperty, 直接在一个对象上定义一个新属性, 或者修改一个已经存在的属性,并返回这个对象。

Object.defineProperty(obj, prop, descriptor)

其接受的第三个参数可以取getset并各自对应一个gettersetter方法:

var a = { zhihu:0 };
Object.defineProperty(a, 'zhihu', {
  get: function() {
    console.log('get:' + zhihu);
    return zhihu;
  },
  set: function(value) {
    zhihu = value;
    console.log('set:' + zhihu);
  }
});
a.zhihu = 2; // set:2
console.log(a.zhihu); // get:2
                      // 2

基于ES5gettersetter可以说几乎完美符合了要求。为什么要说几乎呢?

首先IE8及更低版本IE是无法使用的,而且这个特性是没有polyfill的, 无法在不支持的平台实现, 这也是基于ES5 gettersetterVue.js不支持IE8及更低版本IE的原因。 也许有人会提到avalon, avalon在低版本IE借助Vbscript一些黑魔法实现了类似的功能。

除此之外,还有一个问题就是修改数组的length, 直接用索引设置元素如 items[0] = {}, 以及数组的push等变异方法是无法触发setter的。 如果想要解决这个问题可以参考Vue的做法, 在Vueobserver/array.js中,Vue直接修改了数组的原型方法:

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
/** * Intercept mutating methods and emit events */
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  var original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    var i = arguments.length
    var args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    var result = original.apply(this, args)
    var ob = this.__ob__
    var inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
});

这样重写了原型方法,在执行数组变异方法后依然能够触发视图的更新。

但是这样还是不能解决修改数组的length和直接用索引设置元素如items[0] = {}的问题, 想要解决依然可以参考Vue的做法: 前一个问题可以直接用新的数组代替旧的数组; 后一个问题可以为数组拓展一个$set方法, 在执行修改后顺便触发视图的更新。

已被废弃的Object.observe

Object.observe曾在ES7的草案中,并在提议中进展到stage2,最终依然被废弃。 这里只举一个MDN上的例子:

// 一个数据模型
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();
    }
  });
});

由于是已经废弃了的特性,Chrome虽然曾经支持但也已经废弃了支持, 这里不再讲更多,有兴趣可以搜一搜以前的文章, 这曾经是一个被看好的特性(Object.observe()带来的数据绑定变革)。 当然关于它也有一些替代品Polymer/observe-js

ES6带来的Proxy

人如其名,类似HTTP中的代理:

var p = new Proxy(target, handler);

target为目标对象,可以是任意类型的对象,比如数组,函数,甚至是另外一个代理对象。 handler为处理器对象,包含了一组代理方法,分别控制所生成代理对象的各种行为。

举个例子:

let a = new Proxy({}, {
  set: function(obj, prop, value) {
    obj[prop] = value;
    if (prop === 'zhihu') {
      console.log("set " + prop + ": " + obj[prop]);
    }
    return true;
  }
});
a.zhihu = 100;

当然,Proxy的能力远不止此,还可以实现代理转发等等。

但是要注意的是目前浏览器中只有Firefox 18支持这个特性, 而babel官方也表明不支持这个特性:

Unsupported feature
Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.

目前已经有babel插件可以实现,但是据说实现的比较复杂。 如果是Node的话升级到目前的最新版本应该就可以使用了,上面的例子测试环境为 Node v6.4.0

参考资料
  • Angular沉思录(一)数据绑定
  • Object.defineProperty() - Javascript | MDN
  • vue/array.js at dev · vuejs/vue
  • Object.observe() - Javascript | MDN
  • Proxy - Javascript | MDN


推荐阅读
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • FineReport平台数据分析图表显示部分系列接口的应用场景和实现思路
    本文介绍了FineReport平台数据分析图表显示部分系列接口的应用场景和实现思路。当图表系列较多时,用户希望可以自己设置哪些系列显示,哪些系列不显示。通过调用FR.Chart.WebUtils.getChart("chartID").getChartWithIndex(chartIndex).setSeriesVisible()接口,可以获取需要显示的系列图表对象,并在表单中显示这些系列。本文以决策报表为例,详细介绍了实现方法,并给出了示例。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • Echarts图表重复加载、axis重复多次请求问题解决记录
    文章目录1.需求描述2.问题描述正常状态:问题状态:3.解决方法1.需求描述使用Echats实现了一个中国地图:通过选择查询周期&#x ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
author-avatar
lifetime8_797
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有