Vue面试相关

MVVM相关

什么是MVVM, 谈谈你对MVVM开发模式的理解?

​ MVVM是是Model-View-ViewModel的缩写,Model代表数据模型,定义数据操作的业务逻辑,View代表视图层,负责将数据模型渲染到页面上,ViewModel通过双向绑定把View和Model进行同步交互,不需要手动操作DOM的一种设计思想。

MVVM分为Model、View、ViewModel三者。

Model:代表数据模型,数据和业务逻辑都在Model层中定义;

View:代表UI视图,负责数据的展示;

ViewModel:负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

这种模式实现了Model和View的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作dom。

MVVM和MVC区别?和其他框架(jquery)区别?那些场景适用?

​ MVVM和MVC都是一种设计思想,主要就是MVC中的Controller演变成ViewModel,,MVVM主要通过数据来显示视图层而不是操作节点,解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度慢,影响用户体验问题。主要用于数据操作比较多的场景。
场景:数据操作比较多的场景,更加便捷

mvvm框架是什么?它和其它框架(jquery)的区别是什么?哪些场景适合?

一个model+view+viewModel框架,数据模型model,viewModel连接两个

区别:vue数据驱动,通过数据来显示视图层而不是节点操作。

场景:数据操作比较多的场景,更加便捷

自定义指令(v-check、v-focus)的方法有哪些?它有哪些钩子函数?还有哪些钩子函数参数?

全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives

钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)

钩子函数参数:el、binding

指令相关

Vue 有哪些指令,请说出相关一些?

  • v-if?show:判断是否隐藏; 用来显示或隐藏元素,v-show 是通过display实现,v-if 是每次删除后在创建
  • v-for:数据循环出来; 对数组或对象进行循环操作
  • v-bind:class:绑定一个属性;
  • v-model:双向数据绑定,一般用于表单元素。

v-model是什么?怎么使用? vue中标签怎么绑定事件?

​ 可以实现双向绑定,指令(v-class、v-for、v-if、v-show、v-on)。vue的model层的data属性。绑定事件:<input @click=doLog()/>

为什么避免 v-if 和 v-for 用在一起

​ 当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,通过v-if 移动到容器元素,不会再重复遍历列表中的每个值。取而代之的是,我们只检查它一次,且不会在 v-if 为否的时候运算 v-for。

Vue.cli相关

请说出vue.cli项目中src目录每个文件夹和文件的用法?

  • assets文件夹是放静态资源;
  • components是放组件;
  • router是定义路由相关的配置;view视图;
  • app.vue是一个应用主组件;
  • main.js是入口文件

vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?

第一步:在components目录新建你的组件文件(smithButton.vue),script一定要export default {

第二步:在需要用的页面(组件)中导入:import smithButton from ‘../components/smithButton.vue'

第三步:注入到vue的子组件的components属性上面,components:{smithButton}

第四步:在template视图view中使用,

vue-cli如何新增自定义指令?

1、创建局部指令

var app = new Vue({
    el: '#app',
    data: {   
    },
    // 创建指令(可以多个)
    directives: {
        // 指令名称
        dir1: {
            inserted(el) {
                // 指令中第一个参数是当前使用指令的DOM
                console.log(el);
                // 对DOM进行操作
                el.style.width = '200px';
                el.style.height = '200px';
                el.style.background = '#000';
            }
        }
    }
})

2、全局指令

Vue.directive('dir2', {
    inserted(el) {
        console.log(el);
    }
})

3、指令的使用

<p>
    <p v-dir1></p>
	<p v-dir2></p>
</p>

Vuex相关

vuex是什么?怎么使用?哪种功能场景使用它?

​ vue框架中状态管理。在main.js引入store,注入。新建了一个目录store,….. export 。场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车

vue框架中状态管理,就是把组建的共享状态取出来,以一个全局单例模式管理,这样,不管在任何时候,都能获取状态或者触发行为

你是怎么认识vuex的?

vuex可以理解为一种开发模式或框架。 通过状态(数据源)集中管理驱动组件的变化(好比spring的IOC容器对bean进行集中管理)。

应用级的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。

vuex 状态管理模式

采用集中式存储,管理应用所有组件的状态,简单说集中管理数据,类似于react中的redux,基于flux前段状态管理框架。

基本使用:nmp install vuex -S

创建store.js 文件,vuex相关配置。在main.js 中导入,import store fomr './store.js main.js 配置store选项,子组this.$store

import vue fomr vue import vuex from vuex vue.use(vuex);

vuex核心store 相当于一个容器,一个store实例中包含一下属性和方法:

getters 获取属性

actions 定义方法 动作,如流程判断 异步请求 const action={ 方法名(context){})} context 对象有comit dispatch state

