vue面试题
v-if和v-for一起使用的弊端以及解决办法
v-for的优先级比v-if高,导致每循环一次就会去v-if一次,而v-if是通过创建和销毁dom元素
来控制元素的显示与隐藏,所以就会不停的去创建和销毁元素,造成页面卡顿,性能下降。
解决办法:
1.在v-for的外层包裹一个标签来使用v-if
2.将需要的判断在computed里处理,然后在放到v-for里
vue 中的性能优化
1、Vue 应用运行时性能优化措施
(1)引入生产环境的 Vue 文件
(2)使用单文件组件预编译模板
(3)提取组件的 CSS 到单独到文件
(4)利用Object.freeze()提升性能
(5)扁平化 Store 数据结构
(6)合理使用持久化 Store 数据
(7)组件懒加载
2、Vue 应用加载性能优化措施
(1)服务端渲染 / 预渲染
(2)组件懒加载
Vue 的双向数据绑定的原理
VUE 实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了
对象获取属性值(get)和设置属性值(set)的操作来实现的。
Vue3.0 将用原生 Proxy 替换 Object.defineProperty
为什么要替换 Object.defineProperty?(Proxy 相比于 defineProperty 的优势)
在 Vue 中,Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标
给数组设置值,不能实时响应。
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
Vue 2.x里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么
需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。
而要取代它的Proxy有以下两个优点;
可以劫持整个对象,并返回一个新对象
有13种劫持操作
既然Proxy能解决以上两个问题,而且Proxy作为es6的新属性在vue2.x之前就有了,为什么vue2.x
不使用Proxy呢?一个很重要的原因就是:
Proxy是es6提供的新特性,兼容性不好,最主要的是这个属性无法用polyfill来兼容
什么是 Proxy?
1.含义:
Proxy 是 ES6 中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。
Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,
因此提供了一种机制,可以对外界的访问进行过滤和改写。
使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前
记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需
关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
2.基本用法:
let p = new Proxy(target, handler);
参数:
target 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的
行为的函数。
p 是代理后的对象。当外界每次对 p 进行操作时,就会执行 handler 对象上的一些方法。
Proxy共有13种劫持操作,handler代理的一些常用的方法有如下几个:
get:读取
set:修改
has:判断对象是否有该属性
construct:构造函数
对于 Vue 是一套渐进式框架的理解
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
1、使用 vue,你可以在原有大系统的上面,把一两个组件改用它实现,当 jQuery 用;
2、也可以整个用它全家桶开发,当 Angular 用;
3、还可以用它的视图,搭配你自己设计的整个下层用。你可以在底层数据逻辑的地方用 OO(Object–Oriented )面向对象和设计模式的那套理念。 也可以函数式,都可以。
它只是个轻量视图而已,只做了自己该做的事,没有做不该做的事,仅此而已。
你不必一开始就用 Vue 所有的全家桶,根据场景,官方提供了方便的框架供你使用。
场景联想 场景 1: 维护一个老项目管理后台,日常就是提交各种表单了,这时候你可以把 vue 当成一个 js 库来使用,就用来收集 form 表单,和表单验证。
场景 2: 得到 boss 认可, 后面整个页面的 dom 用 Vue 来管理,抽组件,列表用 v-for 来循环,用数据驱动 DOM 的变化
场景 3: 越来越受大家信赖,领导又找你了,让你去做一个移动端 webapp,直接上了 vue 全家桶!
场景 1-3 从最初的只因多看你一眼而用了前端 js 库,一直到最后的大型项目解决方案。
vue.js 的两个核心是什么?
数据驱动和组件化思想
v-on 可以监听多个方法吗?
肯定可以的。
解析:
<input
type="text"
:value="name"
@input="onInput"
@focus="onFocus"
@blur="onBlur"
/>
vue 中 key 值的作用
需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点 所以一句话,key 的作用主要是为了高效的更新虚拟 DOM
$nextTick 的使用
1、什么是 Vue.nextTick()?
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
所以就衍生出了这个获取更新后的 DOM 的 Vue 方法。所以放在 Vue.nextTick()回调函数中的执行的应该是会对 DOM 进行操作的 js 代码;
理解:nextTick(),是将回调函数延迟在下一次 dom 更新数据后调用,简单的理解是:当数据更新了,在 dom 中渲染后,自动执行该函数,
<template>
<div class="hello">
<div>
<button id="firstBtn" @click="testClick()" ref="aa">{{testMsg}}</button>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
testMsg:"原始值",
}
},
methods:{
testClick:function(){
let that=this;
that.testMsg="修改后的值";
console.log(that.$refs.aa.innerText); //that.$refs.aa获取指定DOM,输出:原始值
}
}
}
</script>
使用 this.$nextTick()
methods:{
testClick:function(){
let that=this;
that.testMsg="修改后的值";
that.$nextTick(function(){
console.log(that.$refs.aa.innerText); //输出:修改后的值
});
}
}
注意:Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM,
2、什么时候需要用的 Vue.nextTick()??
1、Vue 生命周期的 created()钩子函数进行的 DOM 操作一定要放在 Vue.nextTick()的回调函数中,原因是在 created()钩子函数执行的时候 DOM 其实并未进行任何渲染,而此时进行 DOM 操作无异于徒劳,所以此处一定要将 DOM 操作的 js 代码放进 Vue.nextTick()的回调函数中。与之对应的就是 mounted 钩子函数,因为该钩子函数执行时所有的 DOM 挂载已完成。
created(){
let that=this;
that.$nextTick(function(){ //不使用this.$nextTick()方法会报错
that.$refs.aa.innerHTML="created中更改了按钮内容"; //写入到DOM元素
});
}
2、当项目中你想在改变 DOM 元素的数据后基于新的 dom 做点什么,对新 DOM 一系列的 js 操作都需要放进 Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用 js 操作新的视图的时候需要使用它
<template>
<div class="hello">
<h3 id="h">{{testMsg}}</h3>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
testMsg:"原始值",
}
},
methods:{
changeTxt:function(){
let that=this;
that.testMsg="修改后的文本值"; //vue数据改变,改变dom结构
let domTxt=document.getElementById('h').innerText; //后续js对dom的操作
console.log(domTxt); //输出可以看到vue数据修改后DOM并没有立即更新,后续的dom都不是最新的
if(domTxt==="原始值"){
console.log("文本data被修改后dom内容没立即更新");
}else {
console.log("文本data被修改后dom内容被马上更新了");
}
},
}
}
</script>
正确的用法是:vue 改变 dom 元素结构后使用 vue.$nextTick()方法来实现 dom 数据更新后延迟执行后续代码
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内容被马上更新了");
}
});
}
3、在使用某个第三方插件时 ,希望在 vue 生成的某些 dom 动态发生变化时重新应用该插件,也会用到该方法,这时候就需要在 $nextTick 的回调函数中执行重新应用插件的方法。
Vue.nextTick(callback) 使用原理:
原因是,Vue 是异步执行 dom 更新的,一旦观察到数据变化,Vue 就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个 watcher 被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和 DOm 操作。而在下一个事件循环时,Vue 会清空队列,并进行必要的 DOM 更新。 当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的 DOM 更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
vue 中子组件调用父组件的方法
第一种方法是直接在子组件中通过 this.$parent.event 来调用父组件的方法
第二种方法是在子组件里用$emit 向父组件触发一个事件,父组件监听这个事件就行了
第三种是父组件把方法传入子组件中,在子组件里直接调用这个方法
vue 中父组件调用子组件的方法
使用$refs
解析:
父组件
<template>
<div>
<button @click="clickParent">点击</button>
<child ref="mychild"></child>
</div>
</template>
<script>
import Child from "./child";
export default {
name: "parent",
components: {
child: Child
},
methods: {
clickParent() {
this.$refs.mychild.parentHandleclick("嘿嘿嘿"); // 划重点!!!!
}
}
};
</script>
子组件
<template>
<div>
child
</div>
</template>
<script>
export default {
name: "child",
props: "someprops",
methods: {
parentHandleclick(e) {
console.log(e);
}
}
};
</script>
vue 中 keep-alive 组件的作用
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
解析:
用法也很简单:
<keep-alive>
<component>
<!-- 该组件将被缓存! -->
</component>
</keep-alive>
props _ include - 字符串或正则表达,只有匹配的组件会被缓存 _ exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
// 组件 a
export default {
name: "a",
data() {
return {};
}
};
<keep-alive include="a">
<component>
<!-- name 为 a 的组件将被缓存! -->
</component> </keep-alive
>可以保留它的状态或避免重新渲染
<keep-alive exclude="a">
<component>
<!-- 除了 name 为 a 的组件都将被缓存! -->
</component> </keep-alive
>可以保留它的状态或避免重新渲染
但实际项目中,需要配合 vue-router 共同使用.
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
<keep-alive>
<router-view>
<!-- 所有路径匹配到的视图组件都会被缓存! -->
</router-view>
</keep-alive>
如果只想 router-view 里面某个组件被缓存,怎么办?
增加 router.meta 属性
// routes 配置
export default [
{
path: "/",
name: "home",
component: Home,
meta: {
keepAlive: true // 需要被缓存
}
},
{
path: "/:id",
name: "edit",
component: Edit,
meta: {
keepAlive: false // 不需要被缓存
}
}
];
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!-- 这里是会被缓存的视图组件,比如 Home! -->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 这里是不被缓存的视图组件,比如 Edit! -->
</router-view>
vue 更新数组时触发视图更新的方法
Vue.set 可以设置对象或数组的值,通过 key 或数组索引,可以触发视图更新
数组修改
Vue.set(array, indexOfItem, newValue)
this.array.$set(indexOfItem, newValue)
对象修改
Vue.set(obj, keyOfItem, newValue)
this.obj.$set(keyOfItem, newValue)
2.Vue.delete 删除对象或数组中元素,通过 key 或数组索引,可以触发视图更新
数组修改
Vue.delete(array, indexOfItem)
this.array.$delete(indexOfItem)
对象修改
Vue.delete(obj, keyOfItem)
this.obj.$delete(keyOfItem)
3.数组对象直接修改属性,可以触发视图更新
this.array[0].show = true;
this.array.forEach(function(item){
item.show = true;
});
4.splice 方法修改数组,可以触发视图更新
this.array.splice(indexOfItem, 1, newElement)
5.数组整体修改,可以触发视图更新
var tempArray = this.array;
tempArray[0].show = true;
this.array = tempArray;
6.用 Object.assign 或 lodash.assign 可以为对象添加响应式属性,可以触发视图更新
//Object.assign的单层的覆盖前面的属性,不会递归的合并属性
this.obj = Object.assign({},this.obj,{a:1, b:2})
//assign与Object.assign一样
this.obj = _.assign({},this.obj,{a:1, b:2})
//merge会递归的合并属性
this.obj = _.merge({},this.obj,{a:1, b:2})
7.Vue 提供了如下的数组的变异方法,可以触发视图更新
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
JS面试题
let newList = [1, 2, 3].push(4); // 4 .push()方法返回数组的长度,而不是数组的本身。
let name = "Lydia";
function getName() {
console.log(name);
let name = "Sarah";
}
getName();