数据监听

作者: 姜治宇 | 来源:发表于2020-04-05 12:08 被阅读0次

上次我们实现了一个数据劫持,今天我们看一下数据的监听。
什么是监听呢?请看以下代码:

var o = {a: {b: 'jack'}}
observe(o)//数据劫持

//数据监听
new Watcher(o,'a.b',function(newVal,oldVal){
    console.log(newVal)
    console.log(oldVal)
})

劫持和监听的区别在于:
劫持是针对object的所有属性,而监听是针对某一个属性。
当对象o的某一个属性发生变化时,我们监听到了,就可以触发后面的回调函数了。
那如何实现呢?
直觉上是用观察者模式。

var o = {a: {b: {c: 'jack'}}}
observe(o)//劫持所有属性

function observe(data) {

    Object.keys(data).forEach(function (key) {

        if (typeof data[key] === 'object') {

            observe(data[key]) //递归

        } else {

            defineReactive(data, key, data[key])

        }

    })
}

//观察者模式
function Dep() {
    this.subs = []

}

Dep.prototype.on = function (fn) {

    this.subs.push(fn)


}
//执行的是对象里面的方法
Dep.prototype.emit = function () {

    this.subs.forEach(fn => {
       fn()
    })
}


function defineReactive(obj, key, val) {
    let dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get () {


            dep.on(fn)//回调函数从哪里来?

            return val
        },
        set (newval) {

            console.log('object属性发生了变化')
            val = newval
            dep.emit()
        }
    })
}

问题来了:
订阅的回调函数是什么呢?
应该是Watcher的回调函数。
如何在defineProperty内部订阅Watcher的回调函数呢?
这里我们不妨换一种思路:观察者订阅的一定是回调函数么?可否订阅一个对象呢?然后触发这个对象的一个方法?
很明显是可以的,我们订阅的对象就是Watcher。
下面我们先搭出Watcher的架子看看:

var o = {a: {b: {c: 'jack'}}}
observe(o)//劫持所有属性

function observe(data) {

    Object.keys(data).forEach(function (key) {

        if (typeof data[key] === 'object') {

            observe(data[key]) //递归

        } else {

            defineReactive(data, key, data[key])

        }

    })
}

//观察者模式
function Dep() {
    this.subs = []

}

//往数组里面丢的是对象
Dep.prototype.on = function (obj) {

    this.subs.push(obj)


}
//执行的是对象里面的方法
Dep.prototype.emit = function () {

    this.subs.forEach(obj => {
        obj.update()
    })
}
/*
* new Watcher(o,'a.b.c',function(newVal,oldVal){
    console.log()
})
* */
function Watcher(obj,exp,cb) {//exp是表达式a.b.c,cb是回调函数
    this.obj = obj
    this.exp = exp
    this.cb = cb
    this.value = this.getValue()//在new实例的时候,获取obj.a.b.c的值,触发defineProperty的get
}
Watcher.prototype.getValue = function(){
   //触发defineProperty的get
    let val = this.obj+'.' + this.exp//这里是错误的,[object Object].a.b.c,想想如何实现?
    return val // 也就是obj.a.b.c

}
Watcher.prototype.update = function(){
    //修改o.a.b.c的值,触发defineProperty的set
    let oldValue = this.value;
    let newValue = this.getValue()//重新获取新值
    if(oldValue !==newValue){
        this.cb(newValue,oldValue)
    }

}

let watcher = new Watcher(o,'a.b.c',function(newVal,oldVal){
    console.log('new>>>',newVal)
    console.log('old>>>',oldVal)
})

function defineReactive(obj, key, val) {
    let dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get () {

            dep.on(watcher)//订阅watcher对象

            return val
        },
        set (newval) {

            console.log('object属性发生了变化')
            val = newval
            dep.emit()
        }
    })
}

这段代码是报错的,存在这么几个问题:
1、Watcher在实例化的时候,就应该已经触发了defineProperty的get,但实际上defineProperty内部的get需要等待Watcher实例化才能订阅上。这就成死循环了。
2、如何在Watcher的getValue方法获取obj.a.b.c的值呢?
第一个问题比较重要,我们需要优先解决。
问题的关键就在于:
Watcher在实例化的时候,就需要通知到defineProperty的get。
要解决这件事并不难,我们可以利用对象本身的引用特性来实现:

