vue

185 阅读4分钟

Vue的执行过程

  • 大致划分
  1. 初始化配置阶段
  2. 编译阶段,生成render函数
  3. render函数执行阶段,生成vnode
  4. vnode渲染成真实dom阶段
  • 细致划分
  1. 处理组件配置项
  • 初始化根组件时,进行选项合并操作,即将全局配置合并到根组件的局部配置上;
  • 初始化子组件时,做了一些性能优化,将组件配置对象上的一些深层次属性扁平化放到vm.$options选项中,以提高代码的执行效率;

image.png

  1. 初始化组件实例的关系属性,比如:parent/parent/children/root/root/refs等
  2. 初始化自定义事件
  3. 初始化插槽,获取this.$slots,定义this._c即createElement方法
  4. 调用beforeCreate钩子函数
  5. 初始化组件的inject配置项,得到result[key]=value显示的配置对象,然后将配置对象进行响应式处理,并代理每个key到vm实例上
  6. 初始化组件的props、data、methods、computed、watch等配置项,响应式处理,并代理到vm实例上
  7. 解析组件配置项上的provide对象,将其挂载到vm._provided属性上
  8. 调用created钩子函数
  9. 如果发现配置项上有el选项,则自动调用mount方法,也就是说有了el选项,就不需要手动调用mount方法,反之,没提供el选项则必须手动调用mount
  10. 编译阶段,将template模版字符串编译成render函数
  11. 调用beforeMount钩子函数
  12. 执行render函数生成vnode虚拟dom
  13. vnode渲染dom过程(因为是第一次则不需要新旧vnode的diff对比过程)。
  14. new一个render Watcher
  15. 调用mount钩子函数

Vue的双向绑定

Vue的编译过程(解析-优化-生成)

将一个模版字符串编译(compile)成render函数

  • parse 解析阶段 正则匹配template模版解析成AST树;
  • optimize 优化阶段 AST优化阶段标记静态节点和静态根节点
  1. 递归遍历AST节点,判断每个节点是否是静态的;
  2. 递归遍历父节点中所有子节点是否是静态的,如果所有子节点都为静态,则该父节点为静态根节点;
  3. 在生成render函数阶段,判断一个节点是否是一个静态根节点,如果是静态根节点,生成静态render函数。
  4. 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
  1. 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.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
}
  1. 判断他们的子节点是否是sameVnode,结论是ture
a : {
    key : undefined,
    tag : undefined,
    isComment : false, 
    data : undefined,
    text: 1
}
b : {
    key : undefined,
    tag : undefined,
    isComment : false, 
    data : undefined,
    text: 5
}

  1. 判断新vnode的text是不是undefined或者null,结论为flase
  2. 判断新vnode的text和旧vnode的text是否相等,结论为false
  3. 执行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+事件对父组件进行传值
  • 组件中可以使用parentparent和children获取到父组件实例和子组件实例,进而获取数据
  • 使用attrsattrs和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开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以响应的规则保证状态以一种可预测的方式发生变化。

  • 核心概念
  1. State Vuex使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。

在Vue中获取Vuex状态:this.$store.state.count 2. Getter 从store中的state中派生出一些状态,例如对列表进行过滤并计数:

  1. Mutation 更改Vuex的store中的状态的唯一方法及时提交mutation。他接受state作为第一个参数;

Mutation通过store.commit触发

Mutation必须是同步函数

  1. 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的实现原理

  • v3.cn.vuejs.org/guide/forms…

  • v-model只是语法糖

  • v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

    • text 和 textarea 元素使用 value property 和 input 事件;
    • checkbox 和 radio 使用 checked property 和 change 事件;
    • select 字段将 value 作为 prop 并将 change 作为事件;
    • 组件上, 使用 modelValue 作为 prop 和 update:modelValue 作为事件。
  • 修饰符

    • .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){}