1.Vue生命周期:
Vue实例从创建到销毁的过程,就是声明周期。从创建实例、初始化数据、编译模板、挂载DOM(->渲染)、更新(->渲染)、卸载等一系列过程,我们称之为Vue的生命周期。
共分为八个阶段,如下:
创建前/后,载入前/后,更新前/后、销毁前/后。
- beforeCreate:可以在这加个loading事件,在加载实例时触发
- created:初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用。
- beforeMount
- mounted:挂载元素,获取到DOM节点。
- beforeUpdate
- updated:如果对数据统一处理,在这里写上相应函数。
- beforeDestroy:可以做一个确认停止事件的确认框。
- destroyed
2.Vue的生命周期的作用:
它的生命周期中有很多事件钩子函数,让我们在控制整个Vue实例的过程时,更容易形成好的逻辑。
生命周期钩子函数(11个) | 类型 | 详细 |
---|---|---|
beforeCreate | Function | 在实例初始化之后 ,数据观测(data observer)和event/watch事件配置之前被调用 |
created | Function | 在实例创建完成后 被立即调用。在这一步,实例已经完成以下配置:数据观测(data observer) ,属性和方法的运算 ,watch/event事件的回调 。然而挂载阶段还没开始,$el属性目前不可见。 |
beforeMount | Function | 在挂载开始之前 被调用,相关的render函数首次被调用 |
mounted | Function | el 被新创建的vm.$el 替换,并挂载到实例上之后 调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.$el也在文档内 |
beforeUpdate | Function | 数据更新时调用 ,发生在虚拟DOM打补丁之前。这是适合更新之前访问现有的DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染才会在服务器端运行 |
updated | Function | 由于数据更新导致的虚拟DOM重新渲染和打补丁 ,在这之后 会调用该钩子。 |
activated | Function | keep-alive组件激活时调用 。该钩子在服务器端渲染期间不被调用。 |
deactivated | Function | keep-alive组件停用时调用 。该钩子在服务器端渲染期间不被调用。 |
beforeDestory | Function | 实例销毁之前 调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。 |
destroyed | Function | 实例销毁后 调用。调用后,实例指示的所有东西都会解绑定,所有事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。 |
errorCaptured(2.5.0+ 新增) | (err: Error, vm: Component, info: string) => ?boolean | 当捕获一个来自子孙组件时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。 |
注意:
mounted
、updated
不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图
都渲染完毕
,可以用vm.$nextTick
替换掉mounted
、updated
:updated: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been re-rendered }) }
- http请求建议在
created
生命周期内发出。需要页面加载完成之后的话就用mounted
。
3.生命周期钩子是如何实现的?
Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法。
补充说明
内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部采用数组的方式存储),在对应的阶段进行发布。
4.Vue
的父组件和子组件生命周期钩子执行顺序?
核心答案:
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子。
渲染过程:
父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后
- 父beforeCreate ->
- 父created ->
- 父beforeMount ->
- 子beforeCreate ->
- 子created ->
- 子beforeMount ->
- 子mounted ->
- 父mounted
子组件更新过程:
影响到父组件:
- 父beforeUpdate ->
- 子beforeUpdate->
- 子updated ->
- 父updted
5.MVVM概念:Model-View-ViewModel
·MVVM的核心是ViewModel层,它就像是一个中转站,负责转换Model中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与Model层通过接口请求进行数据交互,起承上启下作用
- View层:View层是视图层,也就是用户界面。前端主要有HTML+CSS构建。
- Model层:Model是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的API接口。
ViewModel层:ViewModel是前端开发人员组织 生成和维护的视图数据层。在这一层,前端开发者对从后端获取的Model数据进行转换处理,做二次封装,以生成符合View层使用与其视图数据模型。需要注意的是ViewModel所封装出来的视图数据模型包括视图的状态和行为两部分,而Model层的数据模型只包含状态,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
//具体实现 //View层 <div id="app"> <p>{{message}}</p> <button v-on:click="showMessage()">Click me</button> </div> //ViewModel层 var app = new Vue({ el: '#app', data: { // 用于描述视图状态 message: 'Hello Vue!', }, methods: { // 用于描述视图行为 showMessage(){ let vm = this; alert(vm.message); } }, created(){ let vm = this; // Ajax 获取 Model 层的数据 ajax({ url: '/your/server/data/api', success(res){ vm.message = res; } }); } }) //Model层 { "url": "/your/server/data/api", "res": { "success": true, "name": "IoveC", "domain": "www.cnblogs.com" } }
6.Vue是如何实现数据双向绑定的:
·Vue数据双向绑定主要是指:数据变化更新视图,视图变化更新数据。
即:
- 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
- Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。
其中,View变化更新Data,可以通过事件监听的方式来实现。所以Vue的数据双向绑定的工作主要是如何根据Data变化更新View。
Vue主要通过以下四个步骤来实现数据双向绑定
:
- 实现一个
监听器Observer
:对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对属性都加上 setter和getter。这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到数据的变化了。 - 实现一个
解析器Compile
:解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。 - 实现一个
订阅者Watcher
:Watcher订阅者是监听器Observer和解析器Compile之间通信的桥梁,主要的任务是订阅Observer中的属性值变化的消息,当收到属性变化的消息时,触发解析器Compile中对应的更新函数。 - 实现一个
订阅器Dep
:订阅器采用 发布-订阅 设计模式,用来收集订阅者Watcher,对监听器Observer和解析器Compile进行统一管理。
7.虚拟Dom以及diff算法和patch函数**
1.虚拟Dom原理
:利用JS创建HTML,解析成真实的Dom节点appendChild进去,直接给root层减少Dom操作。更新的时候也是先创建虚拟Dom,然后通过diff算法去比较新旧虚拟Dom的差异,再使用snabbdom的h函数和patch函数去修补差异。
2.diff算法
:就是深度优先搜索。递归子节点,并且记录比较两个节点的差异。
8.vue
优点及缺点?
优点:
- 轻量级框架:
只关注视图层,是一个构建数据的视图集合,大小只有几十kb; - 数据绑定:
这也就是vue.js最大的优点,通过MVVM思想实现数据的双向绑定,通过虚拟DOM让我们可以用数据来操作DOM,而不必去操作真实的DOM,提升了性能。且让开发者有更多的时间去思考业务逻辑 - 组件化:
把一个单页应用中的各个模块拆分到一个个组件当中,或者把一些公共的部分抽离出来做成一个可复用的组件。所以组件化带来的好处就是,提高了开发效率,方便重复使用,使项目的可维护性更强。 - 虚拟DOM:
dom操作是非常耗费性能的, 不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;
运行速度更快:
相比较与react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势。
缺点:
- 不利于seo:
- 导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)。
- 初次加载时耗时多。
- 页面复杂度较高(单页面应用)
9.v-show和v-if的区别:
·编译过程:
v-if 是 真正 的 条件渲染,因为它会确保在切换过程中条件块内的事件监听器
和子组件
适当地被销毁
和重建
。v-show
的元素始终会
被渲染并保留在 DOM 中
。v-show
只是简单地切换
元素的 CSS 属性display
。
·编译条件:
v-if 是惰性的:如果在初始渲染时条件为假,则什么也不做。直到条件第一次变为真时,才会开始渲染条件块。v-show
不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
·性能消耗:
v-if有更高的切换消耗。v-show有更高的初始渲染消耗
。
·应用场景:
v-if适合运行时条件很少改变时使用。v-show适合频繁切换
10.v-for 与 v-if 的优先级
当它们处于同一节点时,v-for优先级高于v-if。
如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,应该替换成computed
属性。
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
如上情况,即使100个user中之需要使用一个数据,也会循环整个数组。
omputed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
11.绑定class的数组用法:
·对象方法:v-bind:class="{ active: isActive, 'text-danger': hasError }"
当isActive和hasError为true时,才会将active和text-danger两个样式添加到class中。
·数组方法:<div v-bind:class="[activeClass, errorClass]"></div>
·绑定内联样式:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
12.vue
组件间怎么传递数据?
父子间通信:父亲提供数据通过属性 props传给儿子;儿子通过 $on 绑父亲的事件,再通过 $emit 触发自己的事件(发布订阅)
利用父子关系 $parent 、$children
父组件提供数据,子组件注入。 provide 、 inject ,插件用得多。
ref 获取组件实例,调用组件的属性、方法
vuex 状态管理实现通信
祖孙节点可以使用: $attrs/$listeners
方法一
props/$emit
父组件A
通过props
的方式向子组件B
传递,B to A
通过在 B 组件中 $emit
, A
组件中 v-on
的方式实现。
1.父组件向子组件传值
接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据
userList:["Henry","Bucky","Emily"]
//App.vue父组件
<template>
<div id="app">
<hook-users
:userList="userList"/>
//前者自定义名称便于子组件调用,后者要传递数据名
</div>
</template>
<script>
import AppUsers from "./Components/AppUsers"
export default {
name: 'App',
data(){
return{
userList:["Henry","Bucky","Emily"]
}
},
components:{
"app-users":AppUsers
}
}
//users子组件
<template>
<div class="hello">
<ul>
//遍历传递过来的值,然后呈现到页面
<li v-for="user in userList">{{user}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'AppUsers',
props:{
userList:{
type:Array,
required:true
}
}
}
</script>
总结:
父组件通过props
向下传递数据给子组件。注:组件中的数据共有三种形式:data
、props
、computed
2.子组件向父组件传值(通过事件形式)
// 子组件
<template>
<header>
<h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件
</header>
</template>
<script>
export default {
name: 'AppHeader',
data() {
return {
title:"Vue.js Demo"
}
},
methods:{
changeTitle() {
this.$emit("titleChanged","子向父组件传值");
//自定义事件 传递值“子向父组件传值”
}
}
}
</script>
// 父组件
<template>
<div id="app">
<app-header v-on:titleChanged="updateTitle" ></app-header>
//与子组件titleChanged自定义事件保持一致
// updateTitle($event)接受传递过来的文字
<h2>{{title}}</h2>
</div>
</template>
<script>
import Header from "./components/Header"
export default {
name: 'App',
data(){
return{
title:"传递的是一个值"
}
},
methods:{
updateTitle(e){ //声明这个函数
this.title = e;
}
},
components:{
"app-header":Header,
}
}
</script>
总结:
子组件通过events
给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。
方法二、$emit/$on
这种方法通过一个空的Vue
实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex
。
1.具体实现方式:
var App=new Vue();
App.$emit(事件名,数据);
App.$on(事件名,data => {});
或者自己实现一个
class MyEventEmitter {
constructor() {
this.event = {};
}
// 监听
on(type, listener) {
if (this.event[type]) {
this.event[type].push(listener);
} else {
this.event[type] = [listener];
}
}
//发送监听
emit(type, ...rest) {
if (this.event[type]) {
this.event[type].map(fn => fn.apply(this, rest));
}
}
//移除监听器
removeListener(type) {
if (this.event[type]) {
delete this.event[type];
console.log(this.event);
}
}
//移除所有的监听器
removeAllListener() {
this.event = {};
}
}
var MyEvent=new MyEventEmitter();
MyEvent.$emit(事件名,数据);
MyEvent.$on(事件名,data => {});
但是这种方式,记得在每次触发监听的时候,记得移除上一个监听器
方法三、Vuex与localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
const jsonToString=(json)=>{
return JSON.stringify(json)
}
const stringToJson=(keyName)=>{
//暂不验证数据格式
return window.localStorage.getItem(keyName)?
JSON.parse(window.localStorage.getItem(keyName))
:{};
}
export default new Vuex.Store({
state: {
selectCity:stringToJson("selectCity")
},
mutations: {
changeCity(state, selectCity) {
state.selectCity = selectCity
try {
window.localStorage.setItem('selectCity',jsonToString(state.selectCity));
// 数据改变的时候把数据拷贝一份保存到localStorage里面
} catch (e) {}
}
}
})
方法四、$attrs/$listeners
场景
有些变态需求:比如说A父组件里面导入了B组件,可是B组件里面又导入了C组件,现在需要A父组件传值给C组件,或者是C组件需要传值给父组件,这时候就需要用到$attrs和$listeners了。
$attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。(父传孙专用)
$listener
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。(孙传父专用)
在父组件当中,最外层组件
<template>
<div>
<Child1
:child1Info="child1"
:child2Info="child2"
v-on:test1="onTest1"
v-on:test2="onTest2">
</Child1>
</div>
</template>
<script>
import Child1 from './child1';
export default {
data() {
return {
child1:"hahha",
child2:"asdsdasd"
};
},
components: { Child1 },
methods: {
onTest1(msg) {
console.log('test1 running...',msg);
},
onTest2(msg) {
console.log('test2 running',msg);
}
}
};
</script>
//在子组件中
<template>
<div class="child-1">
<p>在子组件当中:</p>
<p>props-child1Info: {{child1Info}}</p>
<p>$attrs: {{$attrs}}</p>
<hr>
<!-- Child2组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
<!-- 通过v-bind 绑定$attrs属性,Child2组件可以直接获取到A组件中传递下来的props(除了child1组件中props声明的) -->
<Child2 v-bind="$attrs" v-on="$listeners"></Child2>
</div>
</template>
<script>
import Child2 from './child2';
export default {
props: ['child1Info'],
data() {
return {};
},
components: { Child2 },
mounted() {
this.$emit('test1','嘻嘻');
}
};
</script>
//在孙子组件当中:
<template>
<div class="child-2">
<p>在最里层组件当中child2:</p>
<p>props-child2Info: {{child2Info}}</p>
<p> $attrs 的值: {{$attrs}}</p>
<hr>
</div>
</template>
<script>
export default {
props: ['child2Info'],
data() {
return {};
},
mounted() {
this.$emit('test2','哈哈');
}
};
</script>
13.computed
和 watch
的区别和运用的场景?
<1>Computed:
·computed是计算属性,更多用于计算值的场景
·computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算
·computed适用于计算比较消耗性能的计算场景
<2>Watch:
·更多的是观察的作用,类似于某些数据的监听回调,用于观察props、$emit或者本组件的值,当数据变化时来执行回调进行后续操作
·无缓存性,页面重新渲染时值不变化也会执行
<3>用法:
1. 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
2. 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
14.单页面应用和多页面应用区别及优缺点?
单页面应用(SPA
),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html
, js
, css
。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc
端。
多页面(MPA
),就是指一个应用中有多个页面,页面跳转时是整页刷新
单页面的优点:
1,用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小
2,前后端分离
3,页面效果会比较炫酷(比如切换页面内容时的专场动画)
单页面缺点:
1,不利于seo
2,导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)
3,初次加载时耗时多
4,页面复杂度提高很多
15.vue-cli工程中文件夹及文件的用处:
·build文件夹
:用于存放webpack相关配置和脚本。例如经常需要用到webpack.dev.config.js来配置less、sass、stylus等css预处理器,或者用来配置UI库;
·config文件夹
:主要存放配置文件,用于区分开发环境和生产环境的不同。常用到config.js配置开发环境的端口号、是否开启热加载或者设置生产环境的静态资源相对路径、是否开启gzip压缩、npm run build命令打包生成静态资源的名称和路径等。(webpack.*.config.js);
·dist文件夹
:存放npm run build打包生成的静态资源文件,用于生产部署;
·node_modules
:存放npm install下载的开发环境和生产环境的依赖包;
·src文件夹
:存放项目源码及需要引用的资源文件;
·src/assets
:存放项目中需要用到的资源文件,js、css、image等;
·src/components
:存放vue开发中一些公共组件;
·src/emit
:自己配置的vue集中式事件机制管理;
·src/router
:vue-router vue路由的配置文件;
·src/service
:自己配置的vue请求后台的接口方法;
·src/page
:存放vue页面组件;
·src/util
:存放公共js方法;
·src/vuex
:存放vuex vue的状态管理器;
·src/app.vue
:使用标签<router-view></router-view>渲染整个工程的.vue组件;
·src/main.js
:vue-cli工程的入口文件;
·src/index.html
:设置项目的一些meta头信息和提供<div id="app"></div>用于挂载vue节点;
·package.json
:用于管理node_modules依赖包和启动、打包项目的npm命令。
16.config文件夹 下 index.js 的对于工程 开发环境 和 生产环境 的配置:
·index
:配置打包后入口.html文件的名称以及文件夹名称;
·assetsRoot
:配置打包后生成的文件名称和路径;
·assetsPublicPath
:配置打包后.html引用静态资源的路径,一般要设置成"./";
·productionGzip
:是否开启GZip压缩,以提升加载速度
·port
:设置端口号;
·autoOpenBrowser
:启动工程时,自动打开浏览器;
·proxyTable
:vue设置的代理,以解决跨域问题
17.package.json配置:
·scripts
:配置npm run xxx命令调用node执行的.js文件;
·dependencies
:生产环境依赖包的名称和版本号,这些依赖包会被打包进生产环境的JS文件里面;
·devDependencies
:开发环境依赖包的名称和版本号,这些依赖包只用于代码开发的时候,不会打包生产环境的JS文件里面。
18、react
和vue
的区别
相同点
- 数据驱动页面提供响应式的试图组件
- 都有
virtual DOM
,组件化的开发通过props
参数进行父子之间组件传递数据都实现了webComponents
规范 - 数据流动单向都支持服务器的渲染SSR
- 都有支持
native
的方法react
有React native vue
有wexx
不同点
- 数据绑定
Vue
实现了双向的数据绑定react
数据流动是单向的 - 数据渲染大规模的数据渲染
react
更快 - 使用场景
React
配合Redux
架构适合大规模多人协作复杂项目Vue适合小快的项目 - 开发风格
react
推荐做法jsx
+inline style
把html
和css
都写在js
了 vue
是采用webpack
+vue-loader
单文件组件格式html
,js
,css
同一个文件
19.vue.js的两个核心:
·双向数据绑定
,也叫数据驱动,数据的改变会驱动视图的自动更新。传统的做法是需要手动改变DOM来使得视图更新,而vue只需要改变数据。
·组件
:组件化开发,优点很多,可以很好的降低数据之间的耦合度。将常用的代码封装成组件之后(vue组件封装方法),就能高度的复用,提高代码的可重用性。一个页面/模块可以由多个组件所组成。
20.Vue
性能优化方法
1)编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher;
- 如果需要使用v-for给每项元素绑定事件时使用事件代理;
- SPA 页面采用keep-alive缓存组件;
- 在更多的情况下,使用v-if替代v-show;
- key保证唯一;
- 使用路由懒加载、异步组件;
- 防抖、节流;
- 第三方模块按需导入;
- 长列表滚动到可视区域动态加载;
- 图片懒加载;
2)用户体验:
- 骨架屏;
- PWA;
- 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
3)SEO优化
- 预渲染;
- 服务端渲染SSR;
4)打包优化
- 压缩代码;
- Tree Shaking/Scope Hoisting;
- 使用cdn加载第三方模块;
- 多线程打包happypack;
- splitChunks抽离公共文件;
- sourceMap优化;
21.v-model
的原理
v-model
本质就是一个语法糖,可以看成是value + input方法的语法糖。可以通过model属性的prop和event属性来进行自定义。
原生的v-model,会根据标签的不同生成不同的事件和属性。
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
1)text 和 textarea 元素使用 value 属性和 input 事件;
2)checkbox 和 radio 使用 checked 属性和 change 事件;
3)select 字段将 value 作为 prop 并将 change 作为事件。
例子
model: {
prop: 'checked',
event: 'change'
}
如果想要更改 checked
这个 prop
可以在 Vue
的 instance
中用以下这行代码发送 change
这个 event
,并将目标的变动值传给 checked
这个 prop
。
this.$emit('change', $event.target.value);
22.nextTick
的实现原理是什么?
在下次 DOM
更新循环结束之后执行延迟回调。nextTick
主要使用了宏任务和微任务。根据执行环境分别尝试采用
- Promise
- MutationObserver
- setImmediate
- 如果以上都不行则采用setTimeout
定义了一个异步方法,多次调用nextTick
会将方法存入队列中,通过这个异步方法清空当前队列。
23.vue的常用指令:
·v-if
:根据表达式的布尔值对元素进行渲染。在切换时,元素及它的数据绑定/组件被销毁或重建;
·v-show
:根据表达式的布尔值,切换元素的display CSS属性值;
·v-for
:循环指令,基于一个数组或对象渲染一个列表。vue2.0以上必须配合key值使用;
·v-bind
:动态的绑定一个或多个特性,或一个组件prop到表达式;
·v-on
:用于监听指定元素的DOM事件,比如点击事件。绑定事件监听器;
·v-model
:实现表单输入和应用状态之间的双向绑定;
·v-pre
:跳过这个元素和它的子元素编译的过程。可以用来显示原始Mustache标签。跳过大量没有指令的节点会加快编译;
·v-once
:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点都被视为静态内容并跳过。这可以优化更新的性能。
24.如何理解自定义指令?
核心答案:
指令的实现原理,可以从编译原理 =>代码生成=> 指令钩子实现进行概述
1)在生成 ast 语法树时,遇到指令会给当前元素添加directives属性
2)通过 genDirectives 生成指令代码
3)在patch前将指令的钩子提取到 cbs中,在patch过程中调用对应的钩子。
4)当执行指令对应钩子函数时,调用对应指令定义的方法
25.vue常用的修饰符:
v-on 指令常用修饰符:
.stop - 调用 event.stopPropagation(),禁止事件冒泡。
.prevent - 调用 event.preventDefault(),阻止事件默认行为。
.capture - 添加事件侦听器时使用 capture 模式。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
.native - 监听组件根元素的原生事件。
.once - 只触发一次回调。
.left - (2.2.0) 只当点击鼠标左键时触发。
.right - (2.2.0) 只当点击鼠标右键时触发。
.middle - (2.2.0) 只当点击鼠标中键时触发。
.passive - (2.3.0) 以 { passive: true } 模式添加侦听器
v-bind 指令常用修饰符:
.prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。
.camel - (2.1.0+) 将 kebab-case 特性名转换为 camelCase.
.sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器。
v-model 指令常用修饰符:
.lazy - 取代 input 监听 change 事件
.number - 输入字符串转为数字
.trim - 输入首尾空格过滤
26.你的接口请求一般放在哪个生命周期中?
接口请求一般放在mounted
中,但需要注意的是服务端渲染时不支持mounted
,需要放到created
中。
- 如果不需要操作Dom元素或子组件, 那放到created或者mounted中都可以, 如果需要操作Dom元素, 则需要放到mounted中. 并且, 需要注意的是, 即便是放到created, Vue不会等待异步返回的数据再去执行下一步. 从体验的角度考虑, 放在mounted中更好.
27.vue中key值的作用:
key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
28.vue事件中使用event对象:
<a href="#" data-id="12" @click="showEvent($event)">event click</a>
showEvent(event){
console.log(event.target.dataset.id)
event.preventDefault();
event.stopPropagation();
}
29.Vue SSR
渲染原理?
优点:
更利于SEO
不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本(Google除外,据说Googlebot可以运行javaScript)。使用了Vue或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。
更利于首屏渲染
首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
场景:
交互少,数据多,例如新闻,博客,论坛类等
原理:
相当于服务端前面加了一层url分配,可以假想为服务端的中间层,
当地址栏url改变或者直接刷新,其实直接从服务器返回内容,是一个包含内容部分的html模板,是服务端渲染
而在交互过程中则是ajax处理操作,局部刷新,首先是在history模式下,通过history. pushState方式进而url改变,然后请求后台数据服务,拿到真正的数据,做到局部刷新,这时候接收的是数据而不是模板
缺点:
服务端压力较大
本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源;
开发条件受限
在服务端渲染中,created和beforeCreate之外的生命周期钩子不可用,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制;
安全问题
因为做了node服务,因此安全方面也需要考虑DDOS攻击和sql注入
30.new Vue()
发生了什么?
1)new Vue()
是创建Vue
实例,它内部执行了根实例的初始化过程。
2)具体包括以下操作:
- 选项合并
$children
,$refs
,$slots
,$createElement
等实例属性的方法初始化- 自定义事件处理
- 数据响应式处理
- 生命周期钩子调用 (
beforecreate created
) - 可能的挂载
总结:
new Vue()
创建了根实例并准备好数据和方法,未来执行挂载时,此过程还会递归的应用于它的子组件上,最终形成一个有紧密关系的组件实例树。
31.Vue.use
是干什么的?原理是什么?
vue.use
是用来使用插件的,我们可以在插件中扩展全局组件、指令、原型方法等。
1、检查插件是否注册,若已注册,则直接跳出;
2、处理入参,将第一个参数之后的参数归集,并在首部塞入 this
上下文;
3、执行注册方法,调用定义好的 install
方法,传入处理的参数,若没有 install
方法并且插件本身为 function
则直接进行注册;
1) 插件不能重复的加载
install
方法的第一个参数是vue的构造函数,其他参数是Vue.set中除了第一个参数的其他参数; 代码:args.unshift(this)
2) 调用插件的install 方法 代码:
typeof plugin.install === 'function'
3) 插件本身是一个函数,直接让函数执行。 代码:
plugin.apply(null, args)
4) 缓存插件。 代码:
installedPlugins.push(plugin)
32.Vue.set
方法是如何实现的?
核心答案
为什么$set
可以触发更新,我们给对象和数组本身都增加了dep
属性,当给对象新增不存在的属性则触发对象依赖的watcher
去更新,当修改数组索引时我们调用数组本身的splice
方法去更新数组。
补充答案
1) 如果是数组,调用重写的splice方法 (这样可以更新视图 )
代码:
target.splice(key, 1, val)
2) 如果不是响应式的也不需要将其定义成响应式属性。
3) 如果是对象,将属性定义成响应式的
defineReactive(ob.value, key, val)
33.Vue
中模板编译原理?
核心答案
将template
转换成render
函数
补充说明
这里要注意的是我们在开发时尽量不要使用template.
因为将template转化成render方法需要在运行时进行编译操作会有性能损耗,同时引用带有complier包的vue体积也会变大.
默认.vue文件中的 template处理是通过vue-loader 来进行处理的并不是通过运行时的编译。
流程如下
1) 将 template 模板转换成 ast 语法树 - parserHTML
2) 对静态语法做静态标记 - markUp
3) 重新生成代码 - codeGen
34.Vue Loader
的作用?
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件
作用:解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理。
35.组件中写 name
选项有哪些好处及作用?
1) 可以通过名字找到对应的组件 ( 递归组件 )
2) 可以通过name属性实现缓存功能 (keep-alive)
3) 可以通过name来识别组件 (跨级组件通信时非常重要)
36.keep-alive
平时在哪里使用?原理是?
keep-alive 主要是组件缓存,采用的是LRU算法。最近最久未使用法。
常用的两个属性include/exclude,允许组件有条件的进行缓存。
两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。
37.Vue.mixin
的使用场景和原理?
核心答案:
Vue.mixin的作用就是抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions方法进行合并,采用策略模式针对不同的属性进行合并,如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。
补充回答:
mixin
中有很多缺陷“命名冲突问题”,“依赖问题”,“数据来源问题”,这里强调一下mixin
的数据是不会被共享的。
注意事项
如果只是提取公用的数据或者通用的方法,并且这些数据或者方法,不需要组件间进行维护,就可以使用mixins
。(类似于js中封装的一些公用的方法)
38.mixins
和vuex
的区别?
vuex
公共状态管理,在一个组件被引入后,如果该组件改变了vuex里面的数据状态,其他引入vuex数据的组件也会对应修改,所有的vue组件应用的都是同一份vuex数据。(在js中,有点类似于浅拷贝)
vue
引入mixins
数据,mixins
数据或方法,在每一个组件中都是独立的,互不干扰的,都属于vue组件自身。(在js中,有点类似于深度拷贝)
39.mixins
和公共组件的区别?
通用的数据和方法,确实可以提出一个通用的组件,由父子组件传参的形式进行分享公用。
公共组件
子组件通过props接收来自父组件(公共组件)的参数或者方法,但vue不建议,子组件直接修改props接收到的父组件的数据。需要在子组件的data中或者computed中定义一个字段来接收。(有点麻烦)
公共组件最主要的作用还是复用相同的vue组件(有视图,有方法,有状态
)。
mixins
如果只是提取公用的数据或者通用的方法,并且这些数据或者方法,不需要组件间进行维护,就可以使用mixins。(类似于js中封装的一些公用的方法)
40.$nextTick():
·作用
:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法Vue.nextTick
一样,不同的是回调的this
自动绑定到调用它的实例上。
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
41.Vue 组件中 data 为什么必须是函数?
<script>
export default {
data(){
// 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
return{
menu: MENU.data,
poi:LIST.data
}
}
}
</script>
Vue.component('my-component', {
template: '<div>OK</div>',
data() {
return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
},
})
这个操作是一个简易操作,实际上,它
- 首先需要创建一个组件构造器,
- 然后注册组件。
- 注册组件的本质其实就是建立一个组件构造器的引用。
- 使用组件才是真正创建一个组件实例。
- 所以,注册组件其实并不产生新的组件类,但会产生一个可以用来实例化的新方式。
类比引用数据类型
Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
javascipt只有函数构成作用域(注意理解作用域,只有函数的{}
构成作用域,对象的{}
以及 if(){}
都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响
const MyComponent = function() {};
MyComponent.prototype.data = {
a: 1,
b: 2,
}
const component1 = new MyComponent();
const component2 = new MyComponent();
component1.data.a === component2.data.a; // true;
component1.data.b = 5;
component2.data.b // 5
如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改;
两个实例应该有自己各自的域才对,需要通过下面的方法来进行处理
const MyComponent = function() {
this.data = this.data();
};
MyComponent.prototype.data = function() {
return {
a: 1,
b: 2,
}
};
42.vue中 keep-alive 组件的作用:
·作用
:主要用于保留组件状态或避免重新渲染;
有一个`列表页面`和一个`详情页面`,那么用户就会经常执行打开`详情=>返回列表=>打开详情`这样的话 列表 和 详情 都是一个`频率很高`的页面,那么就可以`对列表组件`使用`<keep-alive></keep-alive>`进行缓存,这样用户每次`返回列表`的时候,都能`从缓存中快速渲染`,而`不是重新渲染`。
·属性
:
- include:字符串或正则表达式,只有匹配的组件
会被缓存
。 exclude:字符串或正则表达式,任何匹配的组件都
不会被缓存
。
·用法
:
包裹动态组件
时,会缓存
不活动的组件实例,而不是销毁
它们。和<transition>
相似,<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。<!-- 基本 --> <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 属性的使用
:
<!-- 逗号分隔字符串 -->
<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>
43.vue监听键盘事件中的按键
可以为v-on监听键盘事件的时候添加按键修饰符
//基本用法,直接使用keycode,但是难以记住
<input @keyup.13="submit"/>
常规按键修饰符
:.enter、.tab、.delete、.esc、.space、.up、.down、.left、.right
//使用按键别名
<input @keyup.enter="submit" />
新增修饰符
:.ctrl、.shift、.alt、.meta
仅在按下相应按键时才触发鼠标
或键盘事件
的监听器。
请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。
//使用按键修饰符
//ALT+C
<input @keyup.alt.67="clear"/>
//Ctrl + Click
<div @click.ctrl="doSomething">doSomething</div>
.exact
:修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 和ctrl 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
自定义按键修饰符别名
:config.keyCodes
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
44.Vue.js
如何让CSS
只在当前组件中起作⽤?
将当前组件的 <style>
修改为 <style scoped>
45.那为什么new Vue里data可以是一个对象?
new Vue({
el: '#app',
router,
template: '<App/>',
components: {App}
})
原因:因为JS里的对象是引用关系,而且new Vue是不会被复用的,所以不存在引用对象的问题。
46.Vue
中的diff
原理?
核心答案:
vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。
补充回答:
1) 先比较是否是相同节点
2) 相同节点比较属性,并复用老节点
3) 比较儿子节点,考虑老节点和新节点儿子的情况
4) 优化比较:头头、尾尾、头尾、尾头
5) 比对查找进行复用
Vue2 与 Vue3.x 的diff算法:
Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。
Vue3.x借鉴了ivi算法和 inferno算法,该算法中还运用了动态规划的思想求解最长递归子序列。(实际的实现可以结合Vue3.x源码看。)
47.vue更新数组时触发视图更新的方法:
·push()、pop()、sort()、reverse()、shift()、unshift()、splice()
·替换数组的方法:用新数组去覆盖旧的数组去触发视图更新
filter()、concat()、slice()这些方法都会返回一个新数组
items = items.filter((item)=>{
return item.message > 0;
})
//不会重新渲染整个数组,而是重新渲染修改的数组项(新增或删除的等等)
·不会触发视图更新的方法:
当你利用索引直接设置一个项时,vm.items[indexOfItem] = newValue
当你修改数组的长度时,vm.items.length = newLength
替换办法:
// 一下两种都可以解决第一个问题
Vue.set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
//第二个修改数组长度问题
vm.items.splice(newLength)
48.vue中对象更改检测的注意事项
·Vue不能检测对象属性的添加
或删除
,无法响应式的重新渲染视图。但是原属性的修改可以响应。
//解决办法:Vue.set(object, key, value)
var vm = new Vue({
data: {
user: {
name: 'Anika'
}
}
})
Vue.set(user,'age',20);
//也可以使用 `vm.$set`实例方法,它只是全局`Vue.set`的别名
//vm是当前实例,一般组件中用this即可
//vm.$set(user,'age',20);
//添加多个属性可以利用Object.assign()方法覆盖原对象
vm.user = Object.assign({},vm.user,{
age: 20,
favor: 'apple'
})
49.过滤器
·调用过滤器
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
·创建过滤器
//在一个组件的选项中定义本地的过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
//在创建 Vue 实例之前全局定义过滤器
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ...
})
·项目中常用的自定义文件创建方法
//src/filter/filter.js
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]);
})
50.组件以及JS中使用全局常量
·组件中使用全局常量:
//src/const/const.js
export default{
install (Vue,options){
vue.prototype.global = {
title: '全局',
isBack: true,
isAdd: false
};
}
}
//main.js中全局引入
import constant from './const/const.js'
vue.user(constant);
//组件中调用
this.global.title
{{ gloabal.title }}
·JS中使用常量
//src/const/type.js
export const TEST_ADD = "TEST_ADD"
export const TEST_DEC = "TEST_DEC"
//js文件中引入
import * as types from '../types'
types.TEST_ADD
51.计算属性的缓存和方法调用的区别:
得到的结果是一样的
·计算属性是基于它们的依赖进行缓存的,只在相关依赖发生改变的时候才会重新求值。而每当触发重新渲染时,调用方法总会再次执行函数。
·使用计算属性还是methods取决于是否需要缓存,当遍历数组和做大量计算时,应当使用计算属性将结果缓存下来。
·计算属性是根据依赖自动执行的,methods需要事件调用。
52.解决跨域问题:
·vue-cli项目中通过node.js代理服务器来实现跨域请求
//在config/index.js文件里配置dev对象的proxyTable对象
//仅适用于开发环境,生产环境没解决
dev:{
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: false,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable:{
'/api': { //请求服务器接口文件夹名称
target: 'http://www.abc.com', //目标接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/api': '/api' //重写接口
}
},
cssSourceMap: false
}
//'http://localhost:8080/api' ===> 'http://www.abc.com/api'
·在服务器响应客户端的时候,带上Access-Control-Allow-Origin:* 头信息 (推荐)
·通过jQuery的jsonp形式解决跨域问题
53.Vue
中hash
模式和history
模式的区别?
最明显的是在显示上,hash
模式的URL
中会夹杂着#
号,而history没有。
Vue
底层对它们的实现方式不同。hash模式是依靠onhashchange
事件(监听location.hash
的改变),而history
模式是主要是依靠的HTML5 history
中新增的两个方法,pushState()
可以改变url地址且不会发送请求,replaceState()
可以读取历史记录栈,还可以对浏览器记录进行修改。
当真正需要通过URL
向后端发送HTTP请求的时候,比如常见的用户手动输入URL
后回车,或者是刷新(重启)浏览器,这时候history
模式需要后端的支持。
因为history
模式下,前端的URL
必须和实际向后端发送请求的URL一致,例如有一个URL
是带有路径path
的(例如www.lindaidai.wang/blogs/id
),如果后端没有对这个路径做处理的话,就会返回404
错误。所以需要后端增加一个覆盖所有情况的候选资源,一般会配合前端给出的一个404
页面。
hash:
window.onhashchange = function(event){
// location.hash获取到的是包括#号的,如"#heading-3"
// 所以可以截取一下
let hash = location.hash.slice(1);
}
核心答案:
vue-router
有 3 种路由模式:hash
、history
、abstract
。
1) hash
模式:hash + hashChange
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。通过监听 hash(#)的变化来执行js代码 从而实现 页面的改变。
核心代码:
window.addEventListener(‘hashchange‘,function(){
self.urlChange()
})
2) history
模式:historyApi + popState
HTML5
推出的history API,由pushState()记录操作历史,监听popstate事件来监听到状态变更;
因为 只要刷新 这个url(www.ff.ff/jjkj/fdfd/fdf/fd)就会请求服务器,然而服务器上根本没有这个资源,所以就会报404,解决方案就 配置一下服务器端。
说明:
1)hash
: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
2)history
: 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
3)abstract
: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
54.vue-router响应 路由参数 的变化:用watch监听
//例如/user?id=1到/user?id=2
watch:{
'$route'(to,from){
//if(to.path !== from.path)
//if(to.params.id !== from.params.id)
if(to.query.id !== from.query.id){
this.id = to.query.id;
this.init(); //重新加载数据,自定义函数
}
}
}
//使用beforRouteUpdate() vue-router的内置钩子函数
beforeRouteUpdate(to,from,next){
if(to.fullPath !== from.fullPath){
next();
this.changeUser();
}
},
methods:{
changeUser:()=>{
getUserInfo({userId:this.$route.params.id}).then(res => {
if(res.success){
this.userInfo = res.data;
}else{
....
}
})
}
}
//使用v-bind:key,阻止组件复用,只要路由变化就重新创建这个组件
<router-view :key="$route.fullPath"></router-view>
55.vue-router的导航守卫(导航钩子):导航守卫主要用于路由正在发生改变时,通过跳转或者取消的方式守卫导航。
参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route
对象来应对这些变化,或使用beforeRouteUpdate
的组件内守卫
·router.beforeEach(to,from,next)(全局守卫):
·router.beforeResolve(to,from,next)(全局解析守卫):
·router.afterEach(to,from)(全局后置守卫):
·beforeEnter(to,from,next)(路由独享的守卫):
·beforeRouteEnter(to,from,next)、beforeRouteUpdate(to,from,next)、 beforeRouteLeave(to,from,next)(组件内的守卫):
注意
:确保要调用next
方法,否则钩子就不会被 resolved
。
56.参数传递的方式:params、query、meta三种
// 字符串,不带参数
this.$router.push('home')
// 对象,不带参数
this.$router.push({ path: 'home' })
// params(推荐):命名的路由,params 必须和 name 搭配使用
this.$router.push({ name:'user',params: { userId: 123 }})
// 这里的 params 不生效
this.$router.push({ path:'/user',params: { userId: 123 }})
// query:带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
//meta方式:路由元信息
export default new Router({
routes: [
{
path: '/user',
name: 'user',
component: user,
meta:{
title:'个人中心'
}
}
]
})
//通过 $route 对象获取,注意是route
this.$route.params.userId
this.$route.query.userId
this.$route.meta.title
57.vue-router的几种实例方法以及参数传递:
vue-router的实例方法
- this.$router.push(location, onComplete?, onAbort?)
这个方法会
向history添加一个新的记录
,所以当用户点击浏览器后退按钮时,则回退到之 前的URL;点击<router-link>
相当于调用this.$router.push() - this.$router.replace(location, onComplete?, onAbort?)
这个方法不会
向history添加一个新的记录,而是替换掉当前的history记录,所以点击后退按钮时不会回退 - this.$router.go(n)
参数是一个整数,作用是在history记录中向前或者向后后退几步
- onComplete以及onAbort回调会在导航成功完成(在所有的异步钩子被解析之后)或者导航终止(导航的相同的路由、或在当前导航完成之前导航到另一个路由)的时候进行调用;
- 如果目标路由和当前路由只是参数不同(/user/1 -> /user/2),则需要使用组件内的导航守卫beforeRouteUpdate来响应这个变化(抓取渲染页面数据信息)
58.$router
对象有哪些属性?
$router.app
: 配置了router的Vue根实例。$router.mode
: 路由使用的模式。$router.currentRoute
: 当前路由对应的路由信息对象。
59.$route
对象有哪些属性?
$route.path
:
返回字符串,对应当前路由的路径,总是解析为绝对路径。$route.params
:
返回一个key-value对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。$route.query
:
返回一个key-value对象,表示URL查询参数。$route.hash
:
返回当前路由的带#的hash值,如果没有hash值,则为空字符串。$route.fullPath
:
返回完成解析后的URL,包含查询参数和hash的完整路径。$route.matched
:
返回一个数组,包含当前路由的所有嵌套路径片段的路由记录,路由记录就是routes配置数组中的对象副本。$route.name
:
如果存在当前路由名称则返回当前路由的名称。$route.redirectedFrom
:
如果存在重定向,即为重定向来源的路由的名字。
60.$route和$router的区别:
$route
是“路由信息对象”,包含path、fullPath、params、query、name、hash、matched等路由信息参数;$router
是“路由实例对象”,包括了路由的跳转方法和钩子函数等。
61. 动态路由的匹配及使用:
const router = new Router({
routes:[
{
path: '/user/:id', //匹配/user/bar、/user/foo等等
component: User
}
]
})
//template中获取
this.$route.params.id
62.嵌套路由:
const router = new Router({
routes:[{
path: '/user/:id',
component: User,
children: [
//不设置这个子路由将无法匹配/user/:id
{
path:'', //匹配/user/:id,渲染UserHome组件
component: UserHome
},
{
path: 'profile', //匹配/user/:id/profile
component: UserProfile
}
]
}]
})
63.动态加载路由组件(懒加载)
·把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件
·结合 Vue 的异步组件
和 Webpack 的代码分割
功能,轻松实现路由组件的懒加载。
//第一步,在/src/router/index.js里面异步引入组件.vue
const index = () => import('../page/list/index.vue')
//第二步,配置路由
const router = new Router({
routes: [
{
path: '/index',
component: index,
name: 'index'
}
]
})
//第三步,配置webpack.base.config.js的output属性,添加chunkFileName
output: {
path: config.build.assetsRoot,
filename: '[name].js',
//新增chunFilename属性
chunkFilename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
64.Vuex概念:
·vuex是一个专门为vue.js应用程序开发的状态管理器。它采用 集中式存储 管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
·vuex就是专门解决页面与页面之间需要的共享变量的创建、维护和变更的问题
。
·有了vue-router,可以将一个组件视为一个页面来使用。由于组件只维护自身的状态(data),组件创建时或者说进入路由时它们被初始化,切换至其他的组件页时当前页自然要被销毁,从而导致data也随之销毁。
页面与页面之间总会产生各种需要的共享变量,如果通过$router.param或者$router.meta来传递是远远不够的,所以就需要vuex来实现这个需求。
65.vuex的核心概念:
·每一个vuex应用的核心就是store(仓库)。store基本上就是一个容器,它包含着你的应用中的大部分状态state。
·vuex和单纯的全局对象的不同:
- Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,如果store中的状态发生变化,那么相应的组件也会得到高效的更新;
- 你不能直接改变store中的状态。改变store中的状态的唯一途径就是显示的提交(commit)mutation。这样可以使我们方便的追踪每一个状态的变化。
- state:Vuex store实例的
根状态对象
。用于定义共享的状态变量。 - Action:
动作
,向store发出调用通知,执行本地或者远端的某一操作(可以理解为store的methods)。 - Mutations:
修改器
,它只用于修改state中定义的状态变量。 - getter:
读取器
,外部程序通过它获取状态变量的具体值,或者在取值前做一些计算(可以认为是store的计算属性)。
66.VueX
之actions
与mutations
的区别?
actions
1、用于通过提交mutation改变数据
2、会默认将自身封装为一个Promise
3、可以包含任意的异步操作
mutations
1、通过提交commit
改变数据
2、只是一个单纯的函数
3、不要使用异步操作,异步操作会导致变量不能追踪
67.如何在vuex中使用异步修改?
在调用vuex
中的方法action
的时候,用promise
实现异步修改
const actions = {
asyncLogin({ commit }, n){
return new Promise(resolve => {
setTimeout(() => {
commit(types.UserLogin, n);
resolve();
},3000)
})
}
}
68.Vuex在vue-cli中的应用:
·下载Vuex依赖包:npm install vuex --save
·在src/main.js中引入:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
·创建src/store/store.js:导出Vuex.Store实例
import Vue from 'vue'
import Vuex from 'Vuex'
//index.js存放action、mutations、getter 也可以把他们拆分成单独文件再引入
import index from './index.js'
Vue.use(Vuex)
export default new Vuex.Store({
index
})
·创建src/store/index.js:存放action、mutations、getter
import * as types from './types'
const state = {
//vuex初始化值
count: 0
}
const actions = {
increment({ commit },n){
commit(types.TEST_INCREMENT,n)
},
decrement({ commit },state){
commit(types.TEST_DECREMENT,n)
}
}
const getters = {
count: state => state.count
}
const mutations = {
[types.TEST_INCREMENT](state, n){
console.log(n)
state.count = state.count + n + 5
},
[types.TEST_DECREMENT](state, status){
state.count = state.count - 3
}
}
export default {
state,
actions,
getters,
mutations
}
·创建 src/store/types.js:存放常量
export const TEST_INCREMENT = 'TEST_INCREMENT'
export const TEST_DECREMENT = 'TEST_DECREMENT'
69.Vuex的5个核心属性是什么?
分别是 state、getters、mutations、actions、modules 。
70.在Vuex中使用mutationvuex要注意什么?
mutation 必须是同步函数
71.Vuex中action和mutation有什么相同点?
第二参数都可以接收外部提交时传来的参数。
this.$store.dispatch('ACTION_NAME',data)
和
this.$store.commit('SET_NUMBER',10)
在组件中多次提交同一个action,怎么写使用更方便。
72.组件中使用vuex的值和修改值
·直接获取、修改
//获取:state\getter
this.$store.state.count
this.$store.getters.count
//修改
this.$store.actions('increment')
this.$store.actions('increment',{value:123})
·通过辅助函数获取、修改:mapState、mapGetters、mapActions
也就是将vuex的变量或者方法映射到vue组件this指针上,通过map获取到的变量或者方法,可通过 this 直接使用或者调用。
computed:{
mapState([
'count'
])
}
computed:{
mapGetters([
'count'
])
}
methods:{
mapActions({
increment:'increment',
decrement:'decrement'
})
}
73.vuex中使用异步修改:
·在调用Vuex的Action中的方法时,用promise实现异步修改
const actions = {
asyncIncrement({ commit }, n){
return new Promise(resolve => {
setTimeout(()=>{
commit(types.TEST_INCREMENT,n);
resolve();
},3000)
})
}
}
74.axios的基本写法及安装
·npm install axios --save
//post请求
axios.post('/user',{name: 'abc'}).then( res => {
console.log(res.updateName);
});
axios({
method: 'POST',
url: '/user',
data:{
name: 'abc'
}
});
//get请求
axios.get('/user?ID=1234').then( res =>{
console.log(res.data);
});
axios('/user/1234').then( res =>{
consoe.log(res.status);
})
75.axios的特点:
- Axios是一个基于Promise的HTTP库,支持Promise的所有API
- 它可以拦截请求和响应
- 它可以转换请求数据和响应数据,并对响应回来的内容自动转换为JSON类型的数据
- 安全性更高,客户端支持防御CSRF跨域请求伪造
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。