Vue 数据响应式

一、MVVM模式

现在常用的框架 Vue 和 React 都是 MVVM 模式。MVVM是Model-View-ViewModel的简写。M 表示 Model,V 表示 View,VM 表示 View-Model,即数据和模型的桥梁。MVVM 本质上就是 MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

例子:

模版

<span> {{count}}</span>

数据变化

this.count++

数据变化时,视图中的数值也变化。

二、数据响应式

数据响应式的实现分为侵入式和非侵入式。下面几种框架的数据触发视图改变的写法。

Vue 的数据变化

this.count++

React 的数据变化

this.setState({
    count: this.state.count + 1 
})

小程序数据变化

this.setData({
    count: this.data.count + 1 
})
  • 小程序和 React 属于是侵入式的, 数据响应的方式比较直观,是通过主动调函数修改数据的同时触发视图更新。
  • Vue 的方式是非侵入式,Vue 的数据只是通过修改变量本身就触发了视图更新。Vue 利用了 JS 的检测对象属性变化相关的 API 去劫持数据更新视图。

Vue2 侦测数据变化使用的是 Object.defineProperty() 方法,通过定义对象属性上的 get 和 set 方法,在 get 方法中收集观察者,在 set 方法中通知观察者们进行更新视图或处理其他事务。

三、Object.defineProperty()

Vue2 检测数据变化利用的是 Object.defineProperty() 方法来定义 属性的 get 和 set 方法来实现。该方法可直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

const obj = {}
let _count  = 0
Object.defineProperty(obj,"count", {
    get() {
        console.log("获取 count 属性")
        return _count
    },
    set(val) {
        console.log("修改 count 属性为" + val)
        _count = val
    }
})

console.log(obj.count)
obj.count = 10
console.log(obj.count)

四、defineReactive 方法

如上 Object.defineProperty 方法的例子中,在定义 obj.count 属性时,需要使用一个临时变量 _count 来存放属性值供 get 和 set 方法使用,这时候 _count 放于外部会显得很累赘。Vue2 中利用闭包的将
Object.defineProperty 封装到 defineReactive 方法中,临时变量也存放在该方法中。

defineReactive 方法用于将对象的一个属性值定义为响应式。

const obj = {}

function defineReactive(data, key, value) {
    // 当不传 value 时表示监听 data[key],但不修改值。 
    if(arguments.length === 2) {
        value = data[key]
    }
    Object.defineProperty(data, key, {
        enumerable: true,
        configurale: true,
        get () {
            console.log(`获取 ${key} 属性`)
            return value
        },
        set (newValue) {
            console.log(`修改 ${key} 属性为` + newValue)
            if (value === newValue) return
            value = newValue
        }
    })
}
defineReactive(obj, "count", 0)

console.log(obj.count)
obj.count = 10
console.log(obj.count)

四、observe:递归侦测对象全部属性

observe 作用

const obj = {
    a: {
        b: {
            c: 0
        }
    },
    d: 0
}
defineReactive(obj, "a")
defineReactive(obj, "d")

console.log(obj.a.b)
console.log(obj.d)

如上例子,defineReactive 方法只能侦测单个属性值,例子中的使用方式无法侦测 obj.a.b 和 obj.a.b.c 的属性的获取和修改。

在 Vue2 中定义了 observe 方法,用于将一个正常的 obejct 转换成每个层级的属性都是响应式。

observe(obj)

obserse 方法

obserse 方法来将一个普通对象通过 new Oberserver() 处理为响应式对象。
observe 方法实现如下:

/**
 * 将一个普通对象转成响应式对象
*/
function observe(value) {
    if (typeof value !== "object") return;
    let ob
    // 响应式的对象处理后用 __ob__ 属性指向自身,标识为响应式对象
    if (typeof value.__ob__ !== "undefined") {
        ob = value.__ob__
    } else {
        // 
        ob = new Observer(value)
    }
    return ob
}

Observer 类

Observer 类用于将传进来的对象的每个属性都处理成响应式数据。并将 Observer 的实例挂在该对象的 __ob__ 属性上。

class Observer {
    constructor(normalObj) {
        // 将 Observer 的实例挂在该对象的 __ob__ 属性上
        def(normalObj, "__ob__", this, false)
        this.defineReactiveObj(normalObj)
    }
    /**
     * 将对象的所有属性设为响应式
    */
    defineReactiveObj(value) {
        console.log('将对象的所有属性设为响应式', value);
        for (let key in value) {
            defineReactive(value, key)
        }
    }
}
/**
 * 定义一个对象属性值,并可设置是否为可枚举。
*/
function def(obj, key, value, enumerable) {
    Object.defineProperty(obj, key, {
        value,
        enumerable,
        writable: true,
        configurable: true
    })
}

defineReactive 利用 observe 处理对象类型

observe() 、 new Observer() 两个方法将传入对象的每个可枚举属性都通过 defineReactive 方法进行数据劫持。

之前定义的 defineReactive 的只劫持了 data[key] 这个值,这样相当于只对传进来的对象的可枚举属性都做了数据劫持。如果有个属性的属性值为对象类型,则其属性修改就不能被劫持到。

为此需要对
defineReactive 方法做如下修改,让对象的每个属性的属性值都通过 observe 方法进行响应式处理,这样递归下去层层修改为响应式对象。并且在 set 方法传入的新值也需要处理为响应式。

function defineReactive(data, key, value) {
    // 当不传 value 时表示监听 data[key],但不修改值。
    if (arguments.length === 2) {
        value = data[key]
    }
    // 将 object 类型的属性值修改为响应式对象
    let childOb = observe(value)

    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get() {
            console.log(`获取 ${key} 属性`)
            return value
        },
        set(newValue) {
            console.log(`修改 ${key} 属性为` + newValue)
            if (value === newValue || childOb === newValue) {
                return
            }
            // 修改的新值也处理为响应式
            childOb = observe(newValue)
        }
    })
}

使用示例

let obj = {
    a: {
        b: {
            c: 0
        }
    },
    d: 0
}
observe(obj)

obj.a.b.c = 10
console.log(obj.a.b)
/*
输出:
获取 a 属性
获取 b 属性
修改 c 属性为10
获取 a 属性
获取 b 属性
*/

五、数组的响应式处理

let obj = {
    a: [1,2,3]
}
observe(obj)
obj.a.push(4)
/*
输出:
获取 a 属性
*/

如例子所示,当前的 observe 方法对数组的数据劫持处理是不起作用的,这不能侦测到 obj.a 属性被修改。所以还需要对数值类型做进一步处理。

这里需要对 push/pop/shift/splice/sort/reverse 七个数组元素操作的方法进行改写。

未完待续。。。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值