博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
js对象监听实现
阅读量:7005 次
发布时间:2019-06-27

本文共 4098 字,大约阅读时间需要 13 分钟。

前言

随着前端交互复杂度的提升,各类框架如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-----afterconsole.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

效果图

图片描述

代码地址

流程图

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

图片描述

归纳

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

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

最后

本文有什么不完善的地方,或者流程图有待改进的地方,敬请斧正。

转载地址:http://wxytl.baihongyu.com/

你可能感兴趣的文章
mediascanner流程
查看>>
vue axios全攻略
查看>>
GZIP CSS JS
查看>>
HDU 3635 Dragon Balls
查看>>
基础DOM和CSS操作(三)
查看>>
HTTP 02 HTTP1.1 协议
查看>>
手机端网页web开发要点
查看>>
正则表达式中 group groups区别
查看>>
JBoss + EJB3 + MySql : 开发第一个EJB
查看>>
浏览器请求阻塞到底是怎么回事?我们为什么要把静态资源分服务器放置?
查看>>
Oracle数据库基础知识
查看>>
2011年9月最新整理的10个有趣的jQuery插件集合
查看>>
Python的日志配置和处理
查看>>
小程序设置全屏显示
查看>>
c++ bind的简单使用 实例
查看>>
(翻译)Angular 1.3中的验证器管道
查看>>
Web网站的性能测试工具
查看>>
【linux+C】通过几个实例温习指针
查看>>
I.MX6 Manufacturing Tool V2 (MFGTool2) Emmc mksdcard.sh hacking
查看>>
异步复位同步释放
查看>>