6步做一个mini版的vue2.0

461 阅读1分钟

第一步,定义响应式变量

function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get value');
      return value;
    },
    set(newValue) {
      if (newValue != value) {
        console.log('set value');
        value = newValue;
      }
    }
  });
}

const person = { name: 'John', age: 20 };
Object.keys(person).forEach((key) => defineReactive(person, key, person[key]));

person.name = 'Tom';

第二步,定义响应式封装对象

function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get value');
      return value;
    },
    set(newValue) {
      if (newValue != value) {
        console.log('set value');
        observe(newValue);
        value = newValue;
      }
    }
  });
}

function observe(value) {
  if (typeof value != 'object') {
    return;
  }
  Object.keys(value).forEach((key) => {
    if (typeof value[key] == 'object') {
      observe(value[key]);
    }
    defineReactive(value, key, value[key]);
  });
}

const person = { name: 'John', age: 20, family: { father: 'Tom', mother: 'amy' } };
observe(person);

person.family.father = 'Mike';

person.family = { fater: '', mother: '' };

第三步,创建Vue对象原型

function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get value');
      return value;
    },
    set(newValue) {
      if (newValue != value) {
        console.log('set value');
        observe(newValue);
        value = newValue;
      }
    }
  });
}

function observe(value) {
  if (typeof value != 'object') {
    return;
  }
  Object.keys(value).forEach((key) => {
    if (typeof value[key] == 'object') {
      observe(value[key]);
    }
    defineReactive(value, key, value[key]);
  });
}

function compile(node, vm) {}

function Vue(options) {
  this.$data = options.data;
  this.$methods = options.methods;

  observe(this.$data);

  const rootElement = document.querySelector(options.el);
  compile(rootElement, this);
}

new Vue({
  el: '#root',
  data: {
    name: 'John',
    age: 20
  },
  methods: {}
});

第四步,编译vue模板

<!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">
      <p>{{name}}</p>
      <button @click="onClick">Button</button>
    </div>

    <!-- <script src="../node_modules/vue/dist/vue.js"></script> -->
    <script src="vue_4.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          name: 'John'
        },
        methods: {
          onClick() {
            console.log('clicked');
          }
        }
      });
    </script>
  </body>
</html>
function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get value');
      return value;
    },
    set(newValue) {
      if (newValue != value) {
        console.log('set value');
        observe(newValue);
        value = newValue;
      }
    }
  });
}

function observe(value) {
  if (typeof value != 'object') {
    return;
  }
  Object.keys(value).forEach((key) => {
    if (typeof value[key] == 'object') {
      observe(value[key]);
    }
    defineReactive(value, key, value[key]);
  });
}

function compile(rootNode, vm) {
  rootNode.childNodes.forEach((node) => {
    // 元素节点
    if (node.nodeType == 1) {
      compileElement(node, vm);

      // 文本节点
    } else if (node.nodeType === 3) {
      compileText(node, vm);
    }

    // 遍历子节点
    if (node.childNodes) {
      compile(node, vm);
    }
  });
}

function compileText(node, vm) {
  if (/\{\{\ *([^}]+) *}\}/.test(node.textContent)) {
    const key = RegExp.$1;
    console.log(key);
    node.textContent = node.textContent.replace(new RegExp('\\{\\{ *' + key + ' *\\}\\}', 'g'), vm.$data[key]);
  }
}

function compileElement(node, vm) {
  let nodeAttrs = node.attributes;
  Array.from(nodeAttrs).forEach((attr) => {
    if (attr.name.indexOf('@') == 0) {
      const eventName = attr.name.substr(1);
      node.addEventListener(eventName, function () {
        vm.$methods[attr.value]();
      });
    }
  });
}

function Vue(options) {
  this.$data = options.data;
  this.$methods = options.methods;

  observe(this.$data);

  const rootElement = document.querySelector(options.el);
  compile(rootElement, this);
}

第五步,创建对象data映射

<!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">
      <p>sss {{name}} sss</p>
      <button class="red" @click="onClick">Button</button>
    </div>

    <!-- <script src="../node_modules/vue/dist/vue.js"></script> -->
    <script src="vue_5.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          name: 'John'
        },
        methods: {
          onClick() {
            this.name = 'LiChao';
            console.log('123123');
          }
        }
      });
    </script>
  </body>
</html>
function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get value');
      return value;
    },
    set(newValue) {
      if (newValue != value) {
        console.log('set value');
        observe(newValue);
        value = newValue;
      }
    }
  });
}

function observe(value) {
  if (typeof value != 'object') {
    return;
  }
  Object.keys(value).forEach((key) => {
    if (typeof value[key] == 'object') {
      observe(value[key]);
    }
    defineReactive(value, key, value[key]);
  });
}

