Vue数据响应式、模板解析的实现原理(实现一个简易的Vue)

Vue源码理解 - 数据和模板的渲染 在Vue.js源码中,数据和模板的渲染是通过数据的响应式和模板的编译来实现的。通过深入理解Vue.js源码中数据和模板的渲染过程,我们可以更好地理解Vue.js框架的工作原理,并能够更高效地开发Vue.js应用程序。接着,Vue会将新的VNode树与旧的VNode树进行对比,找出差异,并将差异更新到真实的DOM上,完成视图的更新。Vue.js 是一款流行的前端框架,它采用了基于组件的开发模式,通过数据和模板的绑定实现了高效的视图更新机制。以上是关于Vue.js源码中数据和模板的渲染的详细解析。 阅读详情

实现步骤(实现我们自己的Vue—MyVue)

github地址:https://github.com/Rawsdom/my-vue-demo.git

  • MyVue(主入口的构造函数)
  • Observer(用于实现数据响应化)
  • Compile(解析模板、创建 Watcher、保存更新函数)
  • Watcher(执行更新函数、被 Dep 收集)
  • Dep(管理 Watcher、一旦数据更新就通知相应的watcher更新视图)
一、MyVue的构造函数
 new MyVue({
        el: '#app',
        data: {
          name: 'hello world!',       
        }
      });

(PS:MyVue构造函数接收上方这些参数并保存到实例中,并将实例往下传递)

  1. 把 data 使用 observe 进行递归数据劫持
  2. 使用代理方式处理传入的 data(方便在实例中可以直接 this.name 就获取或修改 data 中的值)
  3. 把 el 传到模板解析中

class MyVue {
  constructor(options) {
    this.$options = options;
    this.$data = options.data;
    // 数据劫持 (数据响应化处理)
    observe(this.$data);
    // 数据代理
    proxy(this, "$data");
    // 模板解析 (传递 this 实例)
    new Compiler(options.el, this);
  }
}
二、Dep 和 Watcher 说明
  • 数据响应化的定义是页面绑定的某个数据,数据改变,同时页面也会自动更新
  • 在上方,我们把模板解析和数据响应分成2个方法去执行。但是他们相互之间没有关联的话怎么去实现页面和数据同步呢?
  • 我们就需要借助 Dep 和 Watcher 实现一个订阅、发布模式(观察者模式)来实现他们之间的关联
  • Dep相当于一个容器,在对数据劫持时生成的,每一个Dep对应一个属性
  • addDep方法是用来收集 Watcher
  • notify方法是用来调用所有的 Watcher 中的更新方法来达到页面的更新
class Dep {
  constructor() {
    this.deps = [];
  }

  addDep(dep) {
    this.deps.push(dep);
  }
  notify() {
    this.deps.forEach((dep) => dep.update());
  }
}
  • Watcher 生成在编译解析中,一旦解析到页面有数据(如:{{ name }}) 就会生成一个Watcher
  • 在生成的同时被Dep收集,这里由于Dep是在数据劫持中生成的,我们只需将当前的 Watcher 实例保存到一个变量中,再触发对应的dep劫持的属性的 get 回调 即可收集,收集完再将变量置为null
  • 由于Vue源码也是用Dep.target的保存Watcher实例,我这边也一样参照源码,其实是可以用全局变量也是可以的
  • (ps:这边需要理解一下Object.defineProperty()这个劫持的api才能更好理解)
  • (ps:因为每一个属性劫持都会生成一个Dep,只要this.name访问一下就会触发劫持的 get 回调,然后再 get 回调中判断是否有这个变量,有即调用Dep里的addDep()方法即可收集到Dep中)
class Watcher {
  constructor(vm, key, updateFn) {
    this.vm = vm;
    this.key = key;
    this.updateFn = updateFn;
	//  被Dep收集过程
    Dep.target = this;
    this.vm[this.key];
    Dep.target = null;
  }

  update() {
  //更新函数的调用, 由于更新函数内部可能需要this实例, 所以用call方法改变一下调用 this 指向
    this.updateFn.call(this.vm, this.vm[this.key]);
  }
}
三、数据响应式 (数据劫持)
  • 紧接 MyVue 的构造函数调用 observe() 方法
  • 类型判断处理,同时也是递归点
