Vue部分
Vue编译时声明周期的执行顺序
Vue中父子组件渲染顺序(同步引入子组件:import Son from ‘@/components/son’ )
父子组件编译时的生命周期执行顺序
这里修改data数据时也修改了dom,如过知识通过按钮对数据进行++操作,那么没有修改dom,也就不会调用beforeupdate和updated生命周期
1.beforeUpdate()是针对视图层的,只有页面中渲染的数据发生改变或者渲染,才会触发这个生命周期函数
2.updated()中修改数据(在视图层进行了展示)是会触发beforeUpdate()的
修改父组件的视图数据
修该子组件的视图数据
Vue中父子组件渲染顺序(异步引入子组件:const Son = () => import(‘@/components/son’) )
组件的通讯方式有哪些
父子组件通信
1.父组件使用自定义属性向子组件传递数据(单向数据流),子组件使用props接受父组件传递过来的数据
2.子组件使用事件向父组件传递数据:$emit('事件名','传递的数据'...)
3.父组件使用v-model语法糖向子组件通信(双向绑定,子组件可以修改):相当于父组件使用了自定义属性value,子组件使用$emit('input','xxx')用父组件传递数据
4.sync修饰符:父传子没什么变化,子传父不需要使用事件接受(双向绑定,类似v-model,不过自定义属性名和变量名相同) $emit('update:属性名',‘传递的数据’)
父组件向后代组件通信
依赖注入(传递的对象是响应式的数据,传入的其他格式的数据是非响应式的)
(provide和inject)
非父子组件通信
使用事件总线(创建事件总线之后,使用$emit发送,使用$on接收)
Vuex
Vuex的属性和方法
// 3. 创建 store 实例对象
const store = new Vuex.Store({
/* 这里配置Vuex */
//state属性的值是一个对象,用来存储全局共享的数据
strict: true, //开启vue严格模式,不允许在组件中直接修改state数据
state: {
//在组件中通过 $store.state.属性名调用
age: 20,
username: 'zhangsan'
},
mutations:{ //放同步方法,用来更新state中的数据
//组件中通过 $store.commit(‘方法名’,参数值)调用
updateAge(state,newVal){
//第一个参数是固定的,表示实例中的state对象
//第二个参数及后续参数,需要调用时传递过来
}
},
actions:{ //放异步方法,actions不能直接修改state数据,可以通过调用mutations中的方法修改state数据
//组件中通过$store.dispatch(‘方法名’,参数值)
update(store,newval){
//第一个参数store是固定的,表示当前的store对象,通过它可以很方便的mutations中的方法
//第二个参数及后续参数,需要调用时传递过来
store.commit('方法名',val)
}
}
getters:{//getter是vuex中的计算属性,但是不支持set修改
//组件中通过$store.getters.方法名 调用
isAllDone(state){
//第一个参数state是固定的,表示前面的state对象,通过它可以很方便的找到state中的属性
return xxx
}
}
})
可以通过Vuex 提供了 mapState、mapGetters、mapMutations、mapActions 四个方法,使用他们,也可以在组件中直接使用Vuex。
还有使用模块化思想
为什么不能直接修改state的值
因为state是实时更新的,假如可以直接修改state,当你异步对state进行操作时,还没执行完,这时候如果state已经在其他地方被修改了,这样就会导致程序存在问题了。mutations无法进行异步操作,所以state要同步操作,通过mutations的方式限制了不允许异步。
Vue2的双向数据绑定原理
通过数据劫持实现数据的响应式更新,通过发布-订阅模式(Watcher)实现数据和视图之间的双向绑定。当数据发生变化时,会通知订阅者更新视图,保持数据和视图的同步。这种机制使得Vue能够实现高效的数据绑定和视图更新,提高开发效率和用户体验。
步骤
1.数据劫持(Object.defineProperty):创建实例时,Vue通过Object.defineProperty方法对data对象的属性进行劫持,当访问或修改属性值时,会触发getter和setter函数。在getter函数中收集依赖(将Watcher对象添加到订阅者列表中),在setter函数中通知订阅者更新视图。
2.模板编译:在编译阶段,Vue会解析模板中的指令,插值表达式等,生成可执行的渲染函数。渲染函数会建立起数据属性和视图之间的依赖关系,即将数据属性与侦听器(Watcher)关联起来
3.Watcher:Watcher是Vue中的观察者对象,用于监听数据的变化并更新视图。当模板中的数据发生变化时,Watcher会接收到通知,触发更新视图的操作。
4.视图更新:当数据放生变化时,相应的setter方法会被调用,从而通知Vue数据发生了变化,找到与该数据相关的Watcher对象,并执行Wathcer的更新方法,将更新结果渲染到视图上,实现了数据的双向绑定效果
组件的重新渲染
在Vue2中组件的重新渲染主要发生在组件的updated生命周期钩子函数中,updated生命周期钩子函数会在组件的数据发生变化,并且虚拟DOM重新徐渲染之后执行
组件重新渲染的流程
当组件的数据发生变化或父组件强制更新时。Vue2会重新渲染组件
步骤:
1.更新数据:组件的数据发生变化时,Vue会通知组件进行重新渲染
2.执行渲染函数:Vue会执行组件的渲染函数,生成新的虚拟DOM
3.比较差异:Vue会通过虚拟DOM的diff算法比较新旧虚拟DOM的差异,找出需要更新的部分
4.更新视图:Vue会更新真实DOM,只更新发生变化的部分,而不是重新渲染整个组件
Vue2是如何劫持data对象中的各种数据类型的
在Vue 2中,对data对象中的各种数据类型(包括对象、数组、基本数据类型等)的劫持是通过递归遍历对象属性并使用Object.defineProperty()方法实现的。具体来说,Vue对不同数据类型的劫持方式如下:
1. 对象(Object): 对于对象中的属性,Vue会递归遍历对象的每个属性,并使用Object.defineProperty()方法为每个属性定义getter和setter,以实现对对象属性的劫持。当对象属性被访问或修改时,Vue能够捕获这些操作并触发更新。
2. 数组(Array): 对于数组,Vue会重写数组的原型方法(如push、pop、shift等),以实现对数组的变化进行监听和响应。通过重写数组的原型方法,Vue能够实现对数组的劫持,确保对数组的操作能够触发更新。 Vue会将数组的原型方法进行重写,将这些方法指向Vue定义的新方法,这些新方法会在执行时触发Vue的响应式更新机制。
Vue2并没有劫持数组的下标,因此当通过下标修改数组时,无法触发视图的重新渲染
3. 基本数据类型(Number、String、Boolean等): 对于基本数据类型,Vue会将其包装成响应式对象,然后再进行劫持。Vue会为基本数据类型创建一个包装对象,并使用Object.defineProperty()方法为包装对象定义getter和setter,以实现对基本数据类型的劫持。
通过以上方式,Vue能够实现对data对象中各种数据类型的劫持,实现数据的响应式更新和视图的自动更新。这种劫持机制使得Vue能够实现数据驱动视图的特性,让开发者能够更便捷地处理数据和视图之间的关系。
Vue2中,对data中某个对象的原有属性值进行修改,会不会触发渲染?(会,因为数据劫持
Vue2中,对data中某个对象的新增属性值进行修改,会不会触发渲染 (不会,因为数据劫持发生在vue初始化阶段,用 s e t ,因为 set,因为 set,因为set相当于新一轮的数据劫持)
这是因为Vue在实例化时会对data对象进行响应式处理,只有已经存在于data中的属性才会被Vue的响应式系统追踪和监听变化。
当你向data中的对象新增属性时,这个新增的属性并不会被Vue的响应式系统捕获到,因此对新增属性的修改不会触发视图的重新渲染。如果想要让新增的属性也能触发视图更新,可以使用Vue.set()方法或者this.$set()方法来添加响应式属性,这样新增的属性就会被Vue监听到,并能触发视图的重新渲染。
给Vue2添加新的响应式属性的方法
//this.obj是要添加新属性的对象,'newProperty'是新属性的名称,value是新属性的值。
Vue.set(obj, 'newProperty', value);
this.$set(this.obj, 'newProperty', value);
使用Vue.set()或this.$set()方法添加新的响应式属性后,这个新属性就会被Vue监听到,能够触发视图的重新渲染。这样就可以动态地向Vue实例中的对象添加新的响应式属性,实现数据的动态更新和视图的同步显示。
Vue2中,对data中某个数组,用下标的表示修改值,会不会触发渲染?(不会)
Vue无法直接捕获这个操作,因为Vue只能劫持对象属性的访问和修改,而不是数组的下标操作。因此,Vue无法知道数组元素的变化,也就无法触发视图的重新渲染。
为了解决这个问题,Vue提供了Vue.set()方法或this.$set()方法来确保对数组的修改能够被Vue监听到。这两个方法会向数组中添加一个新的属性并触发视图更新,从而让Vue能够检测到数组的变化。
Vue2中,对data中某个数组进行push、pop会不会触发渲染?(会,vue的初始化阶段对数组方法进行重写,重写逻辑和$set差不多,新做了一次数据劫持)
在Vue 2中,对data中某个数组进行push、pop等操作会触发视图的重新渲染。这是因为Vue 2重写了数组的push、pop等方法,并在这些方法内部添加了更新视图的逻辑。当调用这些数组方法时,Vue能够捕获到数组的变化,并触发视图的重新渲染,确保视图与数据的同步更新。
为什么data属性是一个函数而不是一个对象
当data直接是一个对象时,多个组件会共同引用同一个数据对象,当其中一个组件修改了数据,其他组件也会受到影响,造成数据混乱。通过将data定义为一个函数,每次组件创建实例时,都会调用该函数返回一个新的数据对象,从而避免了数据共享的问题
computed和watch的区别,使用场景,举例
使用目的不同:computed是根据其所依赖的属性的值计算一个新属性的值,并将其作为响应式属性暴露给用户,适用于简单的计算逻辑;而侦听器则用于在某些数据变化时候执行异步或开销较大的操作
触发时机不同:计算属性会在其所依赖的属性发生变化时自动更新,而侦听器可以设置要观察的属性,只有当这些属性发生变化时才触发回调
使用场景举例:
场景一 - 使用computed: 假设在一个电子商务网站的项目中,有一个商品列表页面,每个商品有价格和折扣信息,需要计算出每个商品的实际价格(考虑折扣)并展示在页面上。这时可以使用computed属性来计算每个商品的实际价格
场景二 - 使用watch: 假设在一个社交媒体应用的项目中,用户有一个个人资料页面,需要监控用户个人资料的变化,并在变化时发送请求更新用户信息。这时可以使用watch属性来监听用户个人资料的变化
diff算法(十分重要,必问)
虚拟DOM是真实DOM的js对象
Vue2 Diff算法总结(图文结合)
点击前往
Vuediff算法视频:点击前往
Vue2和Vue3的diff算法的区别(重点看):
- Vue 2中的diff算法:
- Vue 2使用的是双端比较算法(Double Virtual DOM),即通过比较新旧虚拟DOM树的差异来更新真实DOM。
- 在Vue 2中,diff算法是基于深度优先的遍历策略,会递归地比较每个节点及其子节点,然后进行增删改查的操作。
- Vue 2中的diff算法对于列表渲染时,需要使用key来帮助Vue准确识别节点的身份,以提高性能和避免不必要的DOM操作。
- Vue 3中的diff算法:
- Vue 3引入了新的响应性系统和虚拟DOM重写,对diff算法进行了优化。
- Vue 3中采用了更高效的静态分析和编译优化,可以生成更精简的虚拟DOM树,减少diff算法的比较成本。
- Vue 3中引入了递增式更新(Incremental DOM),可以更精准地跟踪和应用节点的变化,减少不必要的DOM操作,提高性能。
- Vue 3对列表渲染的diff算法进行了改进,不再强制要求使用key,而是通过更智能的算法来处理列表中节点的变化,减少开发者的负担。
总的来说,Vue 3在diff算法上进行了一系列的优化和改进,包括静态分析、递增式更新等,以提高性能、效率和开发体验。Vue 3的diff算法更加智能和灵活,减少了开发者在使用列表渲染时需要关注key的要求,同时提升了整体的性能表现。
key值,为什么不推荐用index去当成key使用(基本上这一点要结合diff算法去回答)
v-for没有key值会发生什么
1.在vue虚拟DOM的对比算法中,无法识别两次渲染的具体变化,只能对整个列表进行重新渲染,从而导致性能下降
2.当数组的顺序发生变化时,vue会认为这是两组不同的元素,从而销毁原来的元素,创建一个新元素,可能导致浏览器的重绘与回流,影响渲染性能
3.当数组中有相同的值时,无法区分,可能会引起一起隐藏问题,如选中状态,切换状态等
key的工作原理
1.当Vue更新渲染真实DOM时,他会对新旧节点进行比较,找出他们之间的差异
2.如果两个节点具有相同的key值,则vue会认为它是相同的节点,会尝试复用已存在的真实DOM节点
3.如果节点具有不同的key值,Vue会视为不同的节点,并进行适当的更新,移动或删除操作
为什么不推荐用index去当成key使用
- 节点身份识别问题: 使用index作为key值时,Vue在进行节点比较时无法准确识别节点的身份。因为index是根据节点在数组中的位置生成的,当数组发生变化时,原本的index也会发生变化,导致Vue无法正确识别节点的身份,从而可能出现错误的更新或重渲染。
- 节点重用问题: 使用index作为key值时,Vue无法准确判断哪些节点是新增的、哪些节点是更新的、哪些节点是删除的,从而无法高效地重用节点。这可能导致不必要的DOM操作,影响性能。
vue23区别(答得不够到位,最起码要答到api,数据劫持方式,diff算法,v-if和v-for的顺序,静态提升,Tree shanking、适配ts等等)
1.响应式系统:V2是Object.defineProperty ,V3引入了proxy响应式系统,具有更好的性能和拦截器api
2.组合式api:v2是options API ,v3引入了Composition API能够更好的阻止和复用代码逻辑
3.模板编译:通过模板编译重构,生成的代码更小更快
4.性能优化:引入更多性能优化,比如虚拟DOM重写,Tree sharking提高渲染速度
5.更好的TypeScript支持:能够为开发者提供更好的类型检查
6.引入一些新组件:Fragment、Teleport、Suspense、KeepAlive等
vue3为什么用proxy代替vue2的Object.defineProperty(去看下proxy的原理,就能知道为什么vue3要用proxy了)
参考文章,点击前往
Vue的路由都有哪些,hash路由和history路由有什么不同
Vue框架中常用的路由有以下几种:
- Hash 路由(Hash-based routing): 在 URL 中的 hash(#)部分来作为路由地址的一种路由模式。在 Vue 中,可以使用 Vue Router 来实现 Hash 路由。例如,
http://example.com/#/home
,其中#/home
就是 hash 路由地址。 - History 路由(History-based routing): 使用 HTML5 History API 中的 pushState 和 replaceState 来实现路由跳转的一种路由模式。在 Vue 中,同样可以使用 Vue Router 来实现 History 路由。例如,
http://example.com/home
,其中/home
就是 history 路由地址。
主要区别如下:
- URL 格式:
- Hash 路由使用带有 # 符号的 URL,如
http://example.com/#/home
。 - History 路由使用不带 # 符号的 URL,如
http://example.com/home
。
- Hash 路由使用带有 # 符号的 URL,如
- 兼容性:
- Hash 路由在各种浏览器中都能良好地工作,因为 hash 路由不会触发页面的刷新,而是在当前页面中进行路由切换。
- History 路由需要浏览器支持 HTML5 History API,对于不支持的浏览器,需要进行降级处理。
- 美观性:
- History 路由相对于 Hash 路由来说,URL 更加美观和干净,没有 # 符号的干扰。
- 服务端配置:
- History 路由需要服务端配置来支持,以确保在刷新页面或直接访问路由时能正确返回对应的页面。
- Hash 路由不需要额外的服务端配置,因为 hash 路由的变化不会触发页面的刷新,所有路由切换都在前端完成。
总的来说,Hash 路由和 History 路由在 URL 格式、兼容性、美观性和服务端配置等方面有一些不同。开发者可以根据项目需求和浏览器兼容性要求选择适合的路由模式。
路由守卫(手动敲一下,熟悉对应的钩子函数)
参考文章,点击前往
插槽、实际场景
Vue中的插槽(slot)用于在组件之间传递内容。插槽允许开发者在父组件中将子组件的内容插入到特定的位置,从而实现更灵活的组件复用和定制。以下是一些实际场景中使用插槽的例子:
- 插槽的基本用法:
- 在父组件中使用插槽,可以将子组件中的内容插入到指定位置,实现组件的定制化。例如,可以在一个卡片组件中使用插槽来插入标题、内容和操作按钮等不同部分。
- 具名插槽(Named Slots):
- 可以使用具名插槽来传递多个不同位置的内容。例如,一个布局组件可以定义多个具名插槽,分别用于头部、内容和底部等不同部分的插入。
- 作用域插槽(Scoped Slots):
- 作用域插槽允许子组件向父组件传递数据,实现更灵活的内容定制。例如,一个列表组件可以使用作用域插槽来传递每个列表项的数据给父组件进行定制化展示。
- 动态插槽内容:
- 插槽内容可以是动态的,根据父组件的数据来动态决定插入的内容。这样可以实现更灵活的组件复用和定制。
- 默认插槽内容:
- 可以为插槽定义默认内容,当父组件没有提供插槽内容时,会显示默认内容。这样可以提供更好的用户体验和组件的兼容性。
JS
基本数据类型和引用类型
深拷贝的实现方式(递归和JSON,递归要写熟,注意两者的区别)
for in和for of、for of能不能遍历对象?(for of去看下什么是可迭代对象)(手写一下看看区别)
for...in
和 for...of
是 JavaScript 中用于遍历数据结构的两种不同的循环方式。
-
for...in
循环:for...in
循环用于遍历对象的可枚举属性,包括对象自身的属性和继承的属性。- 适用于遍历对象的键名(属性名),例如遍历对象的属性或数组的索引。
- 不推荐用于遍历数组,因为会遍历数组的所有可枚举属性,包括原型链上的属性。
-
for...of
循环:for...of
循环用于遍历可迭代对象(iterable objects),包括数组、字符串、Map、Set 等实现了迭代器协议的对象。- 适用于遍历对象的值,而不是键名,例如遍历数组的元素或字符串的字符。
- 不能直接用于普通对象的遍历,因为普通对象不是可迭代对象。
可迭代对象
JavaScript中的可迭代对象(iterable objects)是指实现了迭代器协议(Iterator Protocol)的对象,这些对象可以通过迭代器进行遍历。可迭代对象必须具有一个名为 [Symbol.iterator] 的方法,该方法返回一个迭代器对象。
迭代器对象(Iterator Object)包含一个 next() 方法,每次调用 next() 方法都会返回一个包含 value 和 done 属性的对象。其中,value 表示迭代的当前值,done 是一个布尔值,表示迭代是否结束。
怎么判断数据类型 typeof,instanceof,object.protoype.tostring,instanceof原理(原理要懂,而且最好是和递归深拷贝一样,熟练地手写出来,自己封装一个instanceof)
instanceof的判断实现通过原型链
改变this指向方法,call、apply、bind的区别,(没答上来,而且最好是和递归深拷贝一样,熟练地手写出来,自己封装call、apply、bind)
在 JavaScript 中,有几种方法可以改变函数内部 this
指向的方式,主要包括 call()
、apply()
、bind()
和箭头函数。这些方法的区别在于它们如何改变函数内部的 this
指向以及是否会立即执行函数。
- call():
call()
方法可以显式地指定函数内部的this
指向,并立即执行函数。call()
方法的第一个参数是要绑定给this
的值,后续参数是传递给函数的参数列表。
function greet() {
console.log("Hello, " + this.name);
}
let person = { name: "Alice" };
greet.call(person); // 输出:Hello, Alice
- apply():
apply()
方法与call()
类似,也可以显式地指定函数内部的this
指向,并立即执行函数。不同之处在于,apply()
方法接收一个包含参数的数组作为第二个参数。
function greet(greeting) {
console.log(greeting + ", " + this.name);
}
let person = { name: "Bob" };
greet.apply(person, ["Good morning"]); // 输出:Good morning, Bob
- bind():
bind()
方法可以创建一个新的函数,并将函数内部的this
指向指定的值,但不会立即执行函数。bind()
方法返回一个新函数,需要手动调用才会执行。
function greet() {
console.log("Hello, " + this.name);
}
let person = { name: "Charlie" };
let boundGreet = greet.bind(person);
boundGreet(); // 输出:Hello, Charlie
总的来说,call()
、apply()
和 bind()
是用于改变函数内部 this
指向的常用方法,它们可以在函数执行时动态改变 this
的指向;而箭头函数则固定了 this
的指向,无法通过这些方法改变。
原型链(需要答出原型和实例的关系以及什么是链)
在 JavaScript 中,每个对象都有一个原型(prototype),原型是一个对象,用于存储对象共享的属性和方法。每个对象都可以通过原型链(prototype chain)访问其原型,形成了原型和实例之间的关系。
在 JavaScript 中,每个对象都有一个指向其原型的内部链接,这个链接就是原型链。当我们访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到对应的属性或方法或者到达原型链的顶端(Object.prototype)。