Dep.target = null //这是订阅的对象

function Watcher(obj,exp,cb) {//exp是表达式,cb是回调函数
    this.obj = obj
    this.exp = exp
    this.cb = cb
    this.value = this.getValue()//调用自身的方法
}
Watcher.prototype.getValue = function(){

    Dep.target = this //这是最关键的地方哈~~
    //Dep.target已经有了实例,
    let val = this.obj+'.' + this.exp//触发defineProperty的get,观察者会将Watcher实例丢进subs数组
    return val // 也就是obj.a.b.c
}

我们用外部变量Dep.target存放Watcher的实例,然后defineProperty中订阅的对象就是Dep.target。当在Watcher实例化的时候,它就会通知到通知到defineProperty的get。

var o = {a: {b: {c: 'jack'}}}
observe(o)//劫持所有属性

function observe(data) {

    Object.keys(data).forEach(function (key) {

        if (typeof data[key] === 'object') {

            observe(data[key]) //递归

        } else {

            defineReactive(data, key, data[key])

        }

    })
}

//观察者模式
function Dep() {
    this.subs = []

}

//往数组里面丢的是对象
Dep.prototype.on = function(obj){
    if(Dep.target){//如果Dep.target不为空,就丢进subs数组
        this.subs.push(obj)
    }

}
//执行的是对象里面的方法
Dep.prototype.emit = function(){

    this.subs.forEach(obj=>{
        obj.update()
    })
}
/*
* new Watcher(o,'a.b.c',function(newVal,oldVal){
    console.log()
})
* */
Dep.target = null //这是订阅的是Watcher实例

function Watcher(obj,exp,cb) {//exp是表达式a.b.c,cb是回调函数
    this.obj = obj
    this.exp = exp
    this.cb = cb
    this.value = this.getValue()//在new实例的时候,获取obj.a.b.c的值,触发defineProperty的get
}
Watcher.prototype.getValue = function(){
    Dep.target = this //这是最关键的地方哈~~
    //Dep.target已经有了实例,
    let val = this.obj+'.' + this.exp//触发defineProperty的get,观察者会将Watcher实例丢进subs数组
    return val // 也就是obj.a.b.c

}
Watcher.prototype.update = function(){
    //修改o.a.b.c的值,触发defineProperty的set
    let oldValue = this.value;
    let newValue = this.getValue()//重新获取新值
    if(oldValue !==newValue){
        this.cb(newValue,oldValue)
    }

}

new Watcher(o,'a.b.c',function(newVal,oldVal){
    console.log('new>>>',newVal)
    console.log('old>>>',oldVal)
})

function defineReactive(obj,key,val){
    let dep = new Dep()
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get(){

            dep.on(Dep.target)//监听的是watcher实例

            return val
        },
        set(newval){

            console.log('object属性发生了变化')
            val = newval
            dep.emit()
        }
    })
}

下面解决第二个问题。
我们不妨把这个问题抽象出来,单独放在一个函数里解决。

var o = {a: {b: {c: 'jack'}}}
function parsePath(path){

    let segs = path.split('.')
    return function(obj){
        for(let i=0;i<segs.length;i++){
            if(typeof obj !=='object'){
                break;
            }
            // console.log(segs[i])//a
            // console.log(obj[segs[i]])//{ b: { c: 'jack' } }

            obj = obj[segs[i]]

            //console.log(obj)//{ b: { c: 'jack' } }
        }
        return obj
    }
}
console.log(parsePath('a.b.c')(o))

这个函数用到了柯里化,我们再分析一下。
for循环的作用就是逐次往对象内部深入,直到找出非object的那个值来返回。
第一次循环:obj --->{ b: { c: 'jack' } }
第二次循环:obj--->{ c: 'jack' }
第三次循环:obj--->'jack',
跳出循环,返回值。
那个判断跳出for循环的条件其实不加也行,不过如果有人写成a.b.c.多带个点的话,那就会多一次循环,那这样的话判断条件就有用了。
好啦,解决了以上问题,我们就可以把最终代码写出来了:

