Vue前端面试总结上

目录


一、Vue是什么

二、Vue核心特性

三、Vue跟传统开发的区别

四、SPA是什么

五、SPA和MPA的区别

六、实现一个SPA

七、如何给SPA做SEO

八、v-show与v-if

九、v-for与v-if

十、Vue实例挂载的过程

十一、Vue的生命周期

十二、如何解决SPA首屏加载速度慢

十三、为什么data属性是一个函数而不是一个对象

十四、动态给vue的data添加一个新的属性

十五、Vue中的组件和插件

十六、组件间通信

十七、NextTick

十八、vue中的mixin


一、Vue是什么

Vue.js(简称为Vue)是一个用于创建用户界面的开源JavaScript框架,也是一个创建单页应用的Web应用框架。

二、Vue核心特性

数据驱动(MVVM)

这里的详细解释参考之前的文章:http://t.csdn.cn/Mab2F

Model:模型层,负责处理业务逻辑以及和服务器端进行交互

View:视图层:负责将数据模型转化为UI展示出来,可以理解为HTML页面

ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁

双向绑定

此处双向绑定也是考点,参考:http://t.csdn.cn/z97Te

组件化

把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件

组件化优势

1.降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现

2.调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单

3.提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级

指令系统

指令是带有 v- 前缀的特殊属性作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM

常用的指令:

条件渲染指令 v-if

列表渲染指令 v-for

属性绑定指令 v-bind---单向绑定

事件绑定指令 v-on---事件监听

双向数据绑定指令 v-model

三、Vue跟传统开发的区别

Vue所有的界面事件,都是只去操作数据的,Jquery操作DOM

Vue所有界面的变动,都是根据数据自动绑定出来的,Jquery操作DOM

四、SPA是什么

