前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入讲解 Vue 中实现原理

深入讲解 Vue 中实现原理

作者头像
CSDN技术头条
发布2018-07-30 11:33:46
7460
发布2018-07-30 11:33:46
举报

前言

随着 Vue2.0 的发布,前端入门的要求也越来越低,已至于 Vue 已经成为一个前端的标配,最近也面了很多前端开发工程师,发现大部分都停留在用的阶段上,建议大家看看源码,学学 Vue 的思想。

本文中,读者配合作者的 GitHub 去实践,相信会有很大的提升。

双向数据绑定 Model View ViewModel

Angular1.x 当中的双向数据绑定是通过监听的方式来实现的,核心思想为脏值检查,Angular 通过 $watch()去监听值得变化,然后调用 $apply()/ $digest()方法来实现。

Vue 的双向数据绑定是通过数据劫持 + 发布订阅模式(不兼容低版本)+ 数据代理的方式来实现的。

今天主要讲 Vue

Vue 不兼容低版本,是因为低版本浏览器不兼容 Object.defineProperty 这个属性,我们首先了解一下正常情况下定义的对象。

   var obj = {}
   obj.公众号 =  '内推猿',   //console.log(obj) {"公众号","内推猿"}
   delect obj.公众号   //console.log(obj) {}

如果我们用到了 Object.defineProperty 这个属性再去定义一个对象,在控制台中你就会发现不同了, 在原型链上多了一些方法。

  var  obj  = {};
    Object.defineProperty(obj, '公众号',{
      configurable:true,  //属性值可以被删除
      writable:true,  //对属性可进行编写
      enumerable: true, //可枚举
      value:'内推猿'
    })

想要了解这些属性的全部参数的话,可以去 MDN 上查看一下。我们在进行值修改的时候,就会用到 get、set方法。

 var  obj  = {};
    Object.defineProperty(obj, '公众号',{
      configurable:true,  //属性值可以被删除
      enumerable: true, //可枚举
      get() {        //获取obj.公众号值得时候 会调用get方法
        return '内推猿'
      }      set() {        //给obj 属性赋值
      }
    })
console.log(obj.公众号)  //内推猿

我们通常写 Vue 的时候,都会这样写:

<body>
  <div id="app">
      {{gongzhonghao}}  </div></body><script>
    let mvvm = new Mvvm({        el: '#app',        data:{ gongzhonghao:'内推猿'}
    })</script>

那么如何去实现呢?我们用这个 Object.defineProperty 这个属性来实现数据劫持(Observer)。

数据劫持:观察对象,通过递归给每一个对象增加 Object.definePropery,在 set 方法中触发 observe 方法,就能监听到数据的变化,如果数据类型是 {a:{b:1}}多层的,那么就要用到递归去实现。

function observe (data) {    return new Observe(data)
}function Observe (data) {    if(!data || typeof data !== 'object')  return
    //把data属性 通过Object.definePropert  来定义属性
    for (let key in data) {   
        let value = data[key]  
        //递归方式绑定所有属性    数据是 {a:{b:1}}
        observe(value)
        Object.defineProperty(data, key, {
            enumerable:true,            get() {                return value
            },            set(newValue) {                //如果值没有发生改变的话  
                if(newValue  == value) return
                //重新赋值
                value = newValue                observe(value)
            },
        })
    }
}

Vue 中的数据代理

我们会遇到一些比较复杂的数据结构,例如 data:{ gongzhonghao:'内推猿', msg:{vx:214464812,creator: 'zhangzhen' }}

如果你用的是我上面写的 observe 方法就会发现,我要获取 creator 字段的话,需要通过mvvm._data.msg.creator ..... 的形式来获取值。遇到再复杂的数据结构就会更乱。然而我们想要通过mvvm.msg 方式来获取数据(去掉_data)。去掉复杂的查询方式,所以用到了数据代理的方式来处理以上问题,其中 this 代表的是整个数据。

   //数据代理方式
   for(let key in data ) {
     Object.defineProperty(this, key ,{
        enumerable:true,        get() {            return this._data[key];
        },        set(newValue){            this._data[key] = newValue
        }
     })
   }

Vue 特点,不能新增不存在的属性 ,因为不存在的属性没有 get、set 方法。而 Vue 当中的深度响应,会给每一个新对象增加数据劫持,从而去监控新对象的变化。

