学会一个手写一个简单的vue-含视频

Posted _阿锋丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学会一个手写一个简单的vue-含视频相关的知识,希望对你有一定的参考价值。

文章目录

前言

想直接看视频的可以点击下面
视频观看我的手写简单vue全过程

代码地址:https://github.com/Zourunfa/imitate_vue/tree/main

本片文章的讲解步骤不完全按照视频里写代码的思路,但是有详细注释

准备工作

先看下面这张图,我是按照这张图的思路一步一步写的

由于时间原因 ,视频录制中已经把vue的响应式核心的部分串联,但是编译解析部分还只写了mustuche,v-html,v-text的编译解析代码,后面会进一步完善v-model,v-bind等一系列指令

先建立一个文件夹,里面放置index.html如下,然后建立下面引入的几个文件 observer.js和compiler.js,vue.js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <p>emp.name</p>
      <p v-text="emp.hobby"></p>

      <div v-html="username"></div>
    </div>
    <script src="vue.js"></script>
    <script src="observer.js"></script>
    <script src="compiler.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> -->
    <script>
      let vm = new Vue(
        el: '#app',

        data: 
          username: '阿锋',

          msg: 'asdasdsadd',
          emp: 
            name: 'af',
            age: '21',
            slary: 8000,
            hobby: 'guitar',
          ,
        ,
        methods: 
          handleClick() 
            console.log(this.emp.name);
            this.emp.name = '你好啊';
          ,
        ,
      );
    </script>
  </body>
</html>

vue.js

这就相当于new Vue的入口文件,

class Vue 
  constructor(options) 
    this.$data = options.data;
    this.$el = options.el;

    // console.log(options);
    this._init();
  

  _init() 
   // 1.实现一个数据观察者
    new Observer(this.$data);
     // 2.实现一个指令的解析器
    new Compiler(this.$el, this);
  


Observer.js

这部分有三个类,分别是Observer类,watcher类,dep类。分别对应上面响应式图的三个,

注意上面视图的标号有对应的代码,在注释里面 标注 提出来看 更方便理解

Observer

class Observer 
  constructor(data) 
    // console.log(data);
    this.observe(data);
  

  observe(data) 
    if (data && typeof data === 'object') 
      // console.log(Object.keys(data));
      Object.keys(data).forEach((key) => 
        // console.log(`劫持监听所有属性`); 
        // 对应标号1
        this.defineReactive(data, key, data[key]);
      );
    
  
  defineReactive(obj, key, value) 
    // 递归遍历
    this.observe(value);
    const dep = new Dep();
    
    Object.defineProperty(obj, key, 
      enumerable: true,
      configurable: false,
      get() 
        // 编译之前  初始化的时候
        // 订阅数据变化时,玩dep中添加观察者
        // 对应标号4 和 标号 2
        console.log(
          `getter劫持$key时 订阅数据变化时,往dep中添加观察者watcher`,
        );
        Dep.target && dep.addSub(Dep.target);
        return value;
      ,
      set: (newVal) => 
        this.observe(newVal);
        if (newVal !== value) 
          value = newVal;
        
        // 告诉dep 通知变化 
        // 对应标号3
        console.log(`setter设置新值时  调用dep.notify通知变化`);
        dep.notify();
      ,
    );
  


dep

// 收集watcher 通知更新
class Dep 
  constructor() 
    this.subs = [];
  

  // 收集观察者
  addSub(watcher) 
    this.subs.push(watcher);
  
  // 通知观察者更新
  notify() 
    // 通知标号3
    console.log('通知wacher更新', this.subs);
    this.subs.forEach((w) => 
      w.update();
    );
  

watcher

class Watcher 
  constructor(expr, vm, cb) 
    this.vm = vm;
    this.expr = expr;
    this.cb = cb;
    // 把旧值保存
    this.oldVal = this.getSub(expr, vm);
  

  getSub(expr, vm) 
       //当获取旧值时把watcher挂载到当前dep的target上
       console.log(
        `当在watcher中获取旧值$this.expr时把watcher挂载到当前dep的target上`,
      );
      // 对应标号5  编译时会使用new Watcher来使用这个函数
    Dep.target = this;

    const value = compMidWare.getValue(expr, vm);
       //用完之后一定要销毁掉,不然会有重复的watcher
       Dep.target = null;
    return value;
  

  update() 
    const newVal = compMidWare.getValue(this.expr, this.vm);

    this.cb(newVal);
  