commit 提交的意思,修改数据唯一方式, conmit('add) 提交一个叫add的变化

mutations 定义变化

var state= { count:6} //创建store对象 const store=new Vuex.Strore{{state}}; vargetters ={count)(){return state.count}} export default store;

编辑 app。js 编辑store, 作为计算属性:computed:(){return this.$store.state.count};

方式一 this.#store访问

方式二 mapGetters mapActios访问

mapGetter 获取属性

mapActions 获取方法

导入辅助函数 import {mapGetter} from vuex

computed:mapGetters{('count')}

Vue 生命周期

什么是vue生命周期?

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

vue生命周期的作用是什么?

​ 它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

vue生命周期总共有几个阶段?

​ 它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后

第一次页面加载会触发哪几个钩子?

第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子

DOM 渲染在 哪个周期中就已经完成?

DOM 渲染在 mounted 中就已经完成了

简单描述每个周期具体适合哪些场景?

生命周期钩子的一些使用方法:

beforecreate : 可以在这加个loading事件,在加载实例时触发

created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用

mounted : 挂载元素,获取到DOM节点

updated : 如果对数据统一处理,在这里写上相应函数

beforeDestroy : 可以做一个确认停止事件的确认框

nextTick : 更新数据后立即操作dom

Vue生命周期的理解?

​ 生命周期:就是一个组件或者实例开始被初始化、创建开始到这个实例被销毁或者结束的一个过程。

​ 这个过程比如官网表述的:在过程中需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等,同时,在这个过程中vue给我们提供了很多的方法,也就是所说的生命周期钩子函数。

​ 在实例生命周期的不同阶段借助这些钩子函数,“用户在不同阶段添加自己的代码”

由此:真正的生命周期是一个流程,而不是单单那几个钩子函数,钩子函数只是用来在流程的不同阶段帮助我们做更多的事情。

vue的生命周期统分为5大区块:

  • 创建(初始化)
  • 查找与处理(找到组件并渲染)
  • 挂载(插入)
  • 更新(重新渲染并插入)
  • 销毁(卸载所有)

其中每一大块又分几个小的步骤,但是大体规律又是如出一辙:

  • 本区块开始前(一个区块流程开始的钩子告诉你)
  • 本区块开始中
  • 本区块开始后(一个区块流程完毕的钩子告诉你)

一、创建部分

new Vue 这句代码初始化一个vue实例。

开始创建一个vue对象生命周期开始,init event初始化事件,为当前实例做基础配置;

创建之前,这里提供一个钩子函数,beforeCreate 开始创建钩子,这个时候还啥也没做呢,页面一片空白,可以在页面中先展示一个loading组件,给用户一个友好体验;

创建中,init injections(初始化注册) & reactivity 创建过程中,data属性被成功绑定,dom未生成;

创建之后,这里提供一个钩子函数,created 创建完毕钩子,

  这个时候vue对象实例化完毕,dom树依旧未生成,页面还是一片空白,但是,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。

可以在这里ajax获取数据赋给data属性了,以便日后使用;

二、查找部分

也就是new Vue()括号里边的参数开始被执行解析的过程:

new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

​ 查找el属性的对应内容,el对应的DOM 元素作为 Vue 实例的挂载目标。

  • 如果值可以被找到,那么实例将立即进入编译过程

  • 如果找不到,就去查是否在括号后边挂载了$.mount()并有内容,用于手动开启编译

​ 以上官网解说:“如果在实例化时存在el这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。”官网:https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-DOM

  • 如果都没找到,生命周期结束;

  • 如果顺利的都找到了,就继续往下查找{}内的下一个属性template

    • 如果template对应的值当中有组件或者有html内容,那么也算查找成功,
    • 如果为空,继续查找render属性值是否为空
  • 紧接着,如果{}选项中存在渲染函数render,

    • 那么template将被忽略,因为render渲染函数是字符串模板的代替方案,
      • render可以让你发挥 JavaScript 最大的编程能力,而不用写template的静态模板
    • 如果没有或为空,template字符串模板将会替换挂载的元素,即el的属性值
      • 挂载元素el的内容都将被忽略,除非模板template的内容有分发插槽。
    • 如果template和render都不存在,则生命周期结束。

三、挂载部分

开始挂载之前,这里提供一个钩子函数,beforeMount挂载前钩子(编译template里的内容并在虚拟dom中执行,页面上依旧没有任何展示)

  • 挂载中,要做的事就是创建vm$.el,并替换到el元素,
  • 挂载完毕,这里提供一个钩子函数,mounted挂载完毕钩子,
    • el 被新创建的 vm.$el 替换,并挂载到实例上去
    • 至此,所有的dom结构和数据都被展示到页面当中,

四、更新部分

依旧是那个套路,触发了更新的开关后,会给一个开始更新的回调:

  • 更新之前,这里有一个钩子函数,beforeUpdate开始更新前钩子,
    • 在这个钩子里可以提供一个弹窗提示用户确认跟新啥的。或者再展示一个loading;
    • 也可以手动移除已添加的事件监听器
  • 更新中,vue实例要开始将旧数据替换为新数据,在虚拟dom中重新渲染
    • 虚拟dom开始改变,但是页面这时没有任何变化,因为只是改的虚拟dom,还并未真正修改dom结构;
  • 更新完毕,这里有一个钩子函数,updated更新后钩子,
    • 这时真正的dom结构被彻底替换,页面展示上也会发生改变,
    • 在这个钩子里可以提供一个弹窗告诉用户更新完毕。同时去掉loading弹层啥的;

五、销毁部分

  • 开始销毁vue实例之前,会有一个钩子函数提示开发者组件要开始销毁:beforeDestory开始销毁钩子,
    • 在这个钩子中我们可以提醒用户是否删除等,或者做一些开发者与业务有关的相关操作;
  • 销毁中,vue这时的主要目标就是卸载,就像人要洗澡前各种脱一样(具体就不要想象了!),
    • 他要卸载在身上的各种监听、各种事件, 各种绑定以及各种子组件实例销毁,感觉像毁灭一切
      • 比如watchers(我没用过没有发言权)、子组件child components、事件event;
  • 销毁后,这时再次提供最后一个钩子函数,destoryed销毁完毕。
    • 在这里我们可以提示用户删除完毕啥的,也可以清空我们自己的定时器或者做一些其他善后工作。

到这一步,此次整个vue实例的生命周期就彻底结束了。

请详细说下你对vue生命周期的理解?

总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后

① 创建前/后: 在beforeCreated阶段,vue实例的挂载元素$el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el还没有。

② 载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。

③ 更新前/后:当data变化时,会触发beforeUpdate和updated方法。

④ 销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在

什么是vue生命周期?

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

vue生命周期的作用是什么?

它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

vue生命周期总共有几个阶段?

它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后

请列举出3个Vue中常用的生命周期钩子函数

​ created: 实例已经创建完成之后调用,在这一步,实例已经完成数据观测, 属性和方法的运算 watch/event事件回调. 然而, 挂载阶段还没有开始, el属性目前还不可见

​ mounted : el被新创建的 vm.el属性目前还不可见mounted:el被新创建的vm.el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
​ activated: keep-alive组件激活时调用

组件

请说下封装 vue 组件的过程?

​ 首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。

​ 然后,使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,可以在props中接受定义。而子组件修改好数据后,想把数据传递给父组件。可以采用emit方法。

组件之间的传值?

  • 父组件与子组件传值
  • 父组件通过标签上面定义传值
  • 子组件通过props方法接受数据
  • 子组件向父组件传递数据
  • 子组件通过$emit方法传递参数

Vue组件间的参数传递

1.父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件:$emit方法传递参数
2.非父子组件间的数据传递,兄弟组件传值
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道。)