function observe(obj) {
  if (typeof obj !== "object" || obj === null) return;

  new Observer(obj);
}
  • 新建 Observer 实例
  • 因为上方 observe() 方法的类型判断唔法判断 array 和 object 在使用Array原型上的isArray()方法去判断
  • 因为数组原型上有7个原型方法能修改数组的值,所以需要复写数组的7个方法,所以这里需要区分对象和数组2个不同类型的处理
  • (ps:typeof === ‘object’ 判断 object 和 array 都是为 true 的)
  • (ps:重写数组原型的方法目的是在执行完数组操作后要通知视图更新以达到响应式,所以要执行原原型链上的方法,并且还要通知 dep 更新视图)
class Observer {
  constructor(obj) {
    this.obj = obj;
    if (typeof obj === "object") {
      Array.isArray(obj) ? this.walkArr(obj) : this.walk(obj);
    }
  }
 // 对象数据响应化
  walk(obj) {
    Object.keys(obj).forEach((key) => {
    //  这里可以理解为取出对象的键值并对obj的key做劫持
    //  下方会有defineReactive()方法
      defineReactive(obj, key, obj[key]);
    });
  }
  // 数组数据响应化
  walkArr(arr) {
  //	覆盖原型的7个方法
    arr.__proto__ = this.arrayProto;
	// 这里取出的键值其实是数组的下标
    const keys = Object.keys(arr);
    for (let i = 0; i < keys.length; i++) {
    //	以arr的下标做数据劫持
      defineReactive(arr, i, arr[i])
    }
  }
}
//  Observer的类原型保存新的数组原型方法(如:push等)用于数组响应化时覆盖数组的原型方法
const defaultProto = Array.prototype;
//	深拷贝数组的原型链方法
const arrayProto = Object.create(defaultProto);
//  对以下的方法做重写。并生成新的数组原型方法
["push", "pop", "shift", "unshift"].forEach(
  method => {
    arrayProto[method] = function () {
    //	默认原型链上的对应的方法还是需要执行,该 push 就还是要 push
      defaultProto[method].apply(this, arguments);
      // 这里的notify() 会在下方 defineReactive() 时保存到该数据的原型链上从而在此处可以调用
      this.notify()
    };
  }
);
//  保存到 Observer 原型链上
Observer.prototype.arrayProto = arrayProto

defineReactive() 方法

  • 开启递归,如果val是对象就需要继续往下递归,如果不是的话上面说的递归点就起作用,直接就返回到这里
  • 因为当前的属性是唯一的,同时在这新建的dep也是和该属性一一对应,也是唯一的
  • 在对数组做响应化时需要利用闭包保存当前的dep,并在该数据的原型上添加一个notify方法,因为push操作调用者肯定是属性本身(如:this.name.push(xxx)),所以上方在push等操作的时候也就可以使用当前添加的notify()方法从而实现通知视图更新
function defineReactive(obj, key, val) {
  // 递归
  observe(val);

  // 创建一个Dep和当前key 一一对应
  const dep = new Dep()
  // 利用原型链把dep的notify方法保存起来,数组使用push的方法就可以直接通知修改数组的值
  if(Array.isArray(obj[key])){
    	obj[key].__proto__.notify = function () {
      	dep.notify()
    }
  }
  
  

  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集在这里 
      // 这里是配合 Watcher 的构造方法里所触发的访问(get)后,进行收集
      // 这里触发只会在new Watcher实例的时候触发,因为它一新建就被收集,他被收集完就立即置空
      Dep.target && dep.addDep(Dep.target)

      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        // 如果更新数据的是object或者array则需要重新对此做劫持
        observe(newVal);
        val = newVal;

        //  通知更新
        dep.notify()
      }

    },
  });
}
四、数据代理
  • 这里紧接 MyVue 构造函数的 proxy() 方法调用
  • 这里再一次利用 Object.defineProperty() 实现数据代理,其实就是将this.name指向this.$data.name,也就是差不多映射的道理
  • this.$data 数据做响应化也要数据劫持,所以我们通常的 this.name 的方式其实是做了两层数据劫持
//  代理			this,$data
function proxy(vm, sourceKey) {
			// this.$data			key: name
  Object.keys(vm[sourceKey]).forEach((key) => {
  			//			this, name
    Object.defineProperty(vm, key, {
      get() {
      //		this.$data.name
        return vm[sourceKey][key];
      },
      set(newVal) {
      //	this.$data.name = newVal
        vm[sourceKey][key] = newVal;
      },
    });
  });
}
五、模板解析
  • 递归遍历dom树
  • 判断节点类型, 如果是文本,则判断是否是插值绑定
  • 如果是元素,则遍历其属性判断是否是指令(如:my-html、my-text等)或事件(如:@click="xxx"等),然后递归子元素
