Vue.js的响应式原理与为什么这么设计。

749 阅读3分钟

本内容使用vue2.6进行分析。

Vue.js是一款MVVM框架,上手快速简单易用,通过响应式在修改数据的时候更新视图。

那些内卷比较严重的同学(面试八卦文)都知道vue.js基于Object.defineProperty做的响应式原理依赖。

思路原理 数据的可观察

observe 观察者模式,几乎是所有的高级软件的一个通用思路,不要为了卷而卷。大家学完之后要考虑你用观察者模式能用到什么地方。

function observe(value, cb) {
    Object.keys(value).forEach(
        (key) => defineReactive(value, key, value[key] , cb)
    )
}

function defineReactive (obj, key, val, cb) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
            /*....依赖收集等....*/
            /*Github:https://github.com/answershuto*/
            return val
        },
        set:newVal=> {
            val = newVal;
            cb();/*订阅者收到消息的回调*/
        }
    })
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observe(this._data, options.render)
    }
}

let app = new Vue({
    el: '#app',
    data: {
        text: 'text',
        text2: 'text2'
    },
    render(){
        console.log("render");
    }
})

为了便于理解,首先考虑一种最简单的情况,不考虑数组等情况,代码如上所示。在initData中会调用observe这个函数将Vue的数据设置成observable的。当_data数据发生改变的时候就会触发set,对订阅者进行回调(在这里是render)。

那么问题来了,需要对app._data.text操作才会触发set。为了偷懒,我们需要一种方便的方法通过app.text直接设置就能触发set对视图进行重绘。那么就需要用到代理。

为什么这么设计

大家想想如果没有Object.defineProperty你会怎么去做这个操作?

我们先看一个简单的例子,拥有一个objec的对象,现在我们有一个render的html操作

let object = {
    data :{
        test : 0
    }
};

object.data.test = 1;
render(object);

当我们每一次修改了objcet.data的时候就会去执行render重新渲染render操作。 这个时候就比较好玩了,如果你的系统非常庞大,然后你丢给一个团队去调用的时候。 你想每次都手动执行一次render操作吗?

上面的方式已经知道问题了,这个时候。我们就会想,可以通过什么方式进行数据的修改是做一点事情呢?在近十年被oop的编程思维影响下,无疑我们第一反应是java的get/set操作。

于是就有了以下的设计操作

class Controller {

  private data;

  constructor() {
  }

  setData(data:any) {
    this.data = { ...this.data, ...data };
    cb();/*订阅者收到消息的回调*/
  }

}

# 这就有点早期React的那味道,this.setState( { xxx: xxxx} )

这个时候,还是感觉有点多余,于是我们会想有没有类似php的魔法方法呢?我只需需要执行this.xxx 就会帮我触发类似this.setState的操作呢?于是有了Vue的Object.defineProperty

PS PHP 魔法方法的js实现

PHP 里面有 __set __get 可以让我们做到 this>version的操作或者this->version的操作 或者 this->handle() 在我们的js使用代理模式也能做到

alert(proxy.version); 弹出 “1.0”,  alert(proxy.age); 弹出 “none”。sn对象 没有age 属性。

我们还可以设置不让修改某个属性,对属性修改的拦截:


var proxy = new Proxy(new Object,{
  get(target,name){
    if(name in target){
      return target[name]
    }
    else{
      // 这里写正则做自己想做的事情吧
      if((name as string).lastIndexOf('()') > -1){
        return handleFunc()
      }
    }
  },
  set(target,name,value){
    if(name == 'version'){
      alert()
      return false;
    }
    target[name] = value
    return true;
  }
})

好了今天的分享先到这里吧。