前端面试题之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为例)
new Vue()
首先执行初始化,对data
执行相应化处理,这个过程发生在Observer
中- 同时对模板执行编译,找到其中动态绑定的数据,从
data
中获取数据并初始化视图,这个过程发生在Compile
中 - 同时定义一个更新函数
updater
和watcher
,将来对应的数据变化时watcher
会调用更新函数 - 由于
data
的某个key
在一个视图中可能出现多次,所以每个key
都需要一个管家Dep
来管理多个watcher
- 将来
data
中的数据一旦发生变化,会先找到对应的Dep
,通知所有watcher
执行更新函数
Object.defineProperty() 来进行数据劫持有什么缺点
- Object.defineProperty() 是一开始就遍历对象进行监听,所以检测不到对象属性的添加和删除
- 数据的API方法无法监听到,例如push、pop方法等监听不到
- 需要对每个属性进行遍历监听,如果嵌套的对象层级比较深,则会影响性能
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保证唯一
- 体积比较大的图片懒加载
生命周期
- **beforeCreate(创建前)**数据观测和初始化事件还未开始,不能访问到data、computed、watch、methods上的方法和数据
- **created(创建后)**实例创建完成,实例上配置的options包括data、computed、watch、methods都配置完成,但是还未挂载DOM,所以不能访问到DOM
- **beforeMount(挂载前)**在挂载前被调用,相关的render函数首次被调用,编译模板,把data里面的数据和模板生成HTML,此时还没挂载HTML到页面上
- **mounted(挂载后)**完成了模板中的HTML渲染到页面上
- **beforeUpdate(更新前)**响应式数据更新时调用,此时虽然响应式诗句更新了,但是真实的DOM还未渲染
- **updated(更新后)**在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用,此时DOM已经更新
- **beforeDestroy(销毁前)**实例销毁前调用,这一步实例依然可用,this还可以调用
- **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.state
和context.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的整体流程
- 在组件内部通过dispatch来分发action(或者通过辅助函数mapActions)
- action 通过 commit 来调用mutation
- mutation 修改 state的值
- 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 函数中访问 statecount: computed(() => store.state.count),// 在 computed 函数中访问 getterdouble: computed(() => store.getters.double)}}
}
- 访问 Mutation 和 Action
import { useStore } from 'vuex'export default {setup () {const store = useStore()return {// 使用 mutationincrement: () => store.commit('increment'),// 使用 actionasyncIncrement: () => 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,新节点的startIndex
u移动到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++) // 19return {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 scopefunctionalContext: Component | void; // only for functional component root nodeskey: string | number | void;componentOptions: VNodeComponentOptions | void;componentInstance: Component | void; // component instanceparent: VNode | void; // component placeholder noderaw: boolean; // contains raw HTML? (server only)isStatic: boolean; // hoisted static nodeisRootInsert: boolean; // necessary for enter transition checkisComment: 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节点
相关文章:

前端面试题之vue篇
vue基础 vue的基本原理 当一个Vue实例创建时,Vue会遍历data中的属性,用Object.defineProperty(Vue使用proxy)转换为getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例…...

Java进阶(垃圾回收GC)——理论篇:JVM内存模型 垃圾回收定位清除算法 JVM中的垃圾回收器
前言 JVM作为Java进阶的知识,是需要Java程序员不断深度和理解的。 本篇博客介绍JVM的内存模型,对比了1.7和1.8的内存模型的变化;介绍了垃圾回收的语言发展;阐述了定位垃圾的方法,引用计数法和可达性分析发以及垃圾清…...

GaN HEMT 电容的分析建模,包括寄生元件
标题:Analytical Modeling of Capacitances for GaN HEMTs, Including Parasitic Components 来源:IEEE TRANSACTIONS ON ELECTRON DEVICES(14年) 摘要:本文提出了一种基于表面势的终端电荷和电容模型,包…...

Python实战 | 使用 Python 和 TensorFlow 构建卷积神经网络(CNN)进行人脸识别
专栏集锦,大佬们可以收藏以备不时之需 Spring Cloud实战专栏:https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏:https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏:https:/…...

JLink edu mini 10Pin接口定义
注意:SWD接口在阵脚2,4;而20Pin的SWD接口在阵脚7,9 参考:1 官网资料; 2 【润石RS0104YQ Demo开发板测试分享】J-Link EDU Mini调试5V系统_国产运算放大器_模拟开关_线性稳压器_电平转换器_小逻辑_比较器…...

compile: version “go1.19“ does not match go tool version “go1.18.1“
** 1 安装了新版本的go后 为什么go version 还是旧版本? ** 如果你已经按照上述步骤安装了新版本的 Go,但 go version 命令仍然显示旧版本,可能是因为你的环境变量设置不正确或未正确生效。你可以尝试以下方法来解决问题: 重新…...
spring boot security 自定义AuthenticationProvider
spring boot security 自定义AuthenticationProvider 基于 spring boot 3.x 场景实现 手机验证码登陆 实现 CaptureCodeAuthenticationFilter public class CaptureCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private static final Strin…...
某电力设计公司绩效考核优化项目成功案例纪实
——引入角色定位考核法,建立多维度评价体系,支持业务转型后的客观评价 【客户行业】电力行业 【问题类型】绩效考核 【客户背景及现状分析】 某电力设计公司成立于2000年左右,是一家从事输变电工程勘察、设计、咨询的专业公司,…...
力扣371周赛
力扣第371场周赛 找出强数对的最大异或值 I 枚举 class Solution { public:int maximumStrongPairXor(vector<int>& a) {int n a.size() , res 0;for(int i 0 ; i < n ; i ){for(int j 0 ; j < n ; j ){if(abs(a[i]-a[j])<min(a[i],a[j])){int c (a…...

Python之字符串、正则表达式练习
目录 1、输出随机字符串2、货币的转换(字符串 crr107)3、凯撒加密(book 实验 19)4、字符替换5、检测字母或数字6、纠正字母7、输出英文中所有长度为3个字母的单词 1、输出随机字符串 编写程序,输出由英文字母大小写或…...

Transmit :macOS 好用的 Ftp/SFtp 工具
Transmit 是一种功能强大的 FTP/SFTP/WebDAV 客户端软件,是一个 Mac OS X 平台上设计的文件传输软件。它由 Panic(一家以软件工具为主的公司)开发和维护,是一款非常受欢迎且易于使用的软件,而且被广泛认为是 Mac OS X …...

【Github】git clone命令下载文件中途停止
方法一: 使用git clone命令下载github上的源代码时,有时文件下载到一定百分比时就停止不动, 这是因为我们所下载的文件很大,超过了git预先分配的Postbuffer容量,所以一直卡在那里。可以使用以下命令查看当前Postbuffe…...

Clickhouse学习笔记(10)—— 查询优化
单表查询 Prewhere 替代 where prewhere与where相比,在过滤数据的时候会首先读取指定的列数据,来判断数据过滤,等待数据过滤之后再读取 select 声明的列字段来补全其余属性 简单来说就是先过滤再查询,而where过滤是先查询出对应…...

[量化投资-学习笔记012]Python+TDengine从零开始搭建量化分析平台-策略回测
上一章节《MACD金死叉策略回测》中,对平安银行这只股票,按照金死叉策略进行了回测。 但通常我们的股票池中有许多股票,每完成一个交易策略都需要对整个股票池进行回测。 下面使用简单的轮询,对整个股票池进行回测。 # 计算单只…...
MySQL 查看 event 执行记录
文章目录 1. 查看 EVENT 执行记录2. 示例3. 结论 MySQL 是一款流行的关系型数据库管理系统,它提供了许多功能来帮助用户管理和操作数据库。其中之一就是 EVENT事件,它允许用户在特定的时间间隔内自动执行指定的操作,类似于计划任务。 在使用 …...

开发知识点-Vue-Electron
Electron ElectronVue打包.exe桌面程序 ElectronVue打包.exe桌面程序 为了不报错 卸载以前的脚手架 npm uninstall -g vue-cli安装最新版脚手架 cnpm install -g vue/cli创建一个 vue 随便起个名 vue create electron-vue-example (随便起个名字electron-vue-example)进入 创建…...

【线性代数】反求矩阵A
...
MyBatis 中的 foreach 的用法
本文将介绍 MyBatis 中的 <foreach> 标签的灵活应用,并结合财经领域的数据处理场景,阐述其在财经系统开发中的重要性和应用价值。 MyBatis中的<foreach>标签简介 MyBatis 是一个优秀的持久层框架,它简化了数据库操作的流程&…...

交叉编译 mysql-connector-c
下载 mysql-connector-c $ wget https://downloads.mysql.com/archives/get/p/19/file/mysql-connector-c-6.1.5-src.tar.gz 注意:mysql-connector 的页面有很多版本,在测试过程中发现很多默认编译有问题,其中上面的 6.1.5 的版本呢是经过测…...
企业如何选择正确的存储服务器租用?
数据时代的发展,让越来越多的企业选择使用存储服务器来存储数据,今天小编就带大家了解一下企业应该怎么正确的选择存储服务器吧,要关注哪些方面的问题呢? 第一点肯定是看自己的需求,不论是选择什么服务器最重要的一点就…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...