v-router 路由相关

路由之间跳转

声明式(标签跳转) 编程式( js跳转)

怎么定义vue-router的动态路由?怎么获取传过来的动态参数?

​ 在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id

vue-router是什么?它有哪些组件?

vue用来写路由一个插件。router-link、router-view

vue-router有哪几种导航钩子?

有三种,

第一种:是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子
第三种:单独路由独享组件

过滤器

vue-router路由

使用vue.js 开发spa 单页面应用,根绝不同url地址,显示不同内容,但实现在统一页面红,称单页面应用。

安装路由:cnpm install vue-router -S

<p id='itsny'>
    <p>
    	<!--使用router-link组件来定义导航,to属性指定链接url -->
    	<router-link to="/home">主页</router-link>
		<router-link to="/news">新闻</router-link>
	</p>
	<p>
      <!--使用router-view用来显示路由内容 -->
       <router-view></router-view>
	</p>
</p>
<script>
//1、定义组件
    var Home={
        template: '<h3>我是主页</h3>'
    }
var News={
    template:'<h3>我是新闻</h3>'
};

// 2、配置路由
const routes=[
    {path:'/home',component:Home},
    {path:'/news/:username/:password',component:News},
    {path:'*',redirect:'/home'} //重定向
];

// 3、创建路由实例
const router = new VueRouter({
    routes, //简写,相当于routers:routes
    //mode:'history' //更改模式
    linkActiveClass: 'active' //更新活动链接的class类名
});

