• vue响应式原理解析


    # Vue响应式原理解析

    首先定义了四个核心的js文件
    - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中
    - 2. watcher.js 监听者函数,用来设置dep.target开启依赖收集的条件,和触发视图的更新函数
    - 3. compile.js 编译者函数,用来编译模版和实例化 watcher 函数
    - 4. index.js 入口文件
    注意dep函数就是一个壳子,用来存放watcher和触发watcher更新的
    首先从index.js开始,定义函数 SelfVue
    ```js
    function SelfVue (options) {
    Object.keys(this.data).forEach(function(key) {
    self.proxyKeys(key);
    //遍历data,给每一个key执行函数 proxyKeys,定义get、set
    //这样就可以通过 this.name 来获取data中的name值了
    //否则的话必须通过 this.data.name 才能获取
    });
    observe(this.data); //观察this.data,设置好各种监听的规则
    new Compile(options.el, this);//在这里处理编译和调用watcher的函数
    options.mounted.call(this); // 所有事情处理好后执行mounted函数
    }
    ```
    然后看 observe函数:
    ```js
    defineReactive: function(data, key, val) {
    var dep = new Dep();//初始化dep,dep用来存放watcher
    Object.defineProperty(data, key, {
    get: function getter () {
    //只有在watcher中才会设置Dep.target,所以只有在watcher中才会去增加监听
    if (Dep.target) {//Dep.target就是保存的Watcher自己
    dep.addSub(Dep.target);
    }
    return val;
    },
    set: function setter (newVal) {
    if (newVal === val) {
    return;
    }
    val = newVal;
    dep.notify();
    }
    });
    }
    ```
    然后看watcher
    ```js
    function Watcher(vm, exp, cb) {
    this.value = this.get(); // 将自己添加到订阅器的操作
    /*
    在这里执行this.get(),也就是调用了 this.vm.data[this.exp]
    即调用了观察者中的get函数:
    首先给Dep.target赋值,在观察者函数中,打开了
    if (Dep.target) {
    dep.addSub(Dep.target); //所以在这里可以向dep中添加watcher函数
    }
    */
    }
    Watcher.prototype = {
    get: function() {
    Dep.target = this; // 缓存自己
    var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
    Dep.target = null; // 添加到 dep 中之后,再释放自己
    return value;
    }
    }
    ```
    这样每次 new watcher的时候就会实例化watcher
    然后就会调用this.value = this.get();
    然后就会执行 this.vm.data[this.exp]
    就会调用观察者函数中的 get方法,由于此时设置了dep.target
    所以就会保存watcher到dep中
    ```js
    new Watcher(this.vm, exp, function (value) {
    self.updateText(node, value);
    });

    ```
    再来看compile.js中是何时触发监听watcher函数的,该文件做了三件事情:
    ```js
    this.fragment = this.nodeToFragment(this.el); //获取挂载元素为代码片段
    this.compileElement(this.fragment);//划分该代码片段的类型,执行编译
    this.el.appendChild(this.fragment);//挂载该代码片段到html上
    ```
    所以核心代码是compileElement函数:
    ```js
    [].slice.call(childNodes).forEach(function(node) {
    var reg = /{{(.*)}}/;
    var text = node.textContent;
    if (self.isElementNode(node)) { //v-model指令、v-on:click方法
    self.compile(node);
    } else if (self.isTextNode(node) && reg.test(text)) {//文本节点
    self.compileText(node, reg.exec(text)[1]);
    }
    if (node.childNodes && node.childNodes.length) {//子节点继续循环遍历
    self.compileElement(node);
    }
    });
    ```
    循环遍历模板代码,按照:文本节点、v-model指令、v-on:click方法做不同的逻辑处理:
    但是都会用到该函数
    ```js
    new Watcher(this.vm, exp, function (value) {
    self.updateText(node, value);
    });
    ```
    如上所述,实例化 Watcher的时候,就是给模板中用到的exp,向dep中增加watcher函数,
    而watcher函数包括的方法:更新和get函数。
    所以遍历完模板后,实例化 watcher,然后就会执行 watcher 中的get函数,实现监听功能。
    ```js
    Watcher.prototype = {
    update: function() {
    var value = this.vm.data[this.exp];
    var oldVal = this.value;
    if (value !== oldVal) {
    this.value = value;
    this.cb.call(this.vm, value, oldVal);
    }
    },
    get: function() {
    Dep.target = this; // 缓存自己
    var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
    Dep.target = null; // 释放自己
    return value;
    }
    };

    ```
    ---
    待数据发生变化时,会触发观察者函数中的 set 函数:
    ```js
    set: function setter (newVal) {
    if (newVal === val) {
    return;
    }
    val = newVal;
    dep.notify();
    }
    ```
    然后就会通知dep更新,这里注意的是,如果该值没有在模板中使用,this.sub就是空数组,所以这里通知函数中也不会更新视图:
    ```js
    notify: function() {
    //虽然上面data的所有值发生变化的时候会触发set和dep.notify();
    //但是在这里只是会循环遍历每个之前监听到的watcher---this.subs
    //所以,如果在html中没有用到的数据,即使在methods中使用到了
    //在这里也不会触发视图更新
    this.subs.forEach(function(sub) {
    sub.update();
    });
    }

    ```

    如果模板中使用了两次data中的title:
    <h2>{{title}}</h2>
    <h1>{{title}}</h1>
    则对data循环后,针对title变量,有两个watcher,存在针对该data值:title的this.sub数组中。
    所以如果没有在模板中使用到的data,比如age变量,
    在set函数中,由于模板中没有用到-->则不会执行new Watcher-->则不会赋值给dep.target-->则不会给 dep中收集依赖,保存watcher;
    在get函数中,由于模板中没有用到,对应的dep.sub数组中就是空数组。所以即使set函数通知了dep.notify函数,也会应为是空数组,导致不会执行循环,也无法触发watcher的更新视图函数
  • 相关阅读:
    DOM2DOM3续
    总结 @ 在 C# 中的用法 (装载)
    ORACLE10G卸载过程
    .net中访问oracle数据库的几种方式(转载)
    试图运行项目时出错,无法启动调试。没有正确安装调试器。请运行安装程序安装或修复调试器
    设计模式 构造器
    设计模式 抽象工厂
    linq中日期格式转换或者比较,程序报错说不支持方法的解决办法
    bootstrap图标字体不出来问题的解决办法
    JavaScript对象属性访问的两种方式
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/12038895.html
Copyright © 2020-2023  润新知