var o = {a: {b: {c: 'jack'}}}
observe(o)//劫持所有属性

function observe(data){

    Object.keys(data).forEach(function(key){

        if(typeof data[key] === 'object'){

            observe(data[key]) //递归

        } else{

            defineReactive(data,key,data[key])

        }

    })
}

//观察者模式
function Dep(){
    this.subs = []

}
//往数组里面丢的是对象
Dep.prototype.on = function(obj){
    if(Dep.target){
        this.subs.push(obj)
    }

}
//执行的是对象里面的方法
Dep.prototype.emit = function(){

    this.subs.forEach(obj=>{
        obj.update()
    })
}

Dep.target = null //这是订阅的对象watcher

function Watcher(obj,exp,cb) {//exp是表达式,cb是回调函数
    this.obj = obj
    this.exp = exp
    this.cb = cb
    this.value = this.getValue()//调用自身的方法
}
Watcher.prototype.getValue = function(){

    Dep.target = this
    let value = parsePath(this.exp)(this.obj)//这里会触发defineProperty的get
    Dep.target = null
    return value

}

Watcher.prototype.update = function(){
    //获取新值
    let newVal = this.getValue()//这里会触发defineProperty的get
    let oldVal = this.value
    if(newVal !== oldVal){
        this.value = newVal
        this.cb.call(this.obj,newVal,oldVal)
    }

}

function parsePath(path){

    let segs = path.split('.')
    return function(obj){
        for(let i=0;i<segs.length;i++){
            if(typeof obj !=='object'){
                break;
            }
            // console.log(segs[i])//a
            // console.log(obj[segs[i]])//{ b: { c: 'jack' } }

            obj = obj[segs[i]]

            //console.log(obj)//{ b: { c: 'jack' } }
        }
        return obj
    }
}
//监听
new Watcher(o,'a.b.c',function(newVal,oldVal){
    console.log('new>>>',newVal)
    console.log('old>>>',oldVal)
})
o.a.b.c = 'lily'
o.a.b.c = 'lily2'


function defineReactive(obj,key,val){
    let dep = new Dep()
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get(){
            console.log('get>>>')
            console.log(Dep.target)
            dep.on(Dep.target)

            return val
        },
        set(newval){

            console.log('object属性发生了变化')
            val = newval
            dep.emit()
        }
    })
}

相关文章

  • 小程序 监听 data 数据

    小程序 监听 data 数据 组件中监听 普通页面中监听

  • 53-Vue-watch属性

    这里主要实现监听数据变化进行某些操作,两种方法对比 一.监听键盘事件 双向数据绑定 添加数据 绑定键盘监听事件 添...

  • vue,watch监听数据,数据监听

    vue,watch监听数据,数据监听 三个值: 1.第一个handler:其值是一个回调函数。即监听到变化时应该执...

  • 4 Vue计算属性computed、watch监听

    1、计算属性和watch监听的区别: (1).两者都可监听数据的变化,计算属性会return值,watch监听数据...

  • 数据监听

    对象的监听 es5的Object.defineProperty是关键,一个已经定义好的对象,我们可以利用Objec...

  • 数据监听

    上次我们实现了一个数据劫持,今天我们看一下数据的监听。什么是监听呢?请看以下代码: 劫持和监听的区别在于:劫持是针...

  • 数据监听

  • Vue3-watch和watchEffect

    watch watch可以监听一个或多个响应式数据, 一旦数据变化, 就自动执行监听回调 如果监听rective对...

  • Android之WIFI-WifiMonitor分析

    概述 详细1.怎么监听wpa_supplicant发送过来的数据 2.怎么将数据分发给监听者?1)监听者是怎么注册...

  • AngularJS $watch 监听

    监听$watch监听数据变化,有三个参数 $scope.$watch(“监听的属性”,function(new,o...

网友评论

    本文标题:数据监听

    本文链接:https://www.haomeiwen.com/subject/jgnjphtx.html