前端面试题之vue篇

vue基础

vue的基本原理

当一个Vue实例创建时,Vue会遍历data中的属性,用Object.defineProperty(Vue使用proxy)转换为getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例,他会在组件渲染的过程中把属性记录为依赖,之后依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
在这里插入图片描述

双向数据绑定的原理

MVVM 双向绑定,达到数据变化 -> 试图更新;试图变化 -> 数据model变更
在这里插入图片描述

Vue是采用数据劫持结合发布者-订阅者模式的方式, 通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变化时发布消息给订阅者,出发相应的监听回调。

由两个主要的部分组成

  • 监听器(Observer):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模板替代数据,以及绑定相应的更新数据

过程如下(Vue为例)

  1. new Vue() 首先执行初始化,对data执行相应化处理,这个过程发生在Observer
  2. 同时对模板执行编译,找到其中动态绑定的数据,从data中获取数据并初始化视图,这个过程发生在Compile
  3. 同时定义一个更新函数updaterwatcher,将来对应的数据变化时watcher会调用更新函数
  4. 由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个watcher
  5. 将来data中的数据一旦发生变化,会先找到对应的Dep,通知所有watcher执行更新函数

Object.defineProperty() 来进行数据劫持有什么缺点

  1. Object.defineProperty() 是一开始就遍历对象进行监听,所以检测不到对象属性的添加和删除
  2. 数据的API方法无法监听到,例如push、pop方法等监听不到
  3. 需要对每个属性进行遍历监听,如果嵌套的对象层级比较深,则会影响性能

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 type="submit">
  <slot>
    Submit <!-- 默认内容 -->
  </slot>
</button>
  • 具名插槽
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

使用具名插槽的方式

<BaseLayout>
  <template v-slot:header>
    <!-- header 插槽的内容放这里 -->
  </template>
</BaseLayout>

v-slot 有对应的简写 #,因此 可以简写为 <template #header>。

  • 作用域插槽
    在模板中绑定数据:
<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

在调用组件中调用slot 的数据

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

具名作用域插槽

<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>
</MyComponent>

v-if和v-show的区别

  • v-if 生成节点的时候会忽略对应的节点,render的时候不会渲染
  • v-show 会生成vnode,render的时候也会渲染成真实的节点,只是在render的过程中控制display属性

v-model 是如何实现的

v-model 实际上是一个语法糖,如:

<input v-model="searchText" />

实际上相当于:

<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

应用在组件上就变成:

<CustomInput
  :value="searchText"
  @update:value="newValue => searchText = newValue"
/>

实际上是通过prop$.emit来实现的

<!-- CustomInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:value', $event.target.value)"
  />
</template>

data为什么是一个函数而不是对象

Vue组件可能存在多个实例,如果使用对象形式定义data,会导致它们共用一个data对象,状态变化会影响所有组件实例,这是不合理的,采用函数的形式,在initData时会将其作为工厂函数返回新的data对象,有效避免多实例之间状态污染的问题。

Vue的性能优化有哪些

(1)编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
  • v-if和v-for不能连用(vue2 中v-for的优先级高于v-if 会先执行for再根据if干掉不需要的组件)
  • 如果需要使用v-for给每项元素绑定事件时使用代理
  • SPA采用Keep-alive缓存组件
  • 循环组件中的key保证唯一
  • 体积比较大的图片懒加载