// 4、创建根实例并将路由挂载到Vue实例上
new Vue({
    el:'#itany',
    router //注入路由
});
</script>

vue-router使用总结

单页面应用的工作原理

​ 我理解的单页面工作原理是通过浏览器URL的#后面的hash变化就会引起页面变化的特性来把页面分成不同的小模块,然后通过修改hash来让页面展示我们想让看到的内容。

​ 那么为什么hash的不同,为什么会影响页面的展示呢?浏览器在这里面做了什么内容。以前#后面的内容一般会做锚点,但是会定位到一个页面的某个位置,这个是怎么做到的呢,和我们现在的路由有什么不同。(我能想到一个路由的展示就会把其他路由隐藏,是这样的吗)后面会看一看写一下这个疑惑,现在最重要的是先把基本概念弄熟。

正文

​ 当你要把 vue-router 添加进来,我们需要做的是,将组件(components)映射到路由(routes),然后告诉 vue-router 在哪里渲染它们

起步

//*** router-link 告诉浏览器去哪个路由

//*** router-view 告诉路由在哪里展示内容
<p id="app">
    <h1>Hello App!</h1>
	<p>
   	<!-- 使用router-link组件来导航 -->
    <!-- 通过传入`to`属性指定链接 -->
    <!-- <router-link> 默认会被渲染成一个`<a>`标签 -->
		<router-link to="foo">Go to Foo</router-link>
		<router-link to="bar">Go to Bar</router-link>
	</p>
	<!-- 路由出口 -->
    <!-- 路由匹配到的组件将渲染在这里 -->
	<router-link></router-link>
</p>

// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: 'foo' },
const Bar = { template: 'bar' },

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
const routes = [
 	{ path: '/foo', component: Foo },
 	{ path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
 	routes // (缩写)相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
    router
}).$mount('#app')
// 现在,应用已经启动了!

动态路由匹配

​ 相当于同一个组件,因为参数不同展示不同的组件内容,其实就是在 vue-router 的路由路径中使用『动态路径参数』

const router - new VueRouter({
    routes: [
        //动态路径参数,以冒号开头
        {path:'/user/:id',component:User}
    ]
})
那么我们进入uesr/001 和 user/002 其实是进入的同一个路由,可以根据参数的不同在内容页展示不同的内容。一般适用场景:列表,权限控制

定义的时候用: 表示是动态路由,使用 {{ $route.params.id }} 来拿到本路由里面参数的内容

