Vue的执行过程
- 大致划分
- 初始化配置阶段
- 编译阶段,生成render函数
- render函数执行阶段,生成vnode
- vnode渲染成真实dom阶段
- 细致划分
- 处理组件配置项
- 初始化根组件时,进行选项合并操作,即将全局配置合并到根组件的局部配置上;
- 初始化子组件时,做了一些性能优化,将组件配置对象上的一些深层次属性扁平化放到vm.$options选项中,以提高代码的执行效率;
- 初始化组件实例的关系属性,比如:children/refs等
- 初始化自定义事件
- 初始化插槽,获取this.$slots,定义this._c即createElement方法
- 调用beforeCreate钩子函数
- 初始化组件的inject配置项,得到result[key]=value显示的配置对象,然后将配置对象进行响应式处理,并代理每个key到vm实例上
- 初始化组件的props、data、methods、computed、watch等配置项,响应式处理,并代理到vm实例上
- 解析组件配置项上的provide对象,将其挂载到vm._provided属性上
- 调用created钩子函数
- 如果发现配置项上有el选项,则自动调用mount方法,也就是说有了el选项,就不需要手动调用mount方法,反之,没提供el选项则必须手动调用mount
- 编译阶段,将template模版字符串编译成render函数
- 调用beforeMount钩子函数
- 执行render函数生成vnode虚拟dom
- vnode渲染dom过程(因为是第一次则不需要新旧vnode的diff对比过程)。
- new一个render Watcher
- 调用mount钩子函数
Vue的双向绑定
Vue的编译过程(解析-优化-生成)
将一个模版字符串编译(compile)成render函数
- parse 解析阶段 正则匹配template模版解析成AST树;
- optimize 优化阶段 AST优化阶段标记静态节点和静态根节点
- 递归遍历AST节点,判断每个节点是否是静态的;
- 递归遍历父节点中所有子节点是否是静态的,如果所有子节点都为静态,则该父节点为静态根节点;
- 在生成render函数阶段,判断一个节点是否是一个静态根节点,如果是静态根节点,生成静态render函数。
- render函数生成vnode阶段,如果是静态render函数,将其生成的vnode进行缓存,等到下次再执行render函数的时候直接从缓存中取出。
故标记静态根节点是为了缓存vnode,减少render函数生成vnode的时间。
- generate 生成阶段 将优化后的AST生成render函数。
Vue -- key 的特性作用
-分析下面一段代码
<template>
<div class="v-for">
<ul class="ul">
<li v-for="(row, index) in list">{{ row }}</li>
</ul>
<button v-on:click="reverse">反转数组</button>
</div>
</template>
- 对比新旧vnode的第一个li
- vue判断两个节点是否相同的源码
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
function isDef (v) {
return v !== undefined && v !== null
}
function sameInputType (a, b) {
if (a.tag !== 'input') { return true }
var i;
var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;
var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
- 根据1.1源码逻辑分析新旧vnode的第一个li是否是相同的节点,结论是:true
- 旧的vnode第一个li为a;新的vnode第一个li为b
- a.key和b.key都是undefined,顾相等
- a.tag和b.tag都是li,顾相等
- a.isComment和b.isComment都是false,顾相等
- a.data和b.data都是undefined,则isDef(a.data)和isDef(b.data)都是false,顾相等
- 所有最终判定sameNode(a,b)等于true
- 执行patchVnode方法
a : {
key : undefined,
tag : 'li',
isComment : false,
data : undefined
}
b : {
key : undefined,
tag : undefined,
isComment : false,
data : undefined
}
- 判断他们的子节点是否是sameVnode,结论是ture
a : {
key : undefined,
tag : undefined,
isComment : false,
data : undefined,
text: 1
}
b : {
key : undefined,
tag : undefined,
isComment : false,
data : undefined,
text: 5
}
- 判断新vnode的text是不是undefined或者null,结论为flase
- 判断新vnode的text和旧vnode的text是否相等,结论为false
- 执行nodeOps.setTextContent(elm, vnode.text)
- setTextContent的第一个参数elm(var elm = vnode.elm = oldVnode.elm;),即第一个 参数是真实的dom树;
- setTextContent的第二个参数是新vnode的text
- 即将新的值替换到实际dom树上去
function setTextContent (node, text) {
node.textContent = text;
}
var nodeOps = /*#__PURE__*/Object.freeze({
createElement: createElement$1,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
setStyleScope: setStyleScope
});
- 总结 如果v-for不添加key,则直接修改li里面的内容,官网里的表述为就地复用。
为什么data是一个函数并且返回一个对象呢
data之所以是一个函数,是因为一个组件可能会被多处调用,而每一次调用就会执行data函数并返回新的数据对象,这样,可以避免多处调用之间的数据污染。
组件之间的传值方式有哪些?
- 父组件传值给子组件,子组件使用props进行接收
- 子组件传值给父组件,子组件使用$emit+事件对父组件进行传值
- 组件中可以使用children获取到父组件实例和子组件实例,进而获取数据
- 使用listeners,在对一些组件进行二次封装式可以方便传值,例如A->B->C
- 使用$refs获取组件实例,进而获取数据
- 使用Vuex进行状态管理
- 使用eventBus进行跨组件触发事件,进而传递数据
- 使用provide和inject,官方建议我们不要用,elementui中发现大量使用
- 使用浏览器本地缓存,例如localStorage
computed和watch的区别
- computed是依赖已有的变量来计算一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量;并且computed具有缓存机制,依赖值不变的情况下直接读取缓存进行复用;computed不能进行异步操作。
- watch是监听某一个变量的变化,并执行响应的回调函数,通常是一个变量的变化决定多个变量的变化。watch可以进行异步操作。
Vue异步更新和nextTick实现原理
说说nextTick的用处
Vue采用的时异步更新的策略,通俗点说就是,统一事件循环内多次修改,会统一进行视图更新。
Vue是异步更新的,所以数据一更新,视图却还没有更新,此时获取视图数据不是最新的。nextTick可以是
Vue-Router路由有哪些模式呢?
- hash模式:通过#号后面的内容的更改,触发hashchange事件,实现路由切换
- history模式:通过pushState和replaceState切换url,触发popstate事件,实现路由切换,需要后端配合
Vuex有哪些属性?用处是什么?
Vuex是专门为Vue开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以响应的规则保证状态以一种可预测的方式发生变化。
- 核心概念
- State Vuex使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。
在Vue中获取Vuex状态:this.$store.state.count 2. Getter 从store中的state中派生出一些状态,例如对列表进行过滤并计数:
- Mutation 更改Vuex的store中的状态的唯一方法及时提交mutation。他接受state作为第一个参数;
Mutation通过store.commit触发
Mutation必须是同步函数
- Action
Action类似于mutation,不同在于:
Action提交的是mutation,而不是直接变更状态
Action通过store.dispatch触发
5.Module 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象有可能变得相当臃肿。
为了解决以上的问题,Vue允许我们将store分割成模块(module)。每个模块拥有自己的state、getter、mutation、action、甚至于嵌套子模块。
<body>
<div id="app">
<div>{{ countCopy }}</div>
<div><button @click="add">add</button></div>
<div><button @click="asyncAdd">asyncAdd</button></div>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://unpkg.com/vuex@4"></script>
<script>
const store = Vuex.createStore({
state(){
return {
count: 0
}
},
mutations: {
add(state){
state.count ++
}
},
actions: {
add(context){
setTimeout(() => {
context.commit('add')
}, 1000);
}
}
})
const app = Vue.createApp({
computed: {
countCopy(){
return this.$store.state.count
}
},
methods: {
add(){
this.$store.commit('add')
},
asyncAdd(){
this.$store.dispatch('add')
}
}
})
app.use(store)
app.mount('#app')
</script>
</body>
Vuex的实现原理
v-model的实现原理
-
v-model
只是语法糖 -
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件; - 组件上, 使用
modelValue
作为 prop 和update:modelValue
作为事件。
- text 和 textarea 元素使用
-
修饰符
.lazy
在默认情况下,
v-model
在每次input
事件触发后将输入框的值与数据进行同步 (除了上述输入法组织文字时)。你可以添加lazy
修饰符,从而转为在change
事件之后进行同步:.number
如果想自动将用户的输入值转为数值类型,可以给v-model
添加number
修饰符:.trim
如果要自动过滤用户输入的首尾空白字符,可以给v-model
添加trim
修饰符:
// 在“change”时而非“input”时更新
<input v-model.lazy="msg" />
Keep-Alive
- 当组件在
<keep-alive>
内被切换时,它的mounted
和unmounted
生命周期钩子不会被调用,取而代之的是activated
和deactivated
。(这会运用在<keep-alive>
的直接子节点及其所有子孙节点。)
子组件更新props中的属性值的两种方式
vue怎么给绑定的onclick事件传递除event对象之外的参数?
@click="click($event, '1')"
function click(e, index){}