class Compiler{
  // el 是宿主元素
  // vm 是 MyVue 实例
  constructor(el,vm){
    this.$vm = vm;
    this.$el = document.querySelector(el)

    if(this.$el){
      // 执行编译
      this.compile(this.$el)
    }

  }

  compile(el){
    // 遍历 el 树
    const childNodes = el.childNodes
    //  将类数组转换为数组
    Array.from(childNodes).forEach(node => {
      // 判断是否是元素
      if(this.isElement(node)){
      	//  解析元素
        this.compileElement(node)
      }else if(this.isInter(node)){
      	//	解析文本
        this.compileText(node)
      }

      // 递归子节点
      if(node.childNodes && node.childNodes.length > 0){
        this.compile(node)
      }

    })
  }
	// node.nodeType 1:元素 2:属性 3: 文本
  isElement(node) {
    return node.nodeType === 1
  }

  isInter(node){
    // 类型是文本同时也符合插值表达式双大或号包裹 如:{{xxx}}
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }

  compileText(node){
	//  RegExp 是js的内置对象,$1是匹配正则表达式的匹配正确的第一个
	//  由于上方判断如果通过就会立刻调用当前的编译文本的方法所以也就是正则匹配的第一个
	//	如 {{ name }} 这里得出的是 '空格'+name+'空格' 所以要trim()去除前后空格
    this.update(node, RegExp.$1.trim(), 'text')
  }

  compileElement(node){
    // 节点是元素
    // 遍历其属下列表
    const nodeAttrs = node.attributes
    Array.from(nodeAttrs).forEach(attr => {
      // 规定: 指令以 my-text="xxx" 定义
      const attrName = attr.name  // my-text
      const exp = attr.value // xxx
      //	判断是否是指令
      if(this.isDirective(attrName)){
      	//	把 my-text 处理成 text
        const dir = attrName.substring(3)
        //  执行指令 this.text(node, exp)
        this[dir] && this[dir](node, exp)
      }
      // 判断是否是事件
      if(this.isEvent(attrName)){
        // 如 @click='onClick'
        // dir: click 上方定义好的 exp: onClick
        const dir = attrName.substring(1)
        // 事件的处理
        this.eventHandler(node, exp, dir)
      }
    })
  }
//  指令判断
  isDirective(attr){
    return attr.indexOf('my-') === 0
  }
//  事件判断
  isEvent(dir){
    return dir.indexOf('@') === 0
  }
//  更新方法(这里就是new Watcher 实例的地方,这里也就是完成模板解析和数据响应化之间的关联的地方)
//  update() 方法相当于一个枢纽,在解析到指令或者插值表达式就会调用相应的更新方法(替换视图上匹配的插值或指令)
//	同时生成一个 Watcher 去保存更新函数(用于之后数据变化时更新视图上的插值或指令)
  update(node, exp, dir){
    // 指令对应更新函数 xxxUpdater 如:textUpdater()
    const fn = this[dir + 'Updater']
    fn && fn(node, this.$vm[exp])

    // 新建 Watcher 实例并保存该更新方法,一旦数据变化就会通知 dep,dep也就通知Watcher并调用下方的更新方法
    new Watcher(this.$vm, exp ,function(val) {
      fn && fn(node, val)
    })
  }
// 更新函数 (下面使用的是原生的node节点更新里的内容)
//	textContent: 文本  innerHTML: 识别并解析html标签如:(<h1>你好</h1>)value: 用于input 输入框的 value 属性
  textUpdater(node, value){
    node.textContent = value
  }
  htmlUpdater(node, value){
    node.innerHTML = value
  }
  modelUpdater(node, value) {
    node.value = value
  }
  
//  指令解析
  text(node, exp){
    this.update(node, exp, 'text')
  }
  html(node, exp){
    this.update(node, exp, 'html')
  }
  model(node, exp) 
    this.update(node, exp, 'model')
    //  事件监听 这里仅仅只是对input做的v-model所以监听的是input输入事件
    node.addEventListener('input', (e) =>{
      // 新的赋值给数据即可
      this.$vm[exp] = e.target.value
    })

  }

