共计 29630 个字符,预计需要花费 75 分钟才能阅读完成。
Composition API 与 Options API 有什么不同
剖析
Vue3
最重要更新之一就是 Composition API
,它具备一些列长处,其中不少是针对Options API
裸露的一些问题量身打造。是 Vue3
举荐的写法,因而把握好 Composition API
利用对把握好 Vue3
至关重要
What is Composition API?(opens new window)
Composition API
呈现就是为了解决 Options API 导致雷同性能代码扩散的景象
体验
Composition API
能更好的组织代码,上面用 composition api
能够提取为useCount()
,用于组合、复用
compositon api 提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命周期的
hooks
答复范例
Composition API
是一组API
,包含:Reactivity API
、生命周期钩子
、依赖注入
,使用户能够通过导入函数形式编写vue
组件。而Options API
则通过申明组件选项的对象模式编写组件Composition API
最次要作用是可能简洁、高效复用逻辑。解决了过来Options API
中mixins
的各种毛病;另外Composition API
具备更加麻利的代码组织能力,很多用户喜爱Options API
,认为所有货色都有固定地位的选项搁置代码,然而单个组件增长过大之后这反而成为限度,一个逻辑关注点扩散在组件各处,造成代码碎片,保护时须要重复横跳,Composition API
则能够将它们无效组织在一起。最初Composition API
领有更好的类型推断,对 ts 反对更敌对,Options API
在设计之初并未思考类型推断因素,尽管官网为此做了很多简单的类型体操,确保用户能够在应用Options API
时取得类型推断,然而还是没方法用在mixins
和provide/inject
上Vue3
首推Composition API
,然而这会让咱们在代码组织上多花点心理,因而在抉择上,如果咱们我的项目属于中低复杂度的场景,Options API
仍是一个好抉择。对于那些大型,高扩大,强保护的我的项目上,Composition API
会取得更大收益
可能的诘问
Composition API
是否和Options API
一起应用?
能够在同一个组件中应用两个 script
标签,一个应用 vue3,一个应用 vue2 写法,一起应用没有问题
<!-- vue3 -->
<script setup>
// vue3 写法
</script>
<!-- 降级 vue2 -->
<script>
export default {data() {},
methods: {}}
</script>
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
)
}
}
})
}
)
v-if 和 v -for 哪个优先级更高
- 实际中不应该把
v-for
和v-if
放一起 - 在
vue2
中,v-for
的优先级是高于v-if
,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3
中则齐全相同,v-if
的优先级高于v-for
,所以v-if
执行时,它调用的变量还不存在,就会导致异样 -
通常有两种状况下导致咱们这样做:
- 为了过滤列表中的我的项目 (比方
v-for="user in users" v-if="user.isActive"
)。此时定义一个计算属性 (比方activeUsers
),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive)
) - 为了防止渲染本应该被暗藏的列表 (比方
v-for="user in users" v-if="shouldShowUsers"
)。此时把v-if
挪动至容器元素上 (比方ul
、ol
)或者外面包一层template
即可
- 为了过滤列表中的我的项目 (比方
- 文档中明确指出永远不要把
v-if
和v-for
同时用在同一个元素上,显然这是一个重要的注意事项 - 源码外面对于代码生成的局部,可能清晰的看到是先解决
v-if
还是v-for
,程序上vue2
和vue3
正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的
vue2.x 源码剖析
在 vue 模板编译的时候,会将指令系统转化成可执行的
render
函数
编写一个 p
标签,同时应用 v-if
与 v-for
<div id="app">
<p v-if="isShow" v-for="item in items">
{{item.title}}
</p>
</div>
创立 vue
实例,寄存 isShow
与items
数据
const app = new Vue({
el: "#app",
data() {
return {
items: [{ title: "foo"},
{title: "baz"}]
}
},
computed: {isShow() {return this.items && this.items.length > 0}
}
})
模板指令的代码都会生成在 render
函数中,通过 app.$options.render
就能失去渲染函数
ƒ anonymous() {with (this) { return
_c('div', { attrs: { "id": "app"} },
_l((items), function (item)
{return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e()}), 0) }
}
_l
是vue
的列表渲染函数,函数外部都会进行一次if
判断- 初步失去论断:
v-for
优先级是比v-i
f 高 - 再将
v-for
与v-if
置于不同标签
<div id="app">
<template v-if="isShow">
<p v-for="item in items">{{item.title}}</p>
</template>
</div>
再输入下 render
函数
ƒ anonymous() {with(this){return
_c('div',{attrs:{"id":"app"}},
[(isShow)?[_v("\n"),
_l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
}
这时候咱们能够看到,v-for
与 v-if
作用在不同标签时候,是先进行判断,再进行列表的渲染
咱们再在查看下 vue 源码
源码地位:\vue-dev\src\compiler\codegen\index.js
export function genElement (el: ASTElement, state: CodegenState): string {if (el.parent) {el.pre = el.pre || el.parent.pre}
if (el.staticRoot && !el.staticProcessed) {return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {return genOnce(el, state)
} else if (el.for && !el.forProcessed) {return genFor(el, state)
} else if (el.if && !el.ifProcessed) {return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {return genSlot(el, state)
} else {
// component or element
...
}
在进行 if
判断的时候,v-for
是比 v-if
先进行判断
最终论断:v-for
优先级比 v-if
高
watch 原理
watch
实质上是为每个监听属性 setter
创立了一个 watcher
,当被监听的属性更新时,调用传入的回调函数。常见的配置选项有 deep
和 immediate
,对应原理如下
deep
:深度监听对象,为对象的每一个属性创立一个watcher
,从而确保对象的每一个属性更新时都会触发传入的回调函数。次要起因在于对象属于援用类型,单个属性的更新并不会触发对象setter
,因而引入deep
可能很好地解决监听对象的问题。同时也会引入判断机制,确保在多个属性更新时回调函数仅触发一次,防止性能节约。immediate
:在初始化时间接调用回调函数,能够通过在created
阶段手动调用回调函数实现雷同的成果
用过 pinia 吗?有什么长处?
1. pinia 是什么?
- 在
Vue3
中,能够应用传统的Vuex
来实现状态治理,也能够应用最新的pinia
来实现状态治理,咱们来看看官网如何解释pinia
的:Pinia
是Vue
的存储库,它容许您跨组件 / 页面共享状态。- 实际上,
pinia
就是Vuex
的升级版,官网也说过,为了尊重原作者,所以取名pinia
,而没有取名Vuex
,所以大家能够间接将pinia
比作为Vue3
的Vuex
2. 为什么要应用 pinia?
Vue2
和Vue3
都反对,这让咱们同时应用Vue2
和Vue3
的小伙伴都能很快上手。pinia
中只有state
、getter
、action
,摈弃了Vuex
中的Mutation
,Vuex
中mutation
始终都不太受小伙伴们的待见,pinia
间接摈弃它了,这无疑缩小了咱们工作量。pinia
中action
反对同步和异步,Vuex
不反对- 良好的
Typescript
反对,毕竟咱们Vue3
都举荐应用TS
来编写,这个时候应用pinia
就十分适合了 - 无需再创立各个模块嵌套了,
Vuex
中如果数据过多,咱们通常分模块来进行治理,稍显麻烦,而pinia
中每个store
都是独立的,相互不影响。 - 体积十分小,只有
1KB
左右。 pinia
反对插件来扩大本身性能。- 反对服务端渲染
3. pinna 应用
pinna 文档(opens new window)
- 筹备工作
咱们这里搭建一个最新的 Vue3 + TS + Vite
我的项目
npm create vite@latest my-vite-app --template vue-ts
pinia
根底应用
yarn add pinia
// main.ts
import {createApp} from "vue";
import App from "./App.vue";
import {createPinia} from "pinia";
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount("#app");
2.1 创立store
//sbinsrc/store/user.ts
import {defineStore} from 'pinia'
// 第一个参数是应用程序中 store 的惟一 id
export const useUsersStore = defineStore('users', {// 其它配置项})
创立 store
很简略,调用 p inia
中的 defineStore
函数即可,该函数接管两个参数:
name
:一个字符串,必传项,该store
的惟一id
。options
:一个对象,store
的配置项,比方配置store
内的数据,批改数据的办法等等。
咱们能够定义任意数量的 store
,因为咱们其实一个store
就是一个函数,这也是 pinia
的益处之一,让咱们的代码扁平化了,这和 Vue3
的实现思维是一样的
2.2 应用store
<!-- src/App.vue -->
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
console.log(store);
</script>
2.3 增加state
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
});
2.4 读取 state
数据
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p> 姓名:{{name}}</p>
<p> 年龄:{{age}}</p>
<p> 性别:{{sex}}</p>
</template>
<script setup lang="ts">
import {ref} from "vue";
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
const name = ref<string>(store.name);
const age = ref<number>(store.age);
const sex = ref<string>(store.sex);
</script>
上段代码中咱们间接通过 store.age
等形式获取到了 store
存储的值,然而大家有没有发现,这样比拟繁琐,咱们其实能够用解构的形式来获取值,使得代码更简洁一点
import {useUsersStore, storeToRefs} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store); // storeToRefs 获取的值是响应式的
2.5 批改 state
数据
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p> 姓名:{{name}}</p>
<p> 年龄:{{age}}</p>
<p> 性别:{{sex}}</p>
<button @click="changeName"> 更改姓名 </button>
</template>
<script setup lang="ts">
import child from './child.vue';
import {useUsersStore, storeToRefs} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);
const changeName = () => {
store.name = "张三";
console.log(store);
};
</script>
2.6 重置state
- 有时候咱们批改了
state
数据,想要将它还原,这个时候该怎么做呢?就比方用户填写了一部分表单,忽然想重置为最初始的状态。 - 此时,咱们间接调用
store
的$reset()
办法即可,持续应用咱们的例子,增加一个重置按钮
<button @click="reset"> 重置 store</button>
// 重置 store
const reset = () => {store.$reset();
};
当咱们点击重置按钮时,store
中的数据会变为初始状态,页面也会更新
2.7 批量更改 state
数据
如果咱们一次性须要批改很多条数据的话,有更加简便的办法,应用 store
的$patch
办法,批改 app.vue
代码,增加一个批量更改数据的办法
<button @click="patchStore"> 批量批改数据 </button>
// 批量批改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};
- 有教训的小伙伴可能发现了,咱们采纳这种批量更改的形式仿佛代价有一点大,如果咱们
state
中有些字段无需更改,然而依照上段代码的写法,咱们必须要将 state 中的所有字段例举出了。 - 为了解决该问题,
pinia
提供的$patch
办法还能够接管一个回调函数,它的用法有点像咱们的数组循环回调函数了。
store.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1})
state.hasChanged = true
})
2.8 间接替换整个state
pinia
提供了办法让咱们间接替换整个 state
对象,应用 store
的$state
办法
store.$state = {counter: 666, name: '张三'}
上段代码会将咱们提前申明的 state
替换为新的对象,可能这种场景用得比拟少
getters
属性getters
是defineStore
参数配置项外面的另一个属性- 能够把
getter
设想成Vue
中的计算属性,它的作用就是返回一个新的后果,既然它和Vue
中的计算属性相似,那么它必定也是会被缓存的,就和computed
一样
3.1 增加getter
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 10,
sex: "男",
};
},
getters: {getAddAge: (state) => {return state.age + 100;},
},
})
上段代码中咱们在配置项参数中增加了 getter
属性,该属性对象中定义了一个 getAddAge
办法,该办法会默认接管一个 state
参数,也就是 state
对象,而后该办法返回的是一个新的数据
3.2 应用getter
<template>
<p> 新年龄:{{store.getAddAge}}</p>
<button @click="patchStore"> 批量批改数据 </button>
</template>
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
// 批量批改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};
</script>
上段代码中咱们间接在标签上应用了 store.gettAddAge
办法,这样能够保障响应式,其实咱们 state
中的 name
等属性也能够以此种形式间接在标签上应用,也能够放弃响应式
3.3 getter
中调用其它getter
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
getters: {getAddAge: (state) => {return state.age + 100;},
getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
},
});
3.3 getter
传参
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
getters: {getAddAge: (state) => {return (num: number) => state.age + num;
},
getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
},
});
<p> 新年龄:{{store.getAddAge(1100) }}</p>
actions
属性- 后面咱们提到的
state
和getter
s 属性都次要是数据层面的,并没有具体的业务逻辑代码,它们两个就和咱们组件代码中的data
数据和computed
计算属性一样。 - 那么,如果咱们有业务代码的话,最好就是卸载
actions
属性外面,该属性就和咱们组件代码中的methods
类似,用来搁置一些解决业务逻辑的办法。 actions
属性值同样是一个对象,该对象外面也是存储的各种各样的办法,包含同步办法和异步办法
4.1 增加actions
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
getters: {getAddAge: (state) => {return (num: number) => state.age + num;
},
getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
},
actions: {
// 在理论场景中,该办法能够是任何逻辑,比方发送申请、存储 token 等等。大家把 actions 办法当作一个一般的办法即可,非凡之处在于该办法外部的 this 指向的是以后 store
saveName(name: string) {this.name = name;},
},
});
4.2 应用actions
应用 actions
中的办法也非常简单,比方咱们在 App.vue
中想要调用该办法
const saveName = () => {store.saveName("poetries");
};
总结
pinia
的知识点很少,如果你有 Vuex 根底,那么学起来更是大海捞针
pinia 无非就是以下 3 个大点:
state
getters
actions
Vue3.0 为什么要用 proxy?
在 Vue2 中,0bject.defineProperty 会扭转原始数据,而 Proxy 是创建对象的虚构示意,并提供 set、get 和 deleteProperty 等处理器,这些处理器可在拜访或批改原始对象上的属性时进行拦挡,有以下特点∶
- 不需用应用
Vue.$set
或Vue.$delete
触发响应式。 - 全方位的数组变化检测,打消了 Vue2 有效的边界状况。
- 反对 Map,Set,WeakMap 和 WeakSet。
Proxy 实现的响应式原理与 Vue2 的实现原理雷同,实现形式大同小异∶
- get 收集依赖
- Set、delete 等触发依赖
- 对于汇合类型,就是对汇合对象的办法做一层包装:原办法执行后执行依赖相干的收集或触发逻辑。
参考 前端进阶面试题具体解答
$route
和 $router
的区别
$route
是“路由信息对象”,包含path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息参数。- 而
$router
是“路由实例”对象包含了路由的跳转办法,钩子函数等
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]
Vue 修饰符有哪些
vue 中修饰符分为以下五种
- 表单修饰符
- 事件修饰符
- 鼠标按键修饰符
- 键值修饰符
v-bind
修饰符
1. 表单修饰符
在咱们填写表单的时候用得最多的是 input
标签,指令用得最多的是v-model
对于表单的修饰符有如下:
lazy
在咱们填完信息,光标来到标签的时候,才会将值赋予给 value
,也就是在change
事件之后再进行信息同步
<input type="text" v-model.lazy="value">
<p>{{value}}</p>
trim
主动过滤用户输出的首空格字符,而两头的空格不会过滤
<input type="text" v-model.trim="value">
number
主动将用户的输出值转为数值类型,但如果这个值无奈被 parseFloat
解析,则会返回原来的值
<input v-model.number="age" type="number">
2. 事件修饰符
事件修饰符是对事件捕捉以及指标进行了解决,有如下修饰符
.stop
阻止了事件冒泡,相当于调用了event.stopPropagation
办法
<div @click="shout(2)">
<button @click.stop="shout(1)">ok</button>
</div>
// 只输入 1
.prevent
阻止了事件的默认行为,相当于调用了event.preventDefault
办法
<form v-on:submit.prevent="onSubmit"></form>
.capture
应用事件捕捉模式,使事件触发从蕴含这个元素的顶层开始往下触发
<div @click.capture="shout(1)">
obj1
<div @click.capture="shout(2)">
obj2
<div @click="shout(3)">
obj3
<div @click="shout(4)">
obj4
</div>
</div>
</div>
</div>
// 输入构造: 1 2 4 3
.self
只当在event.target
是以后元素本身时触发处理函数
<div v-on:click.self="doThat">...</div>
应用修饰符时,程序很重要;相应的代码会以同样的程序产生。因而,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素本身的点击
.once
绑定了事件当前只能触发一次,第二次就不会触发
<button @click.once="shout(1)">ok</button>
.passive
通知浏览器你不想阻止事件的默认行为
在挪动端,当咱们在监听元素滚动事件的时候,会始终触发 onscroll
事件会让咱们的网页变卡,因而咱们应用这个修饰符的时候,相当于给 onscroll
事件整了一个 .lazy
修饰符
<!-- 滚动事件的默认行为 (即滚动行为) 将会立刻触发 -->
<!-- 而不会期待 `onScroll` 实现 -->
<!-- 这其中蕴含 `event.preventDefault()` 的状况 -->
<div v-on:scroll.passive="onScroll">...</div>
- 不要把
.passive
和.prevent
一起应用, 因为.prevent
将会被疏忽,同时浏览器可能会向你展现一个正告。passive
会通知浏览器你不想阻止事件的默认行为
native
让组件变成像html
内置标签那样监听根元素的原生事件,否则组件上应用v-on
只会监听自定义事件
<my-component v-on:click.native="doSomething"></my-component>
<!-- 应用.native 修饰符来操作一般 HTML 标签是会令事件生效的 -->
3. 鼠标按钮修饰符
鼠标按钮修饰符针对的就是左键、右键、中键点击,有如下:
.left
左键点击.right
右键点击.middle
中键点击
<button @click.left="shout(1)">ok</button>
<button @click.right="shout(1)">ok</button>
<button @click.middle="shout(1)">ok</button>
4. 键盘事件的修饰符
键盘修饰符是用来润饰键盘事件(onkeyup
,onkeydown
)的,有如下:
keyCode
存在很多,但 vue 为咱们提供了别名,分为以下两种:
- 一般键(
enter
、tab
、delete
、space
、esc
、up
、down
、left
、right
…) - 零碎润饰键(
ctrl
、alt
、meta
、shift
…)
<!-- 只有按键为 keyCode 的时候才触发 -->
<input type="text" @keyup.keyCode="shout()">
还能够通过以下形式自定义一些全局的键盘码别名
Vue.config.keyCodes.f2 = 113
5. v-bind 修饰符
v-bind
修饰符次要是为属性进行操作,用来别离有如下:
- async 能对
props
进行一个双向绑定
// 父组件
<comp :myMessage.sync="bar"></comp>
// 子组件
this.$emit('update:myMessage',params);
以上这种办法相当于以下的简写
// 父亲组件
<comp :myMessage="bar" @update:myMessage="func"></comp>
func(e){this.bar = e;}
// 子组件 js
func2(){this.$emit('update:myMessage',params);
}
应用 async
须要留神以下两点:
- 应用
sync
的时候,子组件传递的事件名格局必须为update:value
,其中value
必须与子组件中props
中申明的名称完全一致 - 留神带有
.sync
修饰符的v-bind
不能和表达式一起应用 - prop 设置自定义标签属性,防止裸露数据,避免净化 HTML 构造
<input id="uid" title="title1" value="1" :index.prop="index">
- camel 将命名变为驼峰命名法,如将
view-Box
属性名转换为viewBox
<svg :viewBox="viewBox"></svg>
利用场景
依据每一个修饰符的性能,咱们能够失去以下修饰符的利用场景:
.stop
:阻止事件冒泡.native
:绑定原生事件.once
:事件只执行一次.self
:将事件绑定在本身身上,相当于阻止事件冒泡.prevent
:阻止默认事件.caption
:用于事件捕捉.once
:只触发一次.keyCode
:监听特定键盘按下.right
:右键
如果让你从零开始写一个 vuex,说说你的思路
思路剖析
这个题目很有难度,首先思考 vuex
解决的问题:存储用户全局状态并提供治理状态 API。
vuex
需要剖析- 如何实现这些需要
答复范例
- 官网说
vuex
是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
- 要实现一个
Store
存储全局状态 - 要提供批改状态所需 API:
commit(type, payload), dispatch(type, payload)
- 实现
Store
时,能够定义Store
类,构造函数接管选项options
,设置属性state
对外裸露状态,提供commit
和dispatch
批改属性state
。这里须要设置state
为响应式对象,同时将Store
定义为一个Vue
插件 commit(type, payload)
办法中能够获取用户传入mutations
并执行它,这样能够按用户提供的办法批改状态。dispatch(type, payload)
相似,但须要留神它可能是异步的,须要返回一个Promise
给用户以解决异步后果
实际
Store
的实现:
class Store {constructor(options) {this.state = reactive(options.state)
this.options = options
}
commit(type, payload) {this.options.mutations[type].call(this, this.state, payload)
}
}
vuex 简易版
/**
* 1 实现插件,挂载 $store
* 2 实现 store
*/
let Vue;
class Store {constructor(options) {
// state 响应式解决
// 内部拜访:this.$store.state.***
// 第一种写法
// this.state = new Vue({
// data: options.state
// })
// 第二种写法:避免外界间接接触外部 vue 实例,避免内部强行变更
this._vm = new Vue({
data: {$$state: options.state}
})
this._mutations = options.mutations
this._actions = options.actions
this.getters = {}
options.getters && this.handleGetters(options.getters)
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
get state () {return this._vm._data.$$state}
set state (val) {return new Error('Please use replaceState to reset state')
}
handleGetters (getters) {Object.keys(getters).map(key => {
Object.defineProperty(this.getters, key, {get: () => getters[key](this.state)
})
})
}
commit (type, payload) {let entry = this._mutations[type]
if (!entry) {return new Error(`${type} is not defined`)
}
entry(this.state, payload)
}
dispatch (type, payload) {let entry = this._actions[type]
if (!entry) {return new Error(`${type} is not defined`)
}
entry(this, payload)
}
}
const install = (_Vue) => {
Vue = _Vue
Vue.mixin({beforeCreate () {if (this.$options.store) {Vue.prototype.$store = this.$options.store}
},
})
}
export default {Store, install}
验证形式
import Vue from 'vue'
import Vuex from './vuex'
// this.$store
Vue.use(Vuex)
export default new Vuex.Store({
state: {counter: 0},
mutations: {
// state 从哪里来的
add (state) {state.counter++}
},
getters: {doubleCounter (state) {return state.counter * 2}
},
actions: {add ({ commit}) {setTimeout(() => {commit('add')
}, 1000)
}
},
modules: {}})
怎么缓存以后的组件?缓存后怎么更新
缓存组件应用 keep-alive
组件,这是一个十分常见且有用的优化伎俩,vue3
中 keep-alive
有比拟大的更新,能说的点比拟多
思路
- 缓存用
keep-alive
,它的作用与用法 - 应用细节,例如缓存指定 / 排除、联合
router
和transition
- 组件缓存后更新能够利用
activated
或者beforeRouteEnter
- 原理论述
答复范例
- 开发中缓存组件应用
keep-alive
组件,keep-alive
是vue
内置组件,keep-alive
包裹动静组件component
时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
<component :is="view"></component>
</keep-alive>
- 联合属性
include
和exclude
能够明确指定缓存哪些组件或排除缓存指定组件。vue3
中联合vue-router
时变动较大,之前是keep-alive
包裹router-view
,当初须要反过来用router-view
包裹keep-alive
<router-view v-slot="{Component}">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
- 缓存后如果要获取数据,解决方案能够有以下两种
beforeRouteEnter
:在有vue-router 的
我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据})
},
actived
:在keep-alive
缓存的组件被激活的时候,都会执行actived
钩子
activated(){this.getData() // 获取数据
},
keep-alive
是一个通用组件,它外部定义了一个map
,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component
组件对应组件的vnode
,如果该组件在map
中存在就间接返回它。因为component
的is
属性是个响应式数据,因而只有它变动,keep-alive
的render
函数就会从新执行
Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期
- 考点:
Vue
的变动侦测原理 - 前置常识: 依赖收集、虚构
DOM
、响应式零碎
根本原因是
Vue
与React
的变动侦测形式有所不同
- 当 React 晓得发生变化后,会应用
Virtual Dom Diff
进行差别检测,然而很多组件实际上是必定不会发生变化的,这个时候须要shouldComponentUpdate
进行手动操作来缩小diff
,从而进步程序整体的性能 Vue
在一开始就晓得那个组件产生了变动,不须要手动管制diff
,而组件外部采纳的diff
形式实际上是能够引入相似于shouldComponentUpdate
相干生命周期的,然而通常正当大小的组件不会有适量的 diff,手动优化的价值无限,因而目前Vue
并没有思考引入shouldComponentUpdate
这种手动优化的生命周期
Vue 中的过滤器理解吗?过滤器的利用场景有哪些?
过滤器本质不扭转原始数据,只是对数据进行加工解决后返回过滤后的数据再进行调用解决,咱们也能够了解其为一个纯函数
Vue 容许你自定义过滤器,可被用于一些常见的文本格式化
ps: Vue3
中已废除filter
如何用
vue 中的过滤器能够用在两个中央:双花括号插值和 v-bind
表达式,过滤器应该被增加在 JavaScript 表达式的尾部,由“管道”符号批示:
<!-- 在双花括号中 -->
{message | capitalize}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
定义 filter
在组件的选项中定义本地的过滤器
filters: {capitalize: function (value) {if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
定义全局过滤器:
Vue.filter('capitalize', function (value) {if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({// ...})
留神:当全局过滤器和部分过滤器重名时,会采纳部分过滤器
过滤器函数总接管表达式的值 (之前的操作链的后果) 作为第一个参数。在上述例子中,capitalize
过滤器函数将会收到 message
的值作为第一个参数
过滤器能够串联:
{message | filterA | filterB}
在这个例子中,filterA
被定义为接管单个参数的过滤器函数,表达式 message
的值将作为参数传入到函数中。而后持续调用同样被定义为接管单个参数的过滤器函数 filterB
,将 filterA
的后果传递到 filterB
中。
过滤器是 JavaScript
函数,因而能够接管参数:
{{message | filterA('arg1', arg2) }}
这里,filterA
被定义为接管三个参数的过滤器函数。
其中 message
的值作为第一个参数,一般字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数
举个例子:
<div id="app">
<p>{{msg | msgFormat('疯狂','--')}}</p>
</div>
<script>
// 定义一个 Vue 全局的过滤器,名字叫做 msgFormat
Vue.filter('msgFormat', function(msg, arg, arg2) {
// 字符串的 replace 办法,第一个参数,除了可写一个 字符串之外,还能够定义一个正则
return msg.replace(/ 单纯 /g, arg+arg2)
})
</script>
小结:
- 部过滤器优先于全局过滤器被调用
- 一个表达式能够应用多个过滤器。过滤器之间须要用管道符“|”隔开。其执行程序从左往右
利用场景
平时开发中,须要用到过滤器的中央有很多,比方 单位转换
、 数字打点
、 文本格式化
、 工夫格式化
之类的等
比方咱们要实现将30000 => 30,000
,这时候咱们就须要应用过滤器
Vue.filter('toThousandFilter', function (value) {if (!value) return ''
value = value.toString()
return .replace(str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g, '$1,')
})
原理剖析
应用过滤器
{{message | capitalize}}
在模板编译阶段过滤器表达式将会被编译为过滤器函数,次要是用过parseFilters
,咱们放到最初讲
_s(_f('filterFormat')(message))
首先剖析一下_f
:
_f
函数全名是:resolveFilter
,这个函数的作用是从 this.$options.filters
中找出注册的过滤器并返回
// 变为
this.$options.filters['filterFormat'](message) // message 为参数
对于resolveFilter
import {indentity,resolveAsset} from 'core/util/index'
export function resolveFilter(id){return resolveAsset(this.$options,'filters',id,true) || identity
}
外部间接调用 resolveAsset
,将option
对象,类型,过滤器id
,以及一个触发正告的标记作为参数传递,如果找到,则返回过滤器;
resolveAsset
的代码如下:
export function resolveAsset(options,type,id,warnMissing){ // 因为咱们找的是过滤器,所以在 resolveFilter 函数中调用时 type 的值间接给的 'filters', 理论这个函数还能够拿到其余很多货色
if(typeof id !== 'string'){ // 判断传递的过滤器 id 是不是字符串,不是则间接返回
return
}
const assets = options[type] // 将咱们注册的所有过滤器保留在变量中
// 接下来的逻辑便是判断 id 是否在 assets 中存在,即进行匹配
if(hasOwn(assets,id)) return assets[id] // 如找到,间接返回过滤器
// 没有找到,代码继续执行
const camelizedId = camelize(id) // 万一你是驼峰的呢
if(hasOwn(assets,camelizedId)) return assets[camelizedId]
// 没找到,继续执行
const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢
if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId]
// 如果还是没找到,则查看原型链(即拜访属性)
const result = assets[id] || assets[camelizedId] || assets[PascalCaseId]
// 如果仍然没找到,则在非生产环境的控制台打印正告
if(process.env.NODE_ENV !== 'production' && warnMissing && !result){warn('Failed to resolve' + type.slice(0,-1) + ':' + id, options)
}
// 无论是否找到,都返回查找后果
return result
}
上面再来剖析一下_s
:
_s
函数的全称是 toString
, 过滤器解决后的后果会当作参数传递给 toString
函数,最终 toString
函数执行后的后果会保留到 Vnode
中的 text 属性中,渲染到视图中
function toString(value){
return value == null
? '': typeof value ==='object'
? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来管制字符串外面的间距
: String(value)
}
最初,在剖析下parseFilters
,在模板编译阶段应用该函数阶段将模板过滤器解析为过滤器函数调用表达式
function parseFilters (filter) {let filters = filter.split('|')
let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回,该办法会更改原数组
let i
if (filters) {for(i = 0;i < filters.length;i++){experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的 expression 实际上是管道符号后面的字符串,即过滤器的第一个参数
}
}
return expression
}
// warpFilter 函数实现
function warpFilter(exp,filter){
// 首先判断过滤器是否有其余参数
const i = filter.indexof('(')
if(i<0){ // 不含其余参数,间接进行过滤器表达式字符串的拼接
return `_f("${filter}")(${exp})`
}else{const name = filter.slice(0,i) // 过滤器名称
const args = filter.slice(i+1) // 参数,但还多了‘)’return `_f('${name}')(${exp},${args}` // 留神这一步少给了一个 ')'
}
}
小结:
- 在编译阶段通过
parseFilters
将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的后果是后一个过滤器函数的参数) - 编译后通过调用
resolveFilter
函数找到对应过滤器并返回后果 - 执行后果作为参数传递给
toString
函数,而toString
执行后,其后果会保留在Vnode
的text
属性中,渲染到视图
Vue 中常见性能优化
编码优化:
- 应用
v-show
复用DOM
:防止反复创立组件
<template>
<div class="cell">
<!-- 这种状况用 v -show 复用 DOM,比 v -if 成果好 -->
<div v-show="value" class="on">
<Heavy :n="10000"/>
</div>
<section v-show="!value" class="off">
<Heavy :n="10000"/>
</section>
</div>
</template>
- 正当应用路由懒加载、异步组件,无效拆分
App
尺寸,拜访时才异步加载
const router = createRouter({
routes: [// 借助 webpack 的 import()实现异步组件
{path: '/foo', component: () => import('./Foo.vue') }
]
})
keep-alive
缓存页面:防止反复创立组件实例,且能保留缓存组件状态
<router-view v-slot="{Component}">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
v-once
和v-memo
:不再变动的数据应用v-once
<!-- single element -->
<span v-once>This will never change: {{msg}}</span>
<!-- the element have children -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!-- component -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` directive -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
按条件跳过更新时应用v-momo
:上面这个列表只会更新选中状态变动项
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{item.id}} - selected: {{item.id === selected}}</p>
<p>...more child nodes</p>
</div>
- 长列表性能优化:如果是大数据长列表,可采纳虚构滚动,只渲染少部分区域的内容
<recycle-scroller
class="items"
:items="items"
:item-size="24"
>
<template v-slot="{item}">
<FetchItemView
:item="item"
@vote="voteItem(item)"
/>
</template>
</recycle-scroller>
- 避免外部透露,组件销毁后把全局变量和事件销毁:
Vue
组件销毁时,会主动解绑它的全副指令及事件监听器,然而仅限于组件自身的事件
export default {created() {this.timer = setInterval(this.refresh, 2000)
},
beforeUnmount() {clearInterval(this.timer)
}
}
- 图片懒加载
对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载
<!-- 参考 https://github.com/hilongjw/vue-lazyload -->
<img v-lazy="/static/img/1.png">
- 滚动到可视区域动静加载
https://tangbc.github.io/vue-virtual-scroll-list(opens new window)
- 第三方插件按需引入:(
babel-plugin-component
)
像 element-plus
这样的第三方组件库能够按需引入防止体积太大
import {createApp} from 'vue';
import {Button, Select} from 'element-plus';
const app = createApp()
app.use(Button)
app.use(Select)
- 服务端渲染:SSR
如果 SPA
利用有首屏渲染慢的问题,能够思考SSR
以及上面的其余办法
- 不要将所有的数据都放在
data
中,data
中的数据都会减少getter
和setter
,会收集对应的watcher
v-for
遍历为item
增加key
v-for
遍历防止同时应用v-if
- 辨别
computed
和watch
的应用 - 拆分组件(进步复用性、减少代码的可维护性, 缩小不必要的渲染)
- 防抖、节流
用户体验
app-skeleton
骨架屏pwa
serviceworker
SEO 优化
- 预渲染插件
prerender-spa-plugin
- 服务端渲染
ssr
打包优化
Webpack
对图片进行压缩- 应用
cdn
的形式加载第三方模块 - 多线程打包
happypack
splitChunks
抽离公共文件- 优化
SourceMap
- 构建后果输入剖析,利用
webpack-bundle-analyzer
可视化剖析工具
根底的 Web 技术的优化
- 服务端
gzip
压缩 - 浏览器缓存
CDN
的应用- 应用
Chrome Performance
查找性能瓶颈
请说出 vue cli 我的项目中 src 目录每个文件夹和文件的用法
assets
文件夹是放动态资源;components
是放组件;router
是定义路由相干的配置;view
视图;app.vue
是一个利用主组件;main.js
是入口文件
为什么 Vue 采纳异步渲染
Vue 是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能,Vue 会在本轮数据更新后,在异步更新视图。核心思想
nextTick
源码相干
dep.notify()
告诉watcher
进行更新,subs[i].update
顺次调用watcher
的update
,queueWatcher
将watcher
去重放入队列,nextTick
(flushSchedulerQueue
)在下一tick
中刷新watcher
队列(异步)
update () { /* istanbul ignore else */
if (this.lazy) {this.dirty = true}
else if (this.sync) {this.run()
}
else {queueWatcher(this); // 当数据发生变化时会将 watcher 放到一个队列中批量更新
}
}
export function queueWatcher (watcher: Watcher) {
const id = watcher.id // 会对雷同的 watcher 进行过滤
if (has[id] == null) {has[id] = true
if (!flushing) {queue.push(watcher)
} else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {i--}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue) // 调用 nextTick 办法 批量的进行更新
}
}
}
Vue.set 的实现原理
- 给对应和数组自身都减少了
dep
属性 - 当给对象新增不存在的属性则触发对象依赖的
watcher
去更新 - 当批改数组索引时,咱们调用数组自身的
splice
去更新数组(数组的响应式原理就是从新了splice
等办法,调用splice
就会触发视图更新)
根本应用
以下办法调用会扭转原始数组:
push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
,Vue.set(target, key, value)
-
调用办法:
Vue.set(target, key, value)
target
:要更改的数据源(能够是对象或者数组)key
:要更改的具体数据value
:从新赋的值
<div id="app">{{user.name}} {{user.age}}</div>
<div id="app"></div>
<script>
// 1. 依赖收集的特点:给每个属性都减少一个 dep 属性,dep 属性会进行收集,收集的是 watcher
// 2. vue 会给每个对象也减少一个 dep 属性
const vm = new Vue({
el: '#app',
data: { // vm._data
user: {name:'poetry'}
}
});
// 对象的话:调用 defineReactive 在 user 对象上定义一个 age 属性,减少到响应式数据中,触发对象自身的 watcher,ob.dep.notify()更新
// 如果是数组 通过调用 splice 办法,触发视图更新
vm.$set(vm.user, 'age', 20); // 不能给根属性增加,因为给根增加属性 性能耗费太大,须要做很多解决
// 批改必定是同步的 -> 更新都是一步的 queuewatcher
</script>
相干源码
// src/core/observer/index.js 44
export class Observer {// new Observer(value)
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep() // 给所有对象类型减少 dep 属性}
}
// src/core/observer/index.js 201
export function set (target: Array<any> | Object, key: any, val: any): any {
// 1. 是开发环境 target 没定义或者是根底类型则报错
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 2. 如果是数组 Vue.set(array,1,100); 调用咱们重写的 splice 办法 (这样能够更新视图)
if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)
// 利用数组的 splice 变异办法触发响应式
target.splice(key, 1, val)
return val
}
// 3. 如果是对象自身的属性,则间接增加即可
if (key in target && !(key in Object.prototype)) {target[key] = val // 间接批改属性值
return val
}
// 4. 如果是 Vue 实例 或 根数据 data 时 报错,(更新_data 无意义)const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 5. 如果不是响应式的也不须要将其定义成响应式属性
if (!ob) {target[key] = val
return val
}
// 6. 将属性定义成响应式的
defineReactive(ob.value, key, val)
// 告诉视图更新
ob.dep.notify()
return val
}
咱们浏览以上源码可知,vm.$set 的实现原理是:
- 如果指标是数组,间接应用数组的
splice
办法触发相应式; - 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用
defineReactive
办法进行响应式解决(defineReactive
办法就是Vue
在初始化对象时,给对象属性采纳Object.defineProperty
动静增加getter
和setter
的性能所调用的办法)
构建的 vue-cli 工程都到了哪些技术,它们的作用别离是什么
vue.js
:vue-cli
工程的外围,次要特点是 双向数据绑定 和 组件零碎。vue-router
:vue
官网举荐应用的路由框架。vuex
:专为Vue.js
利用我的项目开发的状态管理器,次要用于保护vue
组件间共用的一些 变量 和 办法。axios
(或者fetch
、ajax
):用于发动GET
、或POST
等http
申请,基于Promise
设计。vuex
等:一个专为vue
设计的挪动端 UI 组件库。- 创立一个
emit.js
文件,用于vue
事件机制的治理。 webpack
:模块加载和vue-cli
工程打包器。
实现双向绑定
咱们还是以 Vue
为例,先来看看 Vue
中的双向绑定流程是什么的
new Vue()
首先执行初始化,对data
执行响应化解决,这个过程产生Observe
中- 同时对模板执行编译,找到其中动静绑定的数据,从
data
中获取并初始化视图,这个过程产生在Compile
中 - 同时定义⼀个更新函数和
Watcher
,未来对应数据变动时Watcher
会调用更新函数 - 因为
data
的某个key
在⼀个视图中可能呈现屡次,所以每个key
都须要⼀个管家Dep
来治理多个Watcher
- 未来 data 中数据⼀旦发生变化,会首先找到对应的
Dep
,告诉所有Watcher
执行更新函数
流程图如下:
先来一个构造函数:执行初始化,对 data
执行响应化解决
class Vue {constructor(options) {
this.$options = options;
this.$data = options.data;
// 对 data 选项做响应式解决
observe(this.$data);
// 代理 data 到 vm 上
proxy(this);
// 执行编译
new Compile(options.el, this);
}
}
对 data
选项执行响应化具体操作
function observe(obj) {if (typeof obj !== "object" || obj == null) {return;}
new Observer(obj);
}
class Observer {constructor(value) {
this.value = value;
this.walk(value);
}
walk(obj) {Object.keys(obj).forEach((key) => {defineReactive(obj, key, obj[key]);
});
}
}
编译Compile
对每个元素节点的指令进行扫描跟解析, 依据指令模板替换数据, 以及绑定相应的更新函数
class Compile {constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el); // 获取 dom
if (this.$el) {this.compile(this.$el);
}
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach((node) => { // 遍历子元素
if (this.isElement(node)) { // 判断是否为节点
console.log("编译元素" + node.nodeName);
} else if (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);
}
}
依赖收集
视图中会用到 data
中某 key
,这称为依赖。同⼀个key
可能呈现屡次,每次都须要收集进去用⼀个 Watcher
来保护它们,此过程称为依赖收集多个 Watcher
须要⼀个 Dep
来治理,须要更新时由 Dep
统⼀告诉
实现思路
defineReactive
时为每⼀个key
创立⼀个Dep
实例- 初始化视图时读取某个
key
,例如name1
,创立⼀个watcher1
- 因为触发
name1
的getter
办法,便将watcher1
增加到name1
对应的Dep
中 - 当
name1
更新,setter
触发时,便可通过对应Dep
告诉其治理所有Watcher
更新
// 负责更新视图
class Watcher {constructor(vm, key, updater) {
this.vm = vm
this.key = key
this.updaterFn = updater
// 创立实例时,把以后实例指定到 Dep.target 动态属性上
Dep.target = this
// 读一下 key,触发 get
vm[key]
// 置空
Dep.target = null
}
// 将来执行 dom 更新函数,由 dep 调用的
update() {this.updaterFn.call(this.vm, this.vm[this.key])
}
}
申明Dep
class Dep {constructor() {this.deps = []; // 依赖治理
}
addDep(dep) {this.deps.push(dep);
}
notify() {this.deps.forEach((dep) => dep.update());
}
}
创立 watcher
时触发getter
class Watcher {constructor(vm, key, updateFn) {
Dep.target = this;
this.vm[this.key];
Dep.target = null;
}
}
依赖收集,创立 Dep
实例
function defineReactive(obj, key, val) {this.observe(val);
const dep = new Dep();
Object.defineProperty(obj, key, {get() {Dep.target && dep.addDep(Dep.target);// Dep.target 也就是 Watcher 实例
return val;
},
set(newVal) {if (newVal === val) return;
dep.notify(); // 告诉 dep 执行更新办法},
});
}
Vue-router 除了 router-link 怎么实现跳转
申明式导航
<router-link to="/about">Go to About</router-link>
编程式导航
// literal string path
router.push('/users/1')
// object with path
router.push({path: '/users/1'})
// named route with params to let the router build the url
router.push({name: 'user', params: { username: 'test'} })
答复范例
vue-router
导航有两种形式:申明式导航和编程形式导航- 申明式导航形式应用
router-link
组件,增加to
属性导航;编程形式导航更加灵便,可传递调用router.push()
,并传递path
字符串或者RouteLocationRaw
对象,指定path
、name
、params
等信息 - 如果页面中简略示意跳转链接,应用
router-link
最快捷,会渲染一个 a 标签;如果页面是个简单的内容,比方商品信息,能够增加点击事件,应用编程式导航 - 实际上外部两者调用的导航函数是一样的