function compile(rootNode, vm) {
  rootNode.childNodes.forEach((node) => {
    // 元素节点
    if (node.nodeType == 1) {
      compileElement(node, vm);
      // 文本节点
    } else if (node.nodeType === 3) {
      compileText(node, vm);
    }

    if (node.childNodes) {
      compile(node, vm);
    }
  });
}

function compileText(node, vm) {
  if (/\{\{\ *([^}]+) *}\}/.test(node.textContent)) {
    const key = RegExp.$1;
    node.textContent = node.textContent.replace(new RegExp('\\{\\{ *' + key + ' *\\}\\}', 'g'), vm.$data[key]);
  }
}

function compileElement(node, vm) {
  let nodeAttrs = node.attributes;
  console.log(nodeAttrs);
  Array.from(nodeAttrs).forEach((attr) => {
    if (attr.name.indexOf('@') == 0) {
      const eventName = attr.name.substr(1);
      node.addEventListener(eventName, function () {
        vm.$methods[attr.value].call(vm);
      });
    }
  });
}

function proxy(vm) {
  Object.keys(vm.$data).forEach((key) => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(v) {
        vm.$data[key] = v;
      }
    });
  });
}

function Vue(options) {
  this.$data = options.data;
  this.$methods = options.methods;

  observe(this.$data);

  proxy(this);

  const rootElement = document.querySelector(options.el);
  compile(rootElement, this);
}

第六步,响应式更新,依赖收集

<!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">
      <p>Hi, {{name}}</p>
      <p>{{gender}}</p>
      <p>{{name}}</p>
      <p>{{c}}</p>
      <button @click="var name='1111'; name='2222';">Button</button>
    </div>

    <!-- <script src="../node_modules/vue/dist/vue.js"></script> -->
    <script src="vue_6.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          name: 'John',
          gender: 'male',
          a: 1,
          b: 2
        },
        mounted() {
          console.log('mounted');
        },
        computed: {
          c() {
            return this.a + this.b;
          }
        },
        methods: {
          onClick() {
            //this.$data.name = 'LiChao';
            this.name = 'LiChao';
            // this.gender = 'female';
            this.a = 10;
          }
        }
      });
    </script>
  </body>
</html>
function defineReactive(obj, key, value) {
  // 每个key对应一个Dep实例
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      console.log('get value');
      Dep.target && dep.addDep(Dep.target);
      return value;
    },
    set(newValue) {
      if (newValue != value) {
        console.log('set value');
        observe(newValue);
        value = newValue;
        dep.notify();
      }
    }
  });
}

function observe(value) {
  if (typeof value != 'object') {
    return;
  }
  Object.keys(value).forEach((key) => {
    if (typeof value[key] == 'object') {
      observe(value[key]);
    }
    defineReactive(value, key, value[key]);
  });
}

function compile(rootNode, vm) {
  rootNode.childNodes.forEach((node) => {
    // 元素节点
    if (node.nodeType == 1) {
      compileElement(node, vm);
      // 文本节点
    } else if (node.nodeType === 3) {
      compileText(node, vm);
    }

    if (node.childNodes) {
      compile(node, vm);
    }
  });
}

function compileText(node, vm) {
  if (/\{\{\ *([^}]+) *}\}/.test(node.textContent)) {
    node._textContent = node.textContent;
    const key = RegExp.$1;
    //初始化数据
    updateText(node, key, vm);
    //监听数据
    new Watcher(vm, key, () => {
      updateText(node, key, vm);
    });
  }
}

function updateText(node, key, vm) {
  node.textContent = node._textContent.replace(new RegExp('\\{\\{ *' + key + ' *\\}\\}', 'g'), vm.$data[key]);
}

function compileElement(node, vm) {
  let nodeAttrs = node.attributes;
  Array.from(nodeAttrs).forEach((attr) => {
    if (attr.name.indexOf('@') == 0) {
      const eventName = attr.name.substr(1);
      node.addEventListener(eventName, function () {
        vm.$methods[attr.value].call(vm);
      });
    }
  });
}

function proxy(vm) {
  Object.keys(vm.$data).forEach((key) => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(v) {
        vm.$data[key] = v;
      }
    });
  });
}

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

    // 创建实例时,把当前实例指定到Dep.target静态属性上
    Dep.target = this;
    // 读一下key,触发get
    vm[key];
    // 置空
    Dep.target = null;
  }

  // 未来执行dom更新函数,由dep调用的
  update() {
    this.updaterFn.call(this.vm, this.vm[this.key]);
  }
}

class Dep {
  constructor() {
    this.watchers = [];
  }
  addDep(watcher) {
    this.watchers.push(watcher);
  }
  notify() {
    this.watchers.forEach((watcher) => watcher.update());
  }
}

function Vue(options) {
  this.$data = options.data;
  this.$methods = options.methods;

  observe(this.$data);

  proxy(this);

  const rootElement = document.querySelector(options.el);
  compile(rootElement, this);
}