即单页应用。SPA是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换,打断用户体验。在单页应用中,所有必要的代码(HTMLJavaScriptCSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面页面在任何时间点都不会重新加载,也不会将控制转移到其他页面

五、SPA和MPA的区别

MPA(多页应用)中,每个页面都是一个独立的主页面。当访问另一个页面时,都需要重新加载htmlcssjs文件

单页面应用(SPA) 多页面应用(MPA)
组成 一个主页面和多个页面片段 多个主页面
刷新方式 局部刷新 整页刷新
url模式 哈希模式 (Hash) 历史模式 (History)
SEO(搜索引擎优化) 难实现,可使用SSR方式改善 容易实现
数据传递 容易 通过url、cookie、localStorage等传递
页面切换 速度快,用户体验良好 切换加载资源,速度慢,用户体验差
维护成本 相对容易 相对复杂

解释SEO和SSR

SEO: 利用搜索引擎的规则,提高网站在有关搜索引擎内的自然排名

SSR:服务器渲染

单页应用优缺点

优点

  • 具有桌面应用的即时性、网站的可移植性和可访问性

  • 用户体验好、快,内容的改变不需要重新加载整个页面

  • 良好的前后端分离,分工更明确

缺点

  • 不利于搜索引擎的抓取

  • 首次渲染速度相对较慢

六、实现一个SPA

步骤

1.显示当前界面,点击界面按钮,或浏览器回退/前进按钮触发 hash 变化

2.检测 hash 或者 pushstate 变化

3.以当前 hash 为索引,加载对应资源

4.等待资源加载完毕,隐藏之前的界面,执行回调

hash 模式

通过监听url中的hash来进行路由跳转

history 模式

借用 HTML5 history apiapi 提供了丰富的 router 相关属性

history 相关的api

history.pushState: 浏览器历史记录添加记录

history.replaceState: 修改浏览器历史纪录中当前记录

history.popState: 当 history 发生变化时触发

七、如何给SPA做SEO

三种方式

1.SSR服务端渲染

将组件或页面通过服务器生成html,再返回给浏览器

2.静态化

目前主流的静态化主要有两种:

(1)一种是通过程序将动态页面抓取并保存为静态页面,这样的页面的实际存在于服务器的硬盘中

(2)另外一种是通过WEB服务器的 URL Rewrite的方式,它的原理是通过web服务器内部模块按一定规则将外部的URL请求转化为内部的文件地址,就是把外部请求的静态地址转化为实际的动态页面地址,而静态页面实际是不存在的。

3.使用Phantomjs针对爬虫处理

通过Nginx配置,判断访问来源是否为爬虫,如果是,则搜索引擎的爬虫请求会转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫。

八、v-show与v-if

共同点

能控制元素在页面是否显示

  • 当表达式为true时,都会占据页面的位置

  • 当表达式都为false时,都不会占据页面位置

区别

控制手段不同

v-show控制显示与隐藏:为该元素添加display:nonedom元素仍存在

v-if控制显示与隐藏:是将dom元素整个添加或删除

编译过程不同

v-show只是基于css切换

v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

编译条件不同

v-showfalse变为true的时候不会触发组件的生命周期

v-if是真正的条件渲染,它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。渲染条件为假时不做操作,直到为真才渲染。由false变为true的时候,触发组件的beforeCreatecreatebeforeMountmounted钩子。由true变为false的时候触发组件的beforeDestorydestoryed方法

性能消耗不同

v-show有更高的初始渲染消耗

v-if有更高的切换消耗

v-show与v-if的使用场景

v-ifv-show 都能控制dom元素在页面的显示

v-if 相比 v-show 开销更大的(直接操作dom节点增加与删除)

如果需要频繁地切换,使用 v-show 较好

如果在运行时条件很少改变,使用 v-if 较好

九、v-for与v-if

1.v-for优先级比v-if

2.v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值时被渲染

3.v-for 指令基于一个数组来渲染一个列表,使用 item in items 形式的特殊语法,其中 items 是源数据数组或对象, item 是被迭代的数组元素

在使用 v-for 的时候,建议设置唯一的key值,便于diff算法进行优化(一般不建议使用index值)

一般不会同时使用两者,如果确实需要同时使用,则不编写在同一元素身上,可以在外层嵌套template标签,在template 层进行 v-if 判断,在内部进行 v-for 循环

如果 v-if 判断 出现在 v-for 循环 内部,可通过计算属性computed提前过滤掉那些不需要显示的项

computed: {
    items: function() {
      return this.list.filter(function (item) {
        return item.isShow
      })
    }
}

十、Vue实例挂载的过程

new Vue会调用_init方法

  • 定义 $set$get$delete$watch 等方法

  • 定义 $on$off$emit$off等事件

  • 定义 _update$forceUpdate$destroy生命周期

  • 调用$mount进行页面的挂载

  • 挂载的时候主要是通过mountComponent方法

  • 定义updateComponent更新函数

  • 执行render生成虚拟DOM

  • _update将虚拟DOM生成真实DOM结构,并且渲染到页面中

十一、Vue的生命周期

生命周期是什么

Vue中,实例从创建到销毁的过程就是生命周期(创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等过程)

使用场景

生命周期 描述
beforeCreate 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
created 组件初始化完毕,各种数据可以使用,常用于异步数据获取
beforeMount 未执行渲染、更新,dom未创建
mounted 初始化结束,dom已创建,可用于获取访问数据和dom元素
beforeUpdate 更新前,可用于获取更新前各种状态
updated 更新后,所有状态已是最新
beforeDestroy 销毁前,可用于一些定时器或订阅的取消
destroyed 组件已销毁,作用同上组件实例完成销毁
activated keep-alive 缓存的组件激活时
deactivated keep-alive 缓存的组件停用时
errorCaptured 捕获来自子孙组件的错误时

为什么一般在created进行异步数据请求

在钩子函数 created、beforeMount、mounted 中都可以尽心异步数据请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

但一般在 created 中调用异步请求,有以下优点:

· 能更快获取到服务端数据,减少页面加载时间,用户体验更好

· SSR 不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性

十二、如何解决SPA首屏加载速度慢

首屏时间是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间。此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容。

加载慢的原因

1.网络延时问题

2.资源文件体积是否过大

3.资源是否重复发送请求去加载

4.加载脚本的时候,渲染内容堵塞

几种SPA首屏优化方式

1.减小入口文件体积

通过路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加

2.静态资源本地缓存

前端合理利用localStorage

后端返回资源问题:

  • 采用HTTP缓存,设置Cache-ControlLast-ModifiedEtag等响应头

  • 采用Service Worker离线缓存

3.UI框架按需加载

不要直接引用整个UI库

import ElementUI from 'element-ui'

而是按需引用,减小文件缓存体积

import { TableColumn, MessageBox } from 'element-ui'

4.组件重复打包

假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载

解决方案:在webpackconfig文件中,修改CommonsChunkPlugin的配置

minChunks: 3 // 表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件

5.图片资源的压缩

6.开启GZip压缩

拆完包之后,用gzip做一下压缩,安装compression-webpack-plugin

cnmp i compression-webpack-plugin -D

vue.congig.js中引入并修改webpack配置

const CompressionPlugin = require('compression-webpack-plugin')
​
configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            // 为生产环境修改配置
            config.mode = 'production'
            return {
                plugins: [new CompressionPlugin({
                    test: /\.js$|\.html$|\.css/, //匹配文件名
                    threshold: 10240, //对超过10k的数据进行压缩
                    deleteOriginalAssets: false //是否删除原文件
                })]
            }
        }