//  事件处理
  eventHandler(node, exp, dir){
  	//  从MyVue实例中获取 methods里的方法并匹配其中的方法
    const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
    //  原生的事件监听
    node.addEventListener(dir, fn.bind(this.$vm))
  }
}
六、整个简易Vue的代码

index.js

// 创建My Vue构造函数

function defineReactive(obj, key, val) {
  // 递归
  observe(val);

  // 创建一个Dep和当前key 一一对应
  const dep = new Dep()

  // 利用原型链把dep的notify方法保存起来,数组等使用push的方法就可以直接通知修改数组的值
  if(Array.isArray(obj[key])){
    obj[key].__proto__.notify = function () {
      dep.notify()
    }
  }


  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集在这里
      Dep.target && dep.addDep(Dep.target)

      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        // 如果传入的是新object需要再经过数据劫持
        observe(newVal);
        val = newVal;

        //  通知更新
        dep.notify()
      }

    },
  });
}

function observe(obj) {
  if(typeof obj !== 'object' && obj !== null){
    return
  }

  new Observer(obj)
}

// 代理函数,方便用户直接访问$data中的数据
function proxy(vm, sourceKey) {
  Object.keys(vm[sourceKey]).forEach(key => {
    Object.defineProperty(vm, key, {
      get(){
        return vm[sourceKey][key];
      },
      set(newVal) {
        vm[sourceKey][key] = newVal;
      }
    })
  })
}

class MyVue {
  constructor(options) {
    // 保存选项
    this.$options = options
    this.$data = options.data

    // 响应式处理
    observe(this.$data)

    // 代理
    proxy(this, '$data')

    // 创建编译器
    new Compiler(options.el, this)

  }


}

// 根据对象类型决定如何做响应化

class Observer {
  constructor(obj) {
    this.obj = obj;
    if (typeof obj === "object") {
      Array.isArray(obj) ? this.walkArr(obj) : this.walk(obj);
    }
  }
 // 对象数据响应化
  walk(obj) {
    Object.keys(obj).forEach((key) => {
      defineReactive(obj, key, obj[key]);
    });
  }
  // 数组数据响应化
  walkArr(arr) {
    arr.__proto__ = this.arrayProto;

    const keys = Object.keys(arr);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(arr, i, arr[i])
    }
  }
}


const defaultProto = Array.prototype;
const arrayProto = Object.create(defaultProto);
["push", "pop", "shift", "unshift"].forEach(
  method => {
    arrayProto[method] = function () {
      defaultProto[method].apply(this, arguments);
      this.notify()
    };
  }
);
Observer.prototype.arrayProto = arrayProto


// 观察者: 保存更新函数, 值发生变化调用更新函数
class Watcher {
  constructor(vm, key, updateFn) {
    this.vm = vm;

    this.key = key;

    this.updateFn = updateFn;

    // Dep.target 静态属性上设置为当前的 watcher 实例
    Dep.target = this
    this.vm[this.key]   // 读取触发了 getter
    Dep.target = null   // 收集完就置空
  }

  update(){
    this.updateFn.call(this.vm, this.vm[this.key])
  }
}


// Dep: 依赖,管理某个key相关所有的 Watcher 实例

class Dep{
  constructor(){
    this.deps = [];
  }

  addDep(dep){
    this.deps.push(dep);
  }

  notify(){
    this.deps.forEach(dep => dep.update())
  }
}

compile.js

// 编译器

class Compiler {
  // el 是宿主元素
  // vm 是 MyVue 实例
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = document.querySelector(el);

