掌握这些 Vue 和 JS 知识,前端开发不再迷茫!
(??金石瓜分计划回归,速戳上图了解详情??)
关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding
前言本人 2025-02-27 裸辞,2025-03-21 收获 offer。该文章记录了在面试过程中被提问到的问题,并进行总结记录。
关关难过关关过Vue2.0 和 Vue3.0 有什么区别1、响应式重新配置,使用 proxy 替换 Object.defineProperty
Object.defineProperty:劫持整个对象,然后进行「深度遍历所有属性」,给每个属性添加getter和setter,实现响应式
proxy : 劫持整个对象,但不用「深度遍历所有属性」,同样需要添加getter和setter、deleteProperty,实现响应式
new Proxy(data, {// 拦截读取属性值get(target, prop) { returnReflect.get(target, prop) },// 拦截设置属性值或添加新属性set(target, prop, value) { returnReflect.set(target, prop, value) },// 拦截删除属性 deleteProperty (target, prop) { returnReflect.deleteProperty(target, prop) }})2、新增组合 API(Composition API),更好的逻辑重用和代码组织
3、v-if和v-for的优先级
5、支持多个根节点(template中不需要唯一根节点,可以直接放文本或者同级标签)
6、打包体积优化 (任何一个函数,如 ref、reavtived、computed 等,仅仅在用到的时候才打包)tree shanking
7、编译阶段的不同
Vue.js 2.x
通过标记静态节点,优化 diff 的过程vue.js 3.x
标记和提升所有的静态节点,diff 的时候「只需要对比动态节点内容」静态提升(hoistStatic), 当使用静态提升时,所有静态的节点都被提升到 render 方法之外。只会在应用启动的时候被创建一次,之后使用只需要应用提取的静态节点,随着每次的渲染被不停的复用。patch flag, 在动态标签末尾加上相应的标记,只能带 patchFlag 的节点才被认为是动态的元素,会被追踪属性的修改,能快速的找到动态节点,而「不用逐个逐层遍历,提高了虚拟 dom diff 的性能」。缓存事件处理函数 cacheHandler, 避免每次触发都要重新生成全新的 function 去更新之前的函数8、生命周期变化
vue3.x 中可以继续使用 vue2.x 的生命周期钩子,但有俩个被更名;beforeDestroy修改成 beforeUnmountdestroyed 修改成 unmounted vue3.x 生命周期钩子,与 vue2.x 中对应关系
vue2.xvue3.x解释beforeCreatesetup()数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说「不能访问到 data、computed、watch、methods 上的方法和数据」。createdsetup()实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时「渲染得节点还未挂载到 DOM,所以不能访问到$el属性」。beforeMountonBeforeMount在挂载开始之前被调用,相关的 render 函数首次被调用。实例已完成以下的配置:「编译模板」,把 data 里面的数据和模板生成 html。「此时还没有挂载 html 到页面上」。mountedonMounted用上面编译好的 html 内容替换 el 属性指向的 DOM 对象。完成模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。beforeUpdateonBeforeUpdate响应式数据更新时调用,此时虽然响应式数据更新了,但是「对应的真实 DOM 还没有被渲染」。updatedonUpdated发生在更新完成之后,当前阶段组件 DOM 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新,「该钩子在服务器端渲染期间不被调用」。beforeDestroyonBeforeUnmount实例销毁之前调用。这一步,实例仍然完全可用,「this仍能获取到实例」。destroyedonUnmounted实例销毁后调用,调用后,「Vue 实例指示的所有东西都会解绑定」,所有的事件监听器会被移除,所有的子实例也会被销毁。「该钩子在服务器端渲染期间不被调用」组件的双向数据绑定vue3.4 之前templateinput :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" //templatescriptsetupconstprops =defineProps(['modelValue'])constemit =defineEmits(['update:modelValue'])/script根据上面的基本写法,同理对于「自定义组件」而言,我们的写法如下:
templateobjRangev-model="range"//templatescriptsetupimport{ ref }from'vue'
constrange =ref([]) /script!-- objRange --template/templatescriptsetupimport{ defineEmits, defineProps }from'vue'constprops =defineProps({// v-model 默认绑定到 modelValue 属性modelValue: { type:Array, default:() =[] }})
// 定义事件抛出 update:xxx 中的 xxx 是对应绑定的属性constemits =defineEmits(['update:modelValue'])
// 改变值constchangeValue= () = {constnewValue = ['GRP-90843']// 将 update:xxx 事件抛出,实现数据双向绑定emits('update:modelValue', newValue)}/scriptstylelang="scss"scoped/stylev-model默认是绑定到modelvalue属性上,我们也可以绑定到其他属性上,由此衍生这里可以衍生出「多个属性的双向数据绑定」,具体写法如下:
templateobjRangev-model:range="range"v-model:area="area"//templatescriptsetupimport{ ref }from'vue'
constrange =ref([]) constarea =ref([])/script!-- objRange --template/templatescriptsetupimport{ defineEmits, defineProps }from'vue'constprops =defineProps({range: { type:Array, default:() =[] },area: { type:Array, default:() =[] }})// 将对应的 update:xxx 抛出即可constemits =defineEmits(['update:range','update:area'])/scriptComposition Api 与 Options Api 有什么不同1、代码组织
Options Api 代码按照「选项」(data、methods、computed、watch)进行分组Composition Api 代码按照「逻辑功能」进行分组2、逻辑复用
Options Api 逻辑复用通常通过mixins来实现,但容易导致命名冲突和代码可读性下降。Composition Api 逻辑复用通过自定义 Hook(类似于 React 的 Hooks)实现,可以将逻辑提取到独立的函数中,更灵活且易于维护。3、this 的使用
Options Api 通过this访问组件实例的属性和方法Composition API 在setup函数中没有this,所有数据和函数都需要通过return暴露给模板Vue 中的 $nextTick 有什么作用Vue 的响应式系统是异步的。
当数据发生变化时,Vue 并不会立即更新 DOM,而是将更新操作推入一个队列,并在下一个事件循环中批量处理。
意味着,如果在数据变化后立即访问 DOM,可能会获取到未更新的 DOM 状态。
$nextTick提供了一种机制,确保在 DOM 更新完成后再执行代码。
keep-alive 有什么作用1、keep-alive 是 vue 的内置组件,主要用来「缓存动态组件」和「路由组件」的,避免组件在切换时被销毁和重新创建。
2、使用场景
缓存路由组件templatekeep-aliverouter-view/router-view/keep-alive/template缓存动态组件templatekeep-alive component:is="currentComponent"/component/keep-alive/template3、keep-alive会触发两个额外的生命周期钩子
「activated」当缓存的组件被激活时调用(即组件再次显示时)「deactivated」当缓存的组件被停用时调用(即组件被隐藏时)4、keep-alive支持以下属性
include:只有名称匹配的组件会被缓存。可以是字符串、正则表达式或数组exclude:名称匹配的组件不会被缓存。可以是字符串、正则表达式或数组5、缓存组件实例会占用内存,如果缓存过多组件,可能会导致内存占用过高。
为什么 data 属性是一个函数而不是一个对象确保每个组件实例都有自己独立的数据副本,避免多个组件实例共享同一个数据对象,从而导致数据污染和状态混乱。
watch、computed 的区别computed 作用:是通过多个变量计算得出一个变量的值(多对一)。并且 computed 有缓存的功能。当多个变量值,没有发生改变时,直接在缓存中读取该值。不支持异步操作。watch 作用:侦听一个变量,从而影响其他变量(一对多)。支持异步操作。Vue 列表为什么要加 keyVue 使用虚拟 DOM 来优化渲染性能。当列表数据发生变化时,Vue 会通过对比新旧虚拟 DOM 来确定需要更新的部分。如果没有key,Vue 会默认使用 “就地复用” 策略,即尽可能复用相同类型的元素,而不是重新创建或移动它们。
MVVM 是什么?和 MVC 有何区别呢?Model(模型):负责从数据库中取数据View(视图):负责展示数据的地方Controller(控制器):用户交互的地方,例如点击事件等等VM: 视图模型在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也观察不到 View,这种低耦合模式提高代码的可重用性。VM 会自动将数据更新到页面中,而 MVC 需要手动操作 dom 将数据进行更新
ref、unref、isRef 、toRef、toRefs、toRaw 区别// 定义响应式变量constname1=ref('name1')// 普通变量constname2='name2'// reactive 定义响应式变量constobj=reactive({name:'name3'})
// isRef 是判断变量是否为 refconsole.log(isRef(name1),isRef(name2),isRef(obj))// true false false
// unref 如果是 ref 返回其内部的值,反之返回参数本身console.log(unref(name1),unref(name2),unref(obj))// name1 name2 { name: 'name3' }(参数本身)
// toRef 针对响应式数据的单一属性constname3=toref(obj,'name')// 此时修改 name3;会影响到 obj.name// 同理修改 obj.name;也会影响到 name3
// toRefs 针对响应式数据的所有属性// 若使用下述代码,解构出来的属性是没有响应式的const{ name4: name } = obj// 正确的解构应该是const{ name5: name } =toRefs(obj)// 此时修改 name5;会影响到 obj.name// 同理修改 obj.name;也会影响到 name5
// toRefs 也可以用于解构 prop,确保解构出来的属性有响应式const{} = prop
// toRaw 可以返回 reactive、readonly、shallowReactive 创建的代理所对应的原始对象constoriginal= { count:0}constreactiveData=reactive(original)
constrawData=toRaw(reactiveData)// 获取原始对象
rawData.count +=10// ? 修改原始对象,不会触发更新isProxy 、isReactive、isReadOnly 区别(很少用到)
isProxy:检查对象是否是由 reactive 或 readonly 创建的代理。isReactive:检查对象是否是 reactive 创建的,或者被包裹在一个 readonly 中的原始 reactive 代理。isReadonly:检查对象是否是 readonly 创建的代理。「方法」「作用」「典型返回值场景」isProxy检测对象是否是「任意代理对象」(由reactive或readonly创建)reactive(obj)→truereadonly(obj)→true普通对象→falseisReactive检测对象是否是「响应式代理」(由reactive创建或被readonly包裹的响应式对象)reactive(obj)→truereadonly(reactive(obj))→truereadonly(obj)→falseisReadonly检测对象是否是「只读代理」(由readonly创建)readonly(obj)→truereactive(obj)→false验证代码
templatediv p原始对象: {{ rawObject }}/p p响应式对象: {{ reactiveObj }}/p p只读对象: {{ readonlyObj }}/p p只读包裹响应式对象: {{ readonlyReactiveObj }}/p/div/template
scriptsetupimport{ reactive, readonly, isProxy, isReactive, isReadonly }from'vue'
// 原始对象constrawObject = {name:'Alice'}
// 响应式对象constreactiveObj =reactive(rawObject)
// 只读对象(直接包裹原始对象)constreadonlyObj =readonly(rawObject)
// 只读包裹响应式对象constreadonlyReactiveObj =readonly(reactive({age:25}))
// 检测函数constcheck= (obj, name) = {console.log(`-----${name}-----`)console.log('isProxy:',isProxy(obj))console.log('isReactive:',isReactive(obj))console.log('isReadonly:',isReadonly(obj))}
// 执行检测check(rawObject,'原始对象') // 全部返回 falsecheck(reactiveObj,'响应式对象') // isProxy: true, isReactive: true, isReadonly: falsecheck(readonlyObj,'只读对象') // isProxy: true, isReactive: false, isReadonly: truecheck(readonlyReactiveObj,'只读包裹响应式对象')// isProxy: true, isReactive: true, isReadonly: true/scriptref、 shallowRef、reactive、shallowReactive 区别refshallowRefrefValue.value.count++ // 触发更新shallowRefValue.value.count++ // 不触发更新
shallowRefValue.value = newObj // 触发更新内部值会被深度代理,修改嵌套属性会触发响应式更新仅监听.value的引用变化,不会深度代理内部属性reactiveshallowReactivereactiveObj.nested.count++ // 触发更新shallowReactiveObj.nested.count++ // 不触发更新
shallowReactiveObj.nested = {count: 100}递归代理所有层级的属性,嵌套对象也会响应式只代理对象的第一层属性,嵌套对象保持原始状态「验证代码」
templatediv h3ref vs shallowRef/h3 pref: {{ refValue.count }}/p pshallowRef: {{ shallowRefValue.count }}/p button@click="changeRefInner"修改 ref 内部属性/button button@click="changeShallowRefInner"修改 shallowRef 内部属性/button button@click="changeShallowRefValue"替换 shallowRef 整个值/button
h3reactive vs shallowReactive/h3 preactive.nested: {{ reactiveObj.nested.count }}/p pshallowReactive.nested: {{ shallowReactiveObj.nested.count }}/p button@click="changeReactiveNested"修改 reactive 嵌套属性/button button@click="changeShallowReactiveNested"修改 shallowReactive 嵌套属性/button button@click="changeShallowReactiveValue"替换 shallowReactive 整个值/button
/div/template
scriptsetupimport{ ref, shallowRef, reactive, shallowReactive }from'vue'
// ----------------------// 1. ref vs shallowRef// ----------------------constrefValue =ref({count:0})// 深层响应式constshallowRefValue =shallowRef({count:0})// 仅监听 .value 变化
constchangeRefInner= () = { refValue.value.count++// 触发更新}
constchangeShallowRefInner= () = { shallowRefValue.value.count++// ? 不会触发更新}
constchangeShallowRefValue= () = { shallowRefValue.value= {count:100}// ? 触发更新}
// ----------------------// 2. reactive vs shallowReactive// ----------------------constreactiveObj =reactive({nested: {count:0}// 深层响应式})
constshallowReactiveObj =shallowReactive({nested: {count:0}// 仅顶层响应式})
constchangeReactiveNested= () = { reactiveObj.nested.count++// ? 触发更新}
constchangeShallowReactiveNested= () = { shallowReactiveObj.nested.count++// ? 不会触发更新}constchangeShallowReactiveValue= () = { shallowReactiveObj.nested= {count:1}// ? 触发更新}/scriptdefineProps 参数有哪些template/templatescriptsetupdefineProps({ theme: { type:String, default:'dark', required:true, validator:(value) ={ return['dark','light'].includes(value) } }})/scriptSuspense 是如何使用的
templateSuspense !-- 默认插槽:显示异步组件 -- template#default AsyncComponent/ /template
!-- fallback 插槽:加载中显示的内容 -- template#fallback div加载中.../div /template/Suspense/template
scriptsetupimport{ defineAsyncComponent }from'vue'
// 定义一个异步组件constAsyncComponent=defineAsyncComponent(() =import('./AsyncComponent.vue'))/scriptv-slotted 选择器如何使用templatedivclass="child-component" !-- 定义插槽 -- slot/slot/div/template
stylescoped.child-component{border:1pxsolid#ccc;padding:10px;}
/* 选择插槽内带有.container 类的元素 */::v-slotted(.container) {background-color: lightyellow;border:1pxsolid#ffcc00;padding:15px;}/styletemplatediv !-- 使用子组件并向插槽传递内容 -- ChildComponent divclass="container" p这是插槽内.container 里的内容/p /div p这是插槽内普通的内容/p /ChildComponent/div/template
scriptimportChildComponentfrom'./ChildComponent.vue'
exportdefault{components: { ChildComponent }}/scriptpina 和 vuex 在使用上有什么区别pina 使用上更为简洁,基于 composition API;而 vuex 是基于 options API;pina 天然模块化,每一个 store 都是独立的;而 vuex 需要手动划分;pina 对 TS 的支持更为友好;vuex 需要额外配置pina 体积更小;vuex 体积稍大pina 允许直接修改状态,更为灵活;vue 需要通过mutations修改状态,更为严格localStorage 、cookie、sessionStorage 三者的区别存储大小:Cookie 4k;Storage 5M;有效期:Cookie 拥有有效期;localStorage 永久存储;sessionStorage 会话存储Cookie 会发送到服务器端,存储在内存中;Storage 只会存储在浏览器端路径:Cookie 有路径限制,Storage 只存储在域名下API:Cookie 没有特定的 API;Storage 有对应的 API;数组去重方法// 方法一constarr1 = [...newSet(originalArr)]
// 方法二(缺点 无法过滤 NaN) [NaN].indexOf(NaN) = -1constarr2 = originalArr.fillter((item, index) =originalArr.indexof(item) === index)
// 方法三constarr3 = originalArr.reduce((acc, cur) =acc.includes(cur) ? acc : [...acc, cur], [])对象拷贝方法// 浅拷贝
// 方法一 扩展运算符constobj = { ... originalObj }
// 方法二 Object.assignconstobj =Object.assign({}, originalObj)
// 方法三 for infor(letkeyinoriginalObj) { if(originalObj.hasOwnProperty(key)) { obj[key] = originalObj[key] }}
// 深拷贝
// 方法一:缺点 无法拷贝函数constobj =JSON.parse(JSON.stringify(originalObj))
// 方法二 递归functiondeepClone(originalObj) { if(obj ===null||typeoforiginalObj !='object')returnoriginalObj
constclone =Array.isArray(originalObj) ? [] : {}
for(letkeyinoriginalObj) { if(originalObj.hasOwnProperty(key)) { clone[key] =deepClone(originalObj[key]) } }
returnclone}数组交集、并集、差集letarr2 = [1,2,3,4,5]letarr3 = [3,4,1,2]
// 交集console.log(arr2.filter(item=arr3.includes(item)))
// 并集console.log(Array.from(newSet([...arr2, ...arr3])))
// arr2 差集console.log(arr3.filter(item=!arr2.includes(item)))
// arr3 差集console.log(arr2.filter((item) =!arr3.includes(item)))数组扁平functionflatter(arr) {if(!arr.length)return;
returnarr.reduce((pre, cur) ={ returnArray.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur] }, []);}
// 测试letarr = [1,2, [1, [2,3, [4,5, [6]]]]]console.log(flatter(arr));CSS 如何实现水平垂直方向居中/* 方法一 flex 布局 */.container{ display: flex; justify-content: center; align-items: center;}
/* 方法二 绝对定位 + transform */.container{ position: relative;}
.child{ position: absolute; top:50%; left:50%; transform:translate(-50%, -50%)}
/* 方法三 绝对定位 + margin */.container{ position: relative;}
.child{ position: absolute; left:0; right:0; top:0; bottom:0; margin: auto;}
/* 方法四 表格布局 */.container{ disaply: table-cell; vertical-align: middle; text-align: center;}
.child{ display: inline-block;}讲一下 let 和 const提出了「块级作用域」概念
1、什么是块级作用域:
在该作用域外无法访问该变量2、块级作用域存在于:
函数内部块中 (字符 { 和} 之间的区域)3、let 和 const 特性
变量不会被提升if(false) { letvalue =1}console.log(value);// Uncaught ReferenceError: value is not defined重复声明该变量会报错
不会绑定到全局作用域上
4、临时性死区(TDZ)
let 和 const 声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,会导致报错
console.log(typeofvalue);// Uncaught ReferenceError: value is not definedletvalue =1;介绍一下箭头函数箭头函数没有 this 指向,需要通过作用域来确定 this 的值
?this绑定的就是最近一层非箭头函数的this
由于没有 this,因此 call,apply,bind 不能被使用
三者的区别:
?三者都可以绑定函数的 this 指向三者第一个参数都是 this 要指向的对象,若该参数为 undefined 或 null,this 则默认指向全局传参不同:apply 是数组;call 是参数列表,而 bind 可以分多次传入,实现参数合并call apply 是立即执行,bind 是返回绑定 this 之后的函数, 如果这个新的函数作为构造函数被调用,那么 this 不再指向传入给 bind 的第一个参数,而是指向新生成的对象箭头函数没有 arguments 对象
不能通过 new 关键字进行调用
没有原型
varFoo= () =console.log(Foo.prototype);// undefined 如何遍历对象可以查看另外一篇文章:# 细究 ES6 中多种遍历对象键名方式的区别[1]
for…of和for…in的区别如下
for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;对于数组的遍历,for…in 会返回数组中所有可枚举的属性 (包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;总结for...in 循环主要是为了遍历对象而生,不适用于遍历数组;
for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
点击关注公众号,“技术干货” 及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线