资讯专栏INFORMATION COLUMN

js对象监听实现

iamyoung001 / 467人阅读

摘要:数组则在对象监听之外额外在数组对象上的原型链上加一层原型对象来拦截掉等方法然后在执行预设的回调函数最后本文有什么不完善的地方或者流程图有待改进的地方敬请斧正。

前言

随着前端交互复杂度的提升,各类框架如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);
详细的介绍

火狐开发者

实现监听的思路

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

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

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

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

数组的封装

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

对应图

路径的定位

在获取数据变化的同时,定位该变化数据在原始根对象的位置,以数组表示如:
如[ "a", "dd", "ffffd" ] 表示对象obj.a.dd.ffffd的属性改变
实现:每个遍历对象属性都通过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", "ffffd" ] 表示对象obj.a.dd.ffffd的属性改变
    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等方法,然后在执行预设的回调函数

最后

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87908.html

相关文章

  • JS事件模型

    摘要:事件模型事件模型共有两个过程事件处理阶段。事件绑定监听函数的方式如下事件移除监听函数的方式如下参数说明指定事件类型注意加是事件处理函数级模型属于标准模型,现代浏览器除之外的浏览器都支持该模型。 JS事件模型 观察者模式 观察者模式又叫做发布订阅者模式(Publish/Subscribe),它可以让多个观察者对象同时监听某一个主题对象,这个主题对象的状态变化时会通知所有的订阅者,使得它们...

    mylxsw 评论0 收藏0
  • Node事件机制小记

    摘要:事件的监听与事件的触发事件一事件机制的实现中大部分的模块,都继承自模块。从另一个角度来看,事件侦听器模式也是一种事件钩子的机制,利用事件钩子导出内部数据或状态给外部调用者。的核心就是事件发射与事件监听器功能的封装。 nodejs事件的监听与事件的触发 nodejs事件(Events)showImg(https://segmentfault.com/img/bV0Sqi?w=692&h=...

    airborne007 评论0 收藏0
  • vue -- 非父子组件传值,事件总线(eventbus)的使用方式

    摘要:我的个人博客地址资源地址非父子组件传值,事件总线的使用方式我的博客地址如果您对我的博客内容有疑惑或质疑的地方,请在下方评论区留言,或邮件给我,共同学习进步。 欢迎访问我的个人博客:http://www.xiaolongwu.cn 前言 先说一下什么是事件总线,其实就是订阅发布者模式; 比如有一个bus对象,这个对象上有两个方法,一个是on(监听,也就是订阅),一个是emit(触发,也就...

    zone 评论0 收藏0
  • 通过源码解析 Node.js 中 events 模块里的优化小细节

    摘要:之前的文章里有说,在中,流是许许多多原生对象的父类,角色可谓十分重要。效率更高的从数组中去除一个元素。不过这个所提供的功能过于多了,它支持去除自定义数量的元素,还支持向数组中添加自定义的元素。 之前的文章里有说,在 Node.js 中,流(stream)是许许多多原生对象的父类,角色可谓十分重要。但是,当我们沿着族谱往上看时,会发现 EventEmitter 类是流(stream)类的...

    cloud 评论0 收藏0
  • 详解JS事件 - 事件模型/事件流/事件代理/事件对象/自定义事件

    摘要:取消事件的默认行为。阻止事件的派发包括了捕获和冒泡阻止同一个事件的其他监听函数被调用。 事件模型 DOM0 级事件模型 -没有事件流,这种方式兼容所有浏览器 // 方式一 将事件直接通过属性绑定在元素上 / 方式二 获取到页面元素后,通过 onclick 等事件,将触发的方法指定为元素的事件 var btn = document.getElementById(btn) btn....

    URLOS 评论0 收藏0

发表评论

0条评论

iamyoung001

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<