Vue 原理分析

1. vue 生命周期

2. vuex 原理

An image

3. 数据代理

3.1 通过定义单个属性实现数据代理

let data = { x: 100 };
let dataProxyer = {};

// dataProxyer对象的x属性值代理了data对象的x属性值
// 1.获取data中的某个key值可以通过dataProxyer的key获取
// 2.修改data中的某个key值可以通过修改dataProxyer的key实现

Object.defineProperty(dataProxyer, "x", {
  get() {
    return data.x;
  },
  set(value) {
    data.x = value;
  },
});

console.log("data.x的初始值为:", data.x);
// data.x的初始值为:100

// 通过数据代理dataProxyer修改了data的x属性的值
dataProxyer.x = 1000;

console.log("通过数据代理修改,data.x的最终值为:", data.x);
// 通过数据代理修改,data.x的最终值为:1000

4. 双向绑定

双向绑定的原理图

Alt text

实现代码 demo

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <span>标题:{{name}}</span>
      <input type="text" v-model="name">
      <span>更多:{{more.like}}</span>
      <input type="text" v-model="more.like">
    </div>

    <script src="./minVue.js"></script>
    <script>
      const vm = new Vue({
        el:"#app",
        data:{
          name:"老师1",
          more:{
            like:"喜欢"
          }
        }
      })
      console.log(vm);
    </script>
  </body>
</html>
class Vue {
  constructor(obj_instance) {
    /*******************************************************************************************************
    * data={
        name:"老师",
        more:{
          like:"喜欢"
        }
    */
    this.$data = obj_instance.data;
    Observer(this.$data);

    // el:"#app",this指vue的实例vm;
    Compile(obj_instance.el, this);
  }
}

/*******数据劫持- 监听实例里的数据************************************************************************************************
  * data_instance = {
      name:"老师",
      more:{
        like:"喜欢"
      }
  */
function Observer(data_instance) {

  if (!data_instance || typeof data_instance !== "object") {
    return;
  }

  const dependency = new Dependency();
  // Object.keys()只遍历自身属性(键)
  Object.keys(data_instance).forEach(key => {
    let value = data_instance[key];

    // 递归-子属性数据劫持
    Observer(value);

    Object.defineProperty(data_instance, key, {
      enumerable: true,
      configurable: true,
      get() {
        console.log(`访问了属性:${key}=> 值:${value}`)
        // 获取属性时添加依赖-收集和通知订阅者
        Dependency.temp && dependency.addSub(Dependency.temp);
        return value;
      },
      set(newValue) {
        console.log(`属性:${key}的值${value}修改为->${newValue}`)
        value = newValue;
        Observer(newValue);

        // 修改属性时添加依赖收集者开始通知
        dependency.notify()
      }
    })
  })
}


/*******************************************************************************************************
* HTML模板解析函数-替换DOM内
*/

function Compile(element, vm) {

  // 找到el的dom元素
  vm.$el = document.querySelector(element);

  // 创建文档碎片
  const fragment = document.createDocumentFragment();

  let child;

  // 遍历vm.$el的元素,每次append第一个元素会被添加到fragment(child节点自身会从vm.$el的dom节点移除)
  // 添加后第一个元素就被移动到fragment中,所以直到最后$el变成无子节点,while判断为假,跳出循环
  while (child = vm.$el.firstChild) {
    fragment.append(child);
  }

  fragment_compile(fragment);

  function fragment_compile(node) {

    const pattern = /\{\{\s*(\S+)\s*\}\}/;

    /*******************************************************************************************************
    *  正则匹配:(\S+)表示排除换行空白的字符
    * 如果是{{more.like}},结果为
    * result_regex[0] = "{{more.like}}"
    * result_regex[1] = "more.like"
    * 如果是"{{name}}",结果为:
    * result_regex[0] = "{{name}}"
    * result_regex[0] = "name"      * 
    */

    // 如果是文本
    if (node.nodeType === 3) {
      const xxx = node.nodeValue;
      const result_regex = pattern.exec(node.nodeValue);
      if (result_regex) {
        const arr = result_regex[1].split('.');
        /*******************************************************************************************************
        * 如果是'more.like',split分割成数组为['more','like']
        * 使用reduce:初始值total为vm.$data
        * 第一次遍历(total=data, current='more')得到结果data['more']
        * 第二次遍历(total=data['more'], current='like')得到结果data['more']['like'];
        * 最终拿到递归的元素值data['more']['like'];
        * 
        * 如果是'name',split分割成数组为['name']
        * 第一次遍历(total=data, current='name')得到结果data['name']
        * 最终拿到递归的元素值data['name'];
        */
        const value = arr.reduce((total, current) => total[current], vm.$data)
        node.nodeValue = xxx.replace(pattern, value);

        // 创建订阅者,回调函数为将节点中插值表达式的key为"more.like"的值更改为最新值,即更新插值表达式
        new Watcher(vm, result_regex[1], newValue => {
          node.nodeValue = xxx.replace(pattern, newValue);
        })
      }
      return;
    }

    if (node.nodeType === 1 && node.nodeName === 'INPUT') {

      const attr = Array.from(node.attributes);

      attr.forEach(i => {

        if (i.nodeName === 'v-model') {
          // v-model='more.like'或者v-model='name' 
          // i.nodeValue为"more.like"或者'name',
          const value = i.nodeValue.split('.').reduce((total, current) => total[current], vm.$data);
          node.value = value;

          new Watcher(vm, i.nodeValue, newValue => {
            node.value = newValue;
          })


          node.addEventListener('input', e => {
            // ["more","like"]
            const arr1 = i.nodeValue.split('.');

            // ['more']
            const arr2 = arr1.slice(0, arr1.length - 1);

            // vm.$data.more
            const final = arr2.reduce(
              (total, current) => total[current], vm.$data
            )

            // vm.$data.more['like']=e.target.value
            final[arr1[arr1.length - 1]] = e.target.value;
          })
        }
      })
    }

    // 有子节点遍历子节点
    node.childNodes.forEach(child => fragment_compile(child));
  }
  vm.$el.appendChild(fragment);
}


/****依赖-收集和通知订阅者***************************************************************************************************
* 
*/

class Dependency {

  constructor() {
    this.subscribers = [];
  }

  addSub(sub) {
    this.subscribers.push(sub);
  }

  notify() {
    this.subscribers.forEach(sub => sub.update());
  }
}

/****订阅者***************************************************************************************************
* 
* 先把当前watcher存到dependency的临时标量,然后触发getter,在getter中奖watcher添加到dep中的订阅者数组
*/

class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm;
    this.key = key;
    this.callback = callback

    // 临时属性-触发getter
    Dependency.temp = this;
    key.split('.').reduce((total, current) => total[current], vm.$data);
    Dependency.temp = null;
  }


  update() {

    // 拿到this.vm.$data中的key的实际的值
    const value = this.key.split('.').reduce((total, current) => total[current], this.vm.$data);

    // 这里的callback更新插值表达式的值为:this.vm.$data中的key值
    this.callback(value);
  }
}
Contributors: masecho