首页 > 其他分享 >【慢慢理解Vue的设计思想】

【慢慢理解Vue的设计思想】

时间:2024-06-05 14:58:43浏览次数:14  
标签:node Vue obj val 慢慢 vm 理解 key data

# 理解Vue的设计思想

  • MVVM框架的三要素:数据响应式、模板引擎及其渲染

  • 数据响应式:监听数据变化并在视图中更新

    • Object.defineProperty()
    • Proxy
  • 模版引擎:提供描述视图的模版语法

    • 插值:{{}}
    • 指令:v-bind,v-on,v-model,v-for,v-if
  • 渲染:如何将模板转换为html

    • 模板 => vdom => dom

# 数据响应式原理

数据变更能够响应在视图中,就是数据响应式。vue2中利用Object.defineProperty() 实现变更检 测。

简单实现

// 数据响应式:// Object.defineProperty()functiondefineReactive(obj, key, val){// val可能还是对象,此时我们需要递归observe(val)// 参数3是描述对象  Object.defineProperty(obj, key,{get(){      console.log('get', key);return val},set(newVal){if(newVal!== val){        console.log('set', key);// 防止newVal是对象,提前做一次observeobserve(newVal)        val= newVal}}})}functionobserve(obj){if(typeof obj!=='object'|| obj===null){return}// 遍历  Object.keys(obj).forEach(key=>defineReactive(obj, key, obj[key]))}// 对于新加入属性,需要单独处理他的响应式functionset(obj, key, val){defineReactive(obj, key, val)}const obj={foo:'foo',bar:'bar',baz:{a:1}}observe(obj)// defineReactive(obj, 'foo', 'foo')// obj.foo// obj.foo = 'fooooooooo'// obj.bar// obj.bar = 'barrrrrrrr'// obj.baz.a = '10'// obj.baz = {a: 10}// obj.baz.a = 100// 新添加一些属性// obj.dong = 'dong' // no okset(obj,'dong','dong')obj.dong// 前面的方法对于数组是不支持// 思路:拦截数组7个变更方法push、pop。。。,扩展他们,使他们在变更数据的同时// 额外的执行一个通知更新的任务
    
    
     
     
      
      
      
      
     
     
     
             
    
    

defineProperty() 不支持数组

解决数组数据的响应化

# Vue中的数据响应化

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title></head><body><div id="app"><h3>KVue</h3><p>{{counter}}</p><p k-text="counter"></p><p k-html="desc"></p></div><script src="kvue.js"></script><script>const app=newKVue({el:'#app',data:{counter:1,desc:'<span style="color:red">kvue可还行</span>'}})setInterval(()=>{      app.counter++},1000);</script></body></html>
    
    
     
     
      
      
      
      
     
     
     
             
    
    

# 原理分析

  • new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observer
  • 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile
  • 同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
  • 由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个Watcher
  • 将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数

# 涉及类型介绍

  • KVue:框架构造函数
  • Observer:执行数据响应化(分辨数据是对象还是数组)
  • Compile:编译模板,初始化视图,收集依赖(更新函数、watcher创建)
  • Watcher:执行更新函数(更新dom)
  • Dep:管理多个Watcher,批量更新

# 实现Vue

# 框架构造函数:执行初始化

执行初始化,对data执行响应化处理,kvue.js

functionobserve(obj){if(typeof obj!=='object'|| obj===null){return}// 响应式newObserver(obj)}
    
    
     
     
      
      
      
      
     
     
     
             
    
    
functiondefineReactive(obj, key, val){}classKVue{constructor(options){this.$options= options;this.$data= options.data;observe(this.$data)}}classObserver{constructor(value){this.value= valuethis.walk(value);}walk(obj){    Object.keys(obj).forEach(key=>{defineReactive(obj, key, obj[key])})}}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

$data做代理

classKVue{constructor(options){// 。。。proxy(this,'$data')}}functionproxy(vm){  Object.keys(vm.$data).forEach(key=>{    Object.defineProperty(vm, key,{get(){return vm.$data[key];},set(newVal){        vm.$data[key]= newVal;}});})}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

# 编译 Compile

编译模板中vue模板特殊语法,初始化视图、更新视图

1. 初始化视图

根据节点类型编译

classCompile{constructor(el, vm){this.$vm= vm;this.$el= document.querySelector(el);if(this.$el){this.compile(this.$el);}}  console.log("编译元素"+ node.nodeName);}elseif(this.isInterpolation(node)){  console.log("编译插值文本"+ node.textContent);}if(node.childNodes&& node.childNodes.length>0){this.compile(node);}});}isElement(node){return node.nodeType==1;}isInterpolation(node){return node.nodeType==3&&/\{\{(.*)\}\}/.test(node.textContent);}}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

