前言

随着前端交互复杂度的提升,各类框架如angular,react,vue等也层出不穷,这些框架一个比较重要的技术点就是数据绑定。数据的监听有较多的实现方案,本文将粗略的描述一番,并对其中一个兼容性较好的深入分析。

实现方案简介

目前对象的监听可行的方案:

  • 脏检查: 需要遍历scope对象树里的$watch数组,使用不当容易造成性能问题

  • ES5 object.defineproperty: 除ie8部分支持 其他基本都完全支持

  • ES7 object.observe : 已经移除(缘由)出ES7草案

  • gecko object.watch :目前只有基于gecko的浏览器如火狐支持,官方建议仅供调试用

  • ES6 Proxy: 目前支持较差,babel也暂不支持转化

ES5现代浏览器基本都支持了,OK,本文将介绍目前支持度最好的object.defineproperty 的Setters 和 Getters方式

object.defineproperty介绍

简洁的介绍

它属于es5规范,有两种定义属性:

  • 一种是 数据属性 包含Writable,Enumerable,Configurable

  • 一种是 访问器属性 包含get 和set

数据属性的例子

obj.key='static';
//等效于
Object.defineProperty(obj, "key", {  enumerable: true,  configurable: true,  writable: true,  value: "static" });

访问器属性例子

var obj = {temperature:'test'
};
var temperature='';
Object.defineProperty(obj, 'temperature', { get: function() { return temperature+'-----after'; }, set: function(value) { temperature = value; } }) obj.temperature='Test'; //Test-----after console.log(obj.temperature); 

详细的介绍

火狐开发者

实现监听的思路

  1. 将需要监听对象/数组 obj和回调函数callback传入构造函数,this.callback = callback 存储回调函数

  2. 遍历对象/数组obj,通过Object.defineProperty将属性全部定义一遍。在set函数里面添加callback函数,设置val值。get函数返回val。

  3. 判断对应的obj[key]是否为对象,是则进入第二步,否则继续遍历

  4. 遍历结束之后判断该对象是否为数组,是则对操作数组函数如push,pop,shift,unshift等进行封装,操作数组前调用callback函数

数组的封装

比较复杂的是数组的封装,结构如下:
新建一个对象newProto,继承Array的原型,并在newProto上面封装push,pop等数组操作方法,再将传入的array对象的原型设置为newProto。

对应图

路径的定位

在获取数据变化的同时,定位该变化数据在原始根对象的位置,以数组表示如:
如[ 'a', 'dd', 'ddd' ] 表示对象obj.a.dd.ddd的属性改变
实现:每个遍历对象属性都通过path.slice(0)的方式复制入参数组path,生成新数组tpath,给tpath数组push对应的对象属性key,最后在执行set的回调函数时候将tpath当参数传入

带注释代码

watch.js