    if (this.$el) {
      // 执行编译
      this.compile(this.$el);
    }
  }

  compile(el) {
    // 遍历 el 树
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach((node) => {
      // 判断是否是元素
      if (this.isElement(node)) {
        this.compileElement(node);
      } else if (this.isInter(node)) {
        this.compileText(node);
      }

      // 递归子节点
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }

  isElement(node) {
    return node.nodeType === 1;
  }

  isInter(node) {
    // 首先是文本标签, 其次内容是 {{xxx}}
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  }

  compileText(node) {
    // node.textContent = this.$vm[RegExp.$1.trim()]
    this.update(node, RegExp.$1.trim(), "text");
  }

  compileElement(node) {
    // 节点是元素
    // 遍历其属下列表
    const nodeAttrs = node.attributes;
    Array.from(nodeAttrs).forEach((attr) => {
      // 规定: 指令以 my-xx="yyy" 定义
      const attrName = attr.name; // xx
      const exp = attr.value; // yyy
      if (this.isDirective(attrName)) {
        const dir = attrName.substring(3);
        //  执行指令
        this[dir] && this[dir](node, exp);
      }

      // 事件处理
      if (this.isEvent(attrName)) {
        // @click='onClick'
        const dir = attrName.substring(1);
        // 事件监听
        this.eventHandler(node, exp, dir);
      }
    });
  }

  isDirective(attr) {
    return attr.indexOf("my-") === 0;
  }

  isEvent(dir) {
    return dir.indexOf("@") === 0;
  }

  update(node, exp, dir) {
    // 初始化
    // 指令对应更新函数xxUpdater
    const fn = this[dir + "Updater"];
    fn && fn(node, this.$vm[exp]);

    // 更新处理
    new Watcher(this.$vm, exp, function (val) {
      fn && fn(node, val);
    });
  }

  textUpdater(node, value) {
    node.textContent = value;
  }

  htmlUpdater(node, value) {
    node.innerHTML = value;
  }

  modelUpdater(node, value) {
    node.value = value;
  }

  // my-text
  text(node, exp) {
    // node.textContent = this.$vm[exp]
    this.update(node, exp, "text");
  }

  // my-html
  html(node, exp) {
    this.update(node, exp, "html");
  }

  // my-model
  model(node, exp) {
    // update 方法只完成赋值和更新
    this.update(node, exp, "model");
    // 事件监听
    node.addEventListener("input", (e) => {
      // 新的赋值给数据即可
      this.$vm[exp] = e.target.value;
    });
  }

  eventHandler(node, exp, dir) {
    const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
    node.addEventListener(dir, fn.bind(this.$vm));
  }
}

index.html

<!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 @click="add">{{ counter }}</p>
      <p my-text="counter"></p>
      <p my-html="desc"></p>
      <input type="text" my-model="name" />
      <p>{{name}}</p>
      <p>{{arr}}</p>
    </div>

    <script src="./index.js"></script>
    <script src="./compile.js"></script>
    <script>
      const app = new MyVue({
        el: "#app",
        data: {
          counter: 0,
          desc: "<h1>这是描述</h1>",
          name: "123",
          arr: [1, 3, 4, 5],
        },
        methods: {
          add() {
            this.counter++;
          },
        },
      });

      setTimeout(() => {
        app.arr.push(9);
      }, 1000);
    </script>
  </body>
</html>


个人的一个学习经历,希望能帮到你,如果有大佬看出有优化的点可以指出,可以相互学习
ps:这个仅仅只是简易的Vue只为了更好的读源码,会有一些与源码不一样的

vue-element-response-demo:基于Vue + Element的响应式后台模板 vue + element响应式后台模板 项目设置 npm install 编译和热重装以进行开发 npm run serve 编译并最小化生产 npm run build 主要功能 响应式侧边栏 面包屑导航(结合router.js) 路由动效 阿里巴巴矢量icon 公用样式 手写笔预CSS 路由懒加载 路由拦截 axios请求拦截 vuex状态分模块管理 另外,在以上基础上做了真实登录注册功能,参考: : Koa2 + MongoDB + Redis登录注册 邮箱验证码服务 立即下载

相关推荐

SMR实战指南:从GWAS数据到eQTL分析的完整流程

本文提供了基于汇总数据的孟德尔随机化(SMR)实战指南,详细解析了如何从GWAS汇总数据出发,整合eQTL数据,完成从数据准备、BESD格式转换到最终SMR与HEIDI分析的完整流程。文章重点阐述了核心操作步骤、常见避坑技巧与结果解读,旨在帮助遗传学研究者高效掌握这一分析基因表达与复杂性状潜在因果关系的强大工具。

weixin_29246195的博客 1070

vue 如何解析模板?

?> Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。 ?> 在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。 模板就是一段字符串,非结构化的数据,没法分析。因此,第一步是将非结构化的模板字符串,转变成结构化的 JS

包磊磊的博客 943

语音识别(ASR)-- Kaldi 学习笔记

语音识别(ASR)-- Kaldi学习笔记语音识别简介GMM-HMM基本原理DNN-HMM(基于神经网络)KaldiKaldi架构简介Kaldi在Windows下的安装工具准备编译OpenFST配置PortAudio配置KaldiKaldi目录说明 语音识别简介 语音识别(Speech Recognition)技术,也被称为自动语音识别(Automatic Speech Recognition, ASR)、计算机语音识别(Computer Speech Recognition)或是语音转文本识别(Spee

qq_43318971的博客 4848

Vue 模板编译原理解析

首先我们看一下什么是编译?所谓编译(Compile),指的是将语言 A 翻译成语言 B,语言 A 就被称之为源码(source code),语言 B 就被称之为目标代码(target code),这个事情谁来做?编译器来做。编译器你也不用想得那么神秘,就是一段程序而已。上面的步骤,如果从大的方面去分,那么可以分为和。编译后的结果是什么?编译后的结果就是渲染函数注意这里,整个编译过程并非一蹴而就,而是经历了一个又一个的步骤,一点一点转换而来的。

共同学习,心怀感激。 1845

Win10蓝屏代码critical process died的解决方法

critical process died”蓝屏错误是Win10操作系统中较为常见的问题之一。当遇到这一问题时,可以尝试通过修复当前系统、检查硬盘槽位或重新安装系统等方法来解决。本文详细介绍了这三种方法的操作步骤和注意事项,希望能帮助大家摆脱这一困扰。同时,也建议大家在日常使用中注意系统的维护和更新,以减少类似问题的发生。

mmoo_python的博客 9536

基于Simulink的超级电容器充放电仿真

为了更好地理解和设计超级电容器的应用系统,通常需要进行详细的仿真分析。Simulink 提供了一个强大的仿真环境,可以用来设计、分析和验证超级电容器充放电系统的控制策略。本教程将详细介绍如何使用 Simulink 进行超级电容器充放电的仿真实例。如果你希望对超级电容器的充放电过程进行精确控制,可以添加一个简单的逻辑控制器来控制开关的状态。通过本教程,我们详细介绍了如何使用 Simulink 进行超级电容器充放电的仿真实例。2. 添加超级电容器模型。7. 添加控制器(可选)1. 调整超级电容器参数。

xiaoheshang_123的博客 518

VUE2.0 模板编译原理(一):解析器

vue的template里可以填充变量、表达式、指令,这些在HTML没有的功能就是靠模板编译实现的。模板编译的作用就是将模板转换为渲染函数,渲染函数执行的时候都会生成当前最新的vnode进行页面渲染。 模板--[输入]-->模板编译--[输出]-->渲染函数 这个过程分为三个步骤: 1、解析器:模板解析成AST(抽象语法树); 2、优化器:遍历AST标记静态节点,这样在虚拟DOM更新节点时避免重新渲染静态节点; 3、代码生成器:使用AST生成render函数。 一、解析

denglouhen的博客 1415

最强游戏投屏串流搭档Sunshine+Moonlight配置方法

因祸得福的是,sunshine作为开源的self-host的服务端,不仅可以代替Novidia的服务端角色,还拓展了moonlight可以服务的主机,非novidia显卡的PC也可以通过这对搭档实现高质量的游戏窜流投屏了。回到moonlight,点击电脑图表,会给你一个Pincode,到Sunshine控制台,菜单点击Pin,输入moonlight给出的pin码。高质量的图像: Moonlight支持高清(HD)和甚至4K分辨率的投屏,同时还提供了对音频的传输支持,确保用户可以享受到高质量的视频和音频。

每日出拳老爷子的博客 1万+

vue 中如何解析模板

模板是什么? 对于vue来说,模板本质就是一个字符串 vue中的模板是有逻辑的,是动态的,如v-if,v-for等 与html格式很想,但有很大区别;html是静态的,而vue模板是动态的 最终还是要转化为html来显示,怎么才能转换为html来显示呢? 看下边 // 这就是vue中的模板 <div id="app"> <input type="text" v-model="value"> <button @click="handleSubmit"&

weixin_45242865的博客 4807

Sigrity SPEED2000 General SI Simulation模式如何进行信号仿真分析操作指导-DDR

本例是几个简单的板载DDR颗粒的结构,本例中使用General SI Simulation模式仿真DDR信号,结构拓扑如下,Addcmd,clk,cke都是T拓扑。Sigrity SPEED2000 General SI Simulation模式可以对信号进行仿真分析,仿真将电源视为理想电源,以下图为例进行说明。DQS的接收端模型设置为DQS_34_ODT60_1333。模式如何进行信号仿真分析操作指导-DDR。附带驱动和接收端的IBIS模型。

weixin_54787054的博客 513

2022年全国职业院校技能大赛高职组大数据技术与应用赛项赛卷⑩—解析答案

工业互联网是工业全要素、全产业链、全价值链的全面连接,是人、机、物、工厂互联互通的新型工业生产制造服务体系,是互联网从消费领域向生产领域、从虚拟经济向实体经济拓展的核心载体,是建设现代化经济体系、实现高质量发展和塑造全球产业竞争力的关键支撑。加快发展工业互联网产业,不仅是各国顺应产业发展大势,抢占产业未来制高点的战略选择,也是我国推动制造业质量变革、效率变革和动力变革,实现高质量发展的客观要求。 为完成工业大数据分析工作,你所在的小组将应用大数据技术,以Scala作为整个项目的基础开发语言,基于大数据平台综

我是网络安全兼技能大赛工程师,专注网络安全技术学习与技能大赛实战!持续分享最新的技术文章,帮你少走弯路,一起在技术赛道上进步~ 583

【Vue原理解析】之模版编译

模版解析是Vue框架中非常重要的一部分,它负责将组件的模版代码转化为可执行的JavaScript代码。通过编译器对模版进行词法分析、语法分析和生成渲染函数代码等处理,实现了数据绑定、指令、条件和循环等功能。深入理解Vue模版解析的原理和源码实现,有助于我们更好地使用Vue框架进行前端开发。

wanghongpu9305的博客 221

从零实现深度学习框架【实现自己的PyTorch(已完结)】

本着“凡我不能创造的,我就不能理解”的思想,此系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。

日积月累,天道酬勤 6862

Vue响应式原理详解

首先我们先来看一下最开始提出的问题。第一个:给属性重新赋值成对象,是否是响应式的?答案:是响应式的。应当我们给data中的属性进行重新赋值的时候,会执行Observer类中的方法的set方法在set方法中,调用了walk方法,该方法中判断重新给data属性中赋的值是否为对象,如果是对象,会将对象中的每个属性都修改成响应式的。第二个问题:给Vue实例新增一个成员是否是响应式的?

panerchao的博客 2032

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Vue响应式原理 源码解析

响应式原理 数据代理:将 data 中的数据代理 this 上,访问数据更方便 详细版 将 data 数据赋值给变量 data 和 this._data 上,这种数据称为原数据 通过 Object.keys 方法提取原数据的所有属性名成为一个数组 遍历这个数据,调用 _proxy 对每一个属性名进行数据代理 所谓数据:就是通过 Object.defineProperty 方法给 this 定义这个属性,设置属性的元属性(属性描述符) 元属性中定义数据的是否可枚举,是否可以重新配置,以及 get 属

探鱼不是鱼 265

【Vuejs】探索 Vue.js 响应式原理

提到“响应式”三个字,大家立刻想到啥?响应式布局?响应式编程?从字面意思可以看出,具有“响应式”特征的事物会根据条件变化,使得目标自动作出对应变化。比如在“响应式布局”中,页面根据不同设...

奇舞周刊 629

vue底层实现原理_vue三要素及底层实现机制

深入解析Vue我们首先来熟悉一下我们这个文档所学习内容的流程。先对比一下jQuery和Vue的区别,再讲述Vue的MVVM模型,接着讲解Vue的实现流程。当然,我是不相信没有对比哪来的伤害,没有伤害哪能让人记忆深入骨髓咧,向来都是被伤害过的人我才会记得你,我要恨你一辈子 ~ ~ 哈哈,这当然是个泛词,我们今天的重点也不是要你真的去恨谁哈,一起来看今日焦点,jQuery和Vue的生死之战。我们来看看...

weixin_39662955的博客 501

模拟vue响应式原理,自己动手完成一个miniVue

Vue的响应式原理是vue框架十分重要的核心,今天我来整理一下vue响应式原理实现的过程,并且完成一个小demo去加强理解和记忆。 我们先来整理一下整体结构: 主要可以分成这五个主要的模块: Vue核心 把 data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter Observer数据劫持 能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep Compiler模板编译 解析每个元素中的指令/插值表达式,并替换成相应的数据 Dep依赖

weixin_44084056的博客 196

手写vue - 实现数据响应式、数据双向绑定和事件监听

手写vue -实现数据响应式、数据双向绑定和事件监听一、理解Vue的设计思想1、MVVM框架的三要素:数据响应式、模板引擎及其渲染1)数据响应式:监听数据变化并在视图中更新2)模版引擎:提供描述视图的模版语法3)渲染:如何将模板转换为html2、数据响应式原理二、原理分析1、流程分析2、涉及类型介绍三、手写VUE1、实现数据响应式1)前置知识点简析2)代码解析说明2、实现JVue类:框架构造函数1)调用示例:2)代码解析说明3、实现Compile模板编译类1)前置知识点简析2)代码解析说明4、实现Watch

