Vue深入了解

Vue深入了解

  • MVVM
  • v-model (双向数据绑定原理)
  • 异步更新
  • keep-alive原理
  • $nextTick原理
  • computed 和 watch 的区别
  • css-scoped
  • 虚拟DOM
  • Vuex && Pinia
  • Vue-router原理
  • proxy 与 Object.defineProperty
  • 组件通信方式

MVVM

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>mini MVVM</title></head><body><div id="app"><p>姓名: <span>{{ name }}</span></p><p>年龄: <span>{{ age }}</span></p></div><script>window.onload = function() {const vue = new Vue({el: '#app',data: {name: '加载中...',age: '加载中...'}})setTimeout(() => {vue.$data.name = '小明'vue.$data.age = 20}, 2000)}class Dep {constructor() {this.watchList = []}add(node) {this.watchList.push(node)}update(newValue) {this.watch.forEach((node) => {node.textContent = value})}}class Vue {constructor(options) {this.options = optionsthis.$data = options.datathis.$el = document.querySelector(options.el)this.obsever(this.$data)this.compile(this.$el) }/*[observe 函数]:利用Object.defineProperty把data中的属性变成响应式的,同时给每一个属性添加一个dep对象(用来存储对应的watcher观察者)首先我们会对需要响应式的 data 对象进行 for 循环遍历,为 data 的每一个 key 映射一个观察者对象在 ES6 中,for 循环每次执行,都可以形成闭包,因此这个观察者对象就存放在闭包中*/observer(data) {Object.keys(data).forEach((key) => {// 给data中的每一个属性添加一个dep对象(该对象用来存储对应的watcher观察者)const dep = new Dep()// 利用闭包 获取和设置属性的时候,操作的都是valuelet value = data[key]Object.defineProperty(data, key, {get() {// 观察者对象添加对应的dom节点Dep.target && dep.add(Dep.target)return value},set(newValue) {// 属性值变化时,更新观察者中所有节点value = newValuedep.update(value)}})})}/*[compile 函数]:我们从根节点向下遍历 DOM,遇到 mustache 形式的文本,则映射成 data.key 对应的值,同时记录到观察者中当遍历到 {{xxx}} 形式的文本,我们正则匹配出其中的变量,将它替换成 data 中的值当data的数据变化时,调用dep对象的update方法,更新所有观察者中的dom节点*/compile(dom) {const mustache = /\{\{(.*)\}\}/Array.from(dom.childNodes).forEach((child) => {// nodeType 为3时为文本节点,并且该节点的内容包含`mustache`(双大括号{{}})if(child.nodeType === 3 && mustache.test(child.textContent)) {const key = mustache.exec(child.textContent)[1].trim()const keyNoTrim = mustache.exec(child.textContent)[1]// 将该节点添加到对应的观察者对象中,在下面的的this.$data[key]中触发对应的get方法Dep.target = childlet value = this.$data[key]child.textContent = child.textContent.replace(`{{${keyNoTrim}}}`, value)Dep.target = null}// 递归遍历子节点if(child.childNodes.length) {this.compile(child)}})}}</script></body>
</html>

v-model (双向数据绑定原理)

采取数据劫持,通过Object.defineProperty()劫持各个属性,给各个属性添加getter和setter,数据变动时触发相应的回调
Observer:给数据加上getter和setter,改变数据时触发setter
Complie:模板解析,将模板中的变量替换成数据,绑定更新函数
Watcher:订阅者,是Observer和Complie之间通信的桥梁,往订阅器中添加自己,有一个update方法,当属性变动通知时,调用update方法,触发complie中绑定的更新函数

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>v-model</title></head><body><div id="app"><div>年龄: <span>{{ info.person.name }}</span></div><p>{{ job }}</p><input v-model="job" placeholder="请输入工作" type="text" /></div></body><script>window.onload = function () {const vue = new Vue({el: '#app',data: {info: {person: {name: '加载中',},},job: '程序猿',},})setTimeout(() => {vue.info.person.name = '小明'}, 2000)}class Dep {constructor() {this.watchList = []}add(node){this.watchList.push(node)}update(value) {this.watchList.forEach((node) => {if(node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {node.value = value } else {node.textContent = value}})}}class Vue {constructor(options){this.options = optionsthis.$data = options.datathis.$el = document.querySelector(options.el)this.observer(this.$data)this.compile(this.$el, this)this.proxy(this.$data, this)}observer(data) {if(data && typeof data === 'object') {const _this = thisObject.keys(data).forEach((key) => {const dep = new Dep()let value = data[key]// 数据劫持,对data增加了递归和设置新值的劫持,让data中每一层数据都是响应式的_this.observer(data[key])Object.defineProperty(data, key, {get(){ Dep.target && dep.add(Dep.target)return value}set(newValue) {value = newValue// 数据劫持,对data增加了递归和设置新值的劫持,让data中每一层数据都是响应式的_this.observer(newValue)dep.upadte(value)}})})}}compile(dom, vm) {const mustache = /\{\{(.*)\}\}/Array.from(dom.childNodes).forEach((child) => {if(child.nodeType === 1) {Array.from(child.attributes).forEach((attr) => {if(attr.name.includes('v-model')) {Dep.target = childchild.value = vm.$data[attr.value]Dep.target = null//给input元素绑定input事件,当输入值变化会触发对应属性的dep.update方法,通知对应的观察者发生变化child.addEventLister('input', (e) => {vm.$data[attr.value] = e.target.value})}})}if(child.nodeType === 3 && mustache.test(child.textContent)) {const key = mustache.exec(child.textContent)[1].trim()const keyNoTrim = mustache.exec(child.textContent)[1]const keyList = key.split('.')Dep.target = childlet value = vm.$dataketList.forEach((item) => value = value[item])child.textContent = child.textContent.replace(`{{${keyNoTrim}}}`, value)Dep.target = null}if(child.childNodes.length) {this.compile(child, vm)}})}// 增加了数据代理,通过this.info.person.name就可以直接修 $data对应的值,实现了this对this.$data的代理proxy(data, vm) {Object.keys(data).forEach((key) => {Object.defineProperty(vm, key, {get() {return data[key]},set(newValue) {data[key] = newValue}})})}}</script>
</html>

异步更新

 Vue数据更新频繁,但dom只会更新一次,为什么?1、Vue更新dom是异步更新,当Vue的数据更新后,不会立即更新dom2、侦听到数据变化,Vue会开启一个队列, 并缓存在同一事件循环中发生的所有数据变更3、同一个watcher被多次出发,只会被推入队列中一次,避免重复修改相同的dom4、同步任务执行完,执行异步watcher队列任务,一次性更新dom

keep-alive原理

缓存策略时LRU,组件切换时,保存一些组件的状态,防止多次渲染三大属性:include、exclude、max- 根据include/exclude配置的组件名,与对应组件的name进行条件匹配
- 根据组件ID和tag生成缓存的key,在缓存对象中查找是否已经缓存,存在取出并更新
- 检查是都超过了max设置的值,超过的话,根据LRU缓存策略,删除最近最久没有使用的组件
- 将KeepAlive属性更改为true,actived和deactivated两个钩子函数会用到

$nextTick原理

本质是对JavaScript执行原理EventLoop的一种应用
核心是模拟对应的微/宏任务的实现,利用JavaScript的异步回调任务队列来实现Vue框架自己的异步回调队列Vue.$nextTick 为什么优先使用微任务实现:根据 event loop 与浏览器更新渲染时机,宏任务 → 微任务 → 渲染更新,使用微任务,本次event loop轮询就可以获取到更新的dom如果使用宏任务,要到下一次event loop中,才能获取到更新的dom

computed 和 watch 的区别

  computed关键点:computed属性用于创建派生数据,这些数据是基于响应式依赖自动计算的。它们提供了缓存机制,只有当依赖项变化时,计算属性才会重新计算。computed适合于声明性地描述数据如何从其他数据派生,常用于视图渲染优化watch关键点:watch用于侦听响应式数据的变化,并在变化发生时执行定义的逻辑。它不具备缓存机制,每次数据变化都会触发回调函数。watch适合于执行复杂的业务逻辑,如异步请求、DOM操作,或者在数据变化时执行条件性响应。computed是声明式的,用于计算并缓存视图所需的数据,它根据响应式数据的变化自动重新计算并提供缓存。只有当其依赖的响应式数据变化时,才会重新执行计算。computed在开始时自动建立依赖关系,默认第一次加载的时候就开始监听watch是命令式的,用于监听响应式数据的变化,每次变化都会触发执行预定义的回调函数。watch默认在开始时不执行监听,除非设置immediate: true,这允许在数据变化时立即执行回调computed原理:1、初始化计算属性时,遍历计算computed对象,给每一个计算属性分别生成一个computed watcher, 并将watcher的dirty设置为true,初始化时不会立即计算,只有在获取计算的值时才会进行计算2、初始化时将Dep.target设置成当前的computer watcher,将computed watcher 添加到所依赖的data值对应的dep中,然后计算computed对应的值,然后将dirty改为false3、当所依赖的data中的值发生变化时,调用set方法触发dep 的notify方法,将watcher中dirty设置为true4、下次获取计算属性的值时,如果dirty为true,重新计算值5、dirty是控制缓存的关键,当依赖的data发生变化时,dirty设置为true,再次获取值时,就会重新计算值watch原理:1、遍历watch对象, 给其中每一个watch属性,生成对应的user watcher2、调用watcher中的get方法,将Dep.target设置成当前的user watcher,并将user watcher添加到监听data值对应的dep中(依赖收集的过程)3、当所监听data中的值发生变化时,会调用set方法触发dep的notify方法,执行watcher中定义的方法4、设置成deep:true的情况,递归遍历所监听的对象,将user watcher添加到对象中每一层key值的dep对象中,这样无论当对象的中哪一层发生变化,wacher都能监听到。通过对象的递归遍历,实现了深度监听功能

css-scoped

  原理:编译时,给每一个Vue文件生成一个唯一的id,将此id添加到当前文件的所有html标签上如:<div class="demo"></div>会被编译成<div class="demo" data-v-27e4e96e></div>编译style标签时,将css选择器改造成为属性选择器如:.demo{color: red;}会被编译成.demo[data-v-27e4e96e]{color: red;}

虚拟DOM

 什么是虚拟Dom使用JS对象模拟真实DOM节点,但是对比真实DOM更加轻量级1、前端性能的优化,尽量减少真实DOM的操作,频繁的操作DOM会导致浏览器的回流会重绘2、使用虚拟DOM,当数据变化,页面需要更新的时候,通过diff算法,对新旧的虚拟dom节点进行对比,比较两棵树的差异生成差异对象,一次性对DOM进行批量操作3、虚拟DOM本质上是JS对象,使用虚拟DOM可以进行更方便的跨平台操作
	// 真实 转 虚拟function dom2Json(dom) {if (!dom.tagName) returnlet obj = {}obj.tag = dom.tagNameobj.props = {}Array.from(dom.attributes).forEach((attr) => {obj.props[attr.name] = attr.value})obj.children = []dom.childNodes.forEach((item) => {// 去除空的节点dom2Json(item) && obj.children.push(dom2Json(item))})return obj}class Element {constructor(type, props, children) {this.type = typethis.props = propsthis.children = children}}// 虚拟 转 真实function render(domObj) {let el = document.querySelector(domObj.type)Object.keys(domObj.props).forEach((key) => {let value = domObj.props[key]switch (key) {case 'value':if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {el.value = value} else {el.setAttribute(key, value)}breakcase 'style':el.style.cssText = valuebreakdefault:el.setAttribute(key, value)}})domObj.childeren.forEach((child) => {child =child instanceof Element? render(child): document.createTextNode(child)})return el}

Vuex && Pinia

Vuex 与 Pinia 的区别语法和结构:Vuex 的语法相对较为复杂,而 Pinia 的语法更加简洁和直观。模块系统:Vuex 支持模块系统,可以将状态拆分成多个模块进行管理,而 Pinia 也提供了类似的功能,但更加灵活和易于使用。类型支持:Pinia 提供了更好的类型支持,可以在代码中获得更好的类型推断和提示。开发体验:Pinia 在开发体验上更加友好,提供了更多的辅助函数和工具,使开发更加高效。
VueX的原理1、store本质就是一个没有template的组件2、利用mixin机制在beforeCreate钩子前混入VuexInit方法3、VuexInit方法实现将store 注册到当前组件的$store中4、state 相当于组件内的data,定义在state上的变量相当于定义在组件的data中的变量,都是响应式的5、当页面中使用了state中的数据,就是依赖收集的过程,6、当state中的数据发生变化,就通过调用对应属性的dep对象的notify方法,去修改视图变化

在这里插入图片描述

Vue-router原理

   1、创建的页面路由会与该页面形成一个路由表(key-value模式,key为路由,value为页面)2、通过监听浏览器地址栏URL的变化,匹配路由表,将对应路由的页面替换旧页面,达到无需刷新的效果3、目前单页面使用的路由有两种实现方式: hash 模式、history 模式4、hash模式(路由中带#号),通过hashchange事件来监听路由的变化window.addEventListener('hashchange', ())=>{})5、history 模式,利用了pushState() 和replaceState() 方法,实现往history中添加新的浏览记录、或替换对应的浏览记录通过popstate事件来监听路由的变化,window.addEventListener('popstate', ())=>{})

proxy 与 Object.defineProperty

1)初始化性能优化:Vue 2 在初始化响应式数据时,会递归遍历对象的所有属性并使用 Object.defineProperty为每个属性添加 getter 和 setter。这样的初始化过程会产生大量的 getter 和 setter,对于大规模的对象或数据,初始化时间会较长。Vue 3 中,使用 Proxy 对象进行拦截,初始化性能得到了显著提升,因为 Proxy 是在整个对象级别上进行拦截,无需遍历每个属性。
2)深层属性监听优化:Vue 2 中,对于深层嵌套的属性,需要通过递归方式为每个属性添加响应式处理,这在大型对象上可能会导致性能下降。Vue 3 中,Proxy 可以递归地拦截整个对象的操作,无需为每个属性单独处理,从而提高了深层属性监听的性能。
3)删除属性性能优化:Vue 2 中,当删除一个属性时,需要通过 Vue.$delete 或者 Vue.delete 方法来触发更新。这是因为 Vue 2 使用的 Object.defineProperty 无法拦截属性的删除操作。Vue 3 中,使用 Proxy 可以直接拦截属性的删除操作,从而简化了删除属性的处理逻辑,并提高了性能。
4)动态添加属性性能优化:Vue 2 中,动态添加新属性需要通过 Vue.set 方法来触发更新,否则新添加的属性将不会是响应式的。Vue 3 中,Proxy 可以直接拦截动态添加属性的操作,并将其设置为响应式属性,无需额外的处理方法,提高了性能和代码的简洁性。

组件通信方式

  • 通信的种类

    • 父组件向子组件通信
    • 子组件向父组件通信
    • 隔代组件间通信
    • 兄弟组件间通信
  • 实现通信的方式

    • props
    • vue自定义事件
    • 消息订阅与发布
    • vuex
    • slot
    • 依赖注入
  • 方式一:props

    • 通过一般属性实现父向子通信
    • 通过函数属性实现子向父通信
    • 缺点:隔代组件和兄弟组件间通信比较麻烦
  • 方式二:vue自定义组件

    • vue内置实现,可以代替函数类型的props
      a.绑定监听:<MyComp @eventName=“callback”>

      b.触发(分发)事件: this.$emit(“eventName” , data)

    • 适用于子组件与父组件通信居多,可以利用事件总线,进行兄弟组件间的通信,类似于vuex

  • 方式三:消息订阅发布

    • 需要引入消息订阅与发布的实现库,如: pubsub-js
      a.订阅消息:PubSub.subscribe(‘msg’, (msg,data)=>{})

      b.发布消息: PubSub.publish(‘msg’, data)

    • 优点:此方式可实现任意关系组件间通信

  • 方式四:vuex

    • 是什么:vuex是vue官方提供的集中式管理vue多组件共享状态数据的vue插件
    • 优点:对组件间关系没有限制,且相比于pubsub库管理更集中,更方便
  • 方式五:slot

    • 是什么:专门用来实现父向子传递带数据的标签

      a.子组件

      b.父组件

    • 注意:通信的标签模板是在父组件中解析好后再传递给子组件的

  • 方式六:依赖注入

    • provide、inject
    provide() { return {     num: this.num  };
    }
    inject: ['num']
    

    注意: 依赖注入所提供的属性是非响应式的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1559162.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

AD原理图编译出现Net XX has no driving source

提示无驱动电压源&#xff0c;这是因为你的芯片管脚设置了电气属性造成的。 两种解决AD中出现Net has no driving source警告的方法。 方法一&#xff1a;取消电气属性检测&#xff0c;但不推荐&#xff1b; 打开原理图编译项&#xff0c;将NET no driving source 修改为no …

PostgreSQL的学习心得和知识总结(一百五十三)|[performance]将 OR 子句转换为 ANY 表达式

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

树控件QTreeWidget

树控件跟表格控件类似&#xff0c;也可以有多列&#xff0c;也可以只有1列&#xff0c;可以有多行&#xff0c;只不过每一行都是一个QTreeWidgetItem&#xff0c;每一行都是一个可以展开的树 常用属性和方法 显示和隐藏标题栏 树控件只有水平标题栏 //获取和设置标题栏的显…

PPT在线画SWOT分析图!这2个在线软件堪称办公必备!

swot分析ppt怎么做&#xff1f; swot分析是一个非常常用的战略分析框架&#xff0c;经常会在ppt中使用。想在ppt中绘制swot分析图&#xff0c;使用自带的形状工具可以制作出来&#xff0c;但绘制效率不够高&#xff0c;在需要大批量制作的场景下&#xff0c;会让人非常心累………

DepthB2R靶机打靶记录

一、靶机介绍 下载地址&#xff1a;https://download.vulnhub.com/depth/DepthB2R.ova 二、信息收集 根据靶机主页显示&#xff0c;确认靶机ip为192.168.242.132 端口扫描 nmap -p- -A 192.168.242.132 发现只开放了8080端口 用dirsearch扫个目录 apt-get update apt-get …

基于LORA的一主多从监测系统_0.96OLED

关联&#xff1a;0.96OLED hal硬件I2C LORA 在本项目中每个节点都使用oled来显示采集到的数据以及节点状态&#xff0c;OLED使用I2C接口与STM32连接&#xff0c;这个屏幕内部驱动IC为SSD1306&#xff0c;SSD1306作为从机地址为0x78 发送数据&#xff1a;起始…

【Linux】基本认知全套入门

目录 Linux简介 Linux发行版本 发行版选择建议 Centos-社区企业操作系统 Centos版本选择 Linux系统目录 Linux常用命令 SSH客户端 Linux文件操作命令 vim重要快捷键 应用下载与安装 netstat&#xff0c;ps与kill命令使用 Linux应用服务化 Linux用户与权限 Linu…

Telephony CarrierConfig配置

1、CarrierConfig配置介绍 CarrierConfig&#xff08;运营商配置&#xff09;&#xff0c;是Android为了针对不同运营商配置不同功能的配置文件&#xff0c;类似Modem的MBN配置&#xff0c;可以实现插入不同运营商卡&#xff0c;不同的功能实现或菜单显示等。 2、CarrierConfig…

力扣之1355.活动参与者

题目&#xff1a; Sql 测试用例&#xff1a; Create table If Not Exists Friends (id int, name varchar(30), activity varchar(30)); Create table If Not Exists Activities (id int, name varchar(30)); Truncate table Friends; insert into Friends (id, name, acti…

【数据结构与算法-高阶】并查集

【数据结构与算法-高阶】并查集 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;数据结构与算法&#x1f345; &#x1f33c;文章目录&#x1f33c; 1. 并查集原理 2. 并查集实现 3. 并查集应用 1. 并查集原理 在一些应用问题中&…

charAt,chartCodeAt,codePointAt,fromCodePoint,fromCharCode

生僻字的length算2,有些空格是特殊空格,比如\u3000 u3000不是全角空格&#xff0c;u3000是表意字空格&#xff08;Ideographic Space&#xff09;&#xff0c;宽度和一个表意字&#xff08;汉字&#xff09;相同。它应当被当做汉字来处理。比如&#xff0c;在一些排版中&#x…

OpenSource - License 开源项目 TrueLicense

文章目录 官网集成Demo 官网 https://truelicense.namespace.global/ https://github.com/christian-schlichtherle/truelicense 集成Demo https://github.com/christian-schlichtherle/truelicense-maven-archetype https://github.com/zifangsky/LicenseDemo https://git…

map和set(c++)

前言 在前面我们在介绍二叉搜索树时我们分别实现了一个key结构和key-val结构&#xff0c;如果我们再进一步完善这棵树&#xff0c;将二叉搜索树升级为红黑树去存储key和key-val那么我们就可以得到我们今天要介绍的主角map和set。当然了标准库的实现还是有很多需要注意的地方&a…

植物大战僵尸修改器-MFC

创建项目 创建mfc应用 基于对话框 打开资源视图下的 IDD_MFCAPPLICTION2_DIALOG 限制对话框大小 将属性中Border的值改为对话框外框 删除对话框中原有的控件 属性-外观-Caption 设置对话框标题 工具箱中拖放一个按钮 修改按钮名称 将按钮ID改为IDC_COURSE 在MFCApplication2…

k8s微服务

一 、什么是微服务 用控制器来完成集群的工作负载&#xff0c;那么应用如何暴漏出去&#xff1f;需要通过微服务暴漏出去后才能被访问 Service是一组提供相同服务的Pod对外开放的接口。 借助Service&#xff0c;应用可以实现服务发现和负载均衡。 service默认只支持4层负载均…

QT安装成功后-在创建项目时,发现仅有项目名文件

&#xff08;1&#xff09;QT安装成功后&#xff0c;发现仅有项目名文件其他可编辑文件缺失 &#xff08;2&#xff09;点击文件名左上角的感叹号显示【No kits are enabled for this project. Enable】 小编在尝试多次后发现&#xff0c;可以通过以下方式解决&#xff1a;QT软…

接着上一篇stp 实验继续

理论看上一篇&#xff0c;我们直接实验 首先找出&#xff52;&#xff4f;&#xff4f;&#xff54; 桥 很明显 &#xff53;&#xff57;&#xff11; 为&#xff52;&#xff4f;&#xff4f;&#xff54; 桥&#xff0c;所谓&#xff53;&#xff57;&#xff11;  &a…

从Hinton获得今年的诺贝尔物理学奖说起

“深度人工智能”是成都深度智谷科技旗下的人工智能教育机构订阅号&#xff0c;主要分享人工智能的基础知识、技术发展、学习经验等。此外&#xff0c;订阅号还为大家提供了人工智能的培训学习服务和人工智能证书的报考服务&#xff0c;欢迎大家前来咨询&#xff0c;实现自己的…

JavaSE——集合1:Collection接口(Iterator和增强for遍历集合)

目录 一、集合框架体系(重要) 二、集合引入 (一)集合的理解与好处 三、Collection接口 (一)Collection接口实现类的特点 (二)Collection接口常用方法 (三)Collection接口遍历元素的方式(Iterator和增强for) 1.使用Iterator(迭代器) 1.1Iterator(迭代器)介绍 1.2Itera…

OmniH2O——通用灵巧且可全身远程操作并学习的人形机器人(其前身H2O是HumanPlus的重要参考)

前言 由于我司一直在针对各个工厂、公司、客户特定的业务场景&#xff0c;做解决方案或定制开发&#xff0c;所以针对每一个场景&#xff0c;我们都会反复考虑用什么样的机器人做定制开发 于此&#xff0c;便不可避免的追踪国内外最前沿的机器人技术进展&#xff0c;本来准备…