在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用

const compression = require('compression')
app.use(compression())  // 在其他中间件使用之前调用

7.使用SSR

SSR(服务端渲染),组件或页面通过服务器生成html字符串,再发送到浏览器。使用Nuxt.js实现服务端渲染

十三、为什么data属性是一个函数而不是一个对象

根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况

组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

十四、动态给vue的data添加一个新的属性

定义一个p标签用于显示data属性中的数据,点击按钮实现增加新数据的效果(同时改变vue中的data属性以及UI界面的显示)

{{ value }}

我们在vue实例中,定义data属性和methods方法,然后通过绑定点击事件输出data中的数据,点击按钮,数据更新了(console打印出了新属性),但页面没有更新,仍然只显示 ‘旧属性’

const app = new Vue({
    el:"#app",
   	data:()=>{
       	item:{
            oldProperty:"旧属性"
        }
    },
    methods:{
        addProperty(){
            this.items.newProperty = "新属性"  // 为items添加新属性
            console.log(this.items)  // 输出带有newProperty的items
        }
    }
})

在vue2中,因为初始设置的数据可以触发settergetter,从而变成响应式数据,而按钮点击新增的数据并不会通过Object.defineProperty 变成响应式数据,不会自动触发settergetter

解决方案

  • 如果为对象添加少量的新属性,可以直接采用Vue.set()

  • 如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象

vue3是用过proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式

十五、Vue中的组件和插件

Vue 核心特性中介绍了组件的定义,而插件是用来为 Vue 添加全局功能

插件的功能范围没有严格的限制——一般有下面几种:

  • 添加全局方法或者属性。如: vue-custom-element

  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch

  • 通过全局混入来添加一些组件选项。如vue-router

  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。

  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如vue-router

区别:

编写形式不同

每一个.vue文件都可以看成是一个组件

`vue`文件标准格式




还可以通过template属性来编写组件,如果组件内容多,可以在外部定义template组件内容



