vue高频面试题(一)(下)

简介: vue高频面试题(一)(下)

vue高频面试题(一)(上):https://developer.aliyun.com/article/1414994

9.什么是$nextTick?

简单回答:因为Vue的异步更新队列,$nextTick是用来知道什么时候DOM更新完成的。

异步更新队列:指的是当状态发生变化时,Vue异步执行DOM更新。

DOM的异步更新:

<template>
    <div>
        <div ref="test">{{test}}</div>
        <button @click="handleClick">tet</button>
    </div>
</template>
export default {
    data () {
        return {
            test: 'begin'
        };
    },
    methods () {
        handleClick () {
            this.test = 'end';
            console.log(this.$refs.test.innerText);//打印“begin”
        }
    }
}

vue官方文档中是这样说的:

可能你还没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变化。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个事件循环在“tick”中,Vue刷新队列并执行实际(已去重)的工作

简而言之,就是在一个事件循环中发生的所有数据改变都会在下一个事件循环的Tick中来触发视图更新,这也是一个“批处理”的过程。(注意下一个事件循环的Tick有可能是在当前的Tick微任务执行阶段执行,也可能是在下一个Tick执行,主要取决于nextTick函数到底是使用Promise/MutationObserver还是setTimeout)

为什么要异步更新视图

看下面的代码:

<template>
    <div>
        <div>{{test}}</div>
    </div>
</template>
export default {
    data () {
        return {
            test: 0
        };
    },
    mounted () {
        for(let i = 0; i < 1000; i++) {
            this.test++;
        }
    }
}

现在有这样的一种情况,mounted的时候test的值会被++循环执行 1000 次。 每次++时,都会根据响应式触发setter->Dep->Watcher->update->run。 如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。

所以Vue实现了一个queue队列,在下一个Tick(或者是当前Tick的微任务阶段)的时候会统一执行queue中Watcher的run。同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行 1000 次Watcher的run。最终更新视图只会直接将test对应的DOM的 0 变成 1000 。 保证更新视图操作DOM的动作是在当前栈执行完以后下一个Tick(或者是当前Tick的微任务阶段)的时候调用,大大优化了性能。

Vue会根据当前浏览器环境优先使用原生的Promise.then和MutationObservery以及setImmediate,如果都不支持,就会采用setTimeout代替。

应用场景

在操作DOM节点无效的时候,就要考虑操作的实际DOM节点是否存在,或者相应的DOM是否被更新完毕。

  • 在created钩子中涉及DOM节点的操作肯定是无效的,因为此时还没有完成相关DOM的挂载。
    解决的方法就是在nextTick函数中去处理DOM,这样才能保证DOM被成功挂载而有效操作。
  • 在数据变化之后要执行某个操作,而这个操作需要使用随数据改变而改变的DOM时,这个操作应该放进Vue.nextTick。
  • 获取this.$refs.refName 返回undefined时,证明组件可能尚未存在,这时候可以在Vue.nextTick获取。

10.Vue 组件中 data 为什么必须是函数

因为一个组件是可以共享的,但它们的data是私有的,所以每个组件都return一个新的data对象,返回一个唯一的对象,不要和其他组件共用一个对象。

Vue.component('my-component', {
    template: '<div>OK</div>',
    data() {
        return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
    },
})

实际上,它的使用过程如下:

  • 首先需要创建一个组件构造器
  • 然后注册组件
  • 注册组件的本质其实就是建立一个组件构造器的引用
  • 使用组件才是真正的创建一个组件实例
  • 所以,注册组件其实并不是产生新的组件类,但会产生一个可以用用来实例化的新方式

理解这个过程之后,在理解js的原型链:

var MyComponent = function() {}
MyComponent.prototype.data = {
    a: 1,
    b: 2, 
}

上面是一个虚拟的组件构造器,真实的组件构造器方法很多:

var component1 = new MyComponent()
var component2 = new MyComponent()

