你有对 Vue 我的项目进行哪些优化?
(1)代码层面的优化
- v-if 和 v-show 辨别应用场景
- computed 和 watch 辨别应用场景
- v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化有限列表性能
- 服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
- Webpack 对图片进行压缩
- 缩小 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建后果输入剖析
- Vue 我的项目的编译优化
(3)根底的 Web 技术的优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的应用
- 应用 Chrome Performance 查找性能瓶颈
Vuex 中 action 和 mutation 的区别
mutation 中的操作是一系列的同步函数,用于批改 state 中的变量的的状态。当应用 vuex 时须要通过 commit 来提交须要操作的内容。mutation 十分相似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是理论进行状态更改的中央,并且它会承受 state 作为第一个参数:
const store = new Vuex.Store({
state: {count: 1},
mutations: {increment (state) {state.count++ // 变更状态}
}
})
当触发一个类型为 increment 的 mutation 时,须要调用此函数:
store.commit('increment')
而 Action 相似于 mutation,不同点在于:
- Action 能够蕴含任意异步操作。
- Action 提交的是 mutation,而不是间接变更状态。
const store = new Vuex.Store({
state: {count: 0},
mutations: {increment (state) {state.count++}
},
actions: {increment (context) {context.commit('increment')
}
}
})
Action 函数承受一个与 store 实例具备雷同办法和属性的 context 对象,因而你能够调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
所以,两者的不同点如下:
- Mutation 专一于批改 State,实践上是批改 State 的惟一路径;Action 业务代码、异步申请。
- Mutation:必须同步执行;Action:能够异步,但不能间接操作 State。
- 在视图更新时,先触发 actions,actions 再触发 mutation
- mutation 的参数是 state,它蕴含 store 中的数据;store 的参数是 context,它是 state 的父级,蕴含 state、getters
函数式组件劣势和原理
函数组件的特点
- 函数式组件须要在申明组件是指定
functional:true
- 不须要实例化,所以没有
this
,this
通过render
函数的第二个参数context
来代替 - 没有生命周期钩子函数,不能应用计算属性,
watch
- 不能通过
$emit
对外裸露事件,调用事件只能通过context.listeners.click
的形式调用内部传入的事件 - 因为函数式组件是没有实例化的,所以在内部通过
ref
去援用组件时,理论援用的是HTMLElement
- 函数式组件的
props
能够不必显示申明,所以没有在props
外面申明的属性都会被主动隐式解析为prop
, 而一般组件所有未声明的属性都解析到$attrs
外面,并主动挂载到组件根元素下面 (能够通过inheritAttrs
属性禁止)
长处
- 因为函数式组件不须要实例化,无状态,没有生命周期,所以渲染性能要好于一般组件
- 函数式组件构造比较简单,代码构造更清晰
应用场景:
- 一个简略的展现组件,作为容器组件应用 比方
router-view
就是一个函数式组件 - “高阶组件”——用于接管一个组件作为参数,返回一个被包装过的组件
例子
Vue.component('functional',{ // 构造函数产生虚构节点的
functional:true, // 函数式组件 // data={attrs:{}}
render(h){return h('div','test')
}
})
const vm = new Vue({el: '#app'})
源码相干
// functional component
if (isTrue(Ctor.options.functional)) { // 带有 functional 的属性的就是函数式组件
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on // 处理事件
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn // 解决原生事件
// install component management hooks onto the placeholder node
installComponentHooks(data) // 装置组件相干钩子(函数式组件没有调用此办法,从而性能高于一般组件)
v-model 实现原理
咱们在
vue
我的项目中次要应用v-model
指令在表单input
、textarea
、select
等元素上创立双向数据绑定,咱们晓得v-model
实质上不过是语法糖(能够看成是value + input
办法的语法糖),v-model
在外部为不同的输出元素应用不同的属性并抛出不同的事件:
text
和textarea
元素应用value
属性和input
事件checkbox
和radio
应用checked
属性和change
事件select
字段将value
作为prop
并将change
作为事件
所以咱们能够 v -model 进行如下改写:
<input v-model="sth" />
<!-- 等同于 -->
<input :value="sth" @input="sth = $event.target.value" />
当在
input
元素中应用v-model
实现双数据绑定,其实就是在输出的时候触发元素的input
事件,通过这个语法糖,实现了数据的双向绑定
- 这个语法糖必须是固定的,也就是说属性必须为
value
,办法名必须为:input
。 - 晓得了
v-model
的原理,咱们能够在自定义组件上实现v-model
//Parent
<template>
{{num}}
<Child v-model="num">
</template>
export default {data(){
return {num: 0}
}
}
//Child
<template>
<div @click="add">Add</div>
</template>
export default {props: ['value'], // 属性必须为 value
methods:{add(){
// 办法名为 input
this.$emit('input', this.value + 1)
}
}
}
原理
会将组件的 v-model
默认转化成value+input
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<el-checkbox v-model="check"></el- checkbox>');
// 察看输入的渲染函数:// with(this) {
// return _c('el-checkbox', {
// model: {// value: (check),
// callback: function ($$v) {check = $$v},
// expression: "check"
// }
// })
// }
// 源码地位 core/vdom/create-component.js line:155
function transformModel (options, data: any) {const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
const on = data.on || (data.on = {})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {if (Array.isArray(existing) ? existing.indexOf(callback) === -1 : existing !== callback ) {on[event] = [callback].concat(existing)
}
} else {on[event] = callback
}
}
原生的 v-model
,会依据标签的不同生成不同的事件和属性
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<input v-model="value"/>');
// with(this) {
// return _c('input', {// directives: [{ name: "model", rawName: "v-model", value: (value), expression: "value" }],
// domProps: {"value": (value) },
// on: {"input": function ($event) {// if ($event.target.composing) return;
// value = $event.target.value
// }
// }
// })
// }
编译时:不同的标签解析出的内容不一样
platforms/web/compiler/directives/model.js
if (el.component) {genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {genSelect(el, value, modifiers)
} else if (tag === 'input' && type === 'checkbox') {genCheckboxModel(el, value, modifiers)
} else if (tag === 'input' && type === 'radio') {genRadioModel(el, value, modifiers)
} else if (tag === 'input' || tag === 'textarea') {genDefaultModel(el, value, modifiers)
} else if (!config.isReservedTag(tag)) {genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime
return false
}
运行时:会对元素解决一些对于输入法的问题
platforms/web/runtime/directives/model.js
inserted (el, binding, vnode, oldVnode) {if (vnode.tag === 'select') { // #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {mergeVNodeHook(vnode, 'postpatch', () => {directive.componentUpdated(el, binding, vnode)
})
} else {setSelected(el, binding, vnode.context)
}
el._vOptions = [].map.call(el.options, getValue)
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers
if (!binding.modifiers.lazy) {el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd) /* istanbul ignore if */
if (isIE9) {el.vmodel = true}
}
}
}
v-if 和 v -show 区别
v-show
暗藏则是为该元素增加css--display:none
,dom
元素仍旧还在。v-if
显示暗藏是将dom
元素整个增加或删除- 编译过程:
v-if
切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show
只是简略的基于css
切换 - 编译条件:
v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染 v-show
由false
变为true
的时候不会触发组件的生命周期v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
办法- 性能耗费:
v-if
有更高的切换耗费;v-show
有更高的初始渲染耗费
v-show 与 v -if 的应用场景
v-if
与v-show
都能管制dom
元素在页面的显示v-if
相比v-show
开销更大的(间接操作dom 节
点减少与删除)- 如果须要十分频繁地切换,则应用 v-show 较好
- 如果在运行时条件很少扭转,则应用
v-if
较好
v-show 与 v -if 原理剖析
v-show
原理
不论初始条件是什么,元素总是会被渲染
咱们看一下在 vue 中是如何实现的
代码很好了解,有 transition
就执行 transition
,没有就间接设置display
属性
// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {beforeMount(el, { value}, {transition}) {
el._vod = el.style.display === 'none' ? '' : el.style.display
if (transition && value) {transition.beforeEnter(el)
} else {setDisplay(el, value)
}
},
mounted(el, { value}, {transition}) {if (transition && value) {transition.enter(el)
}
},
updated(el, { value, oldValue}, {transition}) {// ...},
beforeUnmount(el, { value}) {setDisplay(el, value)
}
}
v-if
原理
v-if
在实现上比 v-show
要简单的多,因为还有else
else-if
等条件须要解决,这里咱们也只摘抄源码中解决 v-if
的一小部分
返回一个 node
节点,render
函数通过表达式的值来决定是否生成DOM
// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/,
(node, dir, context) => {return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// ...
return () => {if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
Vue 要做权限治理该怎么做?管制到按钮级别的权限怎么做?
剖析
- 综合实际题目,理论开发中常常须要面临权限治理的需要,考查理论利用能力。
- 权限治理个别需要是两个:页面权限和按钮权限,从这两个方面阐述即可。
思路
- 权限治理需要剖析:页面和按钮权限
- 权限治理的实现计划:分后端计划和前端计划论述
- 说说各自的优缺点
答复范例
- 权限治理个别需要是页面权限和按钮权限的治理
- 具体实现的时候分后端和前端两种计划:
- 前端计划 会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后依据角色过滤出路由表。比方我会配置一个
asyncRoutes
数组,须要认证的页面在其路由的meta
中增加一个roles
字段,等获取用户角色之后取两者的交加,若后果不为空则阐明能够拜访。此过滤过程完结,剩下的路由就是该用户能拜访的页面,最初通过router.addRoutes(accessRoutes)
形式动静增加路由即可
- 后端计划 会把所有页面路由信息存在数据库中,用户登录的时候依据其角色查问失去其能拜访的所有页面路由信息返回给前端,前端再通过
addRoutes
动静增加路由信息 - 按钮权限的管制通常会
实现一个指令
,例如v-permission
,将按钮要求角色通过值传给 v-permission
指令,在指令的moutned
钩子中能够判断以后用户角色和按钮是否存在交加,有则保留按钮,无则移除按钮 - 纯前端计划的长处是实现简略,不须要额定权限治理页面,然而保护起来问题比拟大,有新的页面和角色需要就要批改前端代码从新打包部署;服务端计划就不存在这个问题,通过专门的角色和权限治理页面,配置页面和按钮权限信息到数据库,利用每次登陆时获取的都是最新的路由信息,堪称一劳永逸!
可能的诘问
- 相似
Tabs
这类组件能不能应用v-permission
指令实现按钮权限管制?
<el-tabs>
<el-tab-pane label="⽤户治理" name="first"> ⽤户治理 </el-tab-pane>
<el-tab-pane label="⻆⾊治理" name="third"> ⻆⾊治理 </el-tab-pane>
</el-tabs>
- 服务端返回的路由信息如何增加到路由器中?
// 前端组件名和组件映射表
const map = {//xx: require('@/views/xx.vue').default // 同步的⽅式
xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的 asyncRoutes
const asyncRoutes = [{ path: '/xx', component: 'xx',...}
]
// 遍历 asyncRoutes,将 component 替换为 map[component]
function mapComponent(asyncRoutes) {
asyncRoutes.forEach(route => {route.component = map[route.component];
if(route.children) {route.children.map(child => mapComponent(child))
}
})
}
mapComponent(asyncRoutes)
你是怎么解决 vue 我的项目中的谬误的?
剖析
- 这是一个综合利用题目,在我的项目中咱们经常须要将 App 的异样上报,此时错误处理就很重要了。
- 这里要辨别谬误的类型,针对性做收集。
- 而后是将收集的的错误信息上报服务器。
思路
- 首先辨别谬误类型
- 依据谬误不同类型做相应收集
- 收集的谬误是如何上报服务器的
答复范例
- 利用中的谬误类型分为 ”
接口异样
“ 和“代码逻辑异样
” - 咱们须要依据不同谬误类型做相应解决:接口异样是咱们申请后端接口过程中产生的异样,可能是申请失败,也可能是申请取得了服务器响应,然而返回的是谬误状态。以
Axios
为例,这类异样咱们能够通过封装Axios
,在拦截器中对立解决整个利用中申请的谬误。代码逻辑异样
是咱们编写的前端代码中存在逻辑上的谬误造成的异样,vue
利用中最常见的形式是应用全局谬误处理函数app.config.errorHandler
收集谬误 - 收集到谬误之后,须要对立解决这些异样:剖析谬误,获取须要错误信息和数据。这里应该无效辨别谬误类型,如果是申请谬误,须要上报接口信息,参数,状态码等;对于前端逻辑异样,获取谬误名称和详情即可。另外还能够收集利用名称、环境、版本、用户信息,所在页面等。这些信息能够通过
vuex
存储的全局状态和路由信息获取
实际
axios
拦截器中解决捕捉异样:
// 响应拦截器
instance.interceptors.response.use((response) => {return response.data;},
(error) => {
// 存在 response 阐明服务器有响应
if (error.response) {
let response = error.response;
if (response.status >= 400) {handleError(response);
}
} else {handleError(null);
}
return Promise.reject(error);
},
);
vue
中全局捕捉异样:
import {createApp} from 'vue'
const app = createApp(...)
app.config.errorHandler = (err, instance, info) => {// report error to tracking services}
解决接口申请谬误:
function handleError(error, type) {if(type == 1) {
// 接口谬误,从 config 字段中获取申请信息
let {url, method, params, data} = error.config
let err_data = {
url, method,
params: {query: params, body: data},
error: error.data?.message || JSON.stringify(error.data),
})
}
}
解决前端逻辑谬误:
function handleError(error, type) {if(type == 2) {
let errData = null
// 逻辑谬误
if(error instanceof Error) {let { name, message} = error
errData = {
type: name,
error: message
}
} else {
errData = {
type: 'other',
error: JSON.strigify(error)
}
}
}
}
参考:前端 vue 面试题具体解答
Vue3.2 setup 语法糖汇总
提醒:vue3.2
版本开始能力应用语法糖!
在 Vue3.0
中变量必须 return
进去,template
中能力应用;而在 Vue3.2
中只须要在 script
标签上加上 setup
属性,无需 return
,template
便可间接应用,十分的香啊!
1. 如何应用 setup 语法糖
只需在 script
标签上写上 setup
<template>
</template>
<script setup>
</script>
<style scoped lang="less">
</style>
2. data 数据的应用
因为 setup
不需写 return
,所以间接申明数据即可
<script setup>
import {
ref,
reactive,
toRefs,
} from 'vue'
const data = reactive({
patternVisible: false,
debugVisible: false,
aboutExeVisible: false,
})
const content = ref('content')
// 应用 toRefs 解构
const {patternVisible, debugVisible, aboutExeVisible} = toRefs(data)
</script>
3. method 办法的应用
<template >
<button @click="onClickHelp"> 帮忙 </button>
</template>
<script setup>
import {reactive} from 'vue'
const data = reactive({aboutExeVisible: false,})
// 点击帮忙
const onClickHelp = () => {console.log(` 帮忙 `)
data.aboutExeVisible = true
}
</script>
4. watchEffect 的应用
<script setup>
import {
ref,
watchEffect,
} from 'vue'
let sum = ref(0)
watchEffect(()=>{
const x1 = sum.value
console.log('watchEffect 所指定的回调执行了')
})
</script>
5. watch 的应用
<script setup>
import {
reactive,
watch,
} from 'vue'
// 数据
let sum = ref(0)
let msg = ref('hello')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{salary:20}
}
})
// 两种监听格局
watch([sum,msg],(newValue,oldValue)=>{console.log('sum 或 msg 变了',newValue,oldValue)
},
{immediate:true}
)
watch(()=>person.job,(newValue,oldValue)=>{console.log('person 的 job 变动了',newValue,oldValue)
},{deep:true})
</script>
6. computed 计算属性的应用
computed
计算属性有两种写法(简写和思考读写的残缺写法)
<script setup>
import {
reactive,
computed,
} from 'vue'
// 数据
let person = reactive({
firstName:'poetry',
lastName:'x'
})
// 计算属性简写
person.fullName = computed(()=>{return person.firstName + '-' + person.lastName})
// 残缺写法
person.fullName = computed({get(){return person.firstName + '-' + person.lastName},
set(value){const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
</script>
7. props 父子传值的应用
父组件代码如下(示例):
<template>
<child :name='name'/>
</template>
<script setup>
import {ref} from 'vue'
// 引入子组件
import child from './child.vue'
let name= ref('poetry')
</script>
子组件代码如下(示例):
<template>
<span>{{props.name}}</span>
</template>
<script setup>
import {defineProps} from 'vue'
// 申明 props
const props = defineProps({
name: {
type: String,
default: 'poetries'
}
})
// 或者
//const props = defineProps(['name'])
</script>
8. emit 子父传值的应用
父组件代码如下(示例):
<template>
<AdoutExe @aboutExeVisible="aboutExeHandleCancel" />
</template>
<script setup>
import {reactive} from 'vue'
// 导入子组件
import AdoutExe from '../components/AdoutExeCom'
const data = reactive({aboutExeVisible: false,})
// content 组件 ref
// 对于零碎暗藏
const aboutExeHandleCancel = () => {data.aboutExeVisible = false}
</script>
子组件代码如下(示例):
<template>
<a-button @click="isOk">
确定
</a-button>
</template>
<script setup>
import {defineEmits} from 'vue';
// emit
const emit = defineEmits(['aboutExeVisible'])
/**
* 办法
*/
// 点击确定按钮
const isOk = () => {emit('aboutExeVisible');
}
</script>
9. 获取子组件 ref 变量和 defineExpose 裸露
即 vue2
中的获取子组件的ref
,间接在父组件中管制子组件办法和变量的办法
父组件代码如下(示例):
<template>
<button @click="onClickSetUp"> 点击 </button>
<Content ref="content" />
</template>
<script setup>
import {ref} from 'vue'
// content 组件 ref
const content = ref('content')
// 点击设置
const onClickSetUp = ({key}) => {content.value.modelVisible = true}
</script>
<style scoped lang="less">
</style>
子组件代码如下(示例):
<template>
<p>{{data}}</p>
</template>
<script setup>
import {
reactive,
toRefs
} from 'vue'
/**
* 数据局部
* */
const data = reactive({
modelVisible: false,
historyVisible: false,
reportVisible: false,
})
defineExpose({...toRefs(data),
})
</script>
10. 路由 useRoute 和 useRouter 的应用
<script setup>
import {useRoute, useRouter} from 'vue-router'
// 申明
const route = useRoute()
const router = useRouter()
// 获取 query
console.log(route.query)
// 获取 params
console.log(route.params)
// 路由跳转
router.push({path: `/index`})
</script>
11. store 仓库的应用
<script setup>
import {useStore} from 'vuex'
import {num} from '../store/index'
const store = useStore(num)
// 获取 Vuex 的 state
console.log(store.state.number)
// 获取 Vuex 的 getters
console.log(store.state.getNumber)
// 提交 mutations
store.commit('fnName')
// 散发 actions 的办法
store.dispatch('fnName')
</script>
12. await 的反对
setup
语法糖中可间接应用 await
,不须要写async
,setup
会主动变成async setup
<script setup>
import api from '../api/Api'
const data = await Api.getData()
console.log(data)
</script>
13. provide 和 inject 祖孙传值
父组件代码如下(示例):
<template>
<AdoutExe />
</template>
<script setup>
import {ref,provide} from 'vue'
import AdoutExe from '@/components/AdoutExeCom'
let name = ref('py')
// 应用 provide
provide('provideState', {
name,
changeName: () => {name.value = 'poetries'}
})
</script>
子组件代码如下(示例):
<script setup>
import {inject} from 'vue'
const provideState = inject('provideState')
provideState.changeName()
</script>
说说你对 slot 的了解?slot 应用场景有哪些
一、slot 是什么
在 HTML 中 slot
元素,作为 Web Components
技术套件的一部分,是 Web 组件内的一个占位符
该占位符能够在前期应用本人的标记语言填充
举个栗子
<template id="element-details-template">
<slot name="element-name">Slot template</slot>
</template>
<element-details>
<span slot="element-name">1</span>
</element-details>
<element-details>
<span slot="element-name">2</span>
</element-details>
template
不会展现到页面中,须要用先获取它的援用,而后增加到 DOM
中,
customElements.define('element-details',
class extends HTMLElement {constructor() {super();
const template = document
.getElementById('element-details-template')
.content;
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(template.cloneNode(true));
}
})
在 Vue
中的概念也是如此
Slot
艺名插槽,花名“占坑”,咱们能够了解为 solt
在组件模板中占好了地位,当应用该组件标签时候,组件标签外面的内容就会主动填坑(替换组件模板中 slot
地位),作为承载散发内容的进口
二、应用场景
通过插槽能够让用户能够拓展组件,去更好地复用组件和对其做定制化解决
如果父组件在应用到一个复用组件的时候,获取这个组件在不同的中央有大量的更改,如果去重写组件是一件不明智的事件
通过 slot
插槽向组件外部指定地位传递内容,实现这个复用组件在不同场景的利用
比方布局组件、表格列、下拉选、弹框显示内容等
Vue.extend 作用和原理
官网解释:
Vue.extend
应用根底Vue
结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
其实就是一个子类结构器 是 Vue
组件的外围 api
实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions
把传入组件的 options
和父类的 options
进行了合并
extend
是结构一个组件的语法器。而后这个组件你能够作用到Vue.component
这个全局注册办法里还能够在任意vue
模板里应用组件。也能够作用到vue
实例或者某个组件中的components
属性中并在外部应用apple
组件。Vue.component
你能够创立,也能够取组件。
相干代码如下
export default function initExtend(Vue) {
let cid = 0; // 组件的惟一标识
// 创立子类继承 Vue 父类 便于属性扩大
Vue.extend = function (extendOptions) {
// 创立子类的构造函数 并且调用初始化办法
const Sub = function VueComponent(options) {this._init(options); // 调用 Vue 初始化办法
};
Sub.cid = cid++;
Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
Sub.prototype.constructor = Sub; //constructor 指向本人
Sub.options = mergeOptions(this.options, extendOptions); // 合并本人的 options 和父类的 options
return Sub;
};
}
为什么要用 Vuex 或者 Redux
因为传参的办法对于多层嵌套的组件将会十分繁琐,并且对于兄弟组件间的状态传递无能为力。咱们常常会采纳父子组件间接援用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式十分软弱,通常会导致代码无奈保护。
所以须要把组件的共享状态抽取进去,以一个全局单例模式治理。在这种模式下,组件树形成了一个微小的 ” 视图 ”,不论在树的哪个地位,任何组件都能获取状态或者触发行为。
另外,通过定义和隔离状态治理中的各种概念并强制恪守肯定的规定,代码将会变得更结构化且易保护。
delete 和 Vue.delete 删除数组的区别?
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
虚构 DOM 的优缺点?
长处:
- 保障性能上限: 框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
- 无需手动操作 DOM: 咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
- 跨平台: 虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。
毛病:
- 无奈进行极致优化: 尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。
Vue 中 computed 和 watch 有什么区别?
计算属性 computed:
(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。
侦听属性 watch:
(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。
extend 有什么作用
这个 API 很少用到,作用是扩大组件生成一个结构器,通常会与 $mount
一起应用。
// 创立组件结构器
let Component = Vue.extend({template: "<div>test</div>"});
// 挂载到 #app 上 new Component().$mount('#app')
// 除了下面的形式,还能够用来扩大已有的组件
let SuperComponent = Vue.extend(Component);
new SuperComponent({created() {console.log(1);
},
});
new SuperComponent().$mount("#app");
你感觉 vuex 有什么毛病
剖析
相较于 redux
,vuex
曾经相当简便好用了。但模块的应用比拟繁琐,对 ts
反对也不好。
体验
应用模块:用起来比拟繁琐,应用模式也不对立,基本上得不到类型零碎的任何反对
const store = createStore({
modules: {a: moduleA}
})
store.state.a // -> 要带上 moduleA 的 key,内嵌模块的话会很长,不得不配合 mapState 应用
store.getters.c // -> moduleA 里的 getters,没有 namespaced 时又变成了全局的
store.getters['a/c'] // -> 有 namespaced 时要加 path,应用模式又和 state 不一样
store.commit('d') // -> 没有 namespaced 时变成了全局的,能同时触发多个子模块中同名 mutation
store.commit('a/d') // -> 有 namespaced 时要加 path,配合 mapMutations 应用感觉也没简化
答复范例
vuex
利用响应式,应用起来曾经相当方便快捷了。然而在应用过程中感觉模块化这一块做的过于简单,用的时候容易出错,还要常常查看文档- 比方:拜访
state
时要带上模块key
,内嵌模块的话会很长,不得不配合mapState
应用,加不加namespaced
区别也很大,getters
,mutations
,actions
这些默认是全局,加上之后必须用字符串类型的 path 来匹配,应用模式不对立,容易出错;对 ts 的反对也不敌对,在应用模块时没有代码提醒。 - 之前
Vue2
我的项目中用过vuex-module-decorators
的解决方案,尽管类型反对上有所改善,但又要学一套新货色,减少了学习老本。pinia
呈现之后应用体验好了很多,Vue3 + pinia
会是更好的组合
原理
上面咱们来看看 vuex
中store.state.x.y
这种嵌套的门路是怎么搞进去的
首先是子模块装置过程:父模块状态
parentState
下面设置了子模块名称moduleName
,值为以后模块state
对象。放在下面的例子中相当于:store.state['x'] = moduleX.state
。此过程是递归的,那么store.state.x.y
装置时就是:store.state['x']['y'] = moduleY.state
// 源码地位 https://github1s.com/vuejs/vuex/blob/HEAD/src/store-util.js#L102-L115
if (!isRoot && !hot) {
// 获取父模块 state
const parentState = getNestedState(rootState, path.slice(0, -1))
// 获取子模块名称
const moduleName = path[path.length - 1]
store._withCommit(() => {
// 把子模块 state 设置到父模块上
parentState[moduleName] = module.state
})
}
请说出 vue cli 我的项目中 src 目录每个文件夹和文件的用法
assets
文件夹是放动态资源;components
是放组件;router
是定义路由相干的配置;view
视图;app.vue
是一个利用主组件;main.js
是入口文件
Vue computed 实现
- 建设与其余属性(如:
data
、Store
)的分割; - 属性扭转后,告诉计算属性从新计算
实现时,次要如下
- 初始化
data
,应用Object.defineProperty
把这些属性全副转为getter/setter
。 - 初始化
computed
, 遍历computed
里的每个属性,每个computed
属性都是一个watch
实例。每个属性提供的函数作为属性的getter
,应用Object.defineProperty
转化。 Object.defineProperty getter
依赖收集。用于依赖发生变化时,触发属性从新计算。- 若呈现以后
computed
计算属性嵌套其余computed
计算属性时,先进行其余的依赖收集
Vue-router 导航守卫有哪些
- 全局前置 / 钩子:beforeEach、beforeResolve、afterEach
- 路由独享的守卫:beforeEnter
- 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
如何从实在 DOM 到虚构 DOM
波及到 Vue 中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM) - 优化树
- 将
ast
树生成代码