qq_36782473的博客 1116

vue实现(二)- 响应式实现

Vue本质上是使用HTML 的字符串作为模板的, 将字符串的模板 转换成AST, AST 转换成 VNode 模板 -> AST AST -> VNode VNode -> DOM 最消耗性能的是字符串解析阶段 ( 模板 -> AST ) 通过阅读源码,我们可以发现,源码对函数柯里化的使用是很常见,比如对是否是标签的判断 let str = 'div,ul,li,span,table,th,tr,a,h1,h2,h3,h4,h5,h6' function makeMap(str)

qq_38427709的博客 426

MVVM与Vue响应式的实现

M:模型 ==》data中的数据V:视图 ==》模板VM:视图模型 ==》Vue实例对象ViewModel是一个中间的桥梁将视图View与模型Model连接起来,ViewModel内部通过数据绑定,实现数据变化,视图发生更新变化,通过数据劫持实现的数据绑定;通过dom监听,实现事件触发,调用对应的回调函数,比如更新数据(数据变化了,视图就会更新–数据绑定)Vue 的设计也受到了MVVM的启发,View对应的是dom,它的ViewModel对应的式Vue实例,,Model对应的是data对象;通过数据劫持来实

weixin_小白鼠的博客 552

实现Vue响应式原理

Vue的数据双向绑定,响应式原理,其实就是通过Object.defineProperty()结合发布者订阅者模式来实现的。 我们可以先试着拆分一下Vue的核心模块。 Vue构造函数,集中以下模块实现MVVM。 Observer 通过Object.definePropty进行数据劫持 Dep 发布订阅者,添加观察者者以及在数据发生改变的时候通知观察者 Watcher 观察者,对数据进行观察以及保存数据修改需要触发的回调 Compiler 模板编译器,对HTML模板进行编译,提取其中的变量并转化为数据