上面实例化出来两个组件实例,也就是通过调用,创建的两个实例

component1.data.a === component2.data.a // true
component1.data.b = 5
component2.data.b // 5

从上面的代码可以看出,如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改。两个实例应该自己各有自己的实例域才对,做更改:

var MyComponent = function() {
    this.data = this.data()
}
MyComponent.prototype.data = function() {
    return {
        a: 1,
        b: 2,
    }
}

这样每一个实例的data属性都是独立的,不会相互影响了。所以,你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关。

11.v-for 与 v-if 的优先级

当它们处于同一节点,v-for的优先级比v-if更高,这意味着 v-if将分别重复运行于每个 v-for循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

上面的代码只传递了未完成的 todos。 而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if置于外层元素 (或 < template>)上。如:

<ul v-if="todos.length">
    <li v-for="todo in todos">
    {{ todo }}
    </li>
</ul>
<p v-else>No todos left!</p>

注意:当你只想条件性的渲染列表中的某些项的时候建议使用computed进行过滤后使用v-for进行渲染,而不是v-for和v-if同时使用。

四、Vue.js核心知识点高频试题二

4.1 组件传参

props/$emit

组件是Vue.js中最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:

  • 父子关系
  • 兄弟关系
  • 个贷关系

父组件向子组件传值

子组件:

<template>
    <div>
        <ul>
            //遍历传递过来的值,然后呈现到页面
            <li v-for="user in users" :key="user">{{user}}</li>
        </ul>
    </div>
</template>
<script>
export default {
    name: "zizujian",
    props: {
        users: { //这个就是父组件中子标签自定义名字
            type: Array,
            require: true
        }
    }
};
</script>

父组件

<template>
    <div>
        <User :users="users"></User>
    </div>
</template>
<script>
import User from "../zizujian";
export default {
    name: "fuzujian",
    components: { User },
    data() {
        return {
            users: ["ma", "nan", "nan"]
        };
    }
};

总结:父组件通过 props向下传递数据给子组件。

注:组件中的数据共有三种形式: data 、 props、 computed

子组件向父组件传值(通过事件形式)

子组件

<template>
    <header>
        <h1 @click="changeTitle">{{title}}</h1>
    </header>
</template>
<script>
export default {
    name: "zizujian",
    data() {
        return {
            title: "Vue.js Demo"
        };
    },
    methods: {
        changeTitle() {
            //自定义事件,传递值“子组件向父组件传值”
            this.$emit("titleChanged", "子组件向父组件传值"); 
        }
    }
}

父组件

<template>
    <div>
        <Header @titleChanged="updateTitle"></Header>//与子组件titleChanged自定义事件保持一致
        <h2>{{title}}</h2>
    </div>
</template>
<script>
import Header from "../zizujian";
export default {
    name: "fuzujian",
    components: { Header },
    data() {
        return {
            title: "传递的是一个值"
        };
    },
    methods: {
        updateTitle(e) {
            this.title = e;
        }
    }
};

总结:子组件通过 event给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。

$emit/$on

这种方式通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。

当我们的项目比较大时,可以选择更好的转台管理解决方案Vuex

具体实现:

var Event=new Vue();
// 发送数据
Event.$emit(事件名,数据);
// 接收数据
Event.$on(事件名,data => {});

vuex

vuex适用于一个状态需要在多个组件中同时使用的场景。避免传来传去逻辑复杂难以维护。

$attrs/$listeners

多级组件嵌套传递数据,仅仅是传递数据,不做中间处理

$attrs:包含了父作用域中不被 prop所识别(且获取)的特性绑定。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定,并且可以通过 v-bind="$attrs" 传入内部组件。通常配合inheritAttrs选项一起使用。

$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

