前端全部面试题
参考:20+个Vue经典面试题(附源码级详解)-头条-PHP中文网
源码:初始化阶段(new Vue) | Vue源码系列-Vue中文社区
vue面试题
为什么学习Vuejs
Vuejs是目前最火的一个前端框架,是前端开发必备的一个技能
国内前端招聘需求中基本都要求会用
1.渐进式框架的理解(不用背)
vue 被设计为可以自底向上逐层应用
最基础的项目也可以script引入
还可以通过创建的方式
为什么说Vue是一个“渐进式”框架 - 掘金 (juejin.cn)
2.对MVVM的理解
- M:model层,在model层对数据进行操作和修改数据
- V:View(视图层)
- VM:ViewModel监听模型数据的改变和控制视图行为。相当于模型层和视图层的一个桥梁,起连接作用。
在MVVM模式下,View和Modal没有直接的联系,而是通过ViewModal进行交互,Modal和ViewModal之间交互是双向的,因此View数据的变化会同步到Modal上,Modal数据的变化也会立即反应到View上。(简化:v-model,数据发生变化,同步视图,视图发生变化,同步数据)
优势:
- 双向绑定技术,当Model变化时,View也会自动变化,view发生更新,model也跟着同步
- 我们减少了dom的操作,因为我们只需要关注数据就可以
- mvvm的设计思想大大提高了代码的耦合性
MVC:设计典范
MVC是模型(Model),视图(View),控制器(Controller)的简写,是一种软件设计规范,业务逻辑,数据,视图分离的方法来写代码
主要作用:降低了视图与业务逻辑间的双向耦合
MVC的思想:一句话描述就是Controller负责将Model的数据用View显示出来
MVC不是一种设计模式,是一种架构模式
- Model(模型):模型对象拥有很多的处理任务,它负责数据逻辑(业务规则)的处理和实现数据操作
- View(视图):View视图,Web程序中指用户可以看到的并可以与之进行数据交互的界面
- Controller(控制器):负责接收并转发请求,对请求进行处理后,指定视图并将响应结果发送给客户端。
12.VUE双向绑定原理?(重背)
相关问题:
- VUE的数据绑定机制
- Vue 是如何实现数据劫持的
- MVVM 是什么
采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter/getter,在数据发生变化时发布消息给订阅者,触发相应的监听回调。
具体步骤:(背这个)
- 创建Vue实例时,会遍历所有的 data 属性,并使用 Object.defineProperty 将它们转为 getter/setter。 getter/setter 对象实际上是实现了发布-订阅模式中的发布者。
- 在编译 template 时,会将模板字符串中使用到的 data 属性,添加到响应式系统的订阅者列表中。当 data 属性的 setter 被调用时,会通知所有订阅者进行更新 View 操作。
- 监听 DOM 事件,对于绑定了 v-model 的元素,会自动生成与 data 属性同名的属性和 value 属性的 setter。这两个 setter 会通知所有订阅者进行更新 View 操作。
总之,Vue的双向绑定是通过数据劫持和发布-订阅模式实现的,Vue内部利用Observer模式实现了一个发布者,通过监听数据的变化,然后通知所有订阅者进行视图更新,实现了数据与视图的双向绑定。
3.生命周期:Vue实例从创建到销毁的过程。从开始创建,初始化数据,编译模板,挂载DOM->渲染,更新->渲染,销毁等一系列过程。
- beforeCreate(创建前):只是单纯的创建$el和data,数据观测和初始化时间还未开始,因此无法访问methods,data,computed--data:undefiend;$el:undefiend
- created(创建后):data初始化完成,数据观测完成,属性和方法的运算,初始化事件(data),watch/event事件回调,但是$el属性仍在创建中。data可获取,$el:undefiend.
- beforeMounte(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。(自己理解:vue实例的$el和data都初始化完成,但data还是挂载之前虚拟的DOM节点,实例已完成一下配置,编译模板,把data里面的数据和模板生成html,完成了el和data。初始化,data尚未替换,此时还没有挂载html在页面上--data:xxx;$el:xxx('虚拟‘))
- mounted(载入后):在el被创建的vm.$el替换,并挂载到实例上去之后调用。实例已完成以下的配置。用上面编译好的html内容替换el属性指向DOM对象。完成模板的html渲染到html页面上。此过程是ajax交互过程(自己理解:挂在完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作,mounted只会执行一次。用真实数据替换虚拟DOM--data:xxx;$el:真实)
- beforeUpdate(更新前): 重新渲染之前触发(自己理解:data数据已经发生改变,但是未渲染)
- updated(更新后):数据已经更改完成,dom也重新render完成,更改数据会陷入死循环(自己理解:data数据已经发生改变,并且旧数据代替新数据)
- beforeDestory(销毁前):在实例销毁之前调用。实例仍然完全可用
- destoryed(销毁后):在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
4.生命周期总共有几个阶段?
8个,创建前/后;载入前/后;更新前/后;销毁前/后
合并后大致分为4个阶段
- 初始化阶段:为
Vue
实例上初始化一些属性,事件以及响应式数据;
初始化第一件事就是创建new Vue()实例
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染
callHook(vm, 'beforeCreate') // 调用生命周期钩子函数
initInjections(vm) //初始化injections
initState(vm) // 初始化props,methods,data,computed,watch
initProvide(vm) // 初始化 provide
callHook(vm, 'created') // 调用生命周期钩子函数
- 模板编译阶段:将模板编译成渲染函数;
该阶段主要工作是获取到用户传入的模板内容并将其编译成渲染函数。一个完整的Vue
版本是包含编译器的,我们可以使用template
选项进行模板编写。编译器会自动将template
选项中的模板字符串编译成渲染函数的代码,源码中就是render
函数。如果你需要在客户端编译模板 (比如传入一个字符串给 template
选项,或挂载到一个元素上并以其 DOM
内部的 HTML 作为模板),就需要一个包含编译器的版本。 如下:
// 需要编译器的版本
new Vue({
template: '<div>{{ hi }}</div>'
})
- 挂载阶段:将实例挂载到指定的
DOM
上,即将模板渲染到真实DOM
中;
挂载阶段所做的主要工作是创建Vue
实例并用其替换el
选项对应的DOM
元素,同时还要开启对模板中数据(状态)的监控,当数据(状态)发生变化时通知其依赖进行视图更新。
- 销毁阶段:将实例自身从父组件中删除,并取消依赖追踪及事件监听器;
主要工作是将当前的Vue
实例从其父级实例中删除,取消当前实例上的所有依赖追踪并且移除实例上的所有事件监听器。也就是说,当这个阶段完成之后,当前的Vue
实例的整个生命流程就全部走完了
5.第一次页面加载会出发那几个钩子?
beforeCreate, created, beforeMount, mounted
6.DOM渲染在哪个周期已经完成?
mounted
Vue的实例加载完成在哪个生命周期呢?
beforeCreated
7.vue父子组件生命周期执行顺序
加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程:父beforeUpdate->父updated
销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue父子组件生命周期执行顺序 - 朝思暮想的虫 - 博客园
8.简单描述每个周期具体适合那些场景?(重背)
- created:初始化完成时的事件,加loading事件,改变data数据,异步请求调用
- mounted:挂载元素,获取DOM节点
- beforeDestory:清除组件中定时器;停止确认框事件
- destroyed:所有事件都会被移除
- nextTick改变dom元素后,用这个方法获取更新后的DOM,nexTick延时调用函数事件来渲染视图
// element ui回到顶部中用到beforeDestroy,滚动事件销毁
// 离开页面销毁滚动事件,不销毁会造成其他页面页生效
beforeDestroy() {
this.container.removeEventListener('scroll', this.throttledScrollHandler);
}
// 销毁监听事件
destroyed() {
window.removeEventListener('resize', this.resizeWin)
}
// nextTick
changeTxt:function(){
let that=this;
that.testMsg="修改后的文本值"; //修改dom结构
that.$nextTick(function(){ //使用vue.$nextTick()方法可以dom数据更新后延迟执行
let domTxt=document.getElementById('h').innerText;
console.log(domTxt); //输出可以看到vue数据修改后并没有DOM没有立即更新,
if(domTxt==="原始值"){
console.log("文本data被修改后dom内容没立即更新");
}else {
console.log("文本data被修改后dom内容被马上更新了");
}
});
},
9.created和mounted的区别
- created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
- mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
- 深入响应式原理 — Vue.js
10.生命周期钩子是如何实现的
利用发布订阅模式把用户传入的生命周期钩子订阅好(内部采用数组的方法存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
11.vue.js的两个核心是什么
- 数据驱动:ViewModel,保证数据和视图的一致性
- 组件系统:可以看作全部是由组件树构成的
13.v-model和sync的区别
相同点:都是语法糖,都可以实现父子组件中的数据的双向通信
区别点:
- 格式不同。 v-model=“num”, :num.sync=“num”
- v-model: @input + value
- :num.sync: @update:num
- v-model只能用一次;.sync可以有多个。
// v-model的原理
<com1 v-model="val"></com1>
等价于
<com1 :value="val" @input="val=$event.target.value"></com1>
// .sync修饰符的原理
// 正常父传子:
<com1 :a="num" :b="num2"></com1>
// 加上sync之后父传子:
<com1 :a.sync="num" .b.sync="num2"></com1>
// 它等价于
<com1
:a="num" @update:a="val=>num=val"
:b="num2" @update:b="val=>num2=val"></com1>
// 相当于多了一个事件监听,事件名是update:a,回调函数中,会把接收到的值赋值给属性绑定的数据项中。
14.如何让css在当前组件中起作用
就在当前组件内使用,当前组件<style>写成<style scoped>
当一个style标签添加scoped属性时,它的CSS样式就只能作用于当前的组件。通过该属性,可以使组件之间的样式不互相污染。如果项目中所有style标签全部加上了scoped,相当于实现了样式的模块化。
15.scoped原理
原理:scoped后选择器最后默认会加上当前组件的一个标识,比如[data-v-49729759]
用了样式穿透后,在deep之后的选择器最后就不会加上标识。
转译前的vue代码
<template>
<div class="example">hello world</div>
</template>
<style scoped>
.example {
color: red;
}
</style>
转译后:
<template>
<div class="example" data-v-49729759>hello world</div>
</template>
<style scoped>
.example[data-v-49729759] {
color: red;
}
</style>
Vue中的scoped实现原理 - samirah - 博客园
16.说出至少4种vue当中的指令和他的用法?
- v-if
- v-on:事件绑定
- v-bind:class:绑定属性样式、
- v-modal:绑定数据
- v-for:循环
- v-bind(冒号就是v-bind的缩写,是为了动态绑定数据)
注意:
- 加上了冒号是为了动态绑定数据,等号后面可以写变量。
- 如果不使用冒号,等号后面就可以写字符串等原始类型数据。这时就无法进行动态绑定数据了。
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
isActive: true,
hasError: false
}
<template>
<div>
<div class="message">{{ info.message }}</div>
<div><input v-model="info.message" type="text"></div>
<button @click="change">click</button>
</div>
</template>
<script>
export default {
data () {
return {
info: {}
}
},
methods: {
change () {
this.info.message = 'hello world'
}
}
}
</script>
17.v-if与v-show的区别?
- v-show是css切换,为false,display:none在源码中也不显示
- v-if条件渲染,是完整的销毁和创建,当为false时不会渲染页面
18.为什么v-if比v-show级别高(重背)
v-if控制渲染,v-show控制样式
v-if是真正的条件渲染,它会在切换过程中条件块内的事件监听器和子组件适当的被销毁和重建(beforeCreate - created - beforeMount - mounted)
v-if是惰性的,初始化渲染时条件为假,v-if什么也不做,一直到条件第一次变为真,才开始渲染条件块(整个dom元素添加或者删除)
v-show不管初始条件是什么,元素总会被渲染, 只有第一次加载的时候才会执行完整的生命周期,之后每次切换状态都不会执行生命周期的(通过css控制的)
v-if有更高的切换开销,v-show有更高的初始化渲染开销。频繁的切换,用v-show比较好;在运行时改变少,用v-if.
19.为什么不建议v-for和v-if同时存在
<div v-for="item in [1,2,3,4,5,6,7]" v-if="item !== 3">
{{item}}
</div>
上面写法是v-for和v-if同时存在,会先把7个元素遍历出来,然后在一个个判断是否为3,并把3隐藏掉,这样的坏处渲染了无用的3节点,增加无用的dom操作,建议用computed来解决这个问题
<div v-for="item in list">
{{item}}
<div>
computed(){
list(){
return [1,2,3,4,5,6,7].filter(item => item !== 3)
}
}
20.v-for与v-if的优先级
v-for优先级大于v-if,如果在同一个节点,就是先循环在判断
21.vue-loader是什么?用途是什么?
webpack的一个loader,用来处理vue文件
vue文件的一个加载器。因为webpack打包后生成是js文件,需要vue-loader把解析和转换.vue文件转换成js/css/html文件
用途:
css-loader:加载由vue-loader提取出的CSS代码
vue-template-compiler:把vue-loader提取出的HTML模板编译成可执行的js代码
9. vue-loader是什么?使用它的用途有哪些 - 大牛半路出家 - 博客园
22.为什么使用key?(重背)
回答范例:
-
key的作用作为唯一标识,为了更高效的更新虚拟DOM。
-
源码:vue判断两个节点是否相同时主要判断两者的key和元素类型等,如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,导致频繁更新元素,只能去做更新操作,这造成了大量的dom更新操作。(vue在patch过程中判断两个节点是否是相同节点是key是一个必要条件,使整个patch过程比较低效,影响性能。)
思路分析:
-
给出结论,key的作用是用于优化性能
-
key的必要性
-
实际使用方式
-
总结:可从源码层面描述一下vue如何判断两个节点是否相同
23.key对比规则
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:若虚拟DOM中内容没变,直接用之前的真实DOM;若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到页面
24.vue diff算法
- 比较只会在同层级进行, 不会跨层级比较:节点类型不同,直接用新的替换旧的;相同,就比较子节点
- 在 diff 比较的过程中,循环从两边向中间比较
26.说说你对虚拟DOM的理解和原理
回答范例:
-
虚拟dom:模拟DOM树的js对象
好处:(为什么使用虚拟DOM、虚拟DOM与真实DOM的区别)
- 改了很多东西,但是一次性挂载的,不会频繁挂载,减少页面重绘和回流,提高性
缺点:
- 首次渲染大量DOM时,多了一层DOM计算,会比innerHTML插入慢。
思路:
-
vdom是什么
-
引入vdom的好处
25.vue 虚拟 DOM 的作用(重背)
- 不会进行回流和重绘;
- 频繁操作,只进行一次对比差异并修改真实 DOM,减少了真实 DOM 中多次回流重绘引起的性能损耗;
- 有效降低大面积的重绘与排版,只更新差异部分,进行局部渲染
27.vdom如何生成?(不用看)
-
在vue中我们会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们不是真正的dom,所以会在后续的patch过程中进一步转化为dom。
-
挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。
28.说说从template到render处理过程 /vue的compile过程/聊聊对vue.js的template编译的理解
- 将模板字符串转换成 element ASTs(解析器)
- 对 AST 进行静态节点标记,主要用来做虚拟 DOM 的渲染优化(优化器)
- 用element ASTs 生成 render 函数代码字符串(代码生成器)
先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)
22. Vue template 到 render 的过程 · 超级学习资料 · 看云
29.template编译过程?(暂时不用考)
- vue template模板编译过程经过parse()生成ast(抽象语法树),optimize对静态节点优化,generate()生成render字符串
- 之后调用new Watch()函数,用来监听数据的变化,render函数是数据监听的回调所调用的,结果便是重新生成vnode
- 当render函数调用字符串在第一次mount,或者绑定的数据更新的时候,都会被调用生成vnode
- 如果是数据的更新,vnode会与数据改变之前的vnode做diff,对内容做改动之后,更新到真正的DOM
31.AST (抽象语法树)
用来表示代码的数据结构。在vue中我把他理解为嵌套的,携带标签名,属性和父子关系的JS对象,以树来表现DOM结构
vue中的ast类型有以下3种
ASTElement = { // AST标签元素
type: 1;
tag: string;
attrsList: Array<{ name: string; value: any }>;
attrsMap: { [key: string]: any };
parent: ASTElement | void;
children: Array<ASTNode>
...
}
ASTExpression = { // AST表达式 {{ }}
type: 2;
expression: string;
text: string;
tokens: Array<string | Object>;
static?: boolean;
};
ASTText = { // AST文本
type: 3;
text: string;
static?: boolean;
isComment?: boolean;
};
32.vue页面之间传值:
- 使用vue-router通过跳转链接带参数传参。
- 使用本地缓存localStorge。
- 使用vuex数据管理传值。
33.Vue的路由实现:hash模式和history模式
hash模式:在浏览器中符号“#”以及#后面的字符称之为hash,用window.location.hash读取
http://www.abc.com/#/hello hash 的值为 #/hello
特点:hash虽然在URL中,但不被包括在HTTP请求中;hash发生变化的url都会被浏览器记录下来,你会发现浏览器的前进后退都可以用了,用来指导浏览器动作,对服务器安全无用,hash不会重加载页面
hash模式原理:
onhashchange通过监听这个事件拿到哈希值来判断路由,进行页面上组件渲染。
hash模式下,hash符号之前的内容会被包含在请求中。如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
history模式
h5新出的,允许你修改路由而不向服务器发起请求,通过pushState ,replaceState进行监听,这个模式有个问题是,你刷新时候是真正向后台请求页面了,所以要服务器配置就是那个404问题
history api可以分为两大部分:切换和修改
切换历史状态:back、forward
、go
三个方法,对应浏览器的前进,后退,跳转操作
history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进
修改历史状态:pushState、replaceState
两个方法,这两个方法接收三个参数stateObj,title,url
history.pushState({color:'red'}, 'red', 'red')
window.onpopstate = function(event){
console.log(event.state)
if(event.state && event.state.color === 'red'){
document.body.style.color = 'red';
}
}
history.back();
history.forward();
history模式:它采用的是HTML5的新特性;且提供了2个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听状态变更。
区别:hash模式通过onhashchange监听方法去监听hash值;history用pushState,replaceState方法去改变路由
history模式下,前端URL必须和实际向后端发起请求的URL一致,如http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。
35.路由之间跳转
- 声明式(标签跳转<router-link to='home'>)
- 编程式(js跳转router.push(‘/home’))
前提:$router:路由器对象;$route:当前路由信息
// 获取当前的路由信息
this.name = this.$route.name
// 路由器对象
this.$router.push({ path: '/' })
36.声明式路由导航
<router-link>,<router-view>
参数如下:
to:<router-link>会默认解析成a标签,可以通过tag属性生成其他的标签(<router-link to='xxx' tag='li'> To PageB </router-link>;)
replace:点击会调用 router.replace
而不是 router.push
,导航后不会留下 history 记录。
append:在当前 (相对) 路径前添加基路径。例如,从 /a
导航到一个相对路径 b,如果没有配置 append
,则路径为 /b
,如果配了,则为 /a/b
<!--字符串-->
<router-link to="home">Home</router-link>
<!--渲染结果-->
<a href="home">Home</a>
<!--使用v-bind绑定-->
<router-link v-bind:to="home">Home</router-link>
<!--绑定对象的形式-->
<router-link v-bind:to="{path: 'home'}">Home</router-link>
<!--命名路由 下面的结果是:/user/123-->
<router-link :to="{name: 'user',params: {userId: 123}}"
<!--带查询参数,下面结果是:/user?plan=123-->
<router-link :to="{path: 'user', query: {plan: '123'}}">demo</router-link>
<router-link :to="{ path: '/abc'}" replace></router-link>
<router-link :to="{ path: 'relative/path'}" append></router-link>
<router-view>
组件是一个 function 组件,渲染路径匹配到的视图组件。<router-view>
渲染的组件还可以内嵌自己的 <router-view>
,根据嵌套路径,渲染嵌套组件。
name:<router-view>
设置了名称,会渲染对应的路由配置中的components
下的相应组件
<transition>
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
// 在页面中使用
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
<router-view name="A"></router-view>
<router-view name="B"></router-view>
</div>
</template>
//vue2
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
components: {
default: () => import('../views/Home.vue'),
compA: () => import('../components/ComponentOne.vue'),
compB: () => import('../components/ComponentTwo.vue')
}
},
{
path: '/about',
name: 'About',
components: {
A: () => import('../components/ComponentThree.vue'),
B: () => import('../components/ComponentTwo.vue')
}
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
//vue3
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
const router = createRouter({
routes: [
{
path: '/',
name: 'Home',
components: {
default: () => import('../views/Home.vue'),
compA: () => import('../components/ComponentOne.vue'),
compB: () => import('../components/ComponentTwo.vue')
}
},
{
path: '/about',
name: 'About',
components: {
A: () => import('../components/ComponentThree.vue'),
B: () => import('../components/ComponentTwo.vue')
}
}
],
//history:createWebHistory("/xxx地址") // 注意base只有在history模式下才有校
// history:createWebHashHistory() // 定义hash模式路由
history:createWebHistory() // 定义history模式路由
})
export default router
37.编式路由导航
参数两种类型:字符串,对象
this.$router.push("home") // 字符串:直接将路由地址以字符串的方式来跳转,这种方式很简单但是不能传递参数
this.$router.push({path: '/home'}) // 对象:命名路由,查询参数,下面分别说明两种方式的用法和注意事项
38.vue-router是什么?有哪些组件?
Vue Router是Vue.js官方的路由管理器。
<router-link>和<router-view>和<keep-alive>
39.相关API:router:路由对象属性
- this.$router.push(path):相当于点击路由链接(可以返回当前路由页面)===>>>队列方式(先进先出)
- this.$router.replace(path):用新路由替换当前路由(不可返回当前路由页面)===>>>栈的方式(先进后出)
- this.$router.back():请求(返回)上一个记录路由
- this.$router.go(-1):请求(返回)上一个记录路由
<div>
<ul>
<li v-for="(message,index) in messages" :key="message.id">
<router-link :to="`/home/message/detail/${message.id}`">{{message.title}}</router-link>
<button @click="pushShow(message.id)">push查看</button>
<button @click="replaceShow(message.id)">replace查看</button>
</li>
</ul>
<button @click="$router.back()">回退</button>
<hr />
<router-view></router-view>
</div>
methods:{
pushShow(id){
this.$router.push(`/home/message/detail/${id}`)
}
replaceShow(id){
this.$router.replace(`/home/message/detail/${id}`)
}
}
40.说说vue的动态组件
动态组件指的是动态切换组件的显示与隐藏。component
的is属性通过组件名
就可以调用该组件
// 当前要渲染的组件名称
data () {
return{
name:'Left'
}
// 通过is属性,动态指定要渲染的组件
<component :is="name"></component>
// 点击按钮,动态切换组件
<button @click="name='Left'">left组件</button>
<button @click="name='Right'">right组件</button>
41.active-class是哪个组件的属性?(重背)
vue-router模块的route-link组件,用来做选中样式的切换
<div class="menu-btn">
<router-link to="/" class="menu-home" active-class="active">
首页
</router-link>
</div>
<div class="menu-btn">
<router-link to="/my" class="menu-my" active-class="active">
我的
</router-link>
</div>
// 可能是因为 to="/" 引起的,active-class选择样式时根据路由中的路径去匹配,然后显示,例如在my页面中,路由为localhost:8080/#/my,那么to="/”和to="/my"都可以匹配到,所有都会激活选中样式
// 要解决问题也有两种方式,都是通过加入一个exact属性
a.<router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>
b.<router-link to="/home" class="menu-home" active-class="active" exact>首页</router-link>
路由中加入重定向
{
path: '/',
redirect: '/home'
}
42.keep-alive的作用(重背)
作用:
Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。当该组件被切换回来时,会保留当时的状态,可以有效减少组件重渲染的次数,提高性能.
场景:
用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,我们希望:列表页面可以保留用户的筛选(或选中)状态。
原理:
在 created钩子函数调用时将需要缓存的 VNode 节点保存在 this.cache 中,在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从this.cache 中取出之前缓存的 VNode实例进行渲染。
参数(props):
- include:只有名称匹配的组件会被缓存(-字符串或正则表达式)
- exclude:任何名称匹配的组件都不会被缓存(-字符串或正则表达式)
- max:最多可以缓存多少组件实例
43.keep-alive在它生命周期内定义了三个钩子函数:
- created:初始化两个对象分别缓存VNode和VNode对应的键集合(初始化一个
cache、keys
,前者用来存缓存组件的虚拟dom集合,后者用来存缓存组件的key集合) - destroyed:删除this.cache中缓存的VNode实例。
-
mounted:对include和exclude参数进行监听,然后实时地更新(删除)this.cache对象数据
1.在动态组件中的应欧勇
<keep-active :include="whiteList" :exclude="blackList" :max="amount">
<component :is="currentComponents"></component>
</keep-active>
2.在vue-router中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<router-view></router-view>
</keep-alive>
include定义缓存白民丹,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件
将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据
注意:内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
源码:
进阶:
44.keep-Alive的生命周期函数?
activated和deactivated是针对keep-alive的生命周期函数,在组件显示和隐藏是触发.
当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated
<keep-alive>
<router-view v-if="$route.meta.keepAlive" style="min-height:100%"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" style="min-height:100%"></router-view>
//不需要刷新的路由配置里面配置 meta: {keepAlive: true}, 这个路由则显示在上面标签;
//需要刷新的路由配置里面配置 meta: {keepAlive: false}, 这个路由则显示在下面标签;
45.组件中name的作用?
- 配合keep-Alive的include和exclude使用,表明可以被缓存的组件和不可以被缓存的组件.(keep-alive套router-view时).
- 可用于在自身组件中使用name的属性值作为标签名,递归的调()用自身组件.
46.怎么定义vue-router的动态路由以及如何获取传过来的动态参数?(不用看)
回答规范:
-
将给定匹配模式的路由映射到同一个组件,这种情况就需要定义动态路由。
-
可以通过query,param两种方式
-
一个User组件,它对所有用户进行渲染,用户ID不同。可以在路径中使用一个动态字段来实现,例如:{path: '/users/:id',component:User},其中:id就是路径参数
-
路径参数 用冒号:表示
-
参数还可以有多个,例如/users/:username/posts/:postId;
// 动态路由params
在App.vue中
<router-link :to="'/user/'+userId" replace>用户</router-link>
在index.js
{
path:"/user/:userid",
component:User,
}
// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link>
// 方法2:
this.$router.push({name:'users',params:{uname:wade}})
// 方法3:
this.$router.push('/user/' + wade)
<!--动态路由-query -->
//01-直接在router-link 标签上以对象的形式
<router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}">档案</router-link>
/*
02-或者写成按钮以点击事件形式
<button @click='profileClick'>我的</button>
*/
//点击事件
profileClick(){
this.$router.push({
path: "/profile",
query: {
name: "kobi",
age: "28",
height: 198
}
});
}
思路:
-
什么是动态路由
-
什么时候使用动态路由,怎么定义动态路由
-
参数如何获取
-
细节、注意事项
怎么定义vue-router的动态路由?怎么获取传过来的动态参数?_Ben Meng的博客-CSDN博客_怎么定义动态路由
47.vue怎么动态添加路由,route和router的区别:
动态添加路由用addRoutes方法
- route是“路由信息对象”
参数:path,params,hash,query,fullPath,matched,name等信息参数。route相当于当前正在跳转的路由对象。可以从里面获取name,path,params,query等
- 而router是“路由实例“对象包括路由的跳转方法,钩子函数等。相当于一个全局的路由器对象,里面有很多属性和子对象,例如history对象,经常用的跳转链接可以用this.$router.push,和router-link跳转一样。
Vue路由(vue-router)详细讲解指南 - 有梦想的咸鱼前端 - 博客园
48.this.$route.params与this.$route.query的区别。(不用看)
// this.$route.query的使用
A:传参数
this.$router.push({
path:'/custom',
query:{
id:id
}
})
B:接收参数
this.$route.query.id
C.在url中的形式(url带参数)
http://172.19.186.224:8080/#/monitor?id=1
D.页面之间用路由跳转传参时,刷新跳转后传参页面,数据还会显示存在。
// this.$route.params的使用
A.传参
this.$router.push({
path:'/custom',
params:{
id:id
}
})
B:接收参数
this.$route.params.id
C.在url中的形式(url不带参数)
http://172.19.186.224:8080/#/monitor
D.页面之间用路由跳转传参时,刷新页面跳转传参的页面,数据不存在
49.vue-router有哪几种导航钩子?
“导航”:表示路由正在发生改变。
- 全局导航钩子:router.beforeEach(to,from,next);router.afterEach,router.beforeResolve,
- 组件内的钩子(组件声明周期):beforeRouteEnter, beforeRouteUpdate, beforeRouteleave
- 单独路由独享组件:beforeEnter
作用:
- 跳转前进行判断拦截
- to:route即将进入的目标路由对象
- from:route当前导航正要离开的路由
next:是将要跳转到下一个钩子过程中所做的事情(可以继续向下执行,也可以中断执行)
全局解析守卫:router.beforeResolve .这和router.beforeEach类似,区别在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(i18n.t(to.meta.title)))
if (Vue.ls.get(ACCESS_TOKEN)) {
if (to.query.token) {
checkToken(urlQuery.token).then(res => {
if (res.code === 200) {
Vue.ls.set(ACCESS_TOKEN, urlQuery.token)
store.state.token = urlQuery.token
Vue.ls.set(USER_INFO, res.result)
Vue.ls.set(USER_NAME, res.result.user_name)
Vue.ls.set(USER_CODE, res.result.user_code)
store
.dispatch('GetInfo')
.then(res => {
store.dispatch('GenerateRoutes', { roles: res }).then(() => {
if (!res.permissions.actionEntitySet) {
next({ path: '/' })
window.location.reload()
}
})
})
.catch(() => {
next({ path: '/blank' })
})
}
// 之前开发人员的判断
// if (res.code === 500 && res.key === 'E_0003') {
// window.location.replace(process.env.VUE_APP_NO_AUTH_PATH)
// }
if (res.code === 500) {
notification.error({
message: '错误',
description: `${res.message}`
})
// 当前用户无资源权限,请联系管理员
next({ path: '/blank' })
sessionStorage.setItem('isSingle', false)
}
})
} else {
/* has token */
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (to.path === '/blank') {
next()
return
}
if (store.getters.roles.length === 0) {
store
.dispatch('GetInfo')
.then(res => {
store.dispatch('GenerateRoutes', { roles: res }).then(() => {
// 根据roles权限生成可访问的路由表
// 动态添加可访问路由表
router.addRoutes(store.getters.addRouters)
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
next({ ...to, replace: true })
} else {
// 跳转到目的路由
next({ path: redirect })
}
})
})
.catch(() => {
// notification.error({
// message: '错误',
// description: '当前用户无资源权限,请联系管理员'
// })
// store.dispatch('Logout').then(() => {
// next({ path: '/login' })
// })
next({ path: '/blank' })
})
} else {
next()
}
}
}
} else {
if (whiteList.includes(to.name)) {
// 在免登录白名单,直接进入
next()
} else {
next()
next({ path: '/login' })
}
}
})
router.afterEach(() => {
NProgress.done() // finish progress bar
})
next():进行管道中的下一个钩子。如果全部钩子执行完了,则导航状态就是confirmed(确认的)
next(false):中断当前的导航。如果浏览器的URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到form路由对应的地址。
next('/')或者next({path: '/'}):跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航,你可以向next传递任意位置对象,且允许设置诸如replace:true,name:'home'之类的选项以及任何用在router-link的to prop或router.push中的选项。
next(error):如果传入next的参数是一个Error实例,则导航会被终止且错误会被传递给router.onError()注册过的回调。
50.路由拦截(不用看)
主要思路:
- 登录成功的时候存一个状态
- 例如存在localStorage里
- 通过路由钩子判断一下
登录页面:sessionStorage.setItem("isLogin", true);
loginApi.login(params).then((res) => {
if (res.code == 1) {
this.$toast("登录成功");
sessionStorage.setItem("isLogin", true);
// localStorage.setItem("token", res.token);
this.$router.push({
name: "invoiceList",
query: {
housecode: this.housecode,
ownername: this.ownername,
},
});
} else {
this.$toast(res.message);
}
局部拦截的页面
51.组件传值.sync
可以在子组件中修改值
<HelloWorld :msg.sync="msg" @parentFun='change'/> //使用sync修饰符//在父组件中监听msg是否发生了变化
watch:{
msg:(newValue,oldValue)=>{
console.log(oldValue);
console.log(newValue);
}
}
子组件中:要修改值需要触发函数 update:msg : update是固定的,后面跟着的是变量
changeMsg: function() {
this.$emit('update:msg','hello')
console.log(this.$props.msg)
},
52.Vue组件间的参数传递;(vue组件传值)(重背)
组件通信常用方式有以下8种:
- props(父传子)
- $emit/$on(子传父)
- $children/$parent(获取单签组件的父组件和当前组件的子组件)
- $attrs/$listeners(inheritAttrs属性是否接收父组件传过来的属性默认是.inheritAttrs为true或者false,子组件中都能通过$attrs属性获取到父组件中传递过来的属性)
- $ref(获取组件实例)
- $root($root用法:访问根组件的属性和方法;$root只是对根组件有用,不是父组件,$root只是对根组件有用)
- eventbus(兄弟组件:先单独创建一个eventVue.js文件,在兄弟组件中引入这个js事件,并且兄弟之间传递数据需要借助一个中间人,这个中间人可以是父组件,在父组件中引入兄弟组件,传递数据的组件通过触发bus.$emit事件传数据,接收数据的组件通过bus.$on接收参数,这样就达成了兄弟组件的传递)Vue中EventBus(事件总线)的基本用法 - 如是。 - 博客园
- vuex
回答范例:
父向子传值?
- 属性的方式,通过v-bind向子组件传值,在子组件中通过props来接受父组件传过来的值.
- 通过在子组件上标记ref,通过父组件的this.$ref来获取子组件的数据,从而修改.
- 通过this.$children来获取子组件数组,从而获取子组件并修改它上面的数据.
子向父传值?
- 父组件在子组件上绑定函数,this.$emit(父组件函数,传入的值)触发父组件函数.
- 通过this.$parent获取父组件.
- 修饰符.sync,通过子组件this.$emit(‘update:par’,val),父组件给子组件: :par.sync=”fn”来触发.
后代组件传值?
- 可以通过attrs和attrs和listeners来获取父组件传来的值,再通过v-bind和v-on将父组件传给孙子组件.
- 通过inject和provide来控制父组件和嵌套组件的传值问题,provide中return的值会向其所有嵌套子组件中传递,子组件可以通过provide来捕获数据,(并且子组件不可以直接修改数据,vue会报错,但是可以直接获取到父组件实例来修改数据.)
举例子:
一. 父子关系组件
// 首先,在子组件里定义一个props值用来接收父组件数据;然后调用子组件并v-bind绑定这个props值 = 父组件的data值。
// 父组件代码:
<template>
<div class="home">
<HelloWorld :newMsg="msg" />
//绑定子组件newMsg(props值) = 父组件msg(data值)
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "Home",
data(){
return{msg:'Welcome to Vue.js'} //父组件数据msg
},
components: {HelloWorld}
};
</script>
// 子组件代码:
<template>
<div>
<h1>{{ newMsg }}</h1> //直接调用newMsg,显示“Welcome to Vue.js”
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {newMsg: String} //为子组件定义newMsg(props值)接收父组件msg
//或者props:['newMsg']
};
</script>
首先在子组件中用$emit()向上传递一个自定义事件并附带想要传递的数据;然后在父组件v-on监听这个自定义事件并自动接收到数据;最后在响应该事件的方法中进行数据操作。
// 子组件代码:
<script>
export default {
name: "HelloWorld",
data() {
return {msg:"Welcome to Vue"} //data值,即将向上传递的值
},
created(){
this.$emit('change',this.msg) //向上传递自定义事件change和data值。这里我调用created生命周期函数触发$emit()
}
};
</script>
// 父组件代码:
<template>
<div class="home">
<HelloWorld @change="handle" /> //监听到子组件传递来的事件并响应handle方法
<span>{{newMsg}}</span> //直接调用newMsg,显示“Welcome to Vue”
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "Home",
data() {
return {newMsg:""}
},
methods:{
handle(msg){ //定义handle方法,将自动接收到的msg值给了自己的newMsg
this.newMsg = msg
}
},
components: {HelloWorld}
};
</script>
(2)ref是vue的内部属性,类似于id,class标识。首先在子组件上定义ref值(例:ref="son");然后在父组件中用this.\$refs.son即可获取到son这个ref的组件,进行操作。
// 子组件代码:
<script>
export default {
name: "HelloWorld",
data() {
return {msg:"Welcome to Vue"} //即将向父传递的data值
}
};
</script>
// 父组件代码:
<template>
<div class="home">
<HelloWorld ref="son" /> //在子组件上定义一个ref 名为"son"
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "Home",
mounted(){
console.log(this.$refs.son.msg) //获取到ref名为"son"的子组件内msg数据,在控制台显示“Welcome to Vue”
},
components:{HelloWorld}
};
</script>
二:兄弟关系组件:
a 在 main.js 文件内 将 new Vue() 挂载至 vue 原型上
b 在 接受 通信的组件内 使用 this.$bus.$on() 进行初始化
c 在 发起通信 组件内 使用 this.$bus.$emit('方法名', 参数1, 参数2...)
// mian.js
import Vue from 'vue'
import App from './App'
import router from './router'
//重要
Vue.prototype.$bus = new Vue(); // 设置 挂载至 vue 的原型上
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
// 父组件
<template>
<div class="box">
<div>我是父组件</div>
<div style="display: flex">
<!-- 子组件 1 (-_- 我和 子组件2 是同级关系 所以是兄弟组件 -_- )-->
<baby1 class="border"></baby1>
<!-- 子组件 2 (-_- 我和 子组件1 是同级关系 所以是兄弟组件 -_- )-->
<baby2 class="border"></baby2>
</div>
</div>
</template>
<script>
import baby1 from '@/components/html/baby1.vue'
import baby2 from '@/components/html/baby2.vue'
export default {
name: 'home',
components:{
baby1,
baby2
}
}
</script>
// 子组件1
<template>
<div class="box">
<div>我是子组件1</div>
<input v-model="value" style="width: 100%">
<button @click="handleFetchHomeFunction">将值 更新至 子组件2</button>
</div>
</template>
<script>
export default {
name: 'baby1',
data(){
return{
value: ''
}
},
methods: {
// 调用 兄弟组件 方法
handleFetchHomeFunction(){
this.$bus.$emit('valueUpdate', this.value)
}
}
}
</script>
// 子组件2
<template>
<div class="box">
<div>我是子组件2</div>
<input v-model="value" style="width: 100%">
</div>
</template>
<script>
export default {
name: 'baby1',
data(){
return{
value: ''
}
},
mounted(){
// 接收事件
this.$bus.$on('valueUpdate', (value)=>{
this.value = value;
})
},
}
</script>
53.vuex:状态管理库,分5大模块:(重背)
- state:状态库,存放数据的,可以用$store.state.数据名去读取state中的存放的数据
组件中$store.state.xxx
state:{carNum: 10}
映射:mapState把vuex的state 在computed映射为组件的data
...mapState({score:state=>state.test.score})
- getters:对state调用前的状态进行修改,计算出的新数据
组件中$store.getters.xxx
getters:{ fee:function(state){return state.price*0.8}}
映射:mapGetters
...mapGetters(["fee"]),
- mutations:相当于computed属性,改变state状态(同步方法)
组件中$store.commit(方法,数据)
mutations:{
SET_CARTNUM(state,data){
state.cartNum = data;
}
}
映射:mapMutations
...mapMutations({
"setUser":"SET_USERINFO"
})
- actions:对mutations进行改变(异步方法)
组建中 $store.dispatch(方法,数据)
mutations:{
SET_CARTNUM(state,data){
state.cartNum = data;
}
}
映射:mapActions
...mapActions(["login"])}
- modules: 区分模块用的
vuex:“状态管理仓库”,用来管理项目中需要跨页共享反复调用的状态
1.获取sotre中的值:
this.$store.state.origan.departNew
2.mutations:改变state的值
this.$store.commit('SET_CONTRACTSNEW',newName);
dispatch
this.$store.dispatch('Login', this.loginForm).then(() => {
this.$router.push({
path: '/manage/merchant/account'
}); //登录成功之后重定向到首页
this.loading = false
// this.$router.push({ path: this.redirect || '/' })
}).catch(() => {
this.loading = false
})
this.$store.dispatch(‘Login’, this.loginForm)来调取store里的user.js的login方法,从而要更新。
modules
https://segmentfault.com/a/1190000017842777
https://zhuanlan.zhihu.com/p/33036996
54.Vuex是单向数据流还是双向数据流?
vuex 是单向数据流的一种实现
单向数据流是通过 props
将 Model
层的变化通知到 View
层进行修改。双向数据绑定是通过 Object.defineProperty()
的 set()
和 get()
方法来实现的,当某一方发生变化的时候,另一方就会收到更新值的提醒,从而实现数据同步变化。
54.vuex的State特性是?
state属性是Vuex中用于存放组件之间共享的数据;也就是说,我们把一些组件之间共享的状态存放在state属性中;采用单一状态树,用一个对象就包含了全部的应用层级状态。每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
55.
vuex
的Mutation
特性
Action
类似于 mutation
,不同在于:Action
提交的是 mutation
,而不是直接变更状态;Action
可以包含任意异步操作。
56.不用
Vuex
会带来什么问题? (不用看))
可维护性会下降
57.你觉得vuex有什么缺点?
刷新浏览器,vuex中的state会重新变为初始状态
58.为什么刷新页面数据就会丢失?
因为vuex数据是存在内存中的
59.Vuex页面刷新数据丢失怎么解决?
通过vuex存在localStroge等
localStorage存在vuex中,为了方便统一管理,放页面哪里都有,不方便管理
60.为什么不直接存在localstorage?/vuex和单纯的全局对象有什么区别?
因为vuex是基于订阅发布模式的,存在localStroage中不能实现订阅发布,这个页面数据进行更改了,不刷新其他页面是不会被通知。vuex是响应式的,可以用commit去改变state中的状态,方便地跟踪每一个状态的变化
61.vuex是什么?怎么使用?使用场景全局存储数据用的-----状态管理(不用看)
- 在main.js引入store,注入。用来读取状态集中store中
- 新建了一个目录store,….. export 。
场景:单页应用中,组件之间的状态
State:存放数据状态,不可直接修改里面数据
// 安装vuex
// main.js中引入store,Vue.use(vuex):注入插件
// 创建store文件夹,index.js文件,并导出
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
const app = {
state: {
num: 0
},
mutations: {
SET_NUM:(state,type) => {
state.num = type
}
},
getters:{},
actions:{
setNum({commit},type){
commit('SET_NUM', type)
}
},
modules:{}
}
export default app;
// getters.js文件
const getters = {
num: state => state.app.num
}
// vue页面获取数据
this.$store.app.num
// vue页面更改getter中的值
this.$store.commit('SET_NUM',10)
// vue页面更改mutations中的值
this.$store.dispatch('setNum',20) // 可以是异步
this.$store.dispatch('setNum').then(()=>{})
vuex是什么,怎么使用,使用场景?_LuckyandLucky的博客-CSDN博客
先看这个:vue使用--vuex快速学习与使用 - kevin_峰 - 博客园
再看这个:https://segmentfault.com/a/1190000015782272
Vuex工作机制
Vuex的数据流是组件中触发action,action提交mutations,mutations修改states。 组件根据 states或getters来渲染页面。
Vuex是个状态管理器。
它Vuex通过createStore创建了一个数据中心,然后通过发布-订阅模式来让订阅者监听到数据改变。
Vuex的store注入 vue的实例组件的方式,是通过vue的 mixin机制,借助vue组件的生命周期钩子beforeCreate 完成的。这样Vue组件就能通过this.$store获取到store了。
Vuex使用vue中的reactive方法将state设置为响应式,这样组件就可以通过computed来监听状态的改变了。
vuex推荐在哪些场景使用
登录人信息,token,浏览记录,跨组件的较大临时数据传递
多个视图共享一个状态
62.如果让你从0开始写一个vuex,说说你的思路
63.说出vue.cli项目中src目录每个文件夹和文件的用法?
- assets文件夹放静态资源
- components放组件
- router是定义路由相关配置
- view视图
- app.vue是一个应用主组件
- main.js是入口文件
64.vue.cli中怎么使用自定义组件?有遇到过哪些问题吗?
- 在components目录新建你的组件文件(smithButton.vue),script一定要export default {
- 在需要用的页面(组件)中导入:import smithButton from ‘…/components/smithButton.vue’
- 注入到vue的子组件的components属性上面,components:{smithButton}
- 在template视图view中使用,
问题:命名需要注意smithButton命名,使用的时候则smith-button。
65.绑定class的数组用法
- 对象方法:v-bind:class="{'name':'snsn','price':11}
- 数组方法:v-bind:class="[class1,class2]"
- 行内:v-bind:style="{color:color,fontSize:fontSize+'px}"
66.讲一下vue的computed和watch的实现原理?computed是怎么收集依赖的?
computed:
computed是data属性的一个订阅者,它在初始化时候被data属性收集依赖,当computed依赖的data属性改变后,标记该computed为dirty,即数据更改过,当渲染使用到computed时候,再计算出computed的值从而得到最新的正确的值。(不会)
watch:
在组件初始化时候,遍历所有的watch,对每个watch创建订阅者,绑定依赖的data属性,当data属性改变后发布给订阅者,然后会执行相应地回调。
68.computed和watch的区别(重考)
watch:监听值的变化
- handler:深度监听
- deep:是否深度监听
- immediate:是否立即执行
- 应用:监听props,$emit或本组件的值
- 无缓存性,页面重新渲染不发生变化也会执行
computed:监听值,计算出一个新的值
- get:读取当前属性值,根据计算并返回当前属性值
- set:监听当前属性值的变化,当属性值变化时,更新相关属性数据
- 应用:计算props和$emit的传值
- 具有缓存性:页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数
// 计算一个全名,fullName不可以在data中定义
data: {
firstName: 's',
lastName:'ss'
}
computed:{
fullName: {
get(){ //回调函数,读取当前属性值,根据计算并返回当前属性值
return this.firstName + this.lastName
},
set(val){ // 监事当前属性值的变化,当属性值变化时,更新相关属性数据
this.firstName = val[0]
this.lastName = val[1]
}
}
}
<div>
<p>FullName: {{fullName}}</p>
<p>FirstName: <input type="text" v-model="firstName"></p>
</div>
new Vue({
el: '#root',
data: {
firstName: 'Dawei',
lastName: 'Lou',
fullName: ''
},
watch: {
firstName(newName, oldName) {
this.fullName = newName + ' ' + this.lastName;
}
}
})
watch: {
firstName: {
handler(newName, oldName) {
this.fullName = newName + ' ' + this.lastName;
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法,如果设置了false,那么效果和上边例子一样
immediate: true,
deep: true
}
}
69.computed 和 methods 有什么区别?(不用看)
computed是属性访问,methods是函数调用
- computed带有缓存功能,methods没有
- methods是一个方法,可以接受参数,computed不能
<!--HTML部分-->
<div id="app">
<h1>{{message}}</h1>
<p class="test1">{{methodTest}}</p>
<p class="test2-1">{{methodTest()}}</p>
<p class="test2-2">{{methodTest()}}</p>
<p class="test2-3">{{methodTest()}}</p>
<p class="test3-1">{{computedTest}}</p>
<p class="test3-2">{{computedTest}}</p>
</div>
<!--script部分-->
<script>
let vm = new Vue({
el: '#app',
data: {
message: '我是消息,'
},
methods: {
methodTest() {
return this.message + '现在我用的是methods'
}
},
computed: {
computedTest() {
return this.message + '现在我用的是computed'
}
}
})
</script>
javascript - 浅析Vue中computed与method的区别_个人文章 - SegmentFault 思否
举个生活中例子
垃圾分类?为什么我们有可回收垃圾
就是可以把一些可回收的垃圾重复利用,避免了浪费。同样computed就是这个作用,把计算过的数据/处理过的利用缓存功能,存起来,进行资源的复用,避免不必要的请求,优化了用户体验。
70.Vue修饰符有哪些?
事件修饰符
- .stop:阻止冒泡事件发生
- .prevent:阻止默认事件
- .once:事件只执行一次
- .passive:告诉浏览器你不想阻止事件的默认行为
- .self:只会触发⾃⼰范围内的事件,不包含⼦元素;(只处理自身触发的事件)
- .capture:与事件冒泡的⽅向相反,事件捕获由外到内
.prevent
修饰符告诉v-on
指令对于触发的事件调用event.preventDefault()
.native:
<form v-on:submit.prevent="onSubmit">...</form>
v-model修饰符
- .lazy:通过这个修饰符,转变为在change事件在同步
- .number:将自动过滤用户的输入值转化为数值类型
- .trim:自动过滤用户输入的首位空格
键盘修饰符
- .enter:回车
- .delete:删除或回退
- .space:空格
- .up:向上
- .down:向下
- .left:向左
- .right:向右
Vue 事件处理 -- 事件修饰符(prevent、stop、capture、self、once)_CodeJiao的博客-CSDN博客_vue事件修饰符
71.组件中data为什么是函数?(为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?)
组件是用来复用的,JS里对象是引用关系,这样作用域没有隔离。而new Vue的实例,是不会被复用的,因此不存在引用对象的问题。(一个组件的 data
选项必须是一个函数,每个实例可以维护一份被返回对象的独立的拷贝),data对象是引用类型,独有的
vue组件data用箭头函数行不行?
可以使用箭头函数,但是需要注意this指向。
如果使用箭头函数,data函数中的this不会指向vue实例,如果需要访问vue实例,可以通过data函数的参数来实现。
data: vm => ({ a: vm.myProp })
72.怎么理解单向数据流
这个概念出现在组件通信。父组件是通过 prop 把数据传递到子组件的,但是这个 prop 只能由父组件修改,子组件不能修改,否则会报错。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。(简化:在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许)
子组件更改父组件状态,两种方案:
- 子组件的 data 中拷贝一份 prop,data 是可以修改的,但 prop 不能:
- 如果是对 prop 值的转换,可以使用计算属性:
// 第一种
export default {
props: {
value: String
},
data () {
return {
currentValue: this.value
}
}
}
// 第二种
export default {
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase();
}
}
73.v-on可以绑定多个方法吗
可以。原生js支持绑定多个事件,通过onClcik事件名和addEventListener来绑定的,这两个方式都支持绑定多个事件.
- onClcik事件名的方式绑定多个事件,只有最后一次绑定的起作用.
- addEventListener方式绑定的多个事情,都能正常触发
// v-on绑定多个方法(采用的是对象形式)
<button v-on="{click: clickMethods, mousemove: mouseMethods}">按钮<button>
// v-on 一个事件绑定多个方法 (语法糖 @)
<button @click="click1,click2">按钮</button>
74.封装组件的注意事项。(重考)
props属性中添加验证规则:
组件内部 CSS 应该尽可能地与外部隔离,避免样式污染和冲突。建议使用作用域 CSS、CSS 模块化或 CSS in JS 等技术,使组件样式更易于维护。
在设计组件时,需要考虑其性能问题,避免组件过于复杂、重复计算和无效渲染等问题
父组件中处理事件:处理事件的方法尽量写在父组件中,以保证通用组件中的数据不被污染。
props: {
length: {
type: [String],
default: 3
}
}
75.Vue中mixin和extend的使用场景
分发Vue组件中的可复用功能
场景:脱敏(主要用来复用)
缺点:数据来源不明显;如果和原组件属性一致,会覆盖原属性的值
// 定义一个混入对象mixin.js
export const myMixin = {
data(){
return {
num: 1
}
},
created(){
this.hello()
},
methods:{
hello(){
console.log('ppp')
}
}
}
// 把混入对象混入到当前的组件中 template1.vue
<tempalte>
<div class="template1">
组件1 里面的num:{{num}}
</div>
</template>
<script>
import {mymixin} from '@/assets/mixin.js'
export default{
mixins: [mymixin],
created(){
this.num++
}
}
</script>
77.Vue-引入图片的两种方式(你)
- 使用
require
引入:在 data 中通过 require 引入图片,可以按需加载图片。 - 直接使用图片路径
// require引入
<template>
<div>
<img :src="imgUrl" />
</div>
</template>
<script>
export default {
data() {
return {
imgUrl: require('@/assets/img/example.jpg')
}
}
}
</script>
// 直接引入
<template>
<div>
<img src="../assets/img/example.jpg" />
</div>
</template>
78.vue更新数组时触发视图更新的方法(vue2是如何对数组的函数操作进行监听的)(重考)
- 变异方法:改变原始数组:push,pop,shift,unshift,splice,sort,reverse
- 非变异方法:不会改变原始数组,返回一个新数组,用新数组替换旧数组:filter,concat,slice
在实例创建完成之后添加新的属性,他不会触发视图。
解决办法:Vue.set( target, key, value )
- target:要更改的数据源(可以是对象或者数组)
- key:要更改的具体数据
- value :重新赋的值
原理:$set方法动态添加响应式数据,通过重新定义对象属性,实现对象属性的响应式。$set
本质上就是使用这个方法添加新属性的。
举例子:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
56.vue更新数组时触发视图更新的方法 - 大牛半路出家 - 博客园
79.delete和vue.delete删除数组区别(不用看)
- delete只是把删除的元素变成了empty/undefined其他元素的键值不变
- Vue.delete直接删除数组改变了数组的键值
<template>
<div class="vue-delete">
<p class="title tag-blue">
delete和Vue.delete删除数组的区别
</p>
<ul>
<li :key="index" v-for="(item, index) in a">a---{{item}}</li>
</ul>
<button @click="handleA">处理a数组</button>
<ul>
<li :key="index" v-for="(item, index) in b">b---{{item}}</li>
</ul>
<button @click="handleB">处理b数组</button>
</div>
</template>
<script>
export default {
name: "vueDelete",
data() {
return {
a: [1,2,3,4],
b: [1,2,3,4],
}
},
methods: {
handleA() {
delete this.a[1]
this.$set(this.a)
console.log(this.a)
},
handleB() {
this.$delete(this.b, 1)
console.log(this.b)
}
}
}
</script>
80.static和assets的区别
assets里的资源会被webpack打包进代码,static里的资源就直接引用了;
assets:
在vue组件中,所有模板和css都会被vue-html-loader和css-loader解析,并查找资源url。
例:<img src="./logo.png" /> 或者 background: url("./logo.png")
因为./logo.png是相对的资源路径,将会由webpack解析为模块依赖;
由于logo.png不是js,当被视作模块依赖时,需要使用url-loader和file-loader处理它,vue-cli已经配好了这些loader(webpack)因此可以使用相对/模块路径。
由于这些资源可能在构建过程中被内联、复制、重命名,所以它们基本是代码的一部分,即webpack处理的静态资源放在/src目录中,和其它资源文件放在一起。
static:
static目录下的文件并不会被webpack处理,它们会直接复制到最终目录(dist/static)下。
]必须使用绝对路径引用这些文件,这是通过在config.js的build.assetsPublicPath和nuild.assetsSubDirectory连接确定的。
任何放在static/的文件需要以绝对路径形式引用:/static/[name]。
如果更改assetsSubDirectory的值为assets,那么路径需更改为:/assets/[filename]。
https://www.cnblogs.com/dream111/p/13499384.html
81.单页面与多页面的区别及优缺点 (SPA、SSR的区别是什么)(重考)
单页面应用,指只有一个主页面的应用,浏览器一开始要加载所有的 html, js, css。所有的页面内容都包含在这个主页面中。写的时候,还是会分开写,交互时由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。
多页面(MPA,SSR),指一个应用中有多个页面,页面跳转时是整页刷新,用于服务端渲染,后台服务器给返回一个新的html文档,
单页面优点:
- 用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小
- 前后端分离
- 可以通过API实现响应的数据绑定和组合的视图组件
- 页面效果会比较炫酷(比如切换页面内容时的专场动画)
单页面缺点:
- 不利于seo(搜索引擎优化)
- 初次加载时耗时多
- 如果数据很多,页面加载慢
多页面优点:
- 首屏加载快
- 搜索引擎优化效果好
多页面缺点:
- 切换加载比单页面加载慢
87.ref是什么?
基本用法,本页面获取dom元素
<template>
<div id="app">
<div ref="testDom">11111</div>
<button @click="getTest">获取test节点</button>
</div>
</template>
<script>
export default {
methods: {
getTest() {
console.log(this.$refs.testDom)
}
}
};
</script>
获取子组件中的data
子组件
<template>
<div>
{{ msg }}
</div>
</template>
<script>
export default {
data() {
return {
msg: "hello world"
}
}
}
</script>
父组件
<template>
<div id="app">
<HelloWorld ref="hello"/>
<button @click="getHello">获取helloworld组件中的值</button>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
components: {
HelloWorld
},
data() {
return {}
},
methods: {
getHello() {
console.log(this.$refs.hello.msg)
}
}
};
</script>
调用子组件中的方法
子组件
<template>
<div>
</div>
</template>
<script>
export default {
methods: {
open() {
console.log("调用到了")
}
}
}
</script>
父组件
<template>
<div id="app">
<HelloWorld ref="hello"/>
<button @click="getHello">获取helloworld组件中的值</button>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
components: {
HelloWorld
},
data() {
return {}
},
methods: {
getHello() {
this.$refs.hello.open();
}
}
};
</script>
子组件调用父组件方法
子组件
<template>
<div>
</div>
</template>
<script>
export default {
methods: {
open() {
console.log("调用了");
// 调用父组件方法
this.$emit("refreshData");
}
}
}
</script>
父组件
<template>
<div id="app">
<HelloWorld ref="hello" @refreshData="getData"/>
<button @click="getHello">获取helloworld组件中的值</button>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
components: {
HelloWorld
},
data() {
return {}
},
methods: {
getHello() {
this.$refs.hello.open()
},
getData() {
console.log('111111')
}
}
};
</script>
88.说一下Vue.js页面闪烁{{message}}.什么原因?(不用看)
- 可以检查一下样式文件是否放在文件最前面,js文件要放在最后面。或者也可以使用link引用
- v-cloak命令,v-cloak用法:
// 第一种
<style>
@import "${css}/project/index.css";
</style>
css文件
// 第二种
<style>
@import "${css}/project/index.css";
[v-cloak]{
display: none;!important;
}
</style>
html文件
<div id="app" v-cloak>
<div class="container"></div>
</div>
89.如何解决数据层级结构太深的问题? (不用看)
this.$forceUpdate()
90.Vue.js文件中的样式覆盖不生效的问题如何解决? (重考)
解决办法
- 加scoped属性
- 可以使用 >>> 操作符:只作用于css!
<style scoped>
.example{
// ...
}
</style>
<style>
.example span {
// ...
}
</style>
// 第二种
<style scoped>
.a >>> .b { /* ... */ }
</style>
编译成
.a[data-v-f3f3eg9] .b { /* ... */ }
<style lang="less" scoped>
/deep/ .b {
color: #000;
}
}
</style>
91.Vue中如何做样式穿透
示例:
// 父组件
<template>
<div class="cssdeep">
<!-- 样式穿透 -->
<cssdeepcom></cssdeepcom>
</div>
</template>
<script>
import cssdeepcom from '../components/cssdeepcom'
export default {
data(){
return{
}
},
components:{
cssdeepcom,
},
}
</script>
<style lang="less" scoped>
.cssdeep /deep/ .cssdeepcom{
background: blue;
}
</style>
// 子组件
<template>
<div class="cssdeepcom"></div>
</template>
<script>
export default {
data(){
return{
}
},
}
</script>
<style lang="less" scoped>
.cssdeepcom{
width: 100px;
height: 100px;
background: red;
}
</style>
stylus的样式穿透 使用>>>
外层 >>> 第三方组件
样式
.wrapper >>> .swiper-pagination-bullet-active
background: #fff
sass和less的样式穿透 使用/deep/
外层 /deep/ 第三方组件 {
样式
}
.wrapper /deep/ .swiper-pagination-bullet-active{
background: #fff;
}
92.slot插槽(重考)
插槽分为3种:匿名插槽,具名插槽,作用域插槽
- 匿名插槽:<slot>作为我们想要插入内容的占位符
- 具名插槽:给插槽加入name属性
- 作用域插槽:与前两者的不同 slot自定义:name="值" 子组件可向父组件传递信息
// 带有插槽的组件Tips Text.vue(子组件)
<template>
<div>
<div>hello world</div>
<slot></slot>
</div>
</template>
// 父组件Text.vue引入Tips Text.vue
<template>
<div>
<tips-text>
<template v-slot>
<div>我是匿名插槽</div>
</template>
</tips-text>
</div>
</template>
<script>
import TipsText from './TipsText.vue'
export default {
components:{ TipsText }
}
</script>
// 带有插槽 的组件 TipsText.vue(子组件)
<template>
<div>
<div>hello world</div>
<slot name="one"></slot>
<slot name="two"></slot>
</div>
</template>
// 父组件 Test.vue 引入 TipsText.vue
<template>
<div>
<tips-text>
<template v-slot:one>
<div>我是具名插槽one</div>
</template>
<template v-slot:two>
<div>我是具名插槽two</div>
</template>
</tips-text>
</div>
</template>
<script>
import TipsText from './TipsText.vue'
export default {
components:{ TipsText }
}
</script>
93.如何实现自定义指令?它有哪些钩子函数?还有哪些钩子函数参数?(重考)
项目中使用指令的情景是“有无权限”
- 给自定义指令起一个名字v-permission
- 自定义指令包含的一些钩子函数:bind(绑定元素);unbind(解绑元素),inserted(被绑定元素插入父节点时调用),update;componentUpdate
- 钩子函数参数有:el(指绑定的元素,可以直接用来操作DOM),binding(是一个对象,包含指令的一些属性,name,value),vnode(虚拟节点)
Vue.directive('pIndex', {
bind (el,binding, vnode) {
var clientType = getCookie("clientType") || "PC";
if(!Vue.prototype.$permissions(binding.value)){
const tSpan = document.createElement('span');
tSpan.innerHTML = '无访问权限';
el.appendChild(tSpan);
tSpan.style.color = 'rgb(204, 204, 204)';
tSpan.style.fontSize = clientType == "PC" ? "16px" : "12px";
tSpan.style.visibility = 'visible';
el.className += ' no-permissions-single'
const curStyle = window.getComputedStyle(el, '') // 获取当前元素的style
const textSpan = document.createElement('span') // 创建一个容器来记录文字的width
// 设置新容器的字体样式,确保与当前需要隐藏的样式相同
textSpan.style.fontSize = curStyle.fontSize
textSpan.style.fontWeight = curStyle.fontWeight
textSpan.style.fontFamily = curStyle.fontFamily
// 将容器插入body,如果不插入,offsetWidth为0
document.body.appendChild(textSpan)
// 设置新容器的文字
textSpan.innerHTML = el.innerText
// 如果字体元素大于当前元素,则需要隐藏
if (textSpan.offsetWidth > el.offsetWidth) {
// 给当前元素设置超出隐藏
el.style.overflow = 'hidden'
el.style.textOverflow = 'ellipsis'
el.style.whiteSpace = 'nowrap'
// 鼠标移入
tSpan.onmouseenter = function (e) {
// 创建浮层元素并设置样式
const vcTooltipDom = document.createElement('div')
vcTooltipDom.style.cssText = `
overflow: auto;
position:absolute;
top: ${e.clientY}px;
left:${e.clientX-10}px;
padding-left:10px;
background: rgba(0, 0 , 0, .6);
color: #fff;
border-radius:5px;
padding:8px;
display:inline-block;
font-size:12px;
z-index:19999;
pointer-events: none;
`
// 设置id方便寻找
vcTooltipDom.setAttribute('id', 'vc-tooltip')
// 将浮层插入到body中
document.body.appendChild(vcTooltipDom)
// 浮层中的文字
document.getElementById('vc-tooltip').innerHTML = '暂无权限,如申请权限,请提交IT服务热线申请(拨打电话010-59568666或访问itsm@sinochem.com)'
}
// 鼠标移出
tSpan.onmouseleave = function () {
// 找到浮层元素并移出
const vcTooltipDom = document.getElementById('vc-tooltip')
vcTooltipDom && document.body.removeChild(vcTooltipDom)
}
}
// 记得移除刚刚创建的记录文字的容器
document.body.removeChild(textSpan)
}
},
// 指令与元素解绑时
unbind () {
// 找到浮层元素并移除
const vcTooltipDom = document.getElementById('vc-tooltip')
vcTooltipDom && document.body.removeChild(vcTooltipDom)
}
})
// 公共权限的方法
Vue.prototype.$permissions = function(code){
const RIGHTLIST = window.localStorage.getItem('RIGHTLIST');
if(RIGHTLIST != 'undefined' && RIGHTLIST && RIGHTLIST != 'null'){
let permissionsNew = JSON.parse(RIGHTLIST);
let permissions = permissionsNew.map((item,index) =>{
return item.fieldId;
});
return permissions.includes(code)
}else{
return true;
}
};
94.自定义的指令,直接用“:”前面省略了哪个语法糖?
v-bind ===》 :变量
v-on ====》@click
v-bind的缩写
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数 -->
<a :[key]="url">...</a>
v-on的缩写
<!-- 完整语法 -->
<a v-on:click="dosomething">...</a>
<!-- 缩写 -->
<a @click="dosomething">...</a>
<!-- 动态参数的缩写 -->
<a @[event]="doSomething">...</a>
95.如何将获取data中某一个数据的初始状态? (不用看)
this.$options.data().变量
// 开发中,有时候需要拿初始状态去计算
data(){
return {
num: 10
}
},
mounted(){
this.num = 1000
},
methods:{
howMuch(){
// 计算出num增加了多少,那就是1000 - 初始值
// 可以通过this.$options.data().xxx来获取初始值
console.log(1000 - this.$options.data().num)
}
}
96.vue之$options
在Vue实例化过程中,Vue会将传入的选项进行一些处理和合并,最终生成一个新的选项对象,该对象称为Vue实例的options对象。options对象包含了Vue实例使用的选项,如data、props、methods、watch、computed、生命周期函数等。可以在组件实例中通过this.options 访问到 $options 对象.
$options: {
components: {},
directives: {},
filters: {},
_base: Vue,
mixins: [],
name: "ComponentName",
parent: Vue,
extends: Component,
provide: function() {},
inject: {},
template: "<div></div>",
props: {
propA: {
type: String,
required: true,
},
propB: {
type: Number,
default: 100,
},
},
data: function() {},
methods: {
methodA: function() {},
},
computed: {},
watch: {},
beforeCreate: function() {},
created: function() {},
beforeMount: function() {},
mounted: function() {},
beforeUpdate: function() {},
updated: function() {},
activated: function() {},
deactivated: function() {},
beforeDestroy: function() {},
destroyed: function() {},
errorCaptured: function() {},
render: function() {},
staticRenderFns: [],
}
new Vue({
data: {
msg: "Hello World!"
},
created: function() {
console.log(this.$options.data()); // {msg: "Hello World!"}
}
});
97.vueI18n的使用。vue中实现语言切换的方式如何实现的
- NPM 项目安装
- 使用方法
- 页面数据使用
// 第一步
npm i vue-i18n
// 第二步
/* 国际化使用规则 */
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// 注册
Vue.use(VueI18n)
<!-- 需要国际化的数据定义在此处 -->
const messages = {
en: {
message: {
hello: 'world hello'
}
},
zh: {
message: {
hello: '世界'
}
}
}
<!-- 使用i18n -->
const i18n = new VueI18n({
locale: 'zh',
messages
})
export default {
data () {
return {
hello: this.$t('message.hello')
}
},
i18n
}
// 第三步
<div id="#app">
<p>{{ $t("message.hello") }}</p>
</div>
98.vue项目是打包一个js文件,一个css文件,还是有多个文件???
一个
99.什么是数据的丢失?
如果在初始化时没有定义数据,之后更新的数据是无法触发页面渲染更新的,这部分数据是丢失的数据(因为没有设置特性),这种现象称为数据的丢失
100.如何检测数据变化?
watch
101.如果让你从零开始写一个vue路由,说说你的思路
https://github.com/57code/vue-interview/blob/master/public/14-router/README.md
- 在main.js中引入router并挂载引入router.js文件
- route.js中创建路由实例,引入路由表是,确定路由模式和静态路由表
- 在权限文件中把生成好的路由用addRoutes添加到路由表生成最终的路由表
注意:以上路由挂载完成,如果访问没有的路由直接404
102.HTTP请求的封装
- 创建axios实例,设置请求超时时间,封装请求拦截和响应拦截
- 在api.js中引入request.js文件中的axios实例,写每个请求方法并向外暴露方法名
- 在vue的使用文件中引入
104.拦截器怎么用?
统一处理所有的http请求和响应
- 请求拦截器的作用:在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易。请求头还可以做加签处理
- 响应拦截器的作用:响应接收的数据,状态,处理异常,例如在服务器返回登录状态失效需要重新登录的时候,跳转到登录页面
- 移除拦截器
//request拦截器 添加一个请求拦截器
axios.interceptors.request.use(function (config) {
let token = window.localStorage.getItem("token")
if (token) {
// 设置请求头
config.headers.common['Authorization'] = token
}
return config
}, function (error) {
//如果请求出错在此执行某些操作
return Promise.reject(error);
});
// 添加一个响应拦截器
axios.interceptors.response.use(function (response) {
//登录超时
if(response.data.code==400){
router.push('/')
}
return response;
}, function (error) {
return Promise.reject(error);
});
// 移除
var myInterceptor = axios .interceptors.request.use(function() {/*...*/});
axios.interceptors.request.eject(myInterceptor);
105.权限是怎么做的?路由是怎么根据权限判断的?
- 创建vue实例的时候将vue-router挂载,但这时候vue-router挂载一些登录或者不用权限的公用的页面)
- 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
- 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
- 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。
前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes
动态挂载路由。
router.js
// router.js
import Vue from 'vue';
import Router from 'vue-router';
import Login from '../views/login/';
const dashboard = resolve => require(['../views/dashboard/index'], resolve);
//使用了vue-routerd的[Lazy Loading Routes
](https://router.vuejs.org/en/advanced/lazy-loading.html)
//所有权限通用路由表
//如首页和登录页和一些不用权限的公用页面
export const constantRouterMap = [
{ path: '/login', component: Login },
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: '首页',
children: [{ path: 'dashboard', component: dashboard }]
},
]
//实例化vue的时候只挂载constantRouter
export default new Router({
routes: constantRouterMap
});
//异步挂载的路由
//动态需要根据权限加载的路由表
export const asyncRouterMap = [
{
path: '/permission',
component: Layout,
name: '权限测试',
meta: { role: ['admin','super_editor'] }, //页面需要的权限
children: [
{
path: 'index',
component: Permission,
name: '权限测试页',
meta: { role: ['admin','super_editor'] } //页面需要的权限
}]
},
{ path: '*', redirect: '/404', hidden: true }
];
关键的main.js
// main.js
router.beforeEach((to, from, next) => {
if (store.getters.token) { // 判断是否有token
if (to.path === '/login') {
next({ path: '/' });
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => { // 拉取info
const roles = res.data.role;
store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
}).catch(err => {
console.log(err);
});
} else {
next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next();
} else {
next('/login'); // 否则全部重定向到登录页
}
}
});
permission.js
通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。
// store/permission.js
import { asyncRouterMap, constantRouterMap } from 'src/router';
function hasPermission(roles, route) {
if (route.meta && route.meta.role) {
return roles.some(role => route.meta.role.indexOf(role) >= 0)
} else {
return true
}
}
const permission = {
state: {
routers: constantRouterMap,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers;
state.routers = constantRouterMap.concat(routers);
}
},
actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { roles } = data;
const accessedRouters = asyncRouterMap.filter(v => {
if (roles.indexOf('admin') >= 0) return true;
if (hasPermission(roles, v)) {
if (v.children && v.children.length > 0) {
v.children = v.children.filter(child => {
if (hasPermission(roles, child)) {
return child
}
return false;
});
return v
} else {
return v
}
}
return false;
});
commit('SET_ROUTERS', accessedRouters);
resolve();
})
}
}
};
export default permission;
105.vue中使用了哪些设计模式? 项目中用到过吗
- 工厂模式 - 创建一个对象,传入不同的参数即可创建实例
- 发布-订阅模式。(vue 事件机制)
- 观察者模式。(响应式数据原理)
- 单例模式 - 整个程序有且仅有一个实例
106.你都做过哪些 Vue 的性能优化?
- · 对象层级不要过深,否则性能就会差。
- · 不需要响应式的数据不要放在 data 中(可以使用 Object.freeze() 冻结数据)
- · v-if 和 v-show 区分使用场景
- · computed 和 watch 区分场景使用
- · v-for 遍历必须加 key,key最好是id值,且避免同时使用 v-if
- · 防止内部泄露,组件销毁后把全局变量和时间销毁
- · 图片懒加载
- · 路由懒加载
const router = new vueRouter({
routes: [
{path: '/foo',component: () => import('./Foo.vue')
]
})
- 事件销毁,防止内存泄漏
// vue组建销毁,会自动解绑他的全部指令及事件监听器,但仅限于组件本身的事件
created(){
this.timer = setInterval(this.refresh,2000)
}
beforeDestory(){
clearInterval(this.timer)
}
- 异步路由
- 第三方插件的按需加载
- 适当采用 keep-alive 缓存组件
<template>
<div>
<keep-alive>
<router-view />
</keep-alive>
</div>
</template>
- · 防抖、节流的运用
为什么data中数据多了,加载慢?
在 Vue 中,当 data 中的数据量过大时,每次数据变动都会触发视图更新。这会导致频繁的重新渲染,从而降低页面性能和加载速度。
107.vue的优点是什么?vue如何性能优化
可重用性:可以将公共部分抽取出组件
低耦合。使用了双向绑定原理
性能优化:
1.代码层面优化:
根据不用场景选择合适的条件
- v-show和v-if的使用(v-show:频繁的切换场景),
- computed 和 watch 区分使用场景.
Webpack 层面的优化:
- Webpack 对图片进行压缩 webpack 压缩图片_yin_you_yu的博客-CSDN博客
- 提取公共代码,抽成公共组件
- 减少 ES6 转为 ES5 的冗余代码
https://www.jianshu.com/p/41d161310949
108.vue的异步更新机制是如何实现的? (不用看)
Vue 的异步更新机制的核心是利用了浏览器的异步任务队列来实现的,首选微任务队列,宏任务队列次之。
当响应式数据更新后,会调用 dep.notify 方法,通知 dep 中收集的 watcher 去执行 update 方法,watcher.update 将 watcher 自己放入一个 watcher 队列(全局的 queue 数组).然后通过 nextTick 方法将一个刷新 watcher 队列的方法放入一个全局的 callbacks 数组中。如果此时浏览器的异步任务队列中没有一个叫 flushCallbacks 的函数,则执行 timerFunc 函数,将 flushCallbacks 函数放入异步任务队列。如果异步任务队列中已经存在 flushCallbacks 函数,等待其执行完成以后再放入下一个 flushCallbacks 函数。
Vue 源码解读(4)—— 异步更新 - 李永宁 - 博客园
109.created 为啥不能用箭头函数定义?this 指向不对;如果用箭头函数定义,this 是啥值
因为箭头函数没有this执行,会导致this不能指向组件实例,所以这时候调this的时候会报undefined
例如 created: () => this.fetchTodos()
111.props中的值?(不用看)
父组件中用v-bind绑定传送,子组件用props接收即可。是只读属性,不能直接修改 props 的值。
- 类型属性:type:xx
- 必传属性:required:true
- 默认属性:default:xx
props:{
"name":{
type:String,
required:true
},
"age":{
type:Number,
default:18
},
"sex":{
type:String,
default:'男'
},
parentlistAr: {
type: Array,
default: () => []
},
parentObj: {
type: Object,
default: () => ({})
}
}
112.vue首页白屏问题和优化方式
Vue 首页白屏问题可能由多种因素引起,以下是常见的一些因素及优化方式:
-
大量图片加载过多,导致首屏加载时间过长。 优化方式:使用图片懒加载技术,延迟加载图片,减少首屏加载时间。
-
大量数据请求导致渲染时间过长。 优化方式:使用分页、滚动加载等技术,减少每次请求的数据量,减少数据渲染时间。
-
大量组件渲染导致渲染时间过长。 优化方式:使用组件懒加载技术,将组件按需加载,减少首屏加载时间。
-
打包生成的文件过大,导致加载时间过长。 优化方式:对 JS、CSS 进行压缩和混淆,减小文件大小,缩短首屏加载时间。
113.说说nextTick的使用和原理?
Vue.nextTick()
可以让我们在 DOM 更新之后执行回调函数。
原理:
- vue中有一个异步队列,在每个事件循环中执行。
- 当
Vue
实例中数据发生变化时,Vue
不会立即改变真实DOM
,是把需要更新的操作存到异步队列中。 - 如果数据变化导致了某个
DOM
的多次更新操作,Vue
会在下一个事件循环中,把这些操作合并成一次更新,从而减少渲染次数。 Vue.nextTick()
就是利用了这个异步队列,在下一个事件循环中执行回调函数,以确保回调函数执行时,DOM
已经更新完成。
场景:
思路分析:
-
nextTick是做什么的?
-
为什么需要它呢?
-
开发时何时使用它?抓抓头,想想你在平时开发中使用它的地方
-
下面介绍一下如何使用nextTick
-
原理解读,结合异步更新和nextTick生效方式,会显得你格外优秀
114.VUE中如何扩展一个组件
在Vue中扩展组件有两个主要方式:继承和混入。
// 继承(extend):创建一个新组建,该组件继承了父组件的所有属性和方法,并可以重写父组件的方法以实现自己的功能。在vue中,可以使用extend创建一个继承组件
// 定义一个基础组件
var myComponent = Vue.extend({
template: '<div>A custom component!</div>'
})
// 创建一个子组件并继承父组件
var myExtendedComponent = myComponent.extend({
template: '<div>An extended custom component!</div>'
})
上面的例子中,myExtendedComponent继承了myComponent的模板,同时重写了template属性来实现自己的功能。
// 混入(mixins):指在一个组件中混入另一个组件的属性和方法,使其可以复用。
// 定义一个混入对象
var myMixin = {
created: function () {
console.log('Mixin created!')
}
}
// 创建一个组件并混入myMixin
var myComponent = {
mixins: [myMixin],
created: function () {
console.log('Component created!')
}
}
上面的例子中,myComponent混入了myMixin的created方法,并在自己的created方法中调用了它们。
通过继承和混入,我们可以扩展组件的功能和复用代码,从而提高开发效率。
115.父组件可以监听到子组件的生命周期吗?
可以;$emit或者 @hook
// emit
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
// @hook 来监听即可
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
116.Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?
回答规范:
-
权限管理一般需求是页面权限和按钮权限的管理
-
具体实现的时候分后端和前端两种方案
路由权限:
- 在main.js中引入permission.js文件
- 在router.js文件的路由上标识权限字段
- permission.js中用路由守卫router.beforeEach去判断权限,根据返回的路由表admin,user等和前端路由权限去匹配
- 符合的则显示
多种方式
- 纯前端的做的路由,例如返回的是admin,user等去匹配
- 返回路由表(动态路由),直接和前端路由合并
按钮权限
- 可以使用v-if/v-else
- 还可以用指令的方式(通过指令传一个值,然后在指令中去判断这个值和登录的权限去对比,存在就显示,不存在去做处理(隐藏,删除等))
VUE菜单权限,按钮权限 控制到按钮级别的权限怎么做? _ 重蔚自留地
思路:
-
权限管理需求分析:页面和按钮权限
-
权限管理的实现方案:分后端方案和前端方案阐述
-
说说各自的优缺点
117.vue虚拟滚动加载
虚拟滚动是只渲染可视区域的节点,虚拟滚动进行加载
虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域
内需要的列表项,当滚动发生时,动态通过计算获得可视区域
内的列表项,并将非可视区域
内存在的列表项删除。
vue中使用虚拟滚动加载_尼克_张的博客-CSDN博客_vue 实现虚拟滚动
118.用index作为key可能会引发的问题:
在 Vue 中,key 的作用是给 Vue 的 Virtual DOM 中的节点做一个唯一的标识,从而优化 Virtual DOM 的 diff 算法,在更新 DOM 的时候更高效地找到对应的节点进行更新。
使用 index
作为 key
可能会引发以下问题:
- 渲染性能下降:当
list
中的元素顺序发生改变时,相同index
的元素会被认为是同一个元素,但实际上内容已经改变。这就会导致整个列表重新渲染,性能下降。 - 出现误删除或者更新:如果列表中的某个元素发生删除或者更新,将会影响 index 的值,导致某些元素的
key
重复,使得 Vue 在更新列表时无法正确地判断哪个元素需要被更新或者被删除,可能出现意外的删除或更新,导致应用出现 bug。
不建议使用 index
作为 key
,应该使用具有唯一性的属性值作为 key
,例如每个元素的 id
,以避免上述问题的发生。同时能够提升应用的性能。
120.new Vue的过程
new Vue()是一个创建Vue实例的过程,它会执行一系列的初始化操作,包括一下几个步骤:
- 初始化 Vue 实例的属性和方法,包括 data、props、computed、methods、watch 等。
- 合并配置项,将使用 Vue 构造函数时传入的参数与 Vue.prototype 中定义的参数进行合并,得到最终的配置对象。
- 初始化生命周期钩子函数,包括 beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy 和 destroyed。
- 初始化事件系统,包括实例方法 on、on、emit、$once 和 $off,以及属性 $listeners 和 $children。
- 初始化插槽,包括 $slots 和 $scopedSlots。
- 初始化渲染函数,包括 $createElement 和 $attrs 等方法和属性。
- 完成组件的挂载。Vue 会创建一个虚拟 DOM,然后将其渲染成真实的 DOM,并将组件挂载到 DOM 树上。
总之,new Vue()
的过程是初始化一个完整的 Vue 实例,并为其提供所需的属性和方法,为后续的组件渲染和交互体验奠定基础。同时,Vue 也提供了完善的生命周期,让开发者可以在特定的时间点进行定制化的操作,例如在 created 生命周期中进行数据初始化,在 mounted 生命周期中进行 DOM 操作等。
121.vue组件watch中deep和immediate的作用(不用看)
在Vue的组件中,watch选项可以让我们监听数据的变化并做出相应的操作。watch选项可以接收一个对象作为参数,其中包含要监听的属性和对应的回调函数,同时也有两个可选的参数:deep和immediate。
deep参数默认为false,它的作用是监听一个嵌套的对象内部值的变化。也就是说,当该参数为true时,在嵌套对象内部值变化时,watch也会触发回调函数。
watch: {
obj: {
handler(newValue, oldValue) {
console.log('obj changed')
},
deep: true
}
}
immediate参数默认为false,它的作用是在初始化时立即执行一次回调函数。也就是说,当该参数为true时,在组件初始化时会立即执行watch的回调函数。
watch: {
value: {
handler(newValue, oldValue) {
console.log('value changed')
},
immediate: true
}
}
122.create中修改两次数据,会触发几次页面更新
在 Vue.js 中,每次数据发生变化时,Vue 都会自动进行检测并更新对应的 DOM。修改两次数据会触发两次页面更新。不过,为了提高性能,Vue 会对相邻的数据更新进行合并,最终只触发一次 DOM 更新操作。这个过程被称为“批处理”或“异步更新队列”。
需要注意的是,如果在一次事件循环中修改了同一个响应式数据多次,Vue 只会进行一次 DOM 更新,因为多次修改会被合并为一次更新。但是,如果在同一次事件循环中修改了不同的响应式数据,这些修改会被分别触发,最终会导致多次 DOM 更新。
124.从0到1构建一个vue项目需要注意什么?
- 架子:选用合适的初始化脚手架(
vue-cli2.0
或者vue-cli3.0
) - 请求:数据
axios
请求的配置 - 登录:登录注册系统
- 路由:路由管理页面
- 数据:
vuex
全局数据管理 - 权限:权限管理系统
- 埋点:埋点系统
- 插件:第三方插件的选取以及引入方式
- 错误:错误页面
- 入口:前端资源直接当静态资源,或者服务端模板拉取
SEO
:如果考虑SEO
建议采用SSR
方案- 组件:基础组件/业务组件
- 样式:样式预处理起,公共样式抽取
- 方法:公共方法抽离
埋点系统
需求分析:
设计埋点系统之前,需要先确定埋点系统的需求和目标,需要从以下几个方面考虑:
- 埋点的内容:需要采集哪些数据?如何定义事件?
- 埋点的时机:事件何时触发?每个事件的触发频率是多少?
- 数据存储和加工:如何存储数据?如何分析和加工数据?
- 数据可视化:如何展示分析结果?
事件定义与上报
定义事件是埋点系统最基础的部分
125.减少代码量(不用看)
- 采用
mixin
的方式抽离公共方法 - 抽离公共组件
- 定义公共方法至公共
js
中 - 抽离公共
css
126.数据响应式处理 (不用看)
- 不需要响应式处理的数据可以通过
Object.freeze
处理,或者直接通过this.xxx = xxx
的方式进行定义 - 需要响应式处理的属性可以通过
this.$set
的方式处理,而不是JSON.parse(JSON.stringify(XXX))
的方式
127.设置404页面 (不用看)
用户会经常输错页面,当页面输错页面时,我们希望给一个友好的提示页面,404页面
1.// 设置我们的路由配置文件(/src/router/index.js)
{
path:'*',
component:Error
}
path:'*'就是输入地址不匹配时,自动显示的Error.vue文件内容
2.在/src/components/文件夹下新建一个Error.vue的文件。简单输入一些有关错误页面的内容。
<template>
<div>
<h2>{{ msg }}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Error:404'
}
}
}
</script>
128.在vue项目中如何引入异步组件?
new Vue({
components: () => import('../view/name.vue')
})
componets: {
my-componets: (resolve) => { require(['./my-async-component'], resolve) }
}
import原理
require的原理
详解require和import_import和require的原理-CSDN博客
按需加载原理
js import()异步原理?懒加载原理是什么? - 编程宝库
https://www.cnblogs.com/mamimi/p/7646358.html
下面不用看==========================
123.created
、watch
和computed
的执行顺序 (不用看)
created
钩子函数会在 Vue 实例被创建之后立即执行,此时组件的数据和事件还未挂载。因此,computed
和watch
中用到的数据还不能使用。- 接着将会执行
computed
中的代码,如果这些计算属性依赖的数据被更新,那么它们会自动重新计算,并且会触发计算属性的getter
函数。 - 最后会执行
watch
的代码,根据组件的状态修改 DOM 等。
103.vue的模板渲染 (不用看)
Vue会根据将模板编译成render函数,调用render函数生成虚拟dom,然后将虚拟dom映射成真实dom。当数据变化时候,Vue会触发更新视图,调用render函数返回新的虚拟dom,对比新旧虚拟dom,修改真实dom,从而更新界面
Vue会通过diff算法比较新旧两个虚拟DOM树的差异
119.npm和yarn的区别 (不用看)
npm和yarn都是用来安装和管理JavaScript包的包管理工具。它们的主要区别如下:
- 安装速度
yarn比npm快,这是因为yarn在安装软件包时可以并行下载多个软件包,而npm在执行此操作时是串行的。
- 缓存管理
yarn在缓存管理方面比npm更好。它可以减少软件包的下载次数,从而提高安装速度。
总的来说,虽然yarn比npm功能更强、速度更快、效率更高、可靠性更好,但npm已经非常稳定、可靠,且生态系统更加完善。因此,选择使用npm还是yarn取决于个人喜好和具体需求。
88.vue2的template为什么一定要有一个div进行嵌套
如果在template下有多个div,那么该如何指定这个vue实例的根入口?
为了让组件能够正常的生成一个vue实例,那么这个div会被自然的处理成程序的入口。
通过这个**‘根节点’,**来递归遍历整个vue‘树’下的所有节点,并处理为vdom,最后再渲染成真正的HTML,插入在正确的位置。
104.vue事件绑定原理(不用考)
原生事件绑定是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过Vue自定义的$on实现的。如果要在组件上使用原生事件,需要加.native修饰符,这样就相当于在父组件中把子组件当做普通的HTML标签,然后加上原生事件。
$on、$emit 是基于发布订阅模式的,维护一个事件中心,on的时候将事件按名称存在事件中心里,称之为订阅者,然后emit将对应的事件进行发布,去执行事件中心里的对应的监听器。
103.Vue.use方法的使用
通过调用Vue.use()方法来安装插件,插件是一个构造函数,它必须实现一个install方法,Vue会调用install方法,并传入Vue变量,让插件可以使用Vue来向Vue添加全局功能。
v-model语法糖是怎么实现的
负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
v-model是input+value的语法糖
v-model基本概念
v-model
实际上就是 $emit('input')
以及 props:value
的组合语法糖,只要组件中满足这两个条件,就可以在组件中使用 v-model
。
「虽然在有些交互组件中有些许不同」,例如:
checkbox
和 radio
使用 props:checked
属性和 $emit('change')
事件。
select
使用 props:value
属性和 $emit('change')
事件。
但是,除了上面列举的这些,别的都是 $emit('input')
以及 props:value
。
vue过滤器源码
过滤器有两种使用方式,分别是在双花括号插值中和在 v-bind 表达式。但是无论是哪一种使用方式,过滤器都是写在模板里面的。既然是写在模板里面,那么它就会被编译,会被编译成渲染函数字符串,然后在挂载的时候会执行渲染函数,从而就会使过滤器生效。用一个函数进行过滤,存在则返回
93.vue初始化过程中(new Vue(options))都做了什么 ?(待定)
处理组件配置项;初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上;初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率;
初始化组件实例的关系属性,比如 parent 、 parent、parent、children、root , root、root、refs 等
处理自定义事件
调用 beforeCreate 钩子函数
初始化组件的 inject 配置项,得到 ret[key] = val 形式的配置对象,然后对该配置对象进行响应式处理,并代理每个 key 到 vm 实例上
数据响应式,处理 props、methods、data、computed、watch 等选项
解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
调用 created 钩子函数
如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount 方法,反之,没提供 el 选项则必须调用 $mount
接下来则进入挂载阶段
new Vue(options) - 转眼春夏秋冬如烟 - 博客园
84.为什么搜索引擎优化效果好(SEO)
搜索引擎在做网页排名的时候,要根据网页内容才能给网页权重,来进行网页的排名。搜索引擎是可以识别html内容的,而我们每个页面所有的内容都放在Html中,所以这种多页应用,seo排名效果好。在选择上,如果我们的应用存在首屏加载优化需求,SEO需求,就可以考虑SSR。
101.vue.use的内部原理(需要整理)
vue.use
Vue.use()方法的原理_cxy-Study的博客-CSDN博客_vue.use原理
怎么做国际化
怎么做主题切换
写的时候用变量 然后不同主题定义不同的class名 覆盖变量
切换时候切换class名就行了
不用背
89.vue的dom diff算法
90.vue的ChildNode diff算法
import和require的区别?
5.自定义组件使用v-model,如果想要改变事件名或者属性名应该怎么做
做没做过权限控制,页面级还是控件级,怎么实现的?
vue怎么判断鼠标悬停了一段时间?
82.函数式组件使用场景和原理
函数式组件与普通组件的区别:
- 函数式组件需要在声明组件是指定 functional:true
- 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
- 没有声明周期钩子函数,不能使用计算属性,watch
- 不能通过$emit对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
- 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
- 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到 $attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
优点:
- 由于函数组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
- 函数式组件结构比较简单,代码结构更清晰
缺点:
- 一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
- 高阶组件---用于接收一个组件作为参数,返回一个被包装过的组件
可能的追问
-
如何响应动态路由参数的变化
https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#响应路由参数的变化
-
我们如何处理404 Not Found路由
https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#捕获所有路由或-404-not-found-路由
73.vue中data的属性可以和methods中方法同名吗,为什么?
可以同名,methods的方法名会被data的属性覆盖;调试台也会出现报错信息,但是不影响执行;
原因:源码定义的initState函数内部执行的顺序:props>methods>data>computed>watch
70.子组件可以直接改变父组件的数据吗?说明原因
vue中子组件修改父组件中传入的数据_全村最肉的人的博客-CSDN博客_vue父组件修改子组件中数据
19.从0到1自己构架一个vue项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织
思路:
-
构建项目,创建项目基本结构
-
引入必要的插件:
-
代码规范:prettier,eslint
-
提交规范:husky,lint-staged
-
其他常用:svg-loader,vueuse,nprogress
-
常见目录结构
回答规范:
-
从0创建一个项目我大致会做以下事情:项目构建、引入必要插件、代码规范、提交规范、常用库和组件
-
目前vue3项目我会用vite或者create-vue创建项目
-
接下来引入必要插件:路由插件vue-router、状态管理vuex/pinia、ui库我比较喜欢element-plus和antd-vue、http工具我会选axios
-
其他比较常用的库有vueuse,nprogress,图标可以使用vite-svg-loader
-
下面是代码规范:结合prettier和eslint即可
-
最后是提交规范,可以使用husky,lint-staged,commitlint
实际工作中,你总结的vue最佳实践有哪些?
思路:
查看vue官方文档:
风格指南:https://vuejs.org/style-guide/
性能:https://vuejs.org/guide/best-practices/performance.html#overview
安全:https://vuejs.org/guide/best-practices/security.html
访问性:https://vuejs.org/guide/best-practices/accessibility.html
发布:https://vuejs.org/guide/best-practices/production-deployment.html
回答范例
从编码风格、性能、安全等方面说几条:
-
编码风格方面:
- 命名组件时使用“多词”风格避免和HTML元素冲突
- 使用“细节化”方式定义属性而不是只有一个属性名
- 属性名声明时使用“驼峰命名”,模板或jsx中使用“肉串命名”
- 使用v-for时务必加上key,且不要跟v-if写在一起
-
性能方面:
- 路由懒加载减少应用尺寸
- 利用SSR减少首屏加载时间
- 利用v-once渲染那些不需要更新的内容
- 一些长列表可以利用虚拟滚动技术避免内存过度占用
- 对于深层嵌套对象的大数组可以使用shallowRef或shallowReactive降低开销
- 避免不必要的组件抽象
-
安全:
- 不使用不可信模板,例如使用用户输入拼接模板:
template: <div> + userProvidedString + </div>
- 小心使用v-html,:url,:style等,避免html、url、样式等注入
- 不使用不可信模板,例如使用用户输入拼接模板:
72.怎么实现路由懒加载呢
当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才会加载对应的组件,这样更高效
结合Vue的异步组件和Webpack的代码分割功能,轻松实现路由组件的懒加载
首先,可以将异步组件定义为返回一个Promise的工厂函数
const Foo = () => Promise.resolve({/*组件定义对象*/})
第二:在webpack中,可以使用动态
几种路由懒加载 实现方式 - Kira的学习笔记 - 博客园
什么是生命周期hook?列出一些生命周期hook
vue实例从初始化到销毁和删除的都经理生命周期,在整个过程中,vue允许开发人员运自定义函数的几个阶段,这些函数称为生命周期hook.
以下是一些生命周期 hook 的列表:
created,mounted,updated,destoryed
从0-1自己架构一个vue项目,说说有哪些步骤,哪些重要插件,目录结构你会怎么组织
实际工作中,你总结的vue实践有哪些?
使用vue渲染大量数据时应该怎么优化?说下你的思路
你了解diff算法吗
在什么场景下或用到嵌套路由
路由学习之嵌套路由_实习期小潘的博客-CSDN博客_如何理解嵌套路由
为什么vue和react都选择了hook?
17.scss是什么?在vue.cli中的安装使用步骤是?有那几大特性?
css的预编译
第一步:先装css-loader、node-loader、sass-loader等加载器模块
第二步:在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展.scss
第三步:在同一个文件,配置一个module属性
第四步:然后在组件的style标签加上lang属性 ,例如:lang=”scss”
特性:
可以用变量,例如($变量名称=值);
可以用混合器,例如()
可以嵌套
Vue.js中标签如何绑定事件?
[js]vue中通过ref获取到元素,如何给元素绑定点击事件? - mmaotai - 博客园
谈谈你对keep-alive的了解
diff算法
介绍一下SPA以及SPA有什么缺点?
Vue双向绑定和单向绑定
props和data优先级谁高?
谁先执行?props还是data或是其他? vue组件初始化的执行顺序详解_HzDoctor的博客-CSDN博客
如何定义router-link组件的属性?
Vue中router-link组件的属性_dreamingbaobei3的博客-CSDN博客
v-model是什么?有什么作用?
v-model是什么?怎么使用?_啵雨的博客的博客-CSDN博客_v-model
请描述封装Vue组件的作用工程。
Vue封装组件的过程_感恩、奋进、自信-CSDN博客_vue封装组件
vue-loader是什么?它的用途有哪些?
vue-loader是什么?使用它的用途有哪些? - 让心去旅行 - 博客园
描述Vue.js的一些 特性?
描述Vue.js的特点
sass是什么?如何在Vue中安装和使用?
在vue中安装使用sass的方法_liuxing393724034的博客-CSDN博客_vue 安装sass
如何在Vue.js中循环插入图片?
vue的 v-for 循环中图片加载路径问题 - 前端海 - 博客园
如何为选框元素自定义绑定数据值?
vue的checkbox多选框获取其是否选中和获取其自定义属性值 - 唠叨的意志 - 博客园
什么情况下会产生片段实例?
在以下情况下会产生片段实例模板包含多个顶级元素;模板只包含普通文本;模板只包含其他组件(其他组件可能是一个片段实例);模板只包含一个元素指令,如vue- router的< router-view>;模板根节点有一个流程控制指令,如v-if或v-for。
这些情况让实例有未知数量的顶级元素,模板将把它的DOM内容当作片段。片段实例仍然会正确地渲染内容。不过,模板没有一个根节点,它的$el指向一个锚节点,即一个空的文本节点(在开发模式下是一个注释节点)。
注意:在Vue2.0中,组件的模板只允许有且只有一个根节点。
2021最新VueJS面试题(附答案)_shi_xingwen的博客-CSDN博客_vue面试题2021
44.axios和ajax优缺点的理解
axios和ajax优缺点的理解_changbozizf的博客-CSDN博客_axios优点
实现多个根据不同条件显示不同文字的方法。
通过“v-if,v-else”指令可以实现条件选择。但是,如果是多个连续的条件选择,则需要用到计算属性computed。例如在输入框中未输入内容时,显示字符串‘请输入内容’,否则显示输入框中的内容,代码如下。
<div id="app">
<input type="text v-model="inputvalue">
<hl> { { showValue } }</h1>
</div>
var app = new Vue ( {
e1:"#app',
data:{
inputvalue:' '
computed: {
showValue:function ( ) {
return this. inputvalue | | '请输入内容'
}
}
})
setup和created谁先执行?
setup中为什么没有beforeCreate和created?
换肤都做过什么处理,有没有处理过可能改变尺寸的换肤
110.简述vue的事件机制? (不用考)
vue事件分两类:自定义事件和系统事件
自定义事件也被称为 $emit
/ $on
事件。父组件可以通过 $emit
触发自定义事件,子组件可以通过 $on
监听自定义事件。
原理: 发布-订阅者模式
使用
- on注册事件,
- on注册事件,emit分发事件,
- off删除注册的事件,
- off删除注册的事件,once分发一次后删除.
i18n在团队内部都做过哪些实践
==================
理解响应式和命令式
响应式:数据和视图是分离的;命令式:数据和视图,还有JS逻辑代码是混在一起的
106.native的修饰符的作用(不用看)
在项目开发中如果你用到了自己自定义的组件,并且想给他绑定一个点击事件,当我们点击的时候发现没反应。这时候就是.native发挥作用的时候了,他可以将事件绑定到根组件上,也就是绑到子组件的<button>上,这样click事件就会生效
// 这里是父组件里
<template>
<div>
<my-button @click='submitClick'>提交</my-button>
</div>
</template>
<script>
import myButton from '../components/button.vue'
</script>
<my-button @click.native='submitClick'>提交</my-button>