编译插值

compile(el){// ...}elseif(this.isInerpolation(node)){// console.log("编译插值文本" + node.textContent); this.compileText(node);}});}compileText(node){  console.log(RegExp.$1);  node.textContent=this.$vm[RegExp.$1];}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

编译元素

compile(el){//...if(this.isElement(node)){// console.log("编译元素" + node.nodeName); this.compileElement(node)}}compileElement(node){let nodeAttrs= node.attributes;  Array.from(nodeAttrs).forEach(attr=>{let attrName= attr.name;let exp= attr.value;if(this.isDirective(attrName)){let dir= attrName.substring(2);this[dir]&&this[dir](node, exp);}});}isDirective(attr){return attr.indexOf("k-")==0;}text(node, exp){  node.textContent=this.$vm[exp];}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

# 依赖收集

视图中会用到data中某key,这称为依赖。同一个key可能出现多次,每次都需要收集出来用一个

Watcher来维护它们,此过程称为依赖收集。

多个Watcher需要一个Dep来管理,需要更新时由Dep统一通知。 看下面案例,理出思路:

newVue({template:`<div> <p>{{name1}}</p>            <p>{{name2}}</p><p>{{name1}}</p> <div>`,data:{name1:'name1',name2:'name2'}});
    
    
     
     
      
      
      
      
     
     
     
             
    
    

实现思路

  1. defineReactive时为每一个key创建一个Dep实例
  2. 初始化视图时读取某个key,例如name1,创建一个watcher1
  3. 由于触发name1的getter方法,便将watcher1添加到name1对应的Dep中
  4. 当name1更新,setter触发时,便可通过对应Dep通知其管理所有Watcher更新

创建Watcher,kvue.js

const watchers=[];//临时用于保存watcher测试用// 监听器:负责更新视图 class Watcher {constructor(vm, key, updateFn){// kvue实例this.vm= vm;// 依赖keythis.key= key;// 更新函数this.updateFn= updateFn;// 临时放入watchers数组  watchers.pus// 更新 update() {this.updateFn.call(this.vm,this.vm[this.key]);}}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

编写更新函数、创建watcher

// 调用update函数执插值文本赋值 compileText(node) {// console.log(RegExp.$1);// node.textContent = this.$vm[RegExp.$1]; this.update(node, RegExp.$1, 'text')}text(node, exp){this.update(node, exp,'text')}html(node, exp){this.update(node, exp,'html')}update(node, exp, dir){const fn=this[dir+'Updater'] fn&&fn(node,this.$vm[exp])newWatcher(this.$vm, exp,function(val){    fn&&fn(node, val)})}textUpdater(node, val){  node.textContent= val;}htmlUpdater(node, val){  node.innerHTML= val}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

声明Dep

classDep{constructor(){		this.deps=[]}		addDep(dep){this.deps.push(dep)}notify(){this.deps.forEach(dep=> dep.update());}}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

创建watcher时触发getter

classWatcher{constructor(vm, key, updateFn){    Dep.target=this;this.vm[this.key];    Dep.target=null;}}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

依赖收集,创建Dep实例

defineReactive(obj, key, val){this.observe(val);const dep=newDep() Object.defineProperty(obj, key,{get(){      Dep.target&& dep.addDep(Dep.target);return val},set(newVal){if(newVal=== val)return dep.notify()}})}
    
    
     
     
      
      
      
      
     
     
     
             
    
    

# 完整代码

// 任务:// 1. 数据响应式:是data选项中的对象编程响应式的// 2.// 数据响应式:// Object.defineProperty()functiondefineReactive(obj, key, val){// val可能还是对象,此时我们需要递归observe(val)// 创建Dep实例,他和key一对一对应关系const dep=newDep()// 参数3是描述对象  Object.defineProperty(obj, key,{get(){// console.log('get', key);// 依赖收集:Dep.target就是当前新创建Watcher实例      Dep.target&& dep.addDep(Dep.target)return val},set(newVal){if(newVal!== val){        console.log('set', key);// 防止newVal是对象,提前做一次observeobserve(newVal)        val= newVal// 通知更新        dep.notify()}}})}functionobserve(obj){if(typeof obj!=='object'|| obj===null){return}// 响应式newObserver(obj)}// Observer: 辨别当前对象类型是纯对象还是数组,从而做不同响应式操作classObserver{constructor(value){this.value= value// 辨别类型if(Array.isArray(value)){// todo}else{this.walk(value)}}walk(obj){// 对象响应式    Object.keys(obj).forEach(key=>defineReactive(obj, key, obj[key]))}}// 代理函数:可以将$data代理到KVue的实例// vm是KVue实例functionproxy(vm){  Object.keys(vm.$data).forEach(key=>{// 为当前实例做代理,定义一些key和data相对应    Object.defineProperty(vm, key,{get(){return vm.$data[key]},set(newVal){        vm.$data[key]= newVal}})})}// KVue:解析选项,响应式、编译等等classKVue{constructor(options){this.$options= optionsthis.$data= options.data// 对data选项做响应式处理observe(this.$data)// 代理proxy(this)// 执行编译newCompile(options.el,this)}}// Compile: 遍历视图模板,解析其中的特殊模板语法为更新函数// new Compile(el, vm)classCompile{constructor(el, vm){// el:宿主元素选择器// vm:KVue的实例this.$vm= vm;this.$el= document.querySelector(el)// 执行编译this.compile(this.$el)}compile(el){// 遍历子元素,判断他们类型并做响应处理    el.childNodes.forEach(node=>{// 判断类型if(node.nodeType===1){// 元素节点// console.log('编译元素', node.nodeName);this.compileElement(node)}elseif(this.isInter(node)){// 文本节点// console.log('文本节点', node.textContent);// double killthis.compileText(node)}// 递归子节点if(node.childNodes){this.compile(node)}})}// 是否插值绑定isInter(node){return node.nodeType===3&&/\{\{(.*)\}\}/.test(node.textContent)}// 绑定表达式解析compileText(node){// 获取匹配表达式 RegExp.$1,比如counter, vm['counter']// node.textContent = this.$vm[RegExp.$1]this.update(node, RegExp.$1,'text')}// 编译元素节点:判断指令和事件compileElement(node){// 获取属性const attrs= node.attributes    Array.from(attrs).forEach(attr=>{// k-text="counter"// attr是一个对象{name:'k-text', value: 'counter'}const{ name, value}= attr// 判断是否是指令if(name.indexOf('k-')===0){// 截取指令const dir= name.substring(2)// 指令指令this[dir]&&this[dir](node, value)}// 判断是否是事件 @// else if() {// }})}// k-text文本更新text(node, exp){this.update(node, exp,'text')}// k-htmlhtml(node, exp){this.update(node, exp,'html')}// update方法,高阶函数:除了执行dom操作,创建一个额外watcher实例// dir是指令名称update(node, exp, dir){// 获取更新方法const fn=this[dir+'Updater']// 初始化,让用户看到首次渲染结果    fn&&fn(node,this.$vm[exp])// 创建watcher实例newWatcher(this.$vm, exp,val=>{      fn&&fn(node, val)})}// dom执行方法textUpdater(node, value){    node.textContent= value}htmlUpdater(node, value){    node.innerHTML= value}}// Watcher: 管理依赖,执行更新// const watchers = []classWatcher{// vm是KVue实例// key是data中对应的key名称// fn是更新函数,他知道怎么更新domconstructor(vm, key, fn){this.vm= vmthis.key= keythis.fn= fn// watchers.push(this)// 建立dep和watcher之间的关系    Dep.target=thisthis.vm[this.key]// 读一下key的值触发其getter    Dep.target=null}// 更新函数,由Dep调用update(){// 更新函数调用,设置上下文问KVue实例,传参是当前最新值this.fn.call(this.vm,this.vm[this.key])}}// Dep: 管理多个watcher实例,当对应key发生变化时,通知他们更新classDep{constructor(){this.deps=[]}addDep(dep){// 添加订阅者,dep就是watcher实例this.deps.push(dep)}// 通知更新notify(){this.deps.forEach(w=> w.update())}}
    
    
     
     
      
      
      
      
     
     
     
     
    
    

标签:node,Vue,obj,val,慢慢,vm,理解,key,data
From: https://blog.csdn.net/demotang1/article/details/139471828

相关文章

  • vue中将html导出成pdf
    vue中将页面html内容导出成pdf文件格式,使用 html2canvas、jspdf。首先使用 html2canvas将内容转换为图片,之后写入pdf。1、引用第一个.将页面html转换成图片npminstall--savehtml2canvas第二个.将图片生成pdfnpminstalljspdf--save2、创建  exportTo......
  • VUE网易云【附:资料➕文档】
    前言:我是源码分享交流Coding,专注Java+Vue领域,专业提供程序设计开发、源码分享、技术指导讲解、各类项目免费分享,定制和毕业设计服务!免费获取方式--->>文章末尾处!项目介绍007:项目名:仿网易云技术栈:VUE(页面数据对接网易云官方数据,需联网运行)访问网页:http://localh......
  • 【vuejs】keep-alive组件的原理讲解和使用讲解
    1.keep-alive简介Vue.js框架中的<keep-alive>组件是一个用于缓存组件实例的内置组件,它使得组件在不活动时保持其状态,从而提高应用的性能和用户体验。当使用动态组件<component>切换视图时,不在显示的组件实例会被销毁并重新创建,这会导致状态丢失。而<keep-alive>可......
  • 小小白学习运维 认识运维第一天(纯理论,看懂 去理解)
    云计算是什么公有云(阿里云腾讯云华为云)+私有云(OpenStack)+混合云公有云:阿里云,腾讯云,华为云。。。——————————Iaas选云服务器配置(什么CPU内存磁盘网络运行环境)买一个叫ESC的服务器就好。——————————pass机器硬件+操作系统(Linux)U......
  • vue 项目中使用v-permission 实现按钮级权限控制
    在使用vue-element-admin框架进行开发时,您可以通过自定义指令来实现按钮级的权限控制。这个自定义指令可以根据用户的权限动态地控制按钮的显示。以下是一个详细的实现步骤:1、在src目录下按照如下目录结构,创建一个权限文件,例如permission.js:目录结构:src/directive/permissi......
  • Vue 获取组件名称
    Vue2获取组件名称获取方式:this.$options.name解读:通过Vue2的 this 关键字,可以很容易地访问Vue组件实例对象身上的 $options 的name属性来获取组件名称。 <script>exportdefault{name:"Brand",mounted(){//Brandconsole.log(this.$options.......
  • vue动态加载组件import引入组件找不到组件(Error: Cannot find module)
    更多ruoyi-nbcio功能请看演示系统gitee源代码地址前后端代码:https://gitee.com/nbacheng/ruoyi-nbcio演示地址:RuoYi-Nbcio后台管理系统http://218.75.87.38:9666/更多nbcio-boot功能请看演示系统 gitee源代码地址后端代码:https://gitee.com/nbacheng/nbcio-boot前端......
  • 毕业设计-基于Springboot+Vue的影城管理系统的设计与实现(源码+LW+包运行)
    如需完整项目,请私信博主基于SpringBoot+Vue的影城管理系统开发语言:Java数据库:MySQL技术:SpringBoot+MyBatis+Vue.js工具:IDEA/Ecilpse、Navicat、Maven互联网发展至今,已经解决了很多我们解决不了的难题,使得我们工作更加便捷,提高了我们的工作效率。目前各行各业都在运用网络信......
  • 毕业设计-基于Springboot+Vue的音乐网站与分享平台 的设计与实现(源码+LW+包运行)
    如需完整项目,请私信博主基于SpringBoot+Vue的音乐网站与分享平台开发语言:Java数据库:MySQL技术:SpringBoot+MyBatis+Vue.js工具:IDEA/Ecilpse、Navicat、Maven互联网发展至今,已经解决了很多我们解决不了的难题,使得我们工作更加便捷,提高了我们的工作效率。目前各行各业都在运用......
  • Vue 前端页面利用MediaRecorder实现音频录制
    Don'tTalk,codeishere:重点是startRecord方法<template><div><el-tooltipclass="item"effect="dark"content="再次点击【开始录音】即为重新录制,之前录制的将被作废"placement="top"><el-button:disabled=......