// index.vue
<template>
    <div>
        <h2>浪里行舟</h2>
        <child-com
        :foo="foo"
        :boo="boo"
        :coo="coo"
        :doo="doo"
        title="前端工匠"
        ></child-com1>
    </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
    components: { childCom1 },
    data() {
        return {
            foo: "Javascript",
            boo: "Html",
            coo: "CSS",
            doo: "Vue"
        };
    }
};
</script>
// childCom1.vue
<template class="border">
    <div>
        <p>foo: {{ foo }}</p>
        <p>childCom1的$attrs: {{ $attrs }}</p>
        <child-com2 v-bind="$attrs"></child-com2>
    </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
    components: {childCom},
    inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
    props: {
        foo: String // foo作为props属性绑定
    },
    created() {
        console.log(this.$attrs);
        // { "boo": "Html", "coo": "CSS", "doo":"Vue", "title": "前端工匠" }
    }
};
</script>
// childCom1.vue
<template class="border">
    <div>
        <p>foo: {{ foo }}</p>
        <p>childCom1的$attrs: {{ $attrs }}</p>
        <child-com2 v-bind="$attrs"></child-com2>
    </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
    components: {childCom},
    inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
    props: {
        foo: String // foo作为props属性绑定
    },
    created() {
        console.log(this.$attrs);
        // { "boo": "Html", "coo": "CSS", "doo":"Vue", "title": "前端工匠" }
    }
};
</script>
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
    components: {childCom},
    inheritAttrs: false,
    props: {
        boo: String
    },
    created() {
        console.log(this.$attrs); 
        // { "coo": "CSS", "doo": "Vue", "title":"前端工匠" }
    }
};
</script>

Vue2.4提供了$attrs/$listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。

简单来说:$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。

provide/inject

Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。

provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件

// childCom3.vue
<template>
    <div class="border">
    <p>childCom3: {{ $attrs }}</p>
    </div>
</template>
<script>
export default {
    props: {
        coo: String,
        title: String
    }
};
</script>

 

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

 

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的

所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是"浪里行舟"。

provide与 inject如何实现数据响应式

一般来说,有两种办法:

  • provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
  • 使用2.6最新API Vue.observable 优化响应式 provide(推荐)

我们来看个例子:孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F会跟着变(核心代码如下:)

//A 组件
<div>
    <h1>A 组件</h1>
    <button @click="() => changeColor()">改变color</button>
    <ChildrenB />
    <ChildrenC />
</div>
......
data() {
    return {
        color: "blue"
    };
},
// provide() {
//  return {
//      theme: {
//          color: this.color //这种方式绑定的数据并不是可响应的
//      } // 即A组件的color变化后,组件D、E、F不会跟着变
//  };
// },
provide() {
    return {
        theme: this//方法一:提供祖先组件的实例
    };
},
methods: {
    changeColor(color) {
        if (color) {
            this.color = color;
        } else {
            this.color = this.color === "blue"? "red" : "blue";
        }
    }
}
// 方法二:使用2.6最新API Vue.observable 优化响应式 provide
 provide() {
     this.theme = Vue.observable({
        color: "blue"
     });
     return {
        theme: this.theme
     };
 },
 methods: {
     changeColor(color) {
         if (color) {
            this.theme.color = color;
         } else {
            this.theme.color = this.theme.color === "blue"? "red" :"blue";
         }
     }
 }
// F 组件
<template functional>
    <div class="border2">
        <h3 :style="{ color: injections.theme.color }">F 组件</h3>
      </div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函数式组件取值不一样
      default: () => ({})
    }
} };
</script>

虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!

$parent / $children与 ref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例

需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:

// component-a 子组件
export default {
    data () {
        return {
            title: 'Vue.js'
        }
    },
    methods: {
        sayHello () {
            window.alert('Hello');
        }
    }
}
// 父组件
<template>
    <component-a ref="comA"></component-a>
</template>
<script>
export default {
    mounted () {
        const comA = this.$refs.comA;
        console.log(comA.title); // Vue.js
        comA.sayHello(); // 弹窗
    }
}
</script>