/**** @param obj 需要监听的对象或数组* @param callback 当对应属性变化的时候触发的回调函数* @constructor*/
function Watch(obj, callback) { this.callback = callback; //监听_obj对象 判断是否为对象,如果是数组,则对数组对应的原型进行封装 //path代表相应属性在原始对象的位置,以数组表示. 如[ 'a', 'dd', 'ddd' ] 表示对象obj.a.dd.ddd的属性改变 this.observe = function (_obj, path) { var type=Object.prototype.toString.call(_obj); if (type== '[object Object]'||type== '[object Array]') { this.observeObj(_obj, path); if (type == '[object Array]') { this.cloneArray(_obj, path); } } }; //遍历对象obj,设置set,get属性,set属性能触发callback函数,并将val的值改为newVal //遍历结束后再次调用observe函数 判断val是否为对象,如果是则在对val进行遍历设置set,get this.observeObj = function (obj, path) { var t = this; Object.keys(obj).forEach(function (prop) { var val = obj[prop]; var tpath = path.slice(0); tpath.push(prop); Object.defineProperty(obj, prop, { get: function () { return val; }, set: function (newVal) { t.callback(tpath, newVal, val); val = newVal; } }); t.observe(val, tpath); }); }; //通过对特定数组的原型中间放一个newProto原型,该原型继承于Array的原型,但是对push,pop等数组操作属性进行封装 this.cloneArray = function (a_array, path) { var ORP = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; var arrayProto = Array.prototype; var newProto = Object.create(arrayProto); var t = this; ORP.forEach(function (prop) { Object.defineProperty(newProto, prop, { value: function (newVal) { path.push(prop); t.callback(path, newVal); arrayProto[prop].apply(a_array, arguments); }, enumerable: false, configurable: true, writable: true }); }); a_array.__proto__ = newProto; }; //开始监听obj对象,初始path为[] this.observe(obj, []); } 

index.html

<body>
<ul><li> <a href="javascript:void(0)" onClick="dataOne()"> 将obj b属性改变 </a> </li> <li> <a href="javascript:void(0)" onClick="dataTwo()"> 将obj a属性的dd属性的ddd属性改变 </a> </li> <li> <a href="javascript:void(0)" onClick="dataThree()"> 将obj a属性的g属性数组第一个值的a属性改变 </a> </li> <li> <a href="javascript:void(0)" onClick="dataFour()"> 将obj a属性的g属性数组push新的值 </a> </li> </ul> <div id="path"> </div> <div id="old-val"> </div> <div id="new-val"> </div> </body> <script src="../src/watch.js"></script> <script> var obj = { a: {e: 4, f: 5, g: [{a: 1, b: 2}, [3, 4]], dd: {ddd: 1}}, b: 2, c: 3 }; new Watch(obj, call); function call(path, newVal, oldVal) { document.getElementById('path').innerHTML='路径:'+path; document.getElementById('old-val').innerHTML='新的值:'+newVal; document.getElementById('new-val').innerHTML='老的值:'+oldVal; } function dataOne() { obj.b = Math.floor(Math.random()*10); } function dataTwo() { obj.a.dd.ddd = Math.floor(Math.random()*10); } function dataThree() { obj.a.g[0].a=Math.floor(Math.random()*10); } function dataFour() { obj.a.g.push(Math.floor(Math.random()*10)); } </script> 

效果图

代码地址

完整代码地址

流程图

具体流程的复杂度基于监听对象的深度,所以下图只对父对象做流程分析

归纳

  • 通过定义对象内部属性的setter和getter方法,对将要变化的属性进行拦截代理,在变化前执行预设的回调函数来达到对象监听的目的。

  • 数组则在对象监听之外额外在数组对象上的原型链上加一层原型对象,来拦截掉push,pop等方法,然后在执行预设的回调函数

转载于:https://www.cnblogs.com/chris-oil/p/8506188.html

[转] js对象监听实现相关推荐

  1. js 监听 安卓事件_百行代码实现js事件监听实现跨页面数据传输

    百行代码实现js事件监听实现跨页面数据传输 使用场景 类似消息队列的使用场景,支持同页面和跨页面通信,发送消息和接收消息 技术原理 跨页面通信: 基于事件监听,通过监听 storage事件监听回调机制 ...

  2. html 监听input输入框的值,利用原生JS实时监听input框输入值

    利用原生JS实时监听input框输入值 原生JS中可以使用oninput,onpropertychange,onchange oninput,onpropertychange,onchange的用法 ...

  3. Vue.js:监听属性

    ylbtech-Vue.js:监听属性 1.返回顶部 1. Vue.js 监听属性 本章节,我们将为大家介绍 Vue.js 监听属性 watch,我们可以通过 watch 来响应数据的变化: 实例 & ...

  4. js如何监听元素事件是否被移除_JavaScript 监听元素是否进入/移出可视区域

    JavaScript 监听元素是否进入/移出可视区域 常规操作 防抖节流 IntersectionObserver 兼容的代码 常规操作 通常的做法是,监听srcoll事件,根据元素的offset来判 ...

  5. js如何监听元素事件是否被移除_JS移除事件监听的方法 .removeEventListener( )

    JS用addEventListener添加事件监听方法后,可以用removeEventListener来解除监听: element.removeEventListener(event, myFunct ...

  6. less.js插件监听

    <script>less.watch();</script> 在不手动刷新/重新加载页面会自动监听less的变化,页面做出相应的变化 . 写在这两行后面就好 了 <lin ...

  7. JS如何监听动画结束

    场景描述 在使用JS控制动画时一般需要在动画结束后执行回调去进行DOM的相关操作,所以需要监听动画结束进行回调.JS提供了以下事件用于监听动画的结束,简单总结学习下. CSS3动画监听事件 trans ...

  8. JS实时监听DOM元素变化 - MutationObserver

    使用 MutationObserver API实时监听DOM元素变化 创建 MutationObserver 实列,接受一个用于监听到DOM元素变化的回调函数 const handleListenCh ...

  9. js微信监听返回_微信小程序(2)- 框架结构amp;运行环境

    一.小程序框架结构 小程序框架分场景获取.逻辑层和视图层 场景获取:场景值是用来描述用户进入小程序的路径,可以在小程序的生命周期onLaunch 或 onShow 里获取.也可以通过wx.getLau ...

最新文章

  1. python是大学必修课吗-好消息!今天,审计、会计、税务、财务主管彻底沸腾了……...
  2. shell特殊符号cut命令、sort_wc_uniq命令、tee_tr_split命令、shell特殊符号下
  3. 代理、委托、钩子与打桩
  4. POJ3666序列最小差值
  5. mpandroidchart y轴从0开始_从零开始学Pytorch(十七)之目标检测基础
  6. 留守女孩携笔从戎,被录取为空军飞行员
  7. 石家庄计算机专接本学校有哪些,河北省内的专接本学校都有哪些?
  8. 怎么修改图例位置_学到了学到了,叠加图,原来可以这样处理图例
  9. 使用pt-query-digest时遇到报错can't locate Digest/MD5.pm in @INC (@INC contains: /usr/local/lib64/perl5
  10. sklearn 下的树模型
  11. 计算几何常用算法及numpy仿真
  12. 【重点:BFS】LeetCode 407. Trapping Rain Water II
  13. oracle+imp部分数据,ORACLE数据库IMP逻辑导入出现ORA-00910的处理案例
  14. 如何打造7*24h持续交付通道?阿里高级技术专家的5点思考
  15. Jquery 数组操作(转)
  16. 小学生python编程教程-极度舒适的全套 Python 入门教程,小学生看了也能学会
  17. 谷歌浏览器好用的复制粘贴插件_Chrome粘贴插件:将其粘贴
  18. Monkey测试------报错日志分析参考
  19. 7-10 幸运数17(10分)
  20. 微信开源mars源码分析5—底层核心mars分析(续2)

热门文章

  1. 四大浏览器再战Windows平台:Opera勇夺第一
  2. 中国AI军团争霸机器阅读理解大赛,搜狗创下全球新纪录
  3. 微软发布“史无前例”的恶意软件数据集,设17万奖金征集预测算法
  4. 人民日报发推欢迎Google重返大陆,FB上长文阐述详细立场
  5. 7 -- Spring的基本用法 -- 6... Spring 3.0 提供的Java配置管理
  6. 进程间通信(五)—信号
  7. 表单流程中获取当前执行人填写的审批意见
  8. 使用MATLAB贝叶斯工具箱(BNT),进行吉布斯采样(Gibbs Sampling)之前需要做的编译工作...
  9. 使用CURL出现certificate verify failed错误的解决方法
  10. 揭秘设计模式:策略模式(Strategy)的枚举(Enum)实现