switer / switer.github.io

Personal homepage
https://switer.github.io
5 stars 0 forks source link

The implementation of change listener of data-binding #25

Open switer opened 9 years ago

switer commented 9 years ago

The implementation of change listener of data-binding

React中触发 UI re-render 有两个方法 setProps, setState, setProps 在官方定义上是给外部 JS 调用的,而 setState 是内部逻辑使用的,两个调用方式一致:

component.setProps({
    name: 'switer'
})

component.setState({
    name: 'switer'
})

在此以 setState 为例。 React 的组件内的所有状态都挂载在 this.state 对象下, setState 其实就是 this.state 的一个 setter 。观摩下 setState 的使用:

//  该方法获取初始 state 对象
getInitialState: function () {
    return {
        name: 'switer'
    }
},
// 初始渲染后做些操作
componentDidMount: function () {
    // 我想将 name: switer --> name: guankaishe
    var state = this.state
    state.name = 'switer'
    this.setState(state)
}

使用方式有点残暴,all right?想要方便开发,你可以自定义一个 mixin,根据 keypath 来更新 state:

mixins: [{
    sst: function (keypath, value) {
        var state = this.state
        updateValueByKeypath(state, keypath, value)
        this.setState(state)
    }
}],
componentDidMount: function () {
    // 我想将 name: switer --> name: guankaishe
    this.sst('name', 'switer')
}

ES5 defineProperty -- vue.js

ES5 的 Object.definePropety 方法提供了定义对象的 getter/setter 方法。 由于在 setter 被调用的时候会触发 change 事件,所以每个对象都是 observable 的。

vue.js 中在定义一个类的时候,有个 data 选项,该选项就是该类下的所有 属性/数据/状态 的挂载对象。

new Vue({
    data: {
        props: {},
        ...
        state: {},
        ...
        dataset: {}
        ...
    }
})

Vue 会在 init/created 阶段获取 data 对象的属性的 keypath 并定义 setter, keypath 是用以 DOM 解析阶段绑定数据。如果属性值为 Array/Object 类型,进行递归遍历操作。

data: {
    obj: { // keypath: obj
        name: 'switer' // keypath: obj.name
    },
    arr: [{ // keypath: arr
            name: 'switer' // arr[0].name
        },
        'guankaishe' // arr[1]
    ]
}

同时,data 与每一个 Array/Object 类型的属性,都会添加一个事件分发器对象属性 "__EventEmitter__",当 setter 触发 change 事件后,通过该分发器告诉对象的订阅者处理方法(会有相应的页面渲染方法)。

Notice: Vue.js 在 0.11.x 后将 "__EventEmitter__" 修改为 "__ob__" Notice: Array/Object 类型数据使用新赋值引用,会抹除EventEmitter,导致重新的解析操作。且由于 Vue.js 的 diff 算法不会进行深度遍历,易产生性能问题。

Dirty checking -- angular

Angular 还没有相关的实践资料,相关了解都是网站查资料所得: how-angularjs-implements-dirty-checking . 个人认为,Angular 没有使用 Change-method 的理由是,显性的 setter 方法调用方式影响开发效率, 使用 defineProperty 定义 setter 是最方便的方式,但 IE* 浏览器不支持该方法。 angular 使用交换性能换来兼容性易用性的实现方式 -- dirty checking.

$scope.name = 'switer'
$scope.$watch('name', function (nv, ov) {
    console.llg('name value change to', nv)
})

由上面代码可以看到,直接通过赋值运行 = 可以触发 $watch 的回调(也会有 UI re-redner)。 Angular 没有使用 defineProperty 来定义 setter ,在 setter 调用的时候触发 change 事件, 而是使用 Polling loop 来定时轮询数据是否变脏。数据变脏(change)的时候,就触发相对应的 change 回调方法与及更新 UI。所以称之为 dirty-checking。

conclusion

方式 性能 易用性 兼容性
Change-method
defineProperty
Dirty checking