生命周期

  1. **beforeCreate(创建前)**数据观测和初始化事件还未开始,不能访问到data、computed、watch、methods上的方法和数据
  2. **created(创建后)**实例创建完成,实例上配置的options包括data、computed、watch、methods都配置完成,但是还未挂载DOM,所以不能访问到DOM
  3. **beforeMount(挂载前)**在挂载前被调用,相关的render函数首次被调用,编译模板,把data里面的数据和模板生成HTML,此时还没挂载HTML到页面上
  4. **mounted(挂载后)**完成了模板中的HTML渲染到页面上
  5. **beforeUpdate(更新前)**响应式数据更新时调用,此时虽然响应式诗句更新了,但是真实的DOM还未渲染
  6. **updated(更新后)**在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用,此时DOM已经更新
  7. **beforeDestroy(销毁前)**实例销毁前调用,这一步实例依然可用,this还可以调用
  8. **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中的状态的唯一途径就是显式得提交(commit)mutation,这样可以方便的跟踪每项变化。
    在这里插入图片描述
    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,而不是直接设置状态state,action可以包含任意异步操作。action函数接受一个与store实例具有以相同方法和属性的context对象,可以通过context.commit来提交一个mutation,也可以使用context.statecontext.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 assets)
      state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `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的整体流程

  1. 在组件内部通过dispatch来分发action(或者通过辅助函数mapActions)
  2. action 通过 commit 来调用mutation
  3. mutation 修改 state的值
  4. 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 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 在 computed 函数中访问 state
      count: computed(() => store.state.count),

      // 在 computed 函数中访问 getter
      double: computed(() => store.getters.double)
    }
  }
}
  • 访问 Mutation 和 Action
import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 使用 mutation
      increment: () => store.commit('increment'),

      // 使用 action
      asyncIncrement: () => 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,从而使关联的组件重新渲染
在这里插入图片描述
设想一下,假如一个组件有大量的静态节点,如:

<template>
    <div id="content">
        <p class="text">静态文本</p>
        <p class="text">静态文本</p>
        <p class="text">{{ message }}</p>
        <p class="text">静态文本</p>
        ...
        <p class="text">静态文本</p>
    </div>
</template>

可以看到组件的内部只有一个动态节点,剩余的都是一些静态节点,所以这里的很多diff算法和遍历都是不需要的,造成性能浪费

因此,vue3 在编译阶段,主要做了以下优化:

  • (1)diff 算法优化
  • (2)静态提升
  • (3)事件监听缓存
  • (4)SSR优化

(1)diff优化
vue3 相比于 vue2 在diff算法中增加了静态标记,在会发生变化的地方增加一个 flag 标记

下图这里,已经标记静态节点的p标签在diff过程中则不会比较,把性能进一步提高
在这里插入图片描述
静态提升
Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用,提升为一个常量,不参与 diff 运算

事件监听缓存
默认情况下,绑定事件行为被视为动态绑定,每次都会去追踪它的变化,开启缓存后下次diff算法直接用

SSR优化
当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染

div>
	<div>
		<span>你好</span>
	</div>
	...  // 很多个静态属性
	<div>
		<span>{{ message }}</span>
	</div>
</div>

编译后

import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"

export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
  const _cssVars = { style: { color: _ctx.color }}
  _push(`<div${
    _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
  }><div><span>你好</span>...<div><span>你好</span><div><span>${
    _ssrInterpolate(_ctx.message)
  }</span></div></div>`)
}

2、源码体积

相比Vue2,Vue3整体体积变小了,除了移出一些不常用的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++) // 19

        return {
            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 id="app">
    <p class="p">节点内容</p>
    <h3>{{ 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: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    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的时候为true,textContent的时候为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节点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/133118.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Git】的分支和标签的讲解及实际应用场景

目录 一、讲解 1. 环境讲述 2. 应用原因 3. 分支标签的区别 二、分支 1. 命令 2. 场景应用 三、标签 1. 命令 2. 标签规范 3. 应用场景 每篇一获 一、讲解 1. 环境讲述 当软件从开发到正式环境部署的过程中&#xff0c;不同环境的作用如下&#xff1a; 开发环境&a…

vue3+vite搭建后台项目-1 引入element-plus 中文包,打包时报错问题

vue3vite搭建后台项目-1 引入element-plus 中文包,打包时报错问题 终端报错 If theelement-pluspackage actually exposes this module, try adding a new declaration (.d.ts) file containing are moduleelement-plus/dist/locale/zh-cn.mjsdec import zhCn fromelement-plus…

uniapp——项目02

分类 创建cate分支 渲染分类页面的基本结构 效果页面,包含左右两个滑动区. 利用提供的api获取当前设备的信息。用来计算窗口高度。可食用高度就是屏幕高度减去上下导航栏的高度。 最终效果: 每一个激活项都特殊背景色&#xff0c;又在尾部加了个红条一样的东西。 export d…

LeetCode146.LRU缓存

写了一个小时&#xff0c;终于把示例跑过了&#xff0c;没想到啊提交之后第19/22个测试用例没过 我把测试用例的输出复制在word上看看和我的有什么不同&#xff0c;没想到有18页的word&#xff0c;然后我一直检查终于找出了问题&#xff0c;而且这个bug真的太活该了&#xff0c…

多状态Dp问题——买卖股票的最佳时机含冷冻期

目录 一&#xff0c;题目 二&#xff0c;题目接口 三&#xff0c;解题思路及其代码 一&#xff0c;题目 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成…

【数据结构】树与二叉树(十):二叉树的先序遍历(非递归算法NPO)

文章目录 5.2.1 二叉树二叉树性质引理5.1&#xff1a;二叉树中层数为i的结点至多有 2 i 2^i 2i个&#xff0c;其中 i ≥ 0 i \geq 0 i≥0。引理5.2&#xff1a;高度为k的二叉树中至多有 2 k 1 − 1 2^{k1}-1 2k1−1个结点&#xff0c;其中 k ≥ 0 k \geq 0 k≥0。引理5.3&…

电脑清灰涂硅脂后电脑CPU温度不降反升

目录 一.问题描述二.问题解决三.拆机注意事项四.影响散热的主要因素说明1.通风差2.硅脂材料差3.硅脂涂抹方式错误 一.问题描述 电脑型号&#xff1a;暗影精灵5 测温工具&#xff1a;硬件狗狗&#xff08;只要是测温软件都可以&#xff0c;比如omen hub和Core Temp…&#xff0…

实战Leetcode(四)

Practice makes perfect&#xff01; 实战一&#xff1a; 这个题由于我们不知道两个链表的长度我们也不知道它是否有相交的节点&#xff0c;所以我们的方法是先求出两个链表的长度&#xff0c;长度长的先走相差的步数&#xff0c;使得两个链表处于同一起点&#xff0c;两个链…

【Java 进阶篇】Java Web 开发之 JQuery 快速入门

嗨&#xff0c;各位小伙伴们&#xff01;欢迎来到 Java Web 开发的继续学习之旅。在前面的博客中&#xff0c;我们学习了 Servlet、JSP、Filter、Listener 等基础知识&#xff0c;今天我们将进入前端领域&#xff0c;学习一下如何使用 JQuery 来简化和优化我们的前端开发。 1.…

Python 使用tkinter的Text文本域实时显示光标位置

在Python tkinter中&#xff0c;可以使用Text widget的index()方法来获取实时光标的行和列。该方法接受一个字符串参数&#xff0c;用于指定要获取的索引位置&#xff0c;例如"insert"表示当前光标位置。 重难点&#xff1a;想要获取准确的光标位置&#xff0c;需要…

Python实战:绘制直方图的示例代码,数据可视化获取样本分布特征

文章目录 一、初步二、参数三、绘图类型四、多组数据直方图对比Python技术资源分享1、Python所有方向的学习路线2、学习软件3、精品书籍4、入门学习视频5、实战案例6、清华编程大佬出品《漫画看学Python》7、Python副业兼职与全职路线 一、初步 对于大量样本来说&#xff0c;如…

【开源】基于Vue.js的智能停车场管理系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系统管理员功能1. 停车位模块2. 车辆模块3. 停车记录模块4. IC卡模块5. IC卡挂失模块 三、界面展示3.1 登录注册3.2 车辆模块3.3 停车位模块3.4 停车数据模块3.5 IC卡档案模块3.6 IC卡挂…

华为ensp:交换机接口划分vlan

现在要把 e0/0/1 接口放入vlan1 e0/0/2 接口放入vlan2 e0/0/3 接口放入vlan3 默认所有接口都在vlan1所以 e0/0/0 接口不用动 1.创建vlan 进入系统视图模式 直接输入 vlan 编号 即可创建对应vlan vlan 编号 vlan 2 创建vlan2 vlan 3 创建vlan3 2.将接口进入vlan…

tomcat下载与使用教程

1. tomcat下载 官网&#xff1a;https://tomcat.apache.org/ 镜像地址&#xff1a;https://mirrors.huaweicloud.com/apache/tomcat/ 1、选择一个版本下载&#xff0c;官网下载速度缓慢&#xff0c;推荐镜像 2、对压缩包进行解压&#xff0c;无需进行安装&#xff0c;解压放…

Netty--ByteBuffer

2. ByteBuffer 有一普通文本文件 data.txt&#xff0c;内容为 1234567890abcd 使用 FileChannel 来读取文件内容 Slf4j public class ChannelDemo1 {public static void main(String[] args) {// FileChannel// 1. 输入输出流&#xff0c; 2. RandomAccessFile// try (F…

C语言求解猴子分桃问题

题目&#xff1a; 海滩上有一堆桃子&#xff0c;五只猴子来分。第一只猴子把这堆桃子凭据分为五份&#xff0c;多了 一个&#xff0c;这只猴子把多的一个扔入海中&#xff0c;拿走了一份。第二只猴子把剩下的桃子又平均分 成五份&#xff0c;又多了一个&#xff0c;它同样把多…

分类预测 | Matlab实现PSO-LSTM粒子群算法优化长短期记忆神经网络的数据多输入分类预测

分类预测 | Matlab实现PSO-LSTM粒子群算法优化长短期记忆神经网络的数据多输入分类预测 目录 分类预测 | Matlab实现PSO-LSTM粒子群算法优化长短期记忆神经网络的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-LSTM粒子群算法优化长短…

unity - Blend Shape - 变形器 - 实践

文章目录 目的Blend Shape 逐顶点 多个混合思路Blender3Ds maxUnity 中使用Project 目的 拾遗&#xff0c;备份 Blend Shape 逐顶点 多个混合思路 blend shape 基于&#xff1a; vertex number, vertex sn 相同&#xff0c;才能正常混合、播放 也就是 vertex buffer 的顶点数…

【C++】类型转换 | IO流 | 空间配置器

C语言类型转换 C语言总共有两种形式的类型转换&#xff1a;隐式类型转换 和 显示类型转换。 C语言的转换格式虽然很简单&#xff0c;但也存在不少缺陷&#xff1a; 隐式类型转换有些情况下可能会引发意料之外的结果&#xff0c;比如数据精度丢失。显示类型转换的可视性比较差…

matlab中实现画函数图像添加坐标轴

大家好&#xff0c;我是带我去滑雪&#xff01; 主函数matlab代码&#xff1a; function PlotAxisAtOrigin(x,y); if nargin 2 plot(x,y);hold on; elsedisplay( Not 2D Data set !) end; Xget(gca,Xtick); Yget(gca,Ytick); XLget(gca,XtickLabel); YLget(gca,YtickLabel)…