当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch(监测变化) $route 对象

const User = {
    template: '...',
    watch: {
        '$route'(to,from){
            //对路径变化作出响应...
        }
    }
}
	有时候,同一个路径可以匹配多个路由,此时,**匹配的优先级**就按照路由的定义顺序:**谁先定义的,谁的优先级就最高。**

嵌套路由

//路由里面也会出现 这是嵌套路由展示内容的地方
const User = {

 template: `
	<p class="user">
		<h2>User {{ $route.params.id }}</h2>
		<router-view></router-view>
	</p>
	`
}

//定义路由的时候在 加children 子路由属性
const router = new VueRouter({
 routes: [
 { path: '/user/:id', component: User,
  children: [
  {
   // 当 /user/:id/profile 匹配成功,
   // UserProfile 会被渲染在 User 的 中
   path: 'profile',
   component: UserProfile
  },
  {
   // 当 /user/:id/posts 匹配成功
   // UserPosts 会被渲染在 User 的 中
   path: 'posts',
   component: UserPosts
  }
  ]
 }
 ]
})

设置空路由,在没有指定路由的时候就会展示空路由内容

const router = new VueRouter({

routes: [
{
path: '/user/:id', component: User,
children: [
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 中
{ path: '', component: UserHome },
]
}
]
})

编程式导航

声明式:
编程式:router.push(...)

可以想象编程式 push 可以理解为向浏览器历史里面push一个新的hash,导致路由发生变化

router.replace() 修改路由但是不存在历史里面
router.go(n) 有点像JS的window.history.go(n)

命名路由 就是给每一个路由定义一个名字。

命名视图