不过,这两种方法的弊端是,无法在跨级或兄弟间通信。

// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>

我们想在 component-a 中,访问到引用它的页面中(这里就是parent.vue)的两个 component-b组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案。

组件传参场景总结

常见使用场景可以分为三类:

  • 父子通信: 父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
  • 兄弟通信: Bus;Vuex
  • 跨级通信: Bus;Vuex;provide / inject API、$attrs/$listeners

4.2 vue中 keep-alive 组件的作用

keep-alive:主要是用于保留组件状态和避免重新渲染

其是一个抽象组件(或称为功能性组件),实际上不会被渲染在DOM树中。它的作用是在内存中缓存组件(不让组件销毁),等到下次在渲染的时候,还会保持其中的所有状态,并且会触发 activated钩子函数

属性:(属性表示要缓存的组件名,即组件定义时的name属性)

  • include:字符串或正则表达式,只有匹配的组件会被缓存
  • exclude:字符串或正则表达式,任何匹配的组件都不会被缓存

被包含在 keep-alive中创建的组件,会多出两个生命周期的钩子: activated与 decativated

  • activated:在组件被激活时调用,在组件第一次渲染时也会被调用,之后每次 keep-alive激活时被调用
  • decativated:在组件被停用时调用。

注意:只有组件被 keep-alive 包裹时,这两个生命周期才会被调用,如果作为正常组件使用,是不会被调用,以及在 2.1.0 版本之后,使用 exclude 排除之后,就算被包裹在 keep-alive中,这两个钩子依然不会被调用!另外在服务端渲染时此钩子也不会被调用的。

实际应用:(下面的写法是需要将整个路由页面缓存下来的写法)

在App.vue中修改为如下:

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

在router/index.js中为每一个路由添加:

meta:{
    keepAlive:true/false //true代表这个页面(组件)需要缓存,false代表不需要
}

实际应用:(下面的写法是将组件在内存中进行缓存--没有设么意义)

<!-- 基本 -->
<keep-alive>
    <component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
    <comp-a v-if="a > 1"></comp-a>
    <comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
    <keep-alive>
        <component :is="view"></component>
    </keep-alive>
</transition>

注意:<keep-alive>是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染

include 和 exclude 属性的使用

include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
    <component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
    <component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
    <component :is="view"></component>
</keep-alive>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件components 选项的键值)。匿名组件不能被匹配。 不会在函数式组件中正常工作,因为它们没有缓存实例。

4.3 vue中如何编写可复用的组件?

在编写组件的时候,时刻考虑组件是否可复用是有好处的。一次性组件跟其他组件紧密耦合没关系,但是可复用组件一定要定义一个清晰的公开接口。

Vue.js组件 API 来自 三部分:prop、事件、slot:

  • prop 允许外部环境传递数据给组件,在vue-cli工程中也可以使用vuex等传递数据。
  • 事件允许组件触发外部环境的 action
  • slot 允许外部环境将内容插入到组件的视图结构内。

4.4 vue生命周期

1. 什么是vue生命周期和生命周期钩子函数?

vue的生命周期是:vue实例从创建到销毁,也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程。

在这个过程中也会运行一些叫做生命周期钩子的函数,这给用户在不同阶段添加自己的代码的机会。

2. vue生命周期钩子函数有哪些?

  • beforeCreate:在实例初始化之后,数据观测(data observer)和 event/watcher事件配置之前被调用
  • created:在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。然而挂载阶段还没有开始,$el属性目前不可见
  • beforeMount:在挂载开始之前,render函数首次被调用
  • mounted:el被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例被挂载了一个文档内元素,当mounted被调用时vm. el也在文档内
  • beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。这里 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有出此渲染会在服务器端进行。
  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
  • activated:keep-alive组件被激活的时候调用。该钩子在服务器端渲染期间不被调用
  • deactived:keep-alive组件停用的时候调用。该钩子在服务器端渲染期间不被调用
  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。该 钩子在服务器端渲染期间不被调用
  • destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器都会被移除,所有的子实例也会被消除。该钩子在服务器端渲染期间不被调用
  • errorCaptured(2.5.0新增):当捕获一个来自子孙组件的错误时被调用。此钩子会接收三个参数:错误对象,发生错误的组件实例以及一个 包含错误来源信息的字符串。此钩子可以返回false以组织该错误继续向上传播。

