vue源码初级解析(数据代理、模板解析、数据绑定、双向绑定)

一、数据代理
实现数据代理核心就是 Object.defineProperty()方法,而IE8及以下是不支持该方法的,所以vue不能用在IE8及以下。
回归正题,data里的属性可以直接vm.xxx,那是因为用defineProperty方法将data中的属性增加到vm上了,主要代码如下:

Object.keys(data).forEach(function (key) {
   var me = this;
   Object.defineProperty(me, key, {
      configurable: false,
      enumerable: true,
      //vm.xxx获取属性值时调用该方法
      get: function proxyGetter() {
         return me.data[key];
      },
      //当监听到vm.xxx属性值发生改变时,同时更新data中的属性值
      set: function proxySetter(newVal) {
         me.data[key] = newVal;
      }
   })
})

二、模板解析
模板解析用到了DocumentFragment

  • 将原生节点拷贝到fragment中
  • 对含有大括号的文本节点、指令层层处理
  • 将处理完的fragment添加回页面,渲染完成

初始化:

//事件处理(解析事件指令)
eventHandler: function(node, vm, exp, dir) {
   var eventType = dir.split(':')[1],
         fn = vm.$options.methods && vm.$options.methods[exp];
     if(eventType && fn) {
        node.addEventListener(eventType, fn.bind(vm), false);
     }
}
//根据属性名获取data中的值(解析普通指令及大括号表达式)
_getVal: function(vm, exp) {
   var val = vm._data;
   exp = exp.split('.');
   exp.forEach(function(k) {
      val = val[k];
   })
   return val;
}
//更新节点,以v-text也是{{}}为例
textUpdater: function (node, value) {
   node.textContent = typeof(value) == 'undefined' ? '' : value;
}

三、数据绑定
数据绑定通过数据劫持的方式来更新页面
首先看下面一张图:
在这里插入图片描述
如何进行数据劫持:
利用Object.defineProperty方法将data中的属性重新配置,加上getter和setter方法

Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() {
                if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set: function(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                // 通知订阅者
                dep.notify();
            }
 });

Dep是一个构造函数,每一个属性对应一个dep,数据劫持时创建实例

function Dep() {
    this.id = uid++;
    this.subs = [];
}

watcher也是一个构造函数,每一个表达式对应一个watcher,监视数据变化来更新页面

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    this.depIds = {};
    this.value = this.get();
}

模板解析时每次创建watcher对象,都会去获取data的属性值,这样会触发getter中的dep.depend();,继而会触发addDep方法

addDep: function(dep) {
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this);  //属性值改变时,dep会通知订阅者watcher
            this.depIds[dep.id] = dep;   //这句话加上if条件判断主要是为了防止属性值改变时重复建立联系,只允许初始化的时候建立联系
        }
    }

这样dep和watcher就建立起了联系

举个例子:

<div id="app">
   <p>{{name}}</p>
   <p v-text="name"></p>
   <p>{{frends.age}}</p>
   <button v-on:click="update">更新</button>
</div>
new Mvvm ({
   data:{
      name: 'xiaoming',
      frends: {
         name: 'xiaohong',
         age: 18
      }
   },
   methods: {
      update () {
         this.name = 'xinxin';
         this.frends.age = 20;
      }
   }
})

数据劫持时创建的Dep实例有:

{id: 0, sub[]}  {id: 1, sub[]}  {id: 2, sub[]}  {id: 3, sub[]}

初始化视图模板解析时,每创建一个watcher实例,都要和dep建立联系:

//watcher<0>加尖括号是为了区分每次创建的watcher实例
第一个<p>{{name}}</p>:
name属性对应的dep={id:0, sub[watcher<0>]}
watcher<0>.depIds={0: {id:0, sub[watcher<0>]}}

第二个<p v-text="name"></p>
name属性对应的dep={id:0, sub[watcher<0>, watcher<1>]}
watcher<1>.depIds={0: {id:0, sub[watcher<0>, watcher<1>]}}

第三个<p>{{frends.age}}</p>
frends属性对应的dep={id:1, sub[watcher<2>]}
watcher<2>.depIds={1: {id:1, sub[watcher<2>]}}
age属性对应的dep={id:3, sub[watcher<2>]}
watcher<2>.depIds={1: {id:1, sub[watcher<2>]}, 3: {id:3, sub[watcher<2>]}}

当name属性变化时->setter会被调用->通知dep->调用watcher里的更新页面的方法->getter方法会被调用->通过watcher的depIds属性防止重复创建联系

四、双向绑定
双向绑定中的一个方向model–>view的绑定就是上面说的模板解析,另一个方向就是绑定input事件,监听到input标签value值变化,则更新data,然后更新页面

model: function (node, vm, exp) {
   var me = this;
   var val = this._getVMVal(vm, exp);
   node,addEventListener('input', function (e) {
      var newValue = e.target.value;
      if(val === newValue) {
         return;
      }
      me._setVMVal(vm, exp, newValue);
      val = newValue;
   })
   
}
_setVMVal: function(vm, exp, value) {
   var val = vm._data;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
            // 非最后一个key,更新val的值
            if (i < exp.length - 1) {
                val = val[k];
            } else {
                val[k] = value;
            }
        });
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值