zhengzhengwang的专栏 187

小白一步一步阅读vuejs源码

本篇不直接解读vuejs的双向绑定的原理,而是已读源码的视角来看vue是怎么做的。 1、下载vuejs源码 mkdir project && cd project && git clone https://github.com/vuejs/vue.git npm i 2、开始一步一步读代码 打开package.json。看看scripts,我们先只看dev,这...

3605

vue 用key拿对象value_Vue 基本实现

前言学习前端到了一定的程度,尤其看了部分 jQuery 源码后,我想尝试着自己去动手造轮子。但是我的方向或者参考貌似只能是 Vue 。毕竟它的 MVVM(Model-View-ViewModel)架构实在是好用,我们只需要关注自己的数据,去改变数据后视图会自动的进行更新。所以我决定自己动手实现一遍 Vue 的响应式原理。进而更深刻的理解 MVVM 这种模式及其好处。所以在本文开始之前你必须已经对 ...

weixin_33076057的博客 2260

Vue2 模板解析及与 Vue3 的对比

通过对 Vue2 模板解析原理的深入了解,我们可以更好地掌握 Vue 的工作机制。同时,对比 Vue3 的变化,我们也能看到前端框架的不断发展和进步。在实际开发中,我们可以根据项目需求选择合适的版本,以提高开发效率和代码质量。希望这篇文章对你理解 Vue2 的模板解析以及 Vue3 的变化有所帮助。如果你有任何问题或建议,欢迎在评论区留言。

m0_57836225的博客 627

vue源码分析—数据代理,模板解析,数据绑定

前言 本篇文章分析 vue 作为一个 MVVM 框架的基本实现原理 :数据代理 ;模板解析 ;数据绑定 不直接看 vue.js 的源码 ,剖析 github 上某基友仿 vue 实现的 mvvm 库地址:https://github.com/DMQ/mvvm 必备的知识点 在开始分析vue源码之前,必须要知道的以下知识点 [].slice.call(lis): 将伪数组转...

huangjianfeng21的博客 612
上一篇: 前端开发vscode的常用插件
下一篇: Vue、uniapp(cli 创建)项目的常用配置 eslint 、 prettier 、 stylelint 、 lint-staged 、 husky 代码格式校验
Wilson_gzw
博客等级 码龄7年 0粉丝 8原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值