网站制作软件区别,wordpress软件模板下载,泉州最专业微信网站建设开发,广州十二区分布图vue基础
vue的基本原理
当一个Vue实例创建时#xff0c;Vue会遍历data中的属性#xff0c;用Object.defineProperty(Vue使用proxy)转换为getter/setter#xff0c;并且在内部追踪相关依赖#xff0c;在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例…vue基础
vue的基本原理
当一个Vue实例创建时Vue会遍历data中的属性用Object.defineProperty(Vue使用proxy)转换为getter/setter并且在内部追踪相关依赖在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例他会在组件渲染的过程中把属性记录为依赖之后依赖项的setter被调用时会通知watcher重新计算从而致使它关联的组件得以更新。
双向数据绑定的原理
MVVM 双向绑定达到数据变化 - 试图更新试图变化 - 数据model变更
Vue是采用数据劫持结合发布者-订阅者模式的方式 通过Object.defineProperty()来劫持各个属性的setter和getter在数据变化时发布消息给订阅者出发相应的监听回调。
由两个主要的部分组成
监听器Observer对所有数据的属性进行监听解析器Compiler对每个元素节点的指令进行扫描和解析根据指令模板替代数据以及绑定相应的更新数据
过程如下Vue为例
new Vue() 首先执行初始化对data执行相应化处理这个过程发生在Observer中同时对模板执行编译找到其中动态绑定的数据从data中获取数据并初始化视图这个过程发生在Compile中同时定义一个更新函数updater和watcher将来对应的数据变化时watcher会调用更新函数由于data的某个key在一个视图中可能出现多次所以每个key都需要一个管家Dep来管理多个watcher将来data中的数据一旦发生变化会先找到对应的Dep通知所有watcher执行更新函数
Object.defineProperty() 来进行数据劫持有什么缺点
Object.defineProperty() 是一开始就遍历对象进行监听所以检测不到对象属性的添加和删除数据的API方法无法监听到例如push、pop方法等监听不到需要对每个属性进行遍历监听如果嵌套的对象层级比较深则会影响性能
MVVM、MVC、MVP的区别
MVVM MVVM分为model、view、viewModel
Model代码数据模型数据和业务逻辑都在Model层中定义view代表UI视图负责数据的展示viewModel负责监听Model中的数据的改变并且控制视图的更新处理用户的交互操作
Model和View并无关联而是通过viewModel来进行联系的Model和View之间有着数据绑定的关系Model中的数据发生改变时会触发View层的更新View中数据的变化也会更新到Model层中。
这种模式实现了Model和View的数据自动同步因此开发者只需要专注于数据的维护而不用操作DOM
MVC MVC是Model、View和Controller的方式来组织代码结构其中的View负责显示逻辑Model负责业务数据以及数据操作。Model数据发生变化的时候会通知有关View层更新页面Controller是View和Model之间的纽带带用户页面发生交互的时候COntroller中的事件触发器开始工作通过调用Model层来完成Model的修改Model再去更新View层 MVP MVC中很多逻辑会在View层和Model层耦合代码复杂的时候可能会造成代码的混乱。MVP模式和MVC的不同在于Presenter和Controller。Presenter来实现对View层和Model层的解耦在MVC中Controller只知道Model中的接口不知道View中的逻辑然而在MVP模式中View层的接口同样暴漏给了Presenter因此可将Model层的变化和View的变化绑定在一起实现View和Model的同步更新。
computed和watch的区别
对于Computed
它支持缓存只有依赖的数据发生变化的时候才会重新计算不支持异步当Computed中有异步操作的时候无法监听数据的变化
对于watch
它不支持缓存数据发生变化时它就会触发相应的操作支持异步监听监听的函数接收两个参数第一个参数是最新的值第二个参数是变化之前的值
总结
computed依赖其他属性值并且computed的值有缓存只有依赖的值发生变化才会重新计算computed的值watch更多的是观察的作用无缓存性类似与某些数据监听的回调函数每次监听的数据发生变化都会执行回调函数
slot 插槽
默认插槽
button typesubmitslotSubmit !-- 默认内容 --/slot
/button具名插槽
div classcontainerheaderslot nameheader/slot/headermainslot/slot/mainfooterslot namefooter/slot/footer
/div使用具名插槽的方式
BaseLayouttemplate v-slot:header!-- header 插槽的内容放这里 --/template
/BaseLayoutv-slot 有对应的简写 #因此 可以简写为 template #header。
作用域插槽 在模板中绑定数据
!-- MyComponent 的模板 --
divslot :textgreetingMessage :count1/slot
/div在调用组件中调用slot 的数据
MyComponent v-slotslotProps{{ slotProps.text }} {{ slotProps.count }}
/MyComponent具名作用域插槽
MyComponenttemplate #headerheaderProps{{ headerProps }}/template
/MyComponentv-if和v-show的区别
v-if 生成节点的时候会忽略对应的节点render的时候不会渲染v-show 会生成vnoderender的时候也会渲染成真实的节点只是在render的过程中控制display属性
v-model 是如何实现的
v-model 实际上是一个语法糖如
input v-modelsearchText /实际上相当于
input:valuesearchTextinputsearchText $event.target.value
/应用在组件上就变成
CustomInput:valuesearchTextupdate:valuenewValue searchText newValue
/实际上是通过prop和$.emit来实现的
!-- CustomInput.vue --
templateinput:valuemodelValueinput$emit(update:value, $event.target.value)/
/templatedata为什么是一个函数而不是对象
Vue组件可能存在多个实例如果使用对象形式定义data会导致它们共用一个data对象状态变化会影响所有组件实例这是不合理的采用函数的形式在initData时会将其作为工厂函数返回新的data对象有效避免多实例之间状态污染的问题。
Vue的性能优化有哪些
1编码阶段
尽量减少data中的数据data中的数据都会增加getter和setter会收集对应的watcherv-if和v-for不能连用vue2 中v-for的优先级高于v-if 会先执行for再根据if干掉不需要的组件如果需要使用v-for给每项元素绑定事件时使用代理SPA采用Keep-alive缓存组件循环组件中的key保证唯一体积比较大的图片懒加载
生命周期
**beforeCreate创建前**数据观测和初始化事件还未开始不能访问到data、computed、watch、methods上的方法和数据**created创建后**实例创建完成实例上配置的options包括data、computed、watch、methods都配置完成但是还未挂载DOM所以不能访问到DOM**beforeMount挂载前**在挂载前被调用相关的render函数首次被调用编译模板把data里面的数据和模板生成HTML此时还没挂载HTML到页面上**mounted挂载后**完成了模板中的HTML渲染到页面上**beforeUpdate更新前**响应式数据更新时调用此时虽然响应式诗句更新了但是真实的DOM还未渲染**updated更新后**在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用此时DOM已经更新**beforeDestroy销毁前**实例销毁前调用这一步实例依然可用this还可以调用**destroyed销毁后**实例销毁后调用
另外还有keep-alive独有的生命周期
activated 命中的缓存组件会执行该钩子函数deactivated切换其他组件时调用
组件通信
组件之间的通信方式有哪些
1父子组件间通信
子组件通过props属性来接收父组件的数据子组件通过$emit向父组件发送数据通过ref属性给子组件设置一个名字通过$refs组件名来获取子组件
2兄弟组件间通信
使用eventBus方法它的本质是通过创建空的Vue实例作为消息传递的中转站通讯的组件引用这个实例来实现消息的传递通过$refs获取兄弟组件也可以进行通信
任意组件之间
使用eventBus使用Vuex 仓库进行数据通信
路由
路由的hash和history模式的区别
hash模式 它的URL带着一个#例如https://abc.com/#/vue hash模式的主要原理是onhashchange()事件
window.onhashchange function(event) {console.log(event.newURL, oldURL)
}使用onhashchange()事件的好处是在页面的hash值发生变化的时候不用向后端发送请求
history模式 history模式中的URL中没有#它使用的是传统的路由分发的模式当输入一个路由时服务器会接收到这个请求并解析这个URL然后做出相应的逻辑处理。
这种模式需要后台支持如果访问到不存在的页面则会返回404
$route 和 $router 的区别
$route 是路由信息对象包括path、params、hash、query、等等的属性$router是路由的实例包括了路由的跳转方法钩子函数等
Vuex
vuex的原理
vuex 是专为vue.js 应用开发的状态管理模式每一个Vuex应用的核心是store仓库。
vuex的状态存储是响应式的当Vue组件从store中读取状态的时候若store中的状态发生变化那么相应的组件也相应得到更新。改变store中的状态的唯一途径就是显式得提交commitmutation这样可以方便的跟踪每项变化。 vuex 各个核心模块的解析 vuex 的基本结构
export default createStore({state: {count: 1},getters: {twoCount(state, getters) {return state.count * 2}},mutations: {addCount(state, payload 1) {state.count payload}},actions: {addCountAsync(context, payload 1) {context.commit(addCount, payload)}},modules: {a: moduleA}
})state state 是存储数据的在组件中可以通过this.$store.state.count获取到count的值当然通过this获取的方式非常的麻烦不方便多个属性获取所以还可以通过辅助函数mapState获取 computed: {...mapState([count])},// 或者computed: {...mapState({count: state state.count})},// 或者computed: {...mapState({countAlias: count})},getter getter 相当于vue中的computed属性是基于vuex中的state计算得来第一个参数是state可以获取到state中的值也可以接受其他的getters作为第二个参数。在组件中可以通过this.$store.getters.twoCount来调用它的辅助函数是mapGetters 。 computed: {...mapGetters([twoCount])},// 或者computed: {...mapGetters({twoCount: twoCount})},mutation 更改 vuex 的 store 中的状态的唯一方法是提交mutation它必须是一个同步函数。它的第一个参数是 state 第二个参数的用户自行传入的参数payload其他名字也可以需要注意的是不建议在组件中直接使用mutation中的函数需要使用store.commit操作mutation中的函数。或者可以使用辅助函数mapMutations在组件中使用例如 methods: {// // 本质上也是映射为 this.$store.commit(addCount)...mapMUtations([addCount])},action action类似于mutation不同在于action提交的是mutation而不是直接设置状态stateaction可以包含任意异步操作。action函数接受一个与store实例具有以相同方法和属性的context对象可以通过context.commit来提交一个mutation也可以使用context.state和context.getters获取state和getters的值但它并不是store实例本体 actions: {addCountAsync(context, payload 1) {setTimeout(() {context.commit(addCount, payload)}, 1000);}},action 通过store.dispatch 触发在组件中使用this.$store.dispatch(addCountAsync)触发在action中可以调用异步函数或者调用接口数据可以分发多重mutation它的辅助函数是mapActions methods: {...mapActions([addCountAsync])}action是异步函数意味着还可以在函数中嵌套进Promise函数
actions: {actionA ({ commit }) {return new Promise((resolve, reject) {setTimeout(() {commit(someMutation)resolve()}, 1000)})}
}可以使用then来接收action函数的结果
store.dispatch(actionA).then(() {// ...
})甚至可以嵌套它的action函数进来
module 当一个数据量非常大的时候store就可能变得相当臃肿为了解决这个问题可以将store分割成许多模块
const moduleA {state: () ({ ... }),mutations: { ... },actions: { ... },getters: { ... }
}const moduleB {state: () ({ ... }),mutations: { ... },actions: { ... }
}const store createStore({modules: {a: moduleA,b: moduleB}
})store.state.a // - moduleA 的状态
store.state.b // - moduleB 的状态模块中的使用方法和根目录中的使用方法一致对于模块内部的 mutation 和 getter接收的第一个参数是模块的局部状态对象。对于模块内部的 action局部状态通过 context.state 暴露出来根节点状态则为 context.rootState对于模块内部的 getter根节点状态会作为第三个参数暴露出来
const moduleA {// ...getters: {sumWithRootCount (state, getters, rootState) {return state.count rootState.count}}
}module 的命名空间 默认情况下模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。也就是说可以和根一样的调用会自动匹配Model中的action 或 mutation。Getter 同样也默认注册在全局命名空间。
为了具有更高的封装度和复用性你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名例如
const store createStore({modules: {account: {namespaced: true,// 模块内容module assetsstate: () ({ ... }), // 模块内的状态已经是嵌套的了使用 namespaced 属性不会对其产生影响getters: {isAdmin () { ... } // - getters[account/isAdmin]},actions: {login () { ... } // - dispatch(account/login)},mutations: {login () { ... } // - commit(account/login)},// 嵌套模块modules: {// 继承父模块的命名空间myPage: {state: () ({ ... }),getters: {profile () { ... } // - getters[account/profile]}},// 进一步嵌套命名空间posts: {namespaced: true,state: () ({ ... }),getters: {popular () { ... } // - getters[account/posts/popular]}}}}}
})在组件中使用如下
computed: {...mapState({a: state state.some.nested.module.a,b: state state.some.nested.module.b}),...mapGetters([some/nested/module/someGetter, // - this[some/nested/module/someGetter]some/nested/module/someOtherGetter, // - this[some/nested/module/someOtherGetter]])
},
methods: {...mapActions([some/nested/module/foo, // - this[some/nested/module/foo]()some/nested/module/bar // - this[some/nested/module/bar]()])
}使用vuex的整体流程
在组件内部通过dispatch来分发action或者通过辅助函数mapActionsaction 通过 commit 来调用mutationmutation 修改 state的值state改变 导致页面重新渲染
在vue3 中使用 vuex 首先引用 useStore钩子函数 等价于this.$store
import { useStore } from vuex
export default {setup () {const store useStore()}
}访问State和Getter 为了访问 state 和 getter需要创建 computed 引用以保留响应性这与在选项式 API 中创建计算属性等效。
import { computed } from vue
import { useStore } from vuexexport default {setup () {const store useStore()return {// 在 computed 函数中访问 statecount: computed(() store.state.count),// 在 computed 函数中访问 getterdouble: computed(() store.getters.double)}}
}访问 Mutation 和 Action
import { useStore } from vuexexport default {setup () {const store useStore()return {// 使用 mutationincrement: () store.commit(increment),// 使用 actionasyncIncrement: () store.dispatch(asyncIncrement)}}
}vue diff 算法
diff 算法简述
1什么是diff算法 diff 算法是一种通过同层的树节点进行比较高效的算法
有两个特点
比较只会在同层级进行不会跨层级比较在diff 的过程中循环是从两边向中间比较
diff 不是vue特有的在很多场景下都有应用在vue中作用是用来虚拟DOM和真实DOM之间的vnode节点比较
2比较方式 diff 的整体策略为深度优先同级比较
1.比较只会在同层级比较不会跨层级比较 2.比较的过程中循环从两边向中间靠拢 以下是vue通过diff算法更新的例子
新旧vnode节点如下图所示
第一次循环后发现旧节点D与新节点D相同直接复用D作为diff的第一个真实的节点同时旧节点的endIndex移动到C新节点的startIndexu移动到C 第二次循环后同样是旧节点的末尾和新节点的开头(都是 C)相同同理diff 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点的 endIndex 移动到了 B新节点的 startIndex 移动到了 E 第三次循环中发现E没有找到这时候只能直接创建新的真实节点 E插入到第二次创建的 C 节点之后。同时新节点的 startIndex 移动到了 A。旧节点的 startIndex 和 endIndex 都保持不动 第四次循环中发现了新旧节点的开头(都是 A)相同于是 diff 后创建了 A 的真实节点插入到前一次创建的 E 节点后面。同时旧节点的 startIndex 移动到了 B新节点的startIndex 移动到了 B 第五次循环中情形同第四次循环一样因此 diff 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的 startIndex移动到了 C新节点的 startIndex 移动到了 F 新节点的 startIndex 已经大于 endIndex 了需要创建 newStartIdx 和 newEndIdx 之间的所有节点也就是节点F直接创建 F 节点对应的真实节点放到 B 节点后面 Vue3
Vue3 有什么更新
1监测机制的改变
vue3 基于代理 Proxy 实现数据劫持消除了vue2当中基于Object.defineProperty 的实现所带来的很多限制
2对象式的组件声明方式
vue2 中的组件时通过声明的方式引入一系列的option和typescript结合比较麻烦vue3 修改了组件的声明方式改成了类的写法这样和TypeScript结合变得容易
语法API
vue2 使用options API方式很多逻辑代码都会混在一起vue3 使用composition API方式逻辑组织更加清晰逻辑的复用更加容易
Vue3 性能提升主要是通过那些方面体现的
1、编译阶段
回顾vue2每个组件实例都对应一个watcher他会在组件渲染的过程中把用到的数据property记录为依赖当依赖发生改变触发setter则会通知watcher从而使关联的组件重新渲染 设想一下假如一个组件有大量的静态节点如
templatediv idcontentp classtext静态文本/pp classtext静态文本/pp classtext{{ message }}/pp classtext静态文本/p...p classtext静态文本/p/div
/template可以看到组件的内部只有一个动态节点剩余的都是一些静态节点所以这里的很多diff算法和遍历都是不需要的造成性能浪费
因此vue3 在编译阶段主要做了以下优化
1diff 算法优化2静态提升3事件监听缓存4SSR优化
1diff优化 vue3 相比于 vue2 在diff算法中增加了静态标记在会发生变化的地方增加一个 flag 标记
下图这里已经标记静态节点的p标签在diff过程中则不会比较把性能进一步提高 静态提升 Vue3中对不参与更新的元素会做静态提升只会被创建一次在渲染时直接复用提升为一个常量不参与 diff 运算
事件监听缓存 默认情况下绑定事件行为被视为动态绑定每次都会去追踪它的变化开启缓存后下次diff算法直接用
SSR优化 当静态内容大到一定量级时候会用createStaticVNode方法在客户端去生成一个static node这些静态node会被直接innerHtml就不需要创建对象然后根据对象渲染
divdivspan你好/span/div... // 很多个静态属性divspan{{ message }}/span/div
/div编译后
import { mergeProps as _mergeProps } from vue
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from vue/server-rendererexport function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {const _cssVars { style: { color: _ctx.color }}_push(div${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))}divspan你好/span...divspan你好/spandivspan${_ssrInterpolate(_ctx.message)}/span/div/div)
}2、源码体积
相比Vue2Vue3整体体积变小了除了移出一些不常用的API再重要的是Tree shanking
任何一个函数如ref、reavtived、computed等仅仅在用到的时候才打包没用到的模块都被摇掉打包的整体体积变小
import { computed, defineComponent, ref } from vue;
export default defineComponent({setup(props, context) {const age ref(18)let state reactive({name: test})const readOnlyAge computed(() age.value) // 19return {age,state,readOnlyAge}}
});3、响应式系统
vue2中采用 defineProperty来劫持整个对象然后进行深度遍历所有属性给每个属性添加getter和setter实现响应式
vue3采用proxy重写了响应式系统因为proxy可以对整个对象进行监听所以不需要深度遍历
可以监听动态属性的添加可以监听到数组的索引和数组length属性可以监听删除属性
虚拟DOM
什么是虚拟DOM
虚拟DOM实际上是一层对真实ODM的抽象用JavaScript对象来描述节点最终通过一系列操作使这个树映射到真实的DOM上
这JavaScript 对象中虚拟DOM表现为一个Object对象并且至少包含标签名tag、属性attrs和子元素对象children三个属性创建虚拟DOM就是为了更好的将模拟的节点渲染到页面视图中所以虚拟DOM对象节点和真实DOM节点属性一一对应
例如在真实的DOM中标签如下
div idappp classp节点内容/ph3{{ foo }}/h3
/div实例化DOM
const app new Vue({el:#app,data:{foo:foo}
})当然我们可以根据基本属性生成如下虚拟DOM类似于
{tag: div,attr: {id: app},children: [{tag: p,attr: {class: p},children: 节点内容},{tag: h3,attr: {},children: foo}]
}观察render生成的虚拟DOM
(function anonymous(
) {with(this){return _c(div,{attrs:{id:app}},[_c(p,{staticClass:p},[_v(节点内容)]),_v( ),_c(h3,[_v(_s(foo))])])}})通过vNode vue可以对这颗抽象树进行创建节点、删除节点以及修改节点的操作经过diff计算做出需要修改的最小单位再去更新视图减少DOM操作提高性能
为什么需要虚拟DOM
DOM是很慢的其元素非常庞大页面性能的问题大部分都是由DOM操作引起的 真实的DOM节点包含很多的属性控制台打印直观感受一下 由此可见如果每次都直接去操作DOM的代价是非常昂贵的平凡操作还会出现页面卡顿影响用户体验
虚拟DOM的技术不仅仅可以用在网页上最大的优势在于抽象了原本的抽象过程实现跨平台的能力目标平台可以是安卓和IOS的原生组件也可以是小程序也可以是各种GUI
如何实现虚拟DOM
首先可以看看vue中的vnode的结构 源码位置 src/core/vdom/vnode.js
export default class VNode {tag: string | void;data: VNodeData | void;children: ?ArrayVNode;text: string | void;elm: Node | void;ns: string | void;context: Component | void; // rendered in this components scopefunctionalContext: Component | void; // only for functional component root nodeskey: string | number | void;componentOptions: VNodeComponentOptions | void;componentInstance: Component | void; // component instanceparent: VNode | void; // component placeholder noderaw: boolean; // contains raw HTML? (server only)isStatic: boolean; // hoisted static nodeisRootInsert: boolean; // necessary for enter transition checkisComment: boolean; // empty comment placeholder?isCloned: boolean; // is a cloned node?isOnce: boolean; // is a v-once node?constructor (tag?: string,data?: VNodeData,children?: ?ArrayVNode,text?: string,elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions) {/*当前节点的标签名*/this.tag tag/*当前节点对应的对象包含了具体的一些数据信息是一个VNodeData类型可以参考VNodeData类型中的数据信息*/this.data data/*当前节点的子节点是一个数组*/this.children children/*当前节点的文本*/this.text text/*当前虚拟节点对应的真实dom节点*/this.elm elm/*当前节点的名字空间*/this.ns undefined/*编译作用域*/this.context context/*函数化组件作用域*/this.functionalContext undefined/*节点的key属性被当作节点的标志用以优化*/this.key data data.key/*组件的option选项*/this.componentOptions componentOptions/*当前节点对应的组件的实例*/this.componentInstance undefined/*当前节点的父节点*/this.parent undefined/*简而言之就是是否为原生HTML或只是普通文本innerHTML的时候为truetextContent的时候为false*/this.raw false/*静态节点标志*/this.isStatic false/*是否作为跟节点插入*/this.isRootInsert true/*是否为注释节点*/this.isComment false/*是否为克隆节点*/this.isCloned false/*是否有v-once指令*/this.isOnce false}// DEPRECATED: alias for componentInstance for backwards compat./* istanbul ignore next https://github.com/answershuto/learnVue*/get child (): Component | void {return this.componentInstance}
}代码中
所有对象的context选项都指向了vue实例elm属性则指向了其相对应的真实DOM节点