注意:

  1. mounted、updated不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick 替换掉mounted、updated:
  2. http请求建议在created 生命周期内发出

4.5 vue如何监听键盘事件中的按键

在监听键盘事件时,我们经常需要检查常见的键值。Vue允许为 v-on在监听键盘事件时添加按键修饰符:

<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:

<input v-on:keyup.enter="submit">
<!-- 缩写语法 -->
<input @keyup.enter="submit">

全部的按键别名:

.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

可以通过全局 config.keyCodes 对象自定义按键修饰符别名:

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

鼠标按钮修饰符:2.2.0 新增

.left
.right
.middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

4.6 vue更新数组时触发视图更新的方法

Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

替换数组:

例如:filter(), concat()和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用这些非变异方法时,可以用新数组替换旧数组:

example1.items = example1.items.filter(function (item) {
    return item.message.match(/Foo/)
})

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的、启发式的方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

注意事项:

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({
    data: {
        items: ['a', 'b', 'c']
    }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用vm.$set实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可以使用 splice:

vm.items.splice(newLength)

4.7 vue中对象更改检测的注意事项

由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

var vm = new Vue({
    data: {
        a: 1
    }
})
// `vm.a` 现在是响应式的
vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object,key, value)方法向嵌套对象添加响应式属性。例如,对于:

var vm = new Vue({
    data: {
        userProfile: {
            name: 'Anika'
        }
    }
})

你可以添加一个新的 age 属性到嵌套的 userProfile对象:

Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set实例方法,它只是全局Vue.set 的别名:

vm.$set(vm.userProfile, 'age', 27)

有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign()或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

Object.assign(vm.userProfile, {
    age: 27,
    favoriteColor: 'Vue Green'
})

应该这样做:

vm.userProfile = Object.assign({}, vm.userProfile, {
    age: 27,
    favoriteColor: 'Vue Green'
})

4.8 如何解决非工程化项目,网速慢时初始化页面闪动问题?

使用 v-cloak指令,v-cloak不需要表达式,它会在Vue实例结束编译时从绑定的HTML元素上移除,经常和CSS的display:none配合使用。

<div id="app" v-cloak>
    {{message}}
</div>
<script>
var app = new Vue({
    el:"#app",
    data:{
        message:"这是一段文本"
    }
})
</script>

这时虽然已经加了指令v-cloak,但其实并没有起到任何作用,当网速较慢、Vue.js 文件还没加载完时,在页面上会显示{{message}}的字样,直到Vue创建实例、编译模版时,DOM才会被替换,所以这个过程屏幕是有闪动的。只要加一句CSS就可以解决这个问题了:

<!--属性选择器-->
[v-cloak]{
    display:none;
}

在一般情况下,v-cloak是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用。

4.9 v-for产生的列表,如何实现active样式的切换?

通过设置当前 currentIndex + 动态样式实现:

<template>
    <div class="toggleClassWrap">
        <ul>
            <li @click="currentIndex = index" v-bind:class="
            {clicked: index === currentIndex}" v-for="(item, index) in desc"
            :key="index">
                <a href="javascript:;">{{item.ctrlValue}}</a>
            </li>
        </ul>
    </div>
</template>
<script type="text/javascript">
export default{
    data () {
        return {
            desc:[{
                ctrlValue:"test1"
                },{
                ctrlValue:"test2"
                },{
                ctrlValue:"test3"
                },{
                ctrlValue:"test4"
            }],
            currentIndex:0
        }
    }
}
</script>
<style type="text/css" lang="less">
.toggleClassWrap{
    .clicked{
        color:red;
    }
}
</style>

4.10 v-model语法糖的使用

使用v-model来进行双向数据绑定的时:

<input v-model="something">

仅仅是一个语法糖:

<input v-bind:value="something" v-on:input="something =
$event.target.value">

所以要组件的v-model生效,它必须:

  • 接受一个value属性
  • 在有新的value时触发input事件

五、Vue.js核心知识点 高频试题三

5.1 vue-cli工作中如何自定义一个过滤器?

在src目录下创建一个filter目录,在创建一个index.js文件,将过滤器们放在filter/index.js中

这个文件主要是写了过滤器实现的方法,然后export进行导出。

function filterOne(n){
    return n + 10;
}
function filterTwo(n){
    return n + 5;
}
export{
    filterOne,
    filterTwo
}

之后在main.js中:

import * as filters from './filter/filter.js'
//遍历所有导出的过滤器并添加到全局过滤器
Object.keys(filters).forEach((key) => {
    Vue.filter(key, filters[key]);
})

在.vue组件中使用:

{{test | filterOne}}

5.2 vue-cli工作中如何自定义一个指令

1. 什么是vue.js中的自定义指令

自定义一些指令对DOM进行操作

2. 自定义指令的几个钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

3. 钩子函数的参数

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:
  • name:指令名,不包括 v- 前缀。
  • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
  • oldValue:指令绑定的前一个值,仅在update和 componentUpdated钩子中可用。无论值是否改变都可用。
  • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
  • arg:传给指令的参数,可选。例如 v-my-directive:foo中,参数为 "foo"。
  • modiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为{ foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

4. 如何在vue-cli中使用自定义指令

文件结构:

├── src
│ ├── directives
│ │ ├── index.js
│ │ ├── modules
│ └── main.js
└── ...

在modules下新建foucs.js下

// 聚焦指令
export default {
    bind (el, binding, vnode) {},
    inserted (el, binding, vnode) {
        el.focus()
    },
    update (el, binding, vnode) {},
    componentUpdated (el, binding, vnode) {},
    unbind (el, binding, vnode) {}
    }

在src/directives/index.js下

import focus from './modules/focus'
export {focus}

在src/main.js下,使用directives自定义指令

//引入自定义指令
import * as directives from './directives'
//注册指令(全局注册)
Object.keys(directives).forEach(k => Vue.directive(k, directives[k]));

在.vue组件中使用

<input v-focus type="text" />

5.3 vue等单页面应用及其优缺点

单页Web应用(single page web application,SPA):就是只有一张Web页面的应用。

单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。

浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。因此,对单页应用来说模块化的开发和设计显得相当重要。

单页Web应用的优点:

  1. 良好的用户体验: 单页应用的内容的改变不需要重新加载整个页面,web应用更具响应性和更令人着迷。单页应用没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象
  2. 单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
  3. 良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。

单页Web应用的缺点:

  1. 不利于SEO,由于是采用前端渲染的方式,搜索引擎不会去解析Js从而只能够抓取首页未渲染的模板,如果需要单页面应用有更好的SEO,那么通常需要使用SSR服务端渲染,搜索引擎爬虫抓取工具可以直接查看完全渲染的页面,但是由于是服务端进行渲染,那么会对服务器造成一定压力,SSR服务端渲染属CPU密集型,当然如果只是需要SEO少数几个页面,可以采用预渲染的方式。
  2. 首次加载速度慢,SPA单页应用通常首次加载页面时就会将相应的HTML、JavaScript、CSS文件全部加载,通常可以通过采取缓存措施以及懒加载即按需加载组件的方式来优化。

5.4 什么是vue的计算属性(computed)和侦听属性(watch)

1. 计算属性

计算属性是自动监听依赖值的变化,从而动态返回内容,监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情。它有以下特点:

  • 数据可以进行逻辑处理,减少模板中计算逻辑
  • 对计算属性中的数据进行监视
  • 依赖固定的数据类型(响应式数据)

计算属性由两部分构成:get和set,分别用来获取计算属性和设置计算属性。默认只有get,如果需要set,要自己添加。另外set设置属性,并不是直接修改计算属性,而是修改它的依赖。

computed: {
    fullName: {
        // getter
        get: function () {
            return this.firstName + ' ' + this.lastName
        },
        // setter
        set: function (newValue) {
            //this.fullName = newValue 这种写法会报错
            var names = newValue.split(' ')
            this.firstName = names[0]//对它的依赖进行赋值
            this.lastName = names[names.length - 1]
        }
    }
}

计算属性VS普通属性:

可以像绑定普通属性一样在模板中绑定计算属性,在定义上的区别:计算属性的属性值必须是一个函数,函数必须有返回值,属性值就是函数的返回值

计算属性VS方法:

  1. 计算属性必须返回结果
  2. 计算属性是基于它的依赖缓存的。一个计算属性所依赖的数据发生变化时,它才会重新取值。
  3. 使用计算属性还是methods取决于是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性,除非你不希望得到缓存。
  4. 计算属性是根据依赖自动执行的,methods需要事件调用。

两者最主要的区别:computed 是可以缓存的,methods 不能缓存;只要相关依赖没有改变,多次访问计算属性得到的值是之前缓存的计算结果,不会多次执行。

网上有种说法就是方法可以传参,而计算属性不能,其实并不准确,计算属性可以通过闭包来实现传参:

:data="closure(item, itemName, blablaParams)"
computed: {
    closure () {
        return function (a, b, c) {
            /** do something */
            return data
        }
    }
}

2. 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性watch。watch中可以执行任何逻辑,如函数节流,Ajax异步获取数据,甚至操作 DOM(不建议)。

使用 watch 来监听数据变化的时候除了常用到 handler 回调,其实其还有两个参数,便是:

  • deep 设置为 true 用于监听对象内部值的变化
  • immediate 设置为 true 将立即以表达式的当前值触发回调

3. 两者之间的对比

  • watch:监测的是属性值,只要属性值发生变化,其都会触发执行回调函数来执行一系列操作。
  • computed:监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会重新计算。
  • computed:一个数据受多个数据影响
  • watch:一个数据影响多个数据

除此之外,有点很重要的区别是:计算属性不能执行异步任务,计算属性必须同步执行。也就是说计算属性不能向服务器请求或者执行异步任务。如果遇到异步任务,就交给侦听属性。watch也可以检测computed属性。

4. 总结

  • 计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
  • computed能做的,watch都能做,反之则不行。
  • 能用computed的尽量用computed

5.5 vue-cli开发环境使用全局常量

1.如何在组件中使用全局常量

第一步,在 src 下新建 const 文件夹下 新建 const.js

.
├── src
│ ├── const
│ │ ├── const.js
│ │
│ └── main.js
└── ...

第二步,在 const.js 文件下,设置常量

export default {   
    install(Vue,options){
        Vue.prototype.global = {
            title:'全局',
            isBack: true,
            isAdd: false,
        };
    }
}

第三步,在 main.js 下全局引入:

1. import constant from './const/const.js'
2. Vue.use(constant);

第四步,即可在 .vue 组件中使用:

//通过js方式使用:
this.global.title
//或在 html 结构中使用
{{global.title}}

2.在JS中使用常量

第一步,在 src 下新建 const 文件夹下 新建 type.js

.
├── src
│ ├── const
│ │ ├── type.js
│ │
│ └── main.js
└── ...

第二步,在 type.js 文件下,设置常量

export const TEST_INCREMENT='TEST_INCREMENT'
export const TEST_DEREMENT='TEST_DEREMENT'

第三步,在其他 .js 文件下引入并使用:

//以对象的形式引入:
import * as types from '../types'
//使用:
types.TEST_INCREMENT

5.6 父组件如何异步获取动态数据之后传递给子组件

  • v-if:开始的时候让子组件隐藏,然后等数据返回的时候,让子组件显示。(不过这种方式存在问题,当接口返回数据很慢的时候子组件延迟很久显示,这并不符合逻辑)
  • vuex
  • 利用vuex的辅助函数(mapState,mapMutations)mapState是将state里面的数据映射到计算中(computed),mapMutations也是类似,把vuex中mutations的方法映射到组件里面,就可以在组件里面直接使用方法了,在vuex中使用异步(actions)去掉用接口,然后在接口成功的函数里面取触发同步(mutations)里面的方法,把得到数据传给mutations里面的方法里并且给state里面的属性赋值,然后就可以在子组件中使用computed计算中去获取数据并且渲染到页面上
相关文章
|
4月前
|
JavaScript 前端开发 应用服务中间件
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
这篇文章分析了Vue项目在服务器部署后出现404错误的原因,主要是由于history路由模式下服务器缺少对单页应用的支持,并提供了通过修改nginx配置使用`try_files`指令重定向所有请求到`index.html`的解决方案。
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
|
4月前
|
JavaScript
【Vue面试题十五】、说说你对slot的理解?slot使用场景有哪些?
这篇文章深入探讨了Vue中的`slot`概念,包括它的定义、使用场景和分类(默认插槽、具名插槽和作用域插槽),并通过代码示例展示了如何在组件中使用插槽来实现内容的分发和自定义。同时,文章还对插槽的工作原理进行了分析,解释了`renderSlot`函数和`$scopedSlots`对象的角色。
【Vue面试题十五】、说说你对slot的理解?slot使用场景有哪些?
|
4月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
4月前
|
JavaScript 前端开发 数据处理
【Vue面试题二十八】、vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
这篇文章讨论了Vue中实现权限管理的策略,包括接口权限、路由权限、菜单权限和按钮权限的控制方法,并提供了不同的实现方案及代码示例,以确保用户只能访问被授权的资源。
【Vue面试题二十八】、vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
|
4月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
3月前
|
缓存 JavaScript 前端开发
vue面试题
vue面试题
173 64
|
2月前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
43 0
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
|
4月前
|
JavaScript 安全 前端开发
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
这篇文章介绍了Vue项目中解决跨域问题的方法,包括使用CORS设置HTTP头、通过Proxy代理服务器进行请求转发,以及在vue.config.js中配置代理对象的策略。
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
|
4月前
|
JavaScript 前端开发 编译器
【Vue面试题三十二】、vue3有了解过吗?能说说跟vue2的区别吗?
这篇文章介绍了Vue 3相对于Vue 2的改进和新增特性,包括性能提升、体积减小、更易维护、更好的TypeScript支持、新的Composition API、新增的Teleport和createRenderer功能,以及Vue 3中的非兼容性变更和API的移除或重命名。
【Vue面试题三十二】、vue3有了解过吗?能说说跟vue2的区别吗?
|
4月前
|
JavaScript 前端开发 API
【Vue面试题三十一】、你是怎么处理vue项目中的错误的?
这篇文章讨论了Vue项目中错误的处理方式,包括后端接口错误和代码逻辑错误的处理策略。文章详细介绍了如何使用axios的拦截器处理后端接口错误,以及Vue提供的全局错误处理函数`errorHandler`和生命周期钩子`errorCaptured`来处理代码中的逻辑错误。此外,还分析了Vue错误处理的源码,解释了`handleError`、`globalHandleError`、`invokeWithErrorHandling`和`logError`函数的作用和处理流程。
【Vue面试题三十一】、你是怎么处理vue项目中的错误的?