compiler.js

这个文件主要是编译解析vue指令 ,然后需要注意的是 我下面代码的compMidWare就相当于图上的updater

注意上面视图的标号有对应的代码,在注释里面 标注 提出来看 更方便理解

class Compiler 
  constructor(el, vm) 
    this.el = document.querySelector(el);
    this.vm = vm;

    // 1,获取文档碎片对象 放入内存中会减少页面的回流和重绘
    const fragment = this.createFragment(this.el);
    // console.log(this.el);
    // 2,编译模板
    this.compile(fragment);

    // 3,追加子元素到根元素上
    this.el.appendChild(fragment);
  

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

    let firstChild;
       // 注意下面是=是赋值 拿到就填进去 并且判断是不是有child
    // 把所有节点都追加到fragment对象中
    while ((firstChild = el.firstChild)) 
      fragment.appendChild(firstChild);
    

    return fragment;
  

  compile(fragment) 
    const childNodes = fragment.childNodes;
    // console.log(childNodes);
    [...childNodes].forEach((node) => 
      if (node.nodeType === 1) 
          // 如果是元素节点,就编译它
        this.compileNode(node);
       else 
         // 文本节点
        // 编译文本节点
        this.compileText(node);
      
      // 递归
      node.childNodes && this.compile(node);
    );
  

  compileNode(node) 
    // console.log(node);
    const attributes = node.attributes;
    // console.log(attributes);
    [...attributes].forEach((attr) => 
      // console.log(attr);
      const  name, value  = attr;
      // console.log(name, value);
      // 's'.startsWith
      const expr = value;
      if (name.startsWith('v-')) 
        const orderName = name.split('-')[1];
        // console.log(orderName);
        compMidWare[orderName](expr, node, this.vm);
      

      //
    );
  

  compileText(node) 
    const text = node.textContent;
    // console.log(text);
    if (/\\\\(.+?)\\\\/.test(text)) 
      // console.log(node);
      const expr = text.replace(/\\\\(.+?)\\\\/, (...args) => 
        // console.log(args);
        return args[1];
      );

      // console.log(expr);
      console.log(expr);
      compMidWare['text'](expr, node, this.vm);
    
  


const compMidWare = 
  // 这里用到reduce很巧妙 . 之前的pres刚好就是 . 之后的上一级对象
  getValue(expr, vm) 
    return expr.split('.').reduce((pres, cur) => 
      return pres[cur];
    , vm.$data);
  ,

  html(expr, node, vm) 
    // console.log(expr, node, vm);

    const value = this.getValue(expr, vm);
    // console.log(value);
    // 绑定观察者,将来数据发生变化 触发这里的回调 进行更新
    // 对应标号5
    new Watcher(expr, vm, (newValue) => 
      // console.log(newValue);
      this.toView.htmlToView(node, newValue);
    );
    // 对应标号7
    console.log(`更新当前$expr-$value到视图上`);
    this.toView.htmlToView(node, value);
  ,
  text(expr, node, vm) 
    // console.log(expr, node, vm);
    // console.log(expr);
    const value = this.getValue(expr, vm);
    // console.log(value);
    // 绑定一个watcher对值进行监听  标号5
    new Watcher(expr, vm, (newValue) => 
      // console.log(newValue);
      console.log(`订阅数据变化,绑定更新函数:更新$expr$newValue`);
      this.toView.textToView(node, newValue);
    );
    //   // 对应标号7
    console.log(`更新当前$expr-$value到视图上`);
    this.toView.textToView(node, value);
  ,
  on() ,
  model() ,
  toView: 
    htmlToView(node, value) 
      node.innerHTML = value;
    ,
    textToView(node, value) 
      node.textContent = value;
    ,
  ,
;

以上是关于学会一个手写一个简单的vue-含视频的主要内容,如果未能解决你的问题,请参考以下文章

学会一个手写一个简单的vue-含视频

在 Vue.js 项目中包含一个手写笔插件

实现mini-vue3-更新2集含视频

手写一个Vue前后端分离项目

手写一个Vue前后端分离项目

十分钟利用环信WebIM-vue3-Demo,打包上线一个即时通讯项目含音视频通话