​ 有时候想同时(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar(侧导航) 和 main(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置(带上 s):

const router = new VueRouter({

routes: [
{
 path: '/',
 components: {
  default: Foo,
  a: Bar,
  b: Baz
 }
}
]
})

重定向和别名

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:

const router = new VueRouter({

 routes: [
  { path: '/a', redirect: '/b' }
 ]
})

一般首页的时候可以重定向到其他的地方

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({

 routes: [
  { path: '/a', redirect: { name: 'foo' }}
 ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({

 routes: [
  { path: '/a', redirect: to => {
   // 方法接收 目标路由 作为参数
   // return 重定向的 字符串路径/路径对象
  }}
 ]
})

『重定向』的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,那么『别名』又是什么呢?

​ /a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

上面对应的路由配置为:

const router = new VueRouter({

 routes: [
  { path: '/a', component: A, alias: '/b' }
 ]
})

『别名』的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

HTML5 History模式

ue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

const router = new VueRouter({
 mode: 'history',
 routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。

const router = new VueRouter({
 mode: 'history',
 routes: [
  { path: '*', component: NotFoundComponent }
 ]
})

或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。

导航守卫

我的理解 就是组件或者全局级别的 组件的钩子函数

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

全局守卫

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
 // ...
})

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象

  • from: Route: 当前导航正要离开的路由

  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next(‘/') 或者 next({ path: ‘/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

完整的导航解析流程

  • 导航被触发。
  • 在失活的组件里调用离开守卫。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  • 在路由配置里调用 beforeEnter。
  • 解析异步路由组件。
  • 在被激活的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫 (2.5+)。
  • 导航被确认。
  • 调用全局的 afterEach 钩子。
  • 触发 DOM 更新。
  • 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

路由元信息

​ 我的理解就是 他可以把路由的父路径都列举出来,完成一些任务,比如登录,user 组件需要登录,那么user下面的foo组件也需要,那么可以通过这个属性 来检测这个路由线上 的一些状态。

定义路由的时候可以配置 meta 字段:

const router = new VueRouter({
 routes: [
  {
   path: '/foo',
   component: Foo,
   children: [
    {
     path: 'bar',
     component: Bar,
     // a meta field
     meta: { requiresAuth: true }
    }
   ]
  }
 ]
})

首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录

例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航守卫中的路由对象)的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

下面例子展示在全局导航守卫中检查元字段:

router.beforeEach((to, from, next) => {
 if (to.matched.some(record => record.meta.requiresAuth)) {
  // this route requires auth, check if logged in
  // if not, redirect to login page.
  if (!auth.loggedIn()) {
   next({
    path: '/login',
    query: { redirect: to.fullPath }
   })
  } else {
   next()
  }
 } else {
  next() // 确保一定要调用 next()
 }
})

数据获取

我的理解就是在哪里获取数据,可以再组件里面,也可以在组件的守卫里面,也就是组件的生命周期里面。

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。

导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。

导航完成后获取数据

当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:

<template>
    <p class="post">
        <p class="loading" v-if="loading">
            Loding..
       	</p>
		<p v-if="error" class="error">
            {{ error }}
        </p>
		<p v-if="post" class="content">
            <h2>{{ post.title }}</h2>
			<p>{{ post.body }}</p>
		</p>
	</p>   
</template>
export default {
 data () {
  return {
   loading: false,
   post: null,
   error: null
  }
 },
 created () {
  // 组件创建完后获取数据,
  // 此时 data 已经被 observed 了
  this.fetchData()
 },
 watch: {
  // 如果路由有变化,会再次执行该方法
  '$route': 'fetchData'
 },
 methods: {
  fetchData () {
   this.error = this.post = null
   this.loading = true
   // replace getPost with your data fetching util / API wrapper
   getPost(this.$route.params.id, (err, post) => {
    this.loading = false
    if (err) {
     this.error = err.toString()
    } else {
     this.post = post
    }
   })
  }
 }
}

在导航完成前获取数据

通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。

export default {
 data () {
  return {
   post: null,
   error: null
  }
 },
 beforeRouteEnter (to, from, next) {
  getPost(to.params.id, (err, post) => {
   next(vm => vm.setData(err, post))
  })
 },
 // 路由改变前,组件就已经渲染完了
 // 逻辑稍稍不同
 beforeRouteUpdate (to, from, next) {
  this.post = null
  getPost(to.params.id, (err, post) => {
   this.setData(err, post)
   next()
  })
 },
 methods: {
  setData (err, post) {
   if (err) {
    this.error = err.toString()
   } else {
    this.post = post
   }
  }
 }
}

在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

vue-router是怎么传值的

1、在路由处配置

path:'/detail/:id'
//调用:
this.$router.push({
    path:'/home/${id}'
})

在组件内通过this.$route.params.id即可获取。

2、在router-link标签中传递参数

<router-link :to = {
    params:{
        id:1
    }
}/>

也可通过:this.$route.params.id获取

这里通过router-link传参方式是隐形传参

3、另一种params的是通过params传参,通过name配置路由。

//路由处:
{
    path:'/home',
    name:'Home',
    component:Home
}
//调用:
this.$router.push({
    name:'Home',
    params:{
        id:id
    }
})

获取:this.$route.params.id

4、通过query来传递参数,参数会在url后边的?id=?中显示

//路由处:
{
    path:'/home',
    name:'Home',
    component:Home
}
//调用:
this.$router.push({
    path:'/home',
    query:{
       id:id
    }
})

获取:this.$route.query.id

过滤器

vue如何自定义一个过滤器?

html代码

<p id="app">
    <input type="text" v=model="msg" />
    {{ msg | capitalize }}
</p>

JS代码:

var vm=new Vue({
    el:"#app",
    data:{
        msg:''
    },
    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
})

全局定义过滤器

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

其他

Vue公司的双向数据绑定原理是什么?

​ vue.js是采用数据劫持结合发布者 - 订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:

第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

vue和react区别

​ 相同点:都鼓励组件化,都有’props’的概念,都有自己的构建工具,Reat与Vue只有框架的骨架,其他的功能如路由、状态管理等是框架分离的组件。

​ 不同点:React:数据流单向,语法—JSX,在React中你需要使用setState()方法去更新状态。Vue:数据双向绑定,语法--HTML,state对象并不是必须的,数据由data属性在Vue对象中进行管理。适用于小型应用,但对于对于大型应用而言不太适合。

v-show和v-if指令的共同点和不同点?

v-show指令是通过修改元素的displayCSS属性让其显示或者隐藏。
v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果。

v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

Vue中如何在组件内部实现一个双向数据绑定?

假设有一个输入框组件,用户输入时,同步父组件页面中的数据。

具体思路:父组件通过props传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值;当输入数据时,父子组件中的数据是同步改变的;我们在父组件中做了两件事,一是给子组件传入props,二是监听input事件并同步自己的value属性。那么这两步操作能否再精简一下呢?答案是可以的,你只需要修改父组件:

v-model 实际上会帮我们完成上面的两步操作.

Vue中如何监控某个属性值的变化?

比如现在要监控data中,obj.a的变化。Vue中监控对象属性的办法你可以这样做:

watch: {
      obj: {
      handler (newValue, oldValue) {
        console.log('obj changed')
      },
      deep: true
    }
  }

deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改

watch: {
   'obj.a': {
      handler (newName, oldName) {
        console.log('obj.a changed')
      }
   }
  }

还有一种方法,可以通过computed 来实现,只需要:

computed: {
    a1 () {
      return this.obj.a
    }
}

利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。

前端如何优化网站性能?

1、减少 HTTP 请求数量

在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。

CSS Sprites

国内俗称CSS精灵,这是将多张图片合并成一张图片达到减少HTTP请求的一种解决方案,可以通过CSS的background属性来访问图片内容。这种方案同时还可以减少图片总字节数。

合并 CSS 和 JS 文件

现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个CSS或者多个JS合并成一个文件。

采用 lazyLoad

俗称懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。

2、控制资源文件加载优先级

浏览器在加载HTML内容时,是将HTML内容从上至下依次解析,解析到link或者script标签就会加载href或者src对应链接内容,为了第一时间展示页面给用户,就需要将CSS提前加载,不要受 JS 加载影响。

一般情况下都是CSS在头部,JS在底部。

3、利用浏览器缓存

浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。

4、减少重排(Reflow)

基本原理:重排是DOM的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的visibility属性,这也是Reflow低效的原因。如果Reflow的过于频繁,CPU使用率就会急剧上升。

减少Reflow,如果需要在DOM操作时添加样式,尽量使用 增加class属性,而不是通过style操作样式。

5、减少 DOM 操作

6、图标使用 IconFont 替换

网页从输入网址到渲染完成经历了哪些过程?

大致可以分为如下7步:

  • 输入网址;

  • 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;

  • 与web服务器建立TCP连接;

  • 浏览器向web服务器发送http请求;

  • web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);

  • 浏览器下载web服务器返回的数据及解析html源文件;

  • 生成DOM树,解析css和js,渲染页面,直至显示完成;

$route和$router的区别

​ $route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等

vue的method,computed,watch的区别:

​ computed是对结果进行缓存的,只要依赖的值没有变化,结果就不会变化.

​ method就是普通的方法。

​ computed减少执行的getter减去了重复计算,节省内存。watch是一直在监听。比如this.num + new Date(),虽然new Date的值一直在变,但只要this.num没变,结果还是一样的。

聊聊你对Vue.js的template编译的理解?

简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)

详情步骤:

​ 首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。

​ 然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)

vue的历史记录

history 记录中向前或者后退多少步

vue-loader是什么?使用它的用途有哪些?

​ 解析.vue文件的一个加载器,跟template/js/style转换成js模块。

​ 用途:js可以写es6、style样式可以scss或less、template可以加jade等

vuex有哪几种属性?

有五种,分别是 State、 Getter、Mutation 、Action、 Module

vuex的State特性
A、Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,对应于一般Vue对象里面的data
B、state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
C、它通过mapState把全局的 state 和 getters 映射到当前组件的 computed 计算属性中
vuex的Getter特性
A、getters 可以对State进行计算操作,它就是Store的计算属性
B、 虽然在组件内也可以做计算属性,但是getters 可以在多组件之间复用
C、 如果一个状态只在一个组件内使用,是可以不用getters
vuex的Mutation特性
Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。

posted on 2020-10-12 15:02  九酒馆  阅读(67)  评论(0编辑  收藏  举报

导航