Vue.component('componentA',{ 
    template: '#testComponent'  
    template: `
component
` // 组件内容少可以通过这种形式 })

vue插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

注册形式不同

vue组件注册主要分为全局注册与局部注册

全局注册通过Vue.component方法,第一个参数为组件的名称,第二个参数为传入的配置项

Vue.component('my-component', { /* ... */ })

局部注册只需在用到的地方通过components属性注册一个组件

const component1 = {...} // 定义一个组件

export default {
	components:{
		component1   // 局部注册
	}
}

插件的注册通过Vue.use()的方式进行注册,第一个参数为插件的名字,第二个参数是可选择的配置项

Vue.use('my-plugin', { /* ... */} )

注册插件的时候,需要在调用 new Vue() 启动应用之前完成

Vue.use会自动阻止多次注册相同插件,只会注册一次

使用场景不同

组件 (Component) 是用来构成 App 的业务模块,它的目标是 App.vue

插件 (Plugin) 是对Vue的功能的增强或补充,它的目标是 Vue 本身,

十六、组件间通信

每个组件之间的都有独自的作用域,组件间通信是为了组件间数据共享

组件间通信的分类

父子组件之间的通信

兄弟组件之间的通信

祖孙与后代组件之间的通信

非关系组件间之间的通信

通信方式

父子关系的组件数据传递选择 props$emit进行传递,也可选择ref

props传递数据(父组件传递数据给子组件)

子组件设置props属性,定义接收父组件传递过来的参数

// Children.vue
props:{  
    // 字符串形式  
    name:String, // 接收的类型参数  
    // 对象形式  
    age:{    
        type:Number, // 接收的类型为数值  
        defaule:18,  // 默认值为18  
        require:true // age属性必须传递  
    }  
}

父组件在使用子组件标签中通过字面量来传递值

// Father.vue
  

$emit 触发自定义事件(子组件传递数据给父组件)

子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值

// Children.vue
this.$emit('add', good)

父组件绑定监听器获取到子组件传递过来的参数

// Father.vue
  

ref(子组件传递数据给父组件)

父组件在使用子组件的时候设置ref,父组件通过设置子组件ref来获取数据

// Father.vue
  
this.$refs.foo  // 获取子组件实例,通过子组件实例能拿到对应的数据  

兄弟关系的组件数据传递可选择$bus,或者选择$parent进行传递

EventBus(兄弟组件传值)

创建一个中央事件总线EventBus

// 创建一个全局事件总线  
class Bus {  
  constructor() {  
    this.callbacks = {};   // 存放事件的名字  
  }  
  $on(name, fn) {  
    this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {  
    if (this.callbacks[name]) {  
      this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  
  
// main.js  
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
// 另一种方式  
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能

兄弟组件通过$emit触发自定义事件,$emit第二个参数为传递的数值

// Brother1.vue
this.$bus.$emit('foo') 

另一个兄弟组件通过$on监听自定义事件

// Brother2.vue
this.$bus.$on('foo', this.handle) 

共同祖辈$parent或者$root(兄弟组件传值)

// Brother1.vue
this.$parent.on('add',this.add)
// Brother2.vue
this.$parent.emit('add')

祖先与后代组件数据传递可选择attrslisteners或者 ProvideInject

$attrs 与$ listeners(祖先传递数据给子孙)

设置批量向下传属性$attrs$listeners

// child:并未在props中声明foo  

{{$attrs.foo}}

// parent

包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外),通过 v-bind="$attrs" 传⼊内部组件

// 给Grandson隔代传值,communication/index.vue  
  
  
// Child2做展开  
  
  
// Grandson使⽤  
{{msg}}

provide 与 inject(祖先传递数据给子孙)

在祖先组件定义provide属性,返回传递的值

// GrandParent
provide(){  
    return {  
        foo:'foo'  
    }  
} 

在后代组件通过inject接收组件传递过来的值

// Grandson
inject:['foo'] // 获取到祖先组件传递过来的值

vuex存放共享的变量的容器(复杂关系的组件数据传递)

state用来存放共享变量

getter可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值

mutations用来存放修改state的方法

actions是在mutations的基础上进行,也用来存放修改state的方法。常用来进行异步操作

十七、NextTick

NextTick是什么

是一种优化策略。在下次 DOM 更新循环结束之后,执行延迟回调。即修改数据和更新数据是异步执行,在修改数据之后立即使用这个方法,获取更新后的 DOM

为什么要用NextTick

如果不适用 nextTick 更新机制,那么 num 每次更新值,都会触发视图更新

{{num}}
for(let i=0; i<100000; i++){
    num = i
}

有了nextTick机制,只需要等更新循环结束之后,更新一次

怎么用NextTick

在修改数据后,想立刻得到更新后的DOM结构,可以使用Vue.nextTick()

第一个参数为:回调函数(可以获取最近的DOM结构)

第二个参数为:执行函数上下文

// 修改数据
vm.message = '修改后的值'
// DOM 还没有更新
console.log(vm.$el.textContent) // 原始的值
Vue.nextTick(function () {
  // DOM 更新了
  console.log(vm.$el.textContent) // 修改后的值
})

组件内使用 vm.$nextTick() 实例方法只需要通过this.$nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上

this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
this.$nextTick(function () {
    console.log(this.$el.textContent) // => '修改后的值'
})

$nextTick() 会返回一个 Promise 对象,可以是用async/await完成相同作用的事情

this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
await this.$nextTick()
console.log(this.$el.textContent) // => '修改后的值'

十八、vue中的mixin

mixin是什么

mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能,本质是一个js对象,可以包含组件中任意功能选项,如datacomponents等,将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时,所有mixins对象的选项都将被混入该组件本身的选项中来

minin的分类

局部混入

定义一个mixin对象

var Mixin = {
  methods: {
    hello: function () {
      console.log('hello mixin!')
    }
  }
}

组件通过mixins属性调用mixin对象

Vue.component('componentA',{
  	mixins: [Mixin]
})

该组件在使用的时候,混合了mixin里面的方法,执行hello方法

全局混入

通过Vue.mixin()进行全局的混入,常用于插件的编写

Vue.mixin({
  	created: function () {
      console.log("全局混入")
    }
})

使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)

什么时候用mixin

当组件A和组件B都要实现hello这个function时,为了减少代码重复,通过Vuemixin功能hello这个方法提出来,在两个组件中都调用mixin即可

const hello = {
  methods: {
    Hello() {
      console.log('hello!')
    }
  }
}
const A = {
  template: '#A',
  mixins: [hello]
};
 
const B = {
  template: '#B',
  mixins: [hello]
}

参考文献:面试官:SSR解决了什么问题?有做过SSR吗?你是怎么做的? | web前端面试 - 面试官系列 

你可能感兴趣的:(vue.js,前端,面试)