精华内容
下载资源
问答
  • vue实现双向绑定原理

    2018-04-11 16:19:20
    vue主要是借助对象的访问器属性(Object.defineProperty)劫持数据,并结合订阅者-发布者模式来实现数据双向绑定。 通过Object.defineProperty把data中的各数据属性改为访问器属性,来劫持每个属性的setter、getter...

    原理:

    vue主要是借助对象的访问器属性(Object.defineProperty)劫持数据,并结合订阅者-发布者模式来实现数据双向绑定。

    通过Object.defineProperty把data中的各数据属性改为访问器属性,来劫持每个属性的setter、getter;setter劫持到数据变化后,作为发布者 发布通知,订阅者们接到通知后更新数据。

    为了进一步说明vue原理,这里我们借助实例来进行讲解。

    实例:

    有一段html代码(mvvm.html),需要保持 输入框中数据和data中数据同步。需求有了,那我们如何实现呢?看代码:

    mvvm.html代码

    <div id="app">
        <input type="text" id="a" v-model='name' />
        {{name}}
    </div>
    

    利用Object.defineProperty把data中的各数据属性改为访问器属性

    //把data中的所有数据属性修改为访问器属性:
    function observe(obj,vm){
        Object.keys(obj).forEach(function(key){
            defineReactive(obj,key,obj[key]);
        })
    }
    //数据属性修改为访问器属性:
    function defineReactive(obj,key,val){
        var dep = new Dep();//主题对象,存放key属性的所有订阅者
        Object.defineProperty(obj,key,{
            get: function(){
                if(Dep.target){
                    dep.subs.push(Dep.target);//添加订阅者
                }
                return val;
            },
            set: function(newVal){
                if(val==newVal)return;
                val = newVal;//数据劫持:当给key属性赋值时,会触发set方法
                dep.notify();//发布通知
            }
        })
    }

    定义主题对象:添加订阅者和发布者方法

    function Dep(){
        this.subs = [];
    }
    Dep.prototype = {
        addSub: function(sub){//添加订阅者
            this.subs.push(sub);
        },
        nodify: function(){//发布通知
            this.subs.forEach(function(sub){
                sub.update();//订阅者更新数据
            })
        }
    }

    订阅者:添加数据更新方法

    function Watcher(vm, node, name){
        Dep.target = this;
        this.name = name;
        this.node = node;
        this.vm = vm;
        this.update();
        Dep.target = null;
    }
    Watcher.prototype = {
        update: function(){//更新数据
            this.get();
            this.node.nodeValue = this.value;
        },
        get: function(){//获取数据
            this.value = this.vm.data[this.name];//触发get
        }
    }

    利用documentFragment(文档片段)来处理节点,处理后再把文档片段插入挂载目标(注:操作documentFragment优于直接操作Dom节)

    function nodeToFragment(node,vm){
        var fragment = document.createDocumentFragment();
        var child;
        while(child = node.firstChild){
            compile(child,vm);//编译数据
            fragment.appendChild(child);
        }
        return fragment;
    }

    编译数据:解析模板指令,将模板中的变量替换成数据

    function compile(node,vm){
        var reg = /\{\{(.*)\}\}/;
        if(node.nodeType==1){
            var attrs = node.attributes;
            for(var i=0,ln=attrs.length;i<ln;i++){
                if(attrs[i].nodeName=='v-model'){
                    var name = attrs[i].nodeValue;
                    node.addEventListener("input",function(event){
                        //给相应的data赋值,触发该属性的set方法
                        vm.data[name]=event.target.value;
                    })
                    node.value = vm.data[name];//将data的值赋值给节点
                    node.removeAttribute('v-model');
                }
            }
        }
        if(node.nodeType==3){
            if(reg.test(node.nodeValue)){
                var name = (node.nodeValue).match(reg)[1];
                name = name.trim();
                new Watcher(vm, node, name);//为节点添加订阅者方法
            }
        }
    }

    创建Vue构造方法:

    function Vue(options){
        this.data = options.data;
        var data = this.data;
    
        observe(data,this);
    
        var id = options.el;
        var dom = nodeToFragment(document.getElementById(id),this);
        document.getElementById(id).appendChild(dom);
    }

    创建Vue示例:调用Vue,实现 输入框数据和data数据同步

    var vm = new Vue({
        el: 'app',
        data: {
            name:'Lucy'
        }
    })

    访问器属性介绍:https://blog.csdn.net/yihanzhi/article/details/79900047
    参考文章:https://segmentfault.com/a/1190000006599500

    展开全文
  • vue数据双向绑定通过‘数据劫持’ + 订阅发布模式实现 数据劫持 指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果 典型的有 1.Object.defineProperty() 2.es6中...

    原理

    vue数据双向绑定通过‘数据劫持’ + 订阅发布模式实现

    数据劫持

    指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果

    典型的有

    1.Object.defineProperty()
    2.es6中Proxy对象

    vue2.x使用Object.defineProperty();
    vue3.x使用Proxy;

    订阅发布模式

    定义:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
    订阅发布模式中事件统一由处理中心处理,订阅者发布者互不干扰。
    优点:实现更多的控制,做权限处理,节流控制之类,例如:发布了很多消息,但是不是所有订阅者都要接收

    // 实现一个处理中心
    let event = {
      clientList: {}, // 订阅事件列表
      // 订阅
      on(key, fn){
        // 如果这个事件没有被订阅,那么创建一个列表用来存放事件
        if(!this.clientList[key]) {
          this.clientList[key] = []
        }
        // 将事件放入已有的事件列表中
        this.clientList[key].push(fn);
      },
      // 发布
      trigger(type, args){
        let fns = this.clientList[type] // 拿到这个事件的所有监听
        if(!fns || fns.length === 0){  // 如果没有这条消息的订阅者
          return false
        }
        // 如果存在这个事件的订阅,那么遍历事件列表,触发对应监听
        fns.forEach(fn => {
          // 可以在此处添加过滤等处理
          fn(args)
        })
      }
    }
    

    vue中如何实现

    利用Object.defineProperty();把内部解耦为三部分
    Observer: 递归的监听对象上的所有属性,当属性改变时触发对应的watcher
    watcher(观察者):当蒋婷的数据值修改时,执行相应的回调函数,更新模板内容
    dep:链接observer和watcher,每一个observer对应一个dep,内部维护一个数组,保存与该observer相关的watcher

    proxy实现观察者模式

    观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行

    const person = observable({
      name: '张三',
      age: 20
    });
    
    function print() {
      console.log(`${person.name}, ${person.age}`)
    }
    
    observe(print);
    person.name = '李四';
    // 输出
    // 李四, 20
    

    代码中。对象person是观察目标,函数print是观察者。一旦数据发生变化,print就会自动执行

    使用proxy实现一个最简单观察者模式,即实现observable和observe这两个函数。
    思路是observable函数返回一个原始对象的proxy代理,拦截复制操作。触发充当观察者的各个函数

    作者:希染丶
    链接:https://www.jianshu.com/p/7e3be3d619e0
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    const queue = new Set();
    
    const observe = fn => queue.add(fn);
    const observable = obj => new Proxy(obj, {set});
    
    function set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      queue.forEach(observer => observer());
      return result;
    } 
    

    上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合,然后,observable函数返回原始对象的代理,拦截赋值操作。
    拦截函数set中,自动执行所有观察者

    展开全文
  • Vue双向数据绑定原理相信大家也都十分了解了,主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里对原理不做过多描述,主要还是来实现一个实例。为了使代码更加的清晰,这里只会...

    目录

    1、原理

    2、实现


     

    在目前的前端面试中,vue的双向数据绑定已经成为了一个非常容易考到的点,即使不能当场写出来,至少也要能说出原理。本篇文章中我将会仿照vue写一个双向数据绑定的实例,名字就叫myVue吧。结合注释,希望能让大家有所收获。

    1、原理

    Vue的双向数据绑定的原理相信大家也都十分了解了,主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里对原理不做过多描述,主要还是来实现一个实例。为了使代码更加的清晰,这里只会实现最基本的内容,主要实现v-model,v-bind 和v-click三个命令,其他命令也可以自行补充。

    添加网上的一张图

    2、实现

    页面结构很简单,如下

    <div id="app">
        <form>
          <input type="text"  v-model="number">
          <button type="button" v-click="increment">增加</button>
        </form>
        <h3 v-bind="number"></h3>
      </div>

    包含:

     1. 一个input,使用v-model指令
     2. 一个button,使用v-click指令
     3. 一个h3,使用v-bind指令。
    

    我们最后会通过类似于vue的方式来使用我们的双向数据绑定,结合我们的数据结构添加注释

    var app = new myVue({
          el:'#app',
          data: {
            number: 0
          },
          methods: {
            increment: function() {
              this.number ++;
            },
          }
        })

    首先我们需要定义一个myVue构造函数:

    function myVue(options) {
      
    }

    为了初始化这个构造函数,给它添加一 个_init属性

    function myVue(options) {
      this._init(options);
    }
    myVue.prototype._init = function (options) {
        this.$options = options;  // options 为上面使用时传入的结构体,包括el,data,methods
        this.$el = document.querySelector(options.el); // el是 #app, this.$el是id为app的Element元素
        this.$data = options.data; // this.$data = {number: 0}
        this.$methods = options.methods;  // this.$methods = {increment: function(){}}
      }

    接下来实现_obverse函数,对data进行处理,重写data的set和get函数

    并改造_init函数

     myVue.prototype._obverse = function (obj) { // obj = {number: 0}
        var value;
        for (key in obj) {  //遍历obj对象
          if (obj.hasOwnProperty(key)) {
            value = obj[key]; 
            if (typeof value === 'object') {  //如果值还是对象,则遍历处理
              this._obverse(value);
            }
            Object.defineProperty(this.$data, key, {  //关键
              enumerable: true,
              configurable: true,
              get: function () {
                console.log(`获取${value}`);
                return value;
              },
              set: function (newVal) {
                console.log(`更新${newVal}`);
                if (value !== newVal) {
                  value = newVal;
                }
              }
            })
          }
        }
      }
     
     myVue.prototype._init = function (options) {
        this.$options = options;
        this.$el = document.querySelector(options.el);
        this.$data = options.data;
        this.$methods = options.methods;
       
        this._obverse(this.$data);
      }

    接下来我们写一个指令类Watcher,用来绑定更新函数,实现对DOM元素的更新

    function Watcher(name, el, vm, exp, attr) {
        this.name = name;         //指令名称,例如文本节点,该值设为"text"
        this.el = el;             //指令对应的DOM元素
        this.vm = vm;             //指令所属myVue实例
        this.exp = exp;           //指令对应的值,本例如"number"
        this.attr = attr;         //绑定的属性值,本例为"innerHTML"
    
        this.update();
      }
    
      Watcher.prototype.update = function () {
        this.el[this.attr] = this.vm.$data[this.exp]; //比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新。
      }

    更新_init函数以及_obverse函数

    myVue.prototype._init = function (options) {
        //...
        this._binding = {};   //_binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
        //...
      }
     
      myVue.prototype._obverse = function (obj) {
        //...
          if (obj.hasOwnProperty(key)) {
            this._binding[key] = {    // 按照前面的数据,_binding = {number: _directives: []}                                                                                                                                                  
              _directives: []
            };
            //...
            var binding = this._binding[key];
            Object.defineProperty(this.$data, key, {
              //...
              set: function (newVal) {
                console.log(`更新${newVal}`);
                if (value !== newVal) {
                  value = newVal;
                  binding._directives.forEach(function (item) {  // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新
                    item.update();
                  })
                }
              }
            })
          }
        }
      }

    那么如何将view与model进行绑定呢?接下来我们定义一个_compile函数,用来解析我们的指令(v-bind,v-model,v-clickde)等,并在这个过程中对view与model进行绑定。

     myVue.prototype._init = function (options) {
       //...
        this._complie(this.$el);
      }
     
    myVue.prototype._complie = function (root) { root 为 id为app的Element元素,也就是我们的根元素
        var _this = this;
        var nodes = root.children;
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes[i];
          if (node.children.length) {  // 对所有元素进行遍历,并进行处理
            this._complie(node);
          }
    
          if (node.hasAttribute('v-click')) {  // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++
            node.onclick = (function () {
              var attrVal = nodes[i].getAttribute('v-click');
              return _this.$methods[attrVal].bind(_this.$data);  //bind是使data的作用域与method函数的作用域保持一致
            })();
          }
    
          if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件
            node.addEventListener('input', (function(key) {  
              var attrVal = node.getAttribute('v-model');
               //_this._binding['number']._directives = [一个Watcher实例]
               // 其中Watcher.prototype.update = function () {
               //    node['vaule'] = _this.$data['number'];  这就将node的值保持与number一致
               // }
              _this._binding[attrVal]._directives.push(new Watcher(  
                'input',
                node,
                _this,
                attrVal,
                'value'
              ))
    
              return function() {
                _this.$data[attrVal] =  nodes[key].value; // 使number 的值与 node的value保持一致,已经实现了双向绑定
              }
            })(i));
          } 
    
          if (node.hasAttribute('v-bind')) { // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可
            var attrVal = node.getAttribute('v-bind');
            _this._binding[attrVal]._directives.push(new Watcher(
              'text',
              node,
              _this,
              attrVal,
              'innerHTML'
            ))
          }
        }
      }

    至此,我们已经实现了一个简单vue的双向绑定功能,包括v-bind, v-model, v-click三个指令。效果如下图

     

    附上全部代码,不到150行

    <!DOCTYPE html>
    <head>
      <title>myVue</title>
    </head>
    <style>
      #app {
        text-align: center;
      }
    </style>
    <body>
      <div id="app">
        <form>
          <input type="text"  v-model="number">
          <button type="button" v-click="increment">增加</button>
        </form>
        <h3 v-bind="number"></h3>
      </div>
    </body>
    
    <script>
      function myVue(options) {
        this._init(options);
      }
    
      myVue.prototype._init = function (options) {
        this.$options = options;
        this.$el = document.querySelector(options.el);
        this.$data = options.data;
        this.$methods = options.methods;
    
        this._binding = {};
        this._obverse(this.$data);
        this._complie(this.$el);
      }
     
      myVue.prototype._obverse = function (obj) {
        var value;
        for (key in obj) {
          if (obj.hasOwnProperty(key)) {
            this._binding[key] = {                                                                                                                                                          
              _directives: []
            };
            value = obj[key];
            if (typeof value === 'object') {
              this._obverse(value);
            }
            var binding = this._binding[key];
            Object.defineProperty(this.$data, key, {
              enumerable: true,
              configurable: true,
              get: function () {
                console.log(`获取${value}`);
                return value;
              },
              set: function (newVal) {
                console.log(`更新${newVal}`);
                if (value !== newVal) {
                  value = newVal;
                  binding._directives.forEach(function (item) {
                    item.update();
                  })
                }
              }
            })
          }
        }
      }
    
      myVue.prototype._complie = function (root) {
        var _this = this;
        var nodes = root.children;
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes[i];
          if (node.children.length) {
            this._complie(node);
          }
    
          if (node.hasAttribute('v-click')) {
            node.onclick = (function () {
              var attrVal = nodes[i].getAttribute('v-click');
              return _this.$methods[attrVal].bind(_this.$data);
            })();
          }
    
          if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
            node.addEventListener('input', (function(key) {
              var attrVal = node.getAttribute('v-model');
              _this._binding[attrVal]._directives.push(new Watcher(
                'input',
                node,
                _this,
                attrVal,
                'value'
              ))
    
              return function() {
                _this.$data[attrVal] =  nodes[key].value;
              }
            })(i));
          } 
    
          if (node.hasAttribute('v-bind')) {
            var attrVal = node.getAttribute('v-bind');
            _this._binding[attrVal]._directives.push(new Watcher(
              'text',
              node,
              _this,
              attrVal,
              'innerHTML'
            ))
          }
        }
      }
    
      function Watcher(name, el, vm, exp, attr) {
        this.name = name;         //指令名称,例如文本节点,该值设为"text"
        this.el = el;             //指令对应的DOM元素
        this.vm = vm;             //指令所属myVue实例
        this.exp = exp;           //指令对应的值,本例如"number"
        this.attr = attr;         //绑定的属性值,本例为"innerHTML"
    
        this.update();
      }
    
      Watcher.prototype.update = function () {
        this.el[this.attr] = this.vm.$data[this.exp];
      }
    
      window.onload = function() {
        var app = new myVue({
          el:'#app',
          data: {
            number: 0
          },
          methods: {
            increment: function() {
              this.number ++;
            },
          }
        })
      }
    </script>

    本面试题为前端常考面试题,后续有机会继续完善。我是歌谣,一个沉迷于故事的讲述者。

    欢迎一起私信交流。

    “睡服“面试官系列之各系列目录汇总(建议学习收藏) 

    展开全文
  • 自定义指令directives ...这是局部的,全局的需要Vue. directives:{ color:{ //自定义指令名 bind:function(el,binding,vnode){ //bind是钩子函数 el.style.color=binging.value ... el:是当前元素,默...

    自定义指令directives

    这是局部的,全局的需要Vue.
    directives:{
        color:{ //自定义指令名
            bind:function(el,binding,vnode){ //bind是钩子函数
                el.style.color=binging.value
                ...
                el:是当前元素,默认传进来的
                binding:是自定义指令的参数,一般在function中使用binding.value;可有可没有
                vnode:虚拟dom节点
            }
    }
    
    <div v-color='red'>abc</color>

    1. 钩子函数

    • bind: 当每次将该自定义指令绑定时自动调用;且只调用一次。
    • unbind:当元素与该指令解绑时
    • inserted:当该元素被渲染到(添加到)html页面的Dom中时
    • update:所有组件的vnode更新时 (vnode就是虚拟dom节点)
    • componentUpdated:该组件的vnode及子vnode全部全部更新后调用

    2.钩子函数的一些参数

    • el:指令所绑定的元素,可以用来直接操作 DOM 。
    • binding:一个对象,包含以下属性:
      • name:指令名,不包括 v- 前缀。binding.name
      • value:指令的绑定值,例如:<p v-my-directive="1 + 1">中,绑定值为2。binding.value。同时也可以<p v-my-directive="{id:1,text:'abc'}"> 这样的话传入的是一个对象,那么就是binding.value.text。
      • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
      • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
      • arg:传给指令的参数(:),可选。例如 v-my-directive:foo 中,参数为 "foo"
      • modifiers:一个包含修饰符(.)的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
    • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
    • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

    3. 钩子函数能够动态定义指令的参数:arg

    <p v-my-directives:[arg]='value'> example 注意引号是否存在 </p>
    
    data:{
        arg:'left',
        value:'200px'
    }

    4. 简写方法

    上面的那个自定义指令例子是全写,简写指的是不注明钩子函数,此时默认绑定bind和update

    directive:{ //指令:v-color
        color:function(...){....} //默认绑定bind和update
    }

    Keep-alive

    https://www.jianshu.com/p/9523bb439950

    • 定义

    在开发中经常有从列表跳到详情页,然后返回详情页的时候需要缓存列表页的状态(比如滚动位置信息),这个时候就需要保存状态,要缓存状态。

    keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。总的来说,keep-alive用于保存组件的渲染状态。

    <keep-alive>
        <com1> 或者 <router-view>
    </keep-alive>
    • 属性 props

    1. include:定义缓存白名单
    2. exclude:定义缓存黑名单
    3. max:最多能缓存多少组件
    4. activated 和 deactivate 生命周期钩子
    • 实现步骤

    1.a,b,c会被keep-alive
    <keep-alive include='a,b,c'>
        <router-view>
    </keep-alive>
    
    2.
    <keep-alive>
        <router-view v-if='this.$router.meta.keepAlive'></router-view>
    </keep-alive>
    同时给router设置meta:
    export default{
        path:'',
        component:'',
        meta:{
            keepAlive:true
        }
    }
    1. 第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;
    2. 第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;
    3. 第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;
    4. 第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key);
    5. 第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true。(keepAlive在meta中)
    • 生命周期过程

             设置了keep-alive的组件:
                 第一次进入: beforeRouteEnter => created => ... => activated => ... => deactivated
                 后续进入:beforeRouteEnter => activated => deactivated,
                 只有第一次进入该组件时,才会走created钩子,需要缓存的组件中activated时每次都会走的钩子函数

    • 页面滚动问题

    https://juejin.im/post/5b2ce07ce51d45588a7dbf76

    在router实例里面添加scrollBehavior方法

    const router=new VueRouter({
            routes:[
                {
                    path:"/",
                    component:Home
                }
            ],
            scrollBehavior(to,form,savedPosition){
             if(savedPosition){
                点击按钮时返回的页面会滚动过到之前按钮
                return savedPosition
             }else{
               设置返回值来指定页面的滚动位置,表示在用户切换路由时让是所有页面都返回到顶部位置
               return {x:0,y:0}
             }
         }
    })

    如果是通过vue路由进行的页面切换。例如a前往b,首先判断a是不是通过keep-alive缓存的组件,如果是,则在a路由的meta中添加一个savedPosition字段,并且值为a的滚动位置。最后return的是页面需要回滚的位置。如此一来,如果打开一个页面,该页面的组件路由中meta.savedPosition为undefined的话,则页面滚动到(0,0)的位置。那么如果打开一个页面,它的路由的meta.savedPosition有值的话,则滚动到上次浏览的位置,因为meta.savedPosition保存的就是上次浏览的位置。


    Vue实现双向绑定

    1.首先背下来什么是MVVM

    2.实现步骤

    首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

    1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

    并且实现一个订阅者管理器。这个管理器中有两个方法:添加订阅者方法和通知订阅者方法。还有一个target属性和一个subs数组。target是当前的订阅者。subs数组是订阅者数组。

    下面的代码最好背下来。

    function defineReactive(data, key, val) {
        observe(val); 
        var dep = new Dep(); // 背下来:定义一个订阅器
        Object.defineProperty(data, key, {
            enumerable: true,  // 背下来
            configurable: true,  // 背下来
            get: function() { // 背下来
                if (dep.target) {  // 背下来
                    dep.addSub(dep.target); // // 背下来:在这里添加一个订阅者
                }
                return val;// 背下来
            },
            set: function(newVal) {
                if (val === newVal) {
                    return;
                }
                val = newVal;
                dep.notify(); // // 背下来:如果数据变化,通知所有订阅者
            }
        });
    }
     
    function Dep () {
        this.subs = [];
    }
    Dep.prototype = {
        addSub: function(sub) { // 背下来:添加订阅者
            this.subs.push(sub);
        },
        notify: function() {
            this.subs.forEach(function(sub) { // 背下来:通知更新订阅者
                sub.update();
            });
        }
    };
    

    2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

    3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。


    $refs和ref

    ref可以加到dom上也可以加到组件上,ref相当于name的作用,返回一个节点。

    <com ref='com'></com>              
    <div ref='div1'>div test</div>     
    
    this.$refs.com           返回一个com组件对象 com:{data,methods,computed ...}
    this.$refs.div1          返回一个节点 <div>div test</div>
    • $refs是当前组件的ref的集合。但是this.$refs. 不能写道模板里<template>,因为他们不是响应式的
    • 在created及beforecreated之前,$refs获取不到。

    三大作用:

    1. 父组件的vue js代码中获得子组件的data、method、computed

    子组件.vue
    data:{
        msg:'son'
    },
    methods:{
        test_son:function(){ ... }
    }
    父组件.vue
    <com ref='com'></com>
    
    methods:{
        test_father:function(){
            this.$refs.com.test_son()
            console.log(this.$refs.com.msg)
        }
    }

    2.子组件获得父组件的方法:通过this.$emit('触发父组件methods的v-on名')

    点击button
    父组件.vue
    <com ref='com' @test2='test_father'></com>
    <button @test1='get_son'>click</button>
    
    methods:{
        get_son:function(){
            this.$refs.com.test_son()
        },
        test_father:function(){
            console.log('output father')
        }
    }
    子组件.vue
    methods:{
        test_son:function(){
            this.$emit('test2')
        }
    }

    $ref 和 v-for一起使用:(ref=变量,前别忘记加:)

    $refs 和 v-for一起用的时候比较特殊,(和v-for一起使用时,返回的一定是一个 数组 );$refs 会受到 v-if、v-for、v-show 这些语句的影响

    <body>
    <div  id="app">
      <ul >
        <li v-for='item in array' :name='item.name' :ref='item.name'>{{item.name}}</li>
      </ul>
       <button @click='c1'>click</button>
    </div>
    
     <script>
    var vm=new Vue({
      el:'#app',
      data:{
        array:[
           {key:1,name:'one'},
           {key:2,name:'two'}
        ]
      },
      methods:{
        c1:function(){
          var a = this.$refs.one
          console.log(this.$refs.one)   // !! attention !!
          console.log(a[0])             // !! attention !!
        }
      }
    })
      </script>
    </body>

    (这里注意一下,如果是:ref='item.key',下面就是undefined了。因为$refs只接收字符串)

    第一个console返回是数组。

    另一种写法:(有几个博客是这么写的)

    <body>
    <div  id="app">
      <ul >
        <li v-for='item in array' :name='item.name' :ref='item.tag'>{{item.name}}</li>
      </ul>
       <button @click='c1'>click</button>
    </div>
     
     <script>
    var vm=new Vue({
      el:'#app',
      data:{
        array:[
           {key:1,name:'one',tag:'arrayx'},
           {key:2,name:'two',tag:'arrayx'}
        ]
      },
      methods:{
        c1:function(){
          var ary=this.$refs.arrayx
          console.log(ary)
          var q=ary[1];
          console.log(q)
          console.log(q.getAttribute('name'))
        }
      }
    })
      </script>
    </body>

    (这里有用模板匹配的方法,我没看:https://blog.csdn.net/LzzMandy/article/details/91372406



    $nextTick()

    (和前面的event loop一起食用更加)

    nextTick(function(){...}),是将其内部的回调函数function延迟在下一次dom更新数据后调用。同一事件循环DOM 更新会引起nextTick回调函数触发

    <template>
      <div class="hello">
        <div>
          <button id="firstBtn" @click="testClick()" ref="aa">{{testMsg}}</button>
        </div>
      </div>
    </template>
     
    <script>
    export default {
      name: 'HelloWorld',
      data () {
        return {
          testMsg:"原始值",
        }
      },
      methods:{
        testClick:function(){
          this.testMsg="修改后的值";
          this.$nextTick(function(){
            console.log(that.$refs.aa.innerText);  //输出:“修改后的值”
          });
          //这里正常应该输出“原始值”
       }
      }
    }
    </script>
     

    nextTick()的用处:

    1. Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳。
    2. 当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中
    3. 一些插件

    nextTick()原理:

    Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个event loo的数据变化的推送进这个队列。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

    当你修改 vm.msg = '新值',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。

    简单的流程:同步代码执行 -> 查找异步队列,推入执行栈,执行Vue.nextTick[事件循环1] ->查找异步队列,推入执行栈,执行Vue.nextTick[事件循环2]...

     

    主线程完成同步环境执行,查询任务队列,提取队首的任务,放入主线程中执行;执行完毕,再重复该操作,该过程称为事件循环。而主线程的每次读取任务队列操作,是一个事件循环的开始。

     

     

    展开全文
  • vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的.本文重点给大家介绍Vue数据双向绑定原理及简单实现方法,感兴趣的朋友跟随脚本之家小编一起学习吧
  • 主要为大家详细介绍了Vue 3.0双向绑定原理实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要为大家详细介绍了Vue 2.0双向绑定原理实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图来吸引各位:...
  • 1. vue数据双向绑定原理 vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西。 var vm = new Vue({ data: { obj:...
  • 学习vue也有一段时间了,对双向绑定原理实现一直有所了解,但是并没有深入了解其实现原理。所以花了时间和查阅了一些资料,自己动手尝试实现了简单的vue的双向绑定。 本文主要分为两部分: vue的数据双向绑定的...
  • 自我评价 vue数据双向绑定原理分析和简单实现 代码总计分为三个版本,v3为最终版本 效果图 博客地址
  • 主要介绍了Vue实现双向绑定原理以及响应式数据的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,078
精华内容 831
关键字:

vue实现双向绑定原理

vue 订阅