模板编译 Compile

Vue 项目中我们通过 {{}} 的方式来替换 data 值,首先我们通过 #el 来确定编译的范围,创建createDocumentFragment 标签,在内存中去更换我们的模板减少 DOM 操作,通过 nodeType 来判断当前的节点,利用正则来匹配 {{}} 通过递归的方式来更换每一个数据。

function Compile(el,vm) {
  vm.$el = document.querySelector(el);  var Fragment = document.createDocumentFragment();  //把模板放入内存当中
  while (child = vm.$el.firstChild) {
    Fragment.appendChild(child)
  }
  replace(Fragment,vm)
  vm.$el.appendChild(Fragment)
}function replace (Fragment,vm){    //类数组转化成数组
  Array.from(Fragment.childNodes).forEach(function(node){    var text = node.textContent ;    var reg = /\{\{(.*)\}\}/;    if(node.nodeType == '3' && reg.test(text)) {        //console.log(RegExp.$1)
        let ary = RegExp.$1.split('.')        //console.log(ary)  [msg, vx]   
        let val = vm  
        ary.forEach(function(key){  //取this.msg  /this.gongzhonghao
            val = val[key]
        });
        node.textContent = text.replace(reg,val)
    }    if(node.childNodes){
        replace(node,vm)
    }
  })
}

发布订阅模式(重点)

以上的操作已经完成了一个简单的数据与模板的绑定,那么大家关心的数据驱动该如何实现?当一个值发生变化的时候视图也发生变化,这就需要我们去订阅一些事件。

ep.addSub(Dep.target) 是增加订阅,dep.notify 函数是发布事件。当值发生改变的时候我们去发布这个事件(调用dep.notify())。

observe(value)
Object.defineProperty(data, key, {
    enumerable:true,    get() {
        console.log(Dep.target)  
        Dep.target && dep.addSub(Dep.target)  //[增加watcher]
        return value
    },    set(newValue) {        //如果值没有发生改变的话
        if(newValue  == value) return
        //重新赋值
        value = newValue        observe(value)
        dep.notify()  //让所有的watcher的update 执行
    },
})

如何去订阅一些事件

说到订阅,那么问题来了,谁是订阅者?怎么往订阅器添加订阅者?在 dep-subs.js 中我指定了 Wathcher 是订阅者。首先要增加 Wathcher 是订阅者,把订阅者放到订阅器(subs)中当值发生变化的时候,订阅器就会调用 update 方法去发布一些事件。
增加订阅
  //绑定的方法都有一个update属性
  function Dep (){    this.subs = []  //订阅器
  }  //增加订阅
  Dep.prototype.addSub = function(watcher) {     this.subs.push(watcher)
  }
发布订阅
//通知Dep.prototype.notify = function() {   this.subs.forEach(watcher => watcher.update())
}
观察者
function Watcher(vm,exp,fn){   this.vm = vm   this.exp = exp   this.fn = fn         //添加到订阅中
   Dep.target = this
   var val  = vm   var arr = exp.split('.');
   arr.forEach(function(key){
       val = val[key]
   })     //在这里调用objectDefineProperty中get方法
   Dep.target = null}
Watcher.prototype.update = function() {  //获取新值
  var val  = this.vm  var arr = this.exp.split('.');
  arr.forEach(function(key){
      val = val[key]
  })  this.fn(val);
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GitChat精品课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Angular1.x 当中的双向数据绑定是通过监听的方式来实现的,核心思想为脏值检查,Angular 通过 $watch()去监听值得变化,然后调用 $apply()/ $digest()方法来实现。
  • 以上的操作已经完成了一个简单的数据与模板的绑定,那么大家关心的数据驱动该如何实现?当一个值发生变化的时候视图也发生变化,这就需要我们去订阅一些事件。
    • 说到订阅,那么问题来了,谁是订阅者?怎么往订阅器添加订阅者?在 dep-subs.js 中我指定了 Wathcher 是订阅者。首先要增加 Wathcher 是订阅者,把订阅者放到订阅器(subs)中当值发生变化的时候,订阅器就会调用 update 方法去发布一些事件。
      • 增加订阅
      • 发布订阅
      • 观察者
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档