1.vue和react的区别
相似之处:
(1).他们都专注于创造前端的富应用,react和vue都只有骨架,其它的功能如路由、状态管理等是框架分离的组件;
(2).虚拟DOM(Virtual DOM),它是一个映射真实DOM的js对象,如果需要改变任何元素的状态,首先在虚拟DOM上进行改变,而不是直接改变真实的DOM。当有变化发生时,一个新的Virtual DOM对象会被创建并计算新旧虚拟DOM之间的差别,然后把这些差别应用在真实的DOM上;
(3).组件化和数据驱动;
(4).可以使用自己的构建工具快速搭建开发环境。React可以使用Create React App(CRA),而Vue对应的则是vue-cli;
(5).配套全家桶;
(6).都有自己的移动端解决方案,React——react native;Vue——weex;
主要区别:
(1).React整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在React中,是单向数据流,强调数据的不可变;而Vue2是通过Object.defineProperty()劫持getter/setter(Vue3通过Proxy)来双向绑定数据,能精确知道数据的变化,使用的是可变数据;
(2).在React中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树;在Vue中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确的知晓哪个组件确实需要被渲染
(3).React使用JSX语法,Vue虽然现在也支持了JSX,但默认使用更多的还是<template>模板
2.单页应用(SPA)的原理和优缺点
什么是单页应用?
单页应用(Single Page Application),是一种只需要将单个页面作为容器加载到浏览器之中的Web应用程序。
原理:主要由History和Hash两种形式实现
History:主要依靠Window.history对象的pushState()方法和replaceState()方法,这两个方法可以用来向历史栈中添加或修改数据,就好像URL变化了一样(过去只有URL变化,历史栈才会变化),前端路由就是基于这个原理实现的,当他们执行修改时,虽然改变了当前的URL,但浏览器不会立即向后端发送请求;
Hash:在URL中可以带上一个#,这个就是hash模式,location.hash的值实际就是#后面的东西,当URL的片段标识符(#后面的东西)发生变化时,将触发window对象中的onhashchange事件,然后做一些操作即可;
优点:
(1).避免了页面的重新加载,用户有良好流畅的交互体检
(2).得益于ajax,可以实现无跳转刷新,没有页面之间的切换
(3).减轻服务器压力,服务器只用出数据就可以了,不用管展示逻辑和页面合成
缺点:
(1).第一次加载首页耗时相对长一些
(2).不利于seo的优化
(3).不适合开发大型项目(设计大量DOM的操作)
3.什么是vuex?以及它的使用场景?
vuex是vue的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
常应用于多个组件共享状态时,如购物车、登录状态等
4.什么是MVVM框架,它和MVC的区别?
MVC:
image.png
Model:数据模型,可以把它看做从数据库中查出来的一条数据
View:视图,用户肉眼可见的界面层
Controller:控制器,数据模型和视图层之间的桥梁层,负责数据的处理、组装等页面业务逻辑。使用MVC的目的就是将M和V的代码分离。MVC是单向通信,也就是View和Model必须通过Controller来承上启下。
MVVM:
image.png
Model:数据模型,可以把它看做从数据库中查出来的一条数据
View:视图,用户肉眼可见的界面层
ViewModel:View和Model之间的桥梁,将二者绑定起来。它是双向的,一个方向是是通过数据绑定的方式,将模型转化成视图,即将数据转化成所看到的的页面;另一个方向是将视图转化成模型,即通过DOM事件监听,将所看到的的页面转成数据。这两个方向称为数据的双向绑定。在MVVM中视图和模型是不能直接通信的,它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者模式,当数据发生变化时,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,viewModel也能监听到视图的变化,然后通知数据做改动。
总结:MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在的目的在于抽离了Controller中展示的业务逻辑,其它视图操作业务等还是应该放在Controller中实现,也就是说MVVM实现的是业务逻辑组件的重用。
5.Vue.js的核心是什么
数据驱动、组件化
6.Vue的生命周期以及调用顺序
创建前/后:
beforeCreate,vue实例的$el和数据对象data此时都为undefined,还未初始化;
created,vue实例的数据对象data有了,但是$el还没有
载入前/后:
beforeMount,vue实例的$el和data都初始化了,data中的模板和数据生成了html,但还没有挂载在html上;
mounted,挂载完成,也就是模版中的html渲染到到html页面中
更新前/后:
beforeUpdate,数据更新之前被调用;
updated,更新完成之后调用,组件Dom已经更新,要避免在此期间更改数据,否则可能会导致无限循环
销毁前/后:
beforeDestory,在实例销毁之前调用,此时实例仍然完全可用,一般可以在这一步做一些重置的操作,比如清除定时器和监听的dom事件;
destoryed,在实例销毁之后调用,此时所有的事件监听器都会被移除,组件已被拆解,数据绑定被卸载,所有的子实例也会被销毁
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父;
组件的销毁操作是先父后子,销毁完成的顺序是先子后父;
7.第一次页面加载会触发哪几个钩子
beforeCreate——created——beforeMounted——mounted
8.Vue的双向绑定原理
Vue2
原理:采用数据劫持结合发布者-订阅者模式的方法,通过Object.defineProperty()来劫持各个属性的getter、setter属性,在数据变动后,通知订阅者,触发更新回调函数,重新渲染视图。当把一个普通js对象传给Vue实例来作为它的data选项时,Vue将遍历它的属性,用Object.defineProperty()将它们转为getter/setter。用户看不到getter/setter,但是在内部它们让Vue追踪依赖,在属性被访问和修改时通知变化。
实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" id="input">
<span id="span"></span>
</div>
</body>
<script>
let obj = {}; //定义一个空对象
let val = 'gwy'; //赋予初始值
Object.defineProperty(obj, 'val', {//定义要修改对象的属性
get: function () {
return val;
},
set: function (newVal) {
//定义val等于修改后的内容
val = newVal
//让文本框的内容等于val
document.getElementById('input').value = val
//让span的内容等于val
document.getElementById('span').innerHTML = val
}
})
document.addEventListener('keyup', function (e) {
//当在文本框输入内容时让对象里你定义的val等于文本框的值
obj.val = e.target.value;
})
</script>
</html>
Vue3
原理:Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由Object.defineProperty更改为Proxy代理,其他代码不变
为什么要用Proxy取代Object.defineProperty?
(1).Object.defineProperty存在一定的局限性,无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。针对数组的某些方法进行了hack处理,只有以下八种方法可以检测到变化:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
(2).Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是一个对象那么需要深层遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
要取代Object.defineProperty的Proxy有两个优点:
(1).可以劫持整个对象,并返回一个新对象
(2).有13种劫持操作
什么是Proxy?
Proxy是 ES6 中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
实现:
const obj = {};
const input = document.getElementById("input")
const title = document.getElementById("title")
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`)
return Reflect.get(target, key, receiver)
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver)
if (key === "text") {
input.value = value
title.innerHTML = value
}
return Reflect.set(target, key, value, receiver);
}
})
input.addEventListener("keyup", function(e) {
newObj.text = e.target.value;
})
什么是Reflect?
Reflect是ES6里的新的对象,非构造函数,不能用new操作符。可以把它跟Math类比,Math是处理JS中数学问题的方法函数集合,Reflect是JS中对象操作方法函数集合,它暴露出来的方法与Object构造函数所带的静态方法大部分重合,实际功能也类似,Reflect的出现一部分原因是想让开发者不直接使用Object这一类语言层面上的方法,还有一部分原因也是为了完善一些功能。Reflect提供的方法还有一个特点,完全与Proxy构造函数里Hander参数对象中的钩子属性一一对应。
9.从template转换成真实DOM的实现机制
先将template中的html转换成函数,然后利用这个函数生成virtual dom对象,遍历这个对象,生成真实的dom
10.virtual DOM原理
Virtual DOM是一个简单的js对象,在页面进行更新的时候,借助虚拟DOM元素的改变可以在内存中进行比较,并将多次比较的结果合并以后一次性的更新到页面,从而减少页面渲染的次数,提高渲染效率。
具体实现步骤如下:
(1).用js对象结构表示DOM树的结构,然后用这个树构建一个真正的DOM树,插入到文档中
(2).当状态变更的时候,重新构造一颗新的对象树,然后用新的树和旧的树进行比较,记录两棵树的差异
(3).把记录到的差异应用到真正的DOM树上,视图就更新了
11.v-if与v-show的区别与应用场景?
v-if是通过重建和销毁dom来达到显示和隐藏的效果
v-show是通过修改dom的display属性来显示和隐藏
当需要频繁切换的时候,用v-show
12.nextTick原理
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
首先来了解一下js的运行机制:
(1).所有的同步任务都在主线程上执行,形成一个执行栈(execution context stack)
(2).主线程之外,会存在一个任务队列(task queue),只要异步任务有了运行结果,就在任务队列中放置一个事件
(3).一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
(4).主线程不断重复上面的第三步
这里主线程的执行过程就是一个tick,而所有的异步结果都是通过任务队列来调度。Event Loop分为宏任务和微任务,无论是执行宏任务还是微任务,完成后都会进入到下一个tick,并在两个tick之间进行UI渲染。
由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了Vue.nextTick()方法,Vue会在内部尝试对异步队列延迟调用,由于宏任务耗费的时间是大于微任务的,所以在浏览器的支持下,优先使用微任务,如果浏览器不支持微任务,再使用宏任务。
延迟调用的优先级:Promise > MutationObserver > setImmediate > setTimeout
13. Vue跨组件通信方案
父传子:属性传值,父组件给子组件标签上定义属性,子组件通过props接收
子传父:事件传值,子组件通过$emit,父组件通过在子组件上定义自定义事件来接收
兄弟组件:通过中央事件总线(Event Bus)
Vuex:全局状态管理
14.Vue组件中的data为什么是一个函数
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
15.v-model的原理
v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。 可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" />
16.vue 常用的修饰符
v-model.number:将用户输入的值转为数值类型
v-model.trim:自动过滤用户输入的首尾空白字符
@click.stop:阻止事件冒泡
@click.once:事件只执行一次
v-on:keyup.enter:(键盘修饰符)按下键盘的时候
17.v-on 监听多个方法
<input type="text" v-on="{input:onInput,focus:onFocus,blur:onBlur}">
18.Vue 中 key 值的作用
Key的作用就是尽可能的复用dom元素,当有相同的标签名和元素切换时,需要通过key的特效设置唯一的值来标记,让vue区分它们,否则vue为了效率只会替换相同标签内部的内容
19.vue 事件中如何使用event对象
<button @click="Event($event)">事件对象</button>
methods: {
Event(e){
console.log(e)
}
}
20.v-for 与v-if的优先级,如何同时使用
v-for比v-if具有更高的优先级
永远不要把 v-if 和 v-for 同时用在同一个元素上
21.Vue更新数组时触发视图更新的方法
改变原数组:push(),pop(),shift(),unshift(),splice(),sort(),reverse()
不改变原数组:filter(),concat(),slice()
Vue.set(target, key, value)
target:要更改的数据源(可以是对象或者数组)
key:要更改的具体数据
value :重新赋的值
22.Vue如何优化首屏加载速度
(1).路由懒加载,把不同路由对应的组件分隔成不同的代码块,按需加载
(2).减少入口文件体积
(3).静态资源本地缓存
(4).使用CDN资源,减小服务器带宽压力
(5).使用Nginx开启gzip压缩,减小网络传输的流量大小
(6).使用SSR
23.router-link和a标签的区别
router-link的to属性跳转比起a标签的href跳转的区别是,router-link进行跳转不会跳转到新页面,避免了重新渲染,它只更新变化的部分从而减少dom性能的消耗
24.Vue模版编译原理
简单来说,Vue的编译过程就是将template转换成render函数的过程,会经历一下阶段:
(1).将模版字符串转换成element ASTs(解析器)
首先是解析模板,生成AST树(一种用js对象的形式来描述整个模板)。使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理
(2).对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
Vue的数据是响应式的,但模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的对比,对运行时的模板起到很大的优化作用
(3).使用element ASTs生成render函数代码字符串(代码生成器)
编译的最后一步是将优化后的AST树转换为可执行的代码
25.Vue2.x和Vue3.x渲染器的diff算法
简单来说,diff算法有以下过程:
(1).同级比较,再比较子节点
(2).先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
(3).比较都有子节点的情况(核心diff)
(4).递归比较子节点
Vue2的核心diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比react的diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅
Vue3借鉴了ivi算法和inferno算法,在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的diff算法,使得性能上较Vue2有了提示
26.keep-alive了解吗
<keep-alive></keep-alive> 包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染
27.关于服务端渲染(SSR)
什么是服务端渲染?
简单理解是将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序
优点:
(1).更利于SEO
爬虫只会爬取源码,不会执行网站的任何脚本(google除外,据说google可以运行JavaScript)。使用了React或其它的MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成的,可供爬虫抓取分析的内容就很少。而服务端渲染返回给客户端的是已经获取了异步数据并执行了JS脚本的最终的HTML,爬虫就可以抓取到完整的信息
(2).更利于首屏渲染
首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
缺点:
(1).服务端压力较大
本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源
(2).开发条件受限
在服务端渲染中,beforeCreate和created,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制
28.你都做过哪些Vue的性能优化
编码阶段:
(1).尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
(2).v-if和v-for不能连用
(3).如果需要使用v-for给每项元素绑定事件时使用事件代理
(4).SPA页面采用keep-alive缓存组件
(5).如果不需要频繁切换,使用v-if替代v-show
(6).Key值保持唯一
(7).使用路由懒加载
(8).防抖,节流
(9).第三方模块按需导入
(10).长列表滚动加载
(11).图片懒加载
SEO优化:
(1).预渲染
(2).服务端渲染SSR
打包优化:
(1).压缩代码
(2).使用cdn加载第三方模块
(3).Tree Shaking/Scope Hoisting
(4).多线程打包happypack
(5).splitChunks抽离公共文件
(6).sourceMap优化
29.Vue2的响应式是如何监听Array的?
Vue中利用Object.defineProperty监听数组和对象是有缺陷的,Vue 不能检测到对象属性的添加或删除,当数组通过下标赋值或者通过改变数组长度去改变数组,都无法更新视图,这是由于Vue没有做相应的处理,但是Vue通过劫持数组的原型方法,将所有可能使得数组产生变化的方法劫持,当数据调用这些方法的时候,dep.notify(),会通知依赖于此数据的视图update(),手动派发更新。