zoukankan      html  css  js  c++  java
  • vue_源码 原理 剖析

    相关基础知识点

    // 可以让 任意函数/方法 成功临时指定成对象的方法进行调用 - call/apply

    // 1. 根据伪数组生成 真数组

    const lis = document.getElementsByTagName("li");

    const arr = [].slice.call(lis);        

    const relArr = Array.from(lis);        // ES6语法

    // 2. node.nodeType        // 得到节点类型

    // 3. 给对象添加属性,指定 属性描述符:存取描述符 get(),set() - 数据描述符

    Object.defineProperty(ojb, propertyName, {

    get(){return this.firstNAme+"-"+this.lastNAme},

    set(newVAlue){const names=newValue.split("-");this.firstNAme=names[0];this.lastNAme=names[1]},

    })

    // 4. Object.keys(obj);        // 得到对象自身可枚举属性组成的数组

    // 5. obj.hasOwnProperty("name")       // 判断 obj 对象自身属性是否包含 name

    // 6. DocumentFragment 文档碎片(高效更新多个节点)

    内存中用来存储多个节点对象 的容器,不与任何 页面/页面节点 对应

    思路: 先将节点复制到内存中,在内存中修改完后,一次性添加到页面中

    // 1. 创建一个内存中 节点容器

    const fragment = document.createDocumentFragment();

    // 2. 取出 div 中所有节点,转移到 fragment 中

    const div = document.getElementById("demo");

    let child;        // newPos = appendChild(child) 会将 child 从原来位置取下来,放到 newPos 的最后位置

    while(child=div.firstChild){fragment.appendChild(child)}        

    // 3. 在内存中遍历修改

    const nodes = fragment.children[0].childNodes;

    Array.prototype.slice.call(nodes).forEach(node=>{

    if(node.nodeType === 1){

    node.textContent = "丘魔的兵"

    }

    })

    // 4. 一次性添加到页面

    div.appendChild(fragment);

     

    在 git 上,有个开源项目,剖析了 vue 原理

    • 数据代理

    为达到简化编码的目的,

    内部实现的关键点: 通过 Object.defineProperty(vm, key, {}) 实现

    对于 data 中状态的数据,通过 ViewModel 实例 来进行 代理读/代理写 操作 

    ---------------------------------------------------------------

    function Vue(configObj){

    var me = this;

    // 1. 保存 配置对象 到 ViewModel

    this.$configObj = configObj;

    // 2. 保存 data 对象到 ViewModel 和 变量 data 上

    var data = this._data = this.$configObj.data;

    // 3. 遍历 data 中所有属性, 实现数据代理

    Object.keys(data).forEach(function (key) {

    me._proxy(key);

    });

    // 实现数据绑定

    observe(data, this);

    // 实现模版解析

    this.$compile = new Compile(options.el || document.body, this)

    }

    Vue.prototype = {

    // 

    _proxy: function(key){

    var vm = this;

    // 给 vm 添加指定属性,使用 属性描述符 (设置 getter/setter)

    Object.defineProperty(vm, key, {

    configurable:  false,

    enumerable: true,

    get: function proxyGetter(){    // 代理读

    return vm._data[key]

    },

    set: function proxySetter(newValue){    // 代理写

    vm._data[key] = newValue

    }

    })

    },

    $watch: function (key, cb, options) {

    new Watcher(this, key, cb);

    }

    }

    • 模板解析
    • 模板表达式

    • 指令
    • 事件指令

    • 一般指令

    this.$compile = new Compile(options.el || document.body, this)

    • function Compile(el, vm) {
          this.$vm = vm;
          this.$el = this.isElementNode(el) ? el : document.querySelector(el);
      
          if (this.$el) {    // 整体流程:
              this.$fragment = this.node2Fragment(this.$el);    // 1. 将 目标元素节点 拷贝到 DocumentFragment
              this.init();    // 2. 内存中编译 DocumentFragment 
              this.$el.appendChild(this.$fragment);    // 3. 将 DocumentFragment 追加到目标元素节点
          }
      }
      
      Compile.prototype = {
          node2Fragment: function(el) {
              var fragment = document.createDocumentFragment(),
                  child;
      
              // 将原生节点拷贝到fragment
              while (child = el.firstChild) {
                  fragment.appendChild(child);
              }
      
              return fragment;
          },
      
          init: function() {
              this.compileElement(this.$fragment);    // 传入待处理节点,进行编译处理
          },
      
          compileElement: function(el) {
              var childNodes = el.childNodes,
                  me = this;
      
              [].slice.call(childNodes).forEach(function(node) {
                  var text = node.textContent;    // 换行
                  var reg = /{{(.*)}}/;    // 匹配 {{模板表达式}}
      
                  if (me.isElementNode(node)) {
                      me.compile(node);
                  } else if (me.isTextNode(node) && reg.test(text)) {    // 如果是 {{模板表达式}}
      // 则 解析 成node.textContent = 模板表达式,不管匹配成功几个表达式,总是只生效第一个

      me.compileText(node, RegExp.$1);
      }
      if (node.childNodes && node.childNodes.length) {    // 如果还有子节点,则递归编译
                      me.compileElement(node);
                  }
              });
          },
      
          compile: function(node) {
              var nodeAttrs = node.attributes,
                  me = this;
      
              [].slice.call(nodeAttrs).forEach(function(attr) {
                  var attrName = attr.name;
                  if (me.isDirective(attrName)) {
                      var exp = attr.value;    // 得到表达式 即 回调函数名
                      var dir = attrName.substring(2);    // 得到指令
                      // 事件指令
                      if (me.isEventDirective(dir)) {
                          compileUtil.eventHandler(node, me.$vm, exp, dir);
                          // 普通指令
                      } else {
                          compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                      }
      
                      node.removeAttribute(attrName);
                  }
              });
          },
      
          compileText: function(node, exp) {
              compileUtil.text(node, this.$vm, exp);
          },
      
          isDirective: function(attr) {
              return attr.indexOf('v-') == 0;
          },
      
          isEventDirective: function(dir) {
              return dir.indexOf('on') === 0;
          },
      
          isElementNode: function(node) {
              return node.nodeType == 1;
          },
      
          isTextNode: function(node) {
              return node.nodeType == 3;
          }
      };
      
      // 用于编译的方法 - 指令处理集合
      var compileUtil = {
          text: function(node, vm, exp) {    // 编译 v-text 或者 {{模板表达式}}
              this.bind(node, vm, exp, 'text');
          },
      
          html: function(node, vm, exp) {    // v-html
              this.bind(node, vm, exp, 'html');
          },
      
          model: function(node, vm, exp) {    // v-model
              this.bind(node, vm, exp, 'model');
      
              var me = this,
                  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;
              });
          },
      
          class: function(node, vm, exp) {    // v-bind:class
              this.bind(node, vm, exp, 'class');
          },
      
          bind: function(node, vm, exp, dir) {
              var updaterFn = updater[dir + 'Updater'];    // 根据指令名 得到一个用于更新节点的更新函数
      
              updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 得到指定表达式对应的值
              
      // 解析每个表达式后 - 为表达式创建对应的 watcher 对象(事件表达式除外)
      new Watcher(vm, exp, function(value, oldValue) { updaterFn && updaterFn(node, value, oldValue); // 更新节点 }); }, // 事件处理 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); } }, _getVMVal: function(vm, exp) { var val = vm._data; exp = exp.split('.'); exp.forEach(function(k) { val = val[k]; }); return val; }, _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; } }); } }; // 更新节点的方法集 var updater = { textUpdater: function(node, value) { // v-text 或者 {{模板表达式}} node.textContent = typeof value == 'undefined' ? '' : value; }, htmlUpdater: function(node, value) { // v-html node.innerHTML = typeof value == 'undefined' ? '' : value; }, classUpdater: function(node, value, oldValue) { // v-bind:class var className = node.className; className = className.replace(oldValue, '').replace(/s$/, ''); var space = className && String(value) ? ' ' : ''; node.className = className + space + value; }, modelUpdater: function(node, value, oldValue) { // v-model node.value = typeof value == 'undefined' ? '' : value; } };

     

    • 数据绑定 -------- 双向数据绑定

    使用 数据劫持技术 实现数据绑定

    思想: 通过 defineProperty() 来监视 data 中所有状态属性(任意层次)的变化,一旦变化了,界面会实时更新

    Observer

    用来对 data 中数据进行监视 的构造函数

    为每个 data 数据设置 getter  和 setter ,并配置 dep 对象

    Compile

    用来解析模板页面对象的构造函数

    利用 compile 对象解析模板页面

    每解析一个表达式都会创建一个 watcher 对象,并建立 watcher 和 dep 的关系

     

    1 个 watcher 对应 n>=1 个 dep -------- 假设表达式是 2 层表达式 mom.son 时,当前 watcher 就对应 2 个 dep

    1 个 dep 对应 n>=0 个 watcher -------- 没有一个表达式用到这个属性; 只有一个表达式用到这个属性; 多个表达式用到这个属性

    每个 dep 都有唯一的 id

    subs 包含 n 个对应 watcher 的数组 ---- subcribes 的简写

    • function Observer(data) {
          this.data = data;
          this.walk(data);
      }
      
      Observer.prototype = {
          walk: function(data) {
              var me = this;
              Object.keys(data).forEach(function(key) {
                  me.convert(key, data[key]);
              });
          },
          convert: function(key, val) {    // 给每个对象设置 响应式属性
              this.defineReactive(this.data, key, val);
          },
      
          defineReactive: function(data, key, val) {    // 对某个属性进行劫持
              var dep = new Dep();    // 在每个属性进行劫持前创建一个对应的 dep 对象
              var childObj = observe(val);    // 递归实现属性下所有层次属性的劫持
      
              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;
                      
                      childObj = observe(newVal);    // 新的值是object的话,进行监听
                      
                      dep.notify();// 通知订阅者
                  }
              });
          }
      };
      
      function observe(value, vm) {
          if (!value || typeof value !== 'object') {
              return;
          }
      
          return new Observer(value);
      };
      
      
      var uid = 0;
      
      function Dep() {
          this.id = uid++;
          this.subs = [];
      }
      
      Dep.prototype = {
          addSub: function(sub) {
              this.subs.push(sub);
          },
      
          depend: function() {
              Dep.target.addDep(this);
          },
      
          removeSub: function(sub) {
              var index = this.subs.indexOf(sub);
              if (index != -1) {
                  this.subs.splice(index, 1);
              }
          },
      
          notify: function() {
              this.subs.forEach(function(sub) {
                  sub.update();
              });
          }
      };
      
      Dep.target = null;

    5

    5

    5

    55

    5

    5

    --------小尾巴 ________一个人欣赏-最后一朵颜色的消逝-忠诚于我的是·一颗叫做野的心.决不受人奴役.怒火中生的那一刻·终将结束...
  • 相关阅读:
    Java快速教程
    让我们来了解一下:操作系统和平台相关性
    初窥Linux 之 我最常用的20条命令
    ES6学习笔记一
    Data时间管理大全
    generator多返回值写法
    箭头函数=>
    闭包
    高阶函数:map/reduce
    函数方法that与apply
  • 原文地址:https://www.cnblogs.com/tianxiaxuange/p/10402258.html
Copyright © 2011-2022 走看看