前端常见的一些问题
1.前端性能优化手段?
1. 尽可能使用雪碧图
2. 使用字体图标代替图片
3. 对HTML,css,js 文件进行压缩
4. 模块按需加载
5. 资源懒加载与资源预加载
6. 避免使用层级较深的选择器及减少DOM深度
2.单页面应用和多页面应用的区别及优缺点?
单页面的概念: 单页面应用(SPA),其实就是指只有一个主页面的应用,类似前端现在的三大框架,React.Vue,Angular 浏览器一开始要加载所有必须的html,js css。所有的页面内容都包含在这个所谓的主页面中。
单页面的原理:利用js感知到URL的变化,通过这一点,可以用js动态的将当前的页面内容清除掉,然后将下一个页面的内容挂载到当前的页面上。页面每次切换跳转时,并不需要做html文件的请求,这样就节约了很多http发送延迟,我们在切换页面的时候速度很快。
单页面的优点:
1. 加载速度快,用户体验好,内容的改变不需要重新加载整个页面,基于这一点SPA对服务器压力较小。
2. 前后端分离
3. 页面视觉效果良好
单页面的缺点:
1. 不利于SEO(搜索引擎优化)
2. 页面初次加载时比较慢
3. 页面复杂度提高很多
多页面的概念: 多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整个页面都刷新,每次都请求一个新的页面。
多页面的优点:首屏加载时间快,SEO效果好
多页面的缺点:页面切换慢,每次切换页面都需要选择性的重新加载公共资源
3. var,let,const的区别?
var:var的作用域是函数作用域,在一个函数内利用var声明一个变量,则这个函数只在这个函数内有效,存在变量提升。
let :作用域是块级作用域 不存在变量提升,不允许重复定义。
const :一般用来声明常量,且声明的常量是不允许被改变的,声明的时候就赋值,不存在变量提升,不允许重复定义。
4. 箭头函数和普通函数的区别?
箭头函数:
- this指向:箭头函数指向 定义时所在的作用域中的this指向
2. 箭头函数作为匿名函数,是不能作为构造函数的,不能使用new
3. 箭头函数不能换行
普通函数:
- this指向:谁调用就指向谁
5. 流式布局和响应式布局?
流式布局: 使用非固定像素来定义网页内容,也就是百分比布局,通过盒子的宽度设置成百分比来根据屏幕的宽度来进 行伸缩,不受固定像素的限制,内容向两侧填充。
响应式布局: 利用CSS3 中的 Media Query(媒介查询),通过查询 screen 的宽度来指定某个宽度区间的网页布局。
6. css优先级算法?
!important>内联>ID选择器>class选择器>元素选择器>通配符选择器>继承>浏览器默认属性
7. null和undefined的区别?
undefined:类型只有一个,即undefined,当声明变量还未被初始化时就是undefined
null:类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象
8. http和https的区别?
https:是以安全为目标的HTTP通道,简单讲是HTTP的安全版本,通过SSL加密。
http:超文本传输协议。是一个客服端和服务器端请求和应答的标准(tcp),使浏览器更加高效,使网络传输减少。
9. ajax的理解?
ajax的原理:原理:相当于在用户和服务器之间加一个中间层(ajax引擎),使用户操作与服务器响应异步化。
ajax的优点:在不刷新整个页面的前提下与服务器通信维护数据。不会导致页面的重载可以把前端服务器的任务转接到客服端来处理,减轻服务器负担,节省宽带。
ajax的劣势:不支持back。对搜索引擎的支持比较弱;不容易调试 怎么解决呢?通过location.hash值来解决Ajax过程中导致的浏览器前进后退按键失效,解决以前被人常遇到的重复加载的问题。主要比较前后的hash值,看其是否相等,在判断是否触发ajax。
10. Html5新增哪些新特性?移出了哪些元素 ?
H5新增特性:
1. 用于绘画的canvas元素
2. 用于媒介回放的video和audio元素
3. 对本地离线存储的更好的支持(本地存储)
4.新增标签: header footer article nav section
5.新增表单控件:calender date time email url search
移除的元素:basefont big center font s strike tt u
11.移动端点透问题,如何解决?
问题:点透问题出现的原因就是移动端click事件300ms延迟问题,当点击上层元素时,先触发touchstart事件,然后在300ms后会触发click事件,而此时上层元素已经消失,所以下边的元素会触发click事件,这就是点透问题。
解决方法:
1. 使用一个透明遮罩,屏蔽所有事件,然后400ms(对于IOS来说是个理想值)后自动屏蔽。
2. touchstart换成touchend,因为触发touchend需要200ms所以可以把触发时间这个原理问题解决掉。
3. zepto最新版已经修复了这个问题,或者使用fastclick等通用库。
4. 直接使用click,不考虑延迟。
5.下层避开click事件,如a链接改为span等标签,使用js跳转页面。
12. rem em px的区别?
px:像素(Pixel)。绝对单位。像素 px 是相对于显示器屏幕分辨率而言的,是一个虚拟长度单位,
em:是相对长度单位,相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置, 则相对于浏览器的默认字体尺寸。它会继承父级元素的字体大小,因此并不是一个固定的值。
rem: 是 CSS3 新增的一个相对单位(root em,根 em),使用 rem 为元素设定字体大小时,仍然是相对大小, 但相对的只是 HTML 根元素。
13. http常见状态码?
405:客户端请求的额方法被禁止
408:服务器等待客户端发送的请求时间过长,超时
200:服务器成功处理了请求
400:客户端发送了一个错误的请求
404:未找到资源
500:服务器内部出现错误
501:服务器遇到错误,使其无法对请求提供服务
14.什么是同步,异步?
同步:由于js单线程,同步任务都在主线程上排队执行,前面任务没有执行完成,后面的任务会一直等待。
异步:不进入主线程,进入任务队列,等待主线程任务执行完成,开始执行。最基本的异步操作SetTimemot和SetInterval,等待主线程任务执行完,在开始执行里面的函数。
15. DOCTYPE的作用是什么?
<!DOCTYPE>位于文档的最顶部,它可以告诉浏览器按何种规范解析页面。
16. 请描述一下cookies、sessionStorage和localStorage的区别?
sessionStorage:用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。
localStorage:用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
Cookie:它的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。WebStorage拥有setItem、getItem、removeItem、clear等方法,不像cookie需要前端开发者自己封装setCookie、getCookie。但是Cookie也是不可以或缺的:Cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在,而WebStorage仅仅是为了在本地“存储”数据而生。
17. 简述一下src与href的区别?
src用于替换当前元素,href用于在当前文档和引用资源之间确立联系。
18. 一个页面上有大量的图片(大型电商网站),加载很慢,你有哪些方法优化这些图片的加载,给用户更好的体验。
1. 图片懒加载,在页面上的未可视区域可以添加一个滚动条事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
2. 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
3. 如果图片为css图片,可以使用CSSsprite,SVGsprite,Iconfont、Base64等技术。
4. 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
5. 如果图片展示区域小于图片的真实大小,则因在服务器端根据业务需要先行进行图片压缩,图片压缩后大小与展示一致。
19. 谈谈以前端角度出发,做好SEO需要考虑什么?
1. 了解搜索引擎如何抓取网页和如何索引网页,以及如何对搜索结果进行排序等。
2. Meta标签优化:主要包括主题(Title),网站描述(Description),和关键词(Keywords)。还有一些其它的隐藏文字比如Author(作者),Category(目录),Language(编码语种)等。
3. 如何选取关键词并在网页中放置关键词,关键词分析和选择是SEO最重要的工作之一。首先要给网站确定主关键词(一般在5个上下),然后针对这些关键词进行优化,包括关键词密度(Density),相关度(Relavancy),突出性(Prominency)等等。
4. 了解主要的搜索引擎,不同的搜索引擎对页面的抓取和索引、排序的规则都不一样。
5. 按点击付费的搜索引擎里面也大有优化和排名的学问,你得学会用最少的广告投入获得最多的点击。
6. 发外链。
7. 合理的标签使用。
20. 有哪些方式可以对一个DOM设置它的CSS样式?
1. 外部样式表,引入一个外部css文件。
2. 内部样式表,将css代码放在<head>标签内部。
3. 内联样式,将css样式直接定义在HTML元素内部。
21. CSS中可以通过哪些属性定义,使得一个DOM元素不显示在浏览器可视范围内?
1. display:none;(隐藏后不占位置)
2. visibility:hidden;(隐藏后依然占位置)
3. overflow:hidden;
4. 设置宽高为0
22. 行内元素和块级元素的具体区别是什么?行内元素的padding和margin可设置吗?
块级元素特性:总是独占一行,表现为另起一行开始,而且其后的元素也必须另起一行显示;宽度(width)、高度(height)、内边距(padding)和外边距(margin)都可控制;
行内元素特性:和相邻的行内元素在同一行;宽度(width)、高度(height)、内边距的top/bottom(padding-top/padding-bottom)和外边距的top/bottom(margin-top/margin-bottom)都不可改变(也就是padding和margin的left和right是可以设置的),就是里面文字或图片的大小。
行内块级元素:<input> 、<img> 、<button> 、<texterea> 、<label>
23. rgba()和opacity的透明效果有什么不同?
imageopacity作用于元素,以及元素内的所有内容的透明度。
rgba()只作用于元素的颜色或其背景色。(设置rgba透明的元素的子元素不会继承透明效果)
24. SASS、LESS是什么?大家为什么要使用它们?
它们是CSS预处理器。它是CSS上的一种抽象层。它们是一种特殊的语法/语言编译成CSS。
例如Less是一种动态样式语言. 将CSS赋予了动态语言的特性,如变量,继承,运算, 函数。LESS 既可以在客户端上运行 (支持IE 6+, Webkit, Firefox),也可以在服务端运行 (借助 Node.js)。
为什么要使用它们?
1. 结构清晰,便于扩展。
2. 可以方便地屏蔽浏览器私有语法差异。这个不用多说,封装对浏览器语法差异的重复处理,减少无意义的机械劳动。
3. 可以轻松实现多重继承。
4. 完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。
25. CSS中link和@import的区别是?
Link属于html标签,而@import是CSS中提供的。
在页面加载的时候,link会同时被加载,而@import引用的CSS会在页面加载完成后才会加载引用的CSS。
@import只有在ie5以上才可以被识别,而link是html标签,不存在浏览器兼容性问题。
Link引入样式的权重大于@import的引用(@import是将引用的样式导入到当前的页面中)
26. 为什么要初始化样式?
由于浏览器兼容的问题,不同的浏览器对标签的默认样式值不同,若不初始化会造成不同浏览器之间的显示差异。初始化CSS会对搜索引擎优化造成小影响。
27. HTML与XHTML有什么区别?
1. 所有的标记都必须要有一个相应的结束标记
2. 所有标签的元素和属性的名字都必须使用小写
3. 所有的 XML 标记都必须合理嵌套
4. 所有的属性必须用引号 "" 括起来
5. 把所有 < 和 & 特殊符号用编码表示
6. 给所有属性赋一个值
7. 不要在注释内容中使用 "--"
8. 图片必须有说明文字
28. 谈谈你对web标准及w3c的理解与认识。
标签闭合,标签小写,不乱嵌套,提高搜索机器人搜索几率,使用外链css和js脚本,结构行为表现的分离,页面下载与加载速度更快,内容能被更多的用户和更广泛的设备访问,更少的代码和组件,容易维护,改版方便,不需要改变页面内容,提供打印版不需要复制页面内容,提高网站的易用性。
29. 行内元素有哪些,块级元素有哪些,css的盒模型包括什么?
行内元素:span input b u i s select
块级元素:div p h1-h6 form ul
css盒模型:内容content + 内边距padding + 边框border + 外边距 margin
30. 列出display的值,说明它们的作用。position的值,relative和absolute的定位原点分别是?
display:
inline:按照行内元素样式显示
block:按照块级元素样式显示
inline-block:按照行内块级元素样式显示
none:隐藏元素
position:
static:静态定位,默认值,标准流中的元素都是静态定位
relation:相对定位,相对于原来的位置移动,依然占据着原来的位置
absolute:绝对定位,若元素没有父元素,或者父元素没有定位,则相对body定位,若父元素有定位(非static),则相对父元素定位,绝对定位的元素脱离标准流
fixed:固定定位,相对浏览器边框定位,固定定位的元素也脱离标准流
31. 清除浮动有哪些方法?
1. 额外标签法(会增加标签,一般不用):在浮动的盒子之下再放一个标签,在这个标签中使用clear:both,以此来清除浮动。
2. 使用overflow:hidden属性:找到浮动盒子的父元素,给它添加overflow:hidden属性,即可清除浮动的影响。(一般也不用此方法清除浮动,因为溢出的元素会被隐藏)
3. 使用伪元素清楚浮动:
.clearfix:after{content:'';height:0;line-height:0;display: block;overflow: hidden;clear: both;}
Javascript方面:
1. 列举你知道的强制类型转换和隐式类型转换?
强制转换:parseInt()、parseFloat()、Number()、String()、.toString()、Boolean()
隐式转换:加(例外:不能是算式中,而需要加在变量前)减乘除及取余,!!
2.split() 、join() 的区别?
前者是切割成数组的形式,后者是将数组转换成字符串。
3. 数组方法pop() push() unshift() shift()?
push()尾部添加、pop()尾部删除、unshift()头部添加、shift()头部删除
4.call和apply的区别?
call:
语法:call(thisObj,Object1,Object2...)
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
apply:
语法:apply(thisObj,[argArray])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
5.添加 删除 替换 插入到某个节点的方法?
obj.appendChid() 添加
obj.insertBefore() 插入
obj.replaceChild() 替换
obj.removeChild() 删除
6. javascript的本地对象,内置对象和宿主对象分别是什么?
本地对象为array、obj、regexp 等可以new实例化
内置对象为gload、Math 等不可以实例化的
宿主为浏览器自带的document、window 等
7.window.onload和document.ready的区别?
1. window.onload是在dom文档树加载完和所有文件加载完之后执行一个函数document.ready原生中没有这个方法,jquery中有$().ready(function),在dom文档树加载完之后执行一个函数(注意,这里面的文档树加载完不代表全部文件加载完)。
2. $(document).ready要比window.onload先执行。
3. window.onload只能出来一次,$(document).ready可以出现多次。
8."=="和"==="的不同?
前者只比较值,会自动转换类型。
后者比较值和类型。
9. javascript的同源策略?
一段脚本只能读取来自于同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合。
10. JavaScript的数据类型都有哪些?
基本数据类型:String、boolean、Number、undefined、null
引用数据类型:Object(Array、Date、RegExp、Function)
- 如何判断某变量是否为数组数据类型?
方法一:判断其是否具有“数组性质”,如slice()方法。可自己给该变量定义slice方法,故有时会失效。
方法二:obj instanceof Array,在某些IE版本中不正确。
方法三:方法一二皆有漏洞,在ECMA Script5中定义了新方法Array.isArray(), 保证其兼容性。
12. 当一个DOM节点被点击时候,我们希望能够执行一个函数,应该怎么做?
直接在DOM里绑定事件:<div onclick='test()'></div>
在JS里通过onclick绑定:xxx.onclick=test()
通过事件添加进行绑定:addEventListener(xxx,'click',test())
13. Javascript的事件流模型都有什么?
“事件冒泡”:事件开始由最具体的元素接收,然后逐级向上传播。
“事件捕捉”:事件由最不具体的节点先接收,然后逐级向下,一直到最具体的。
“DOM事件流”:三个阶段:事件捕捉,目标阶段,事件冒泡。
14. var numberArray=[3,6,2,4,1,5];
实现对该数组的倒排,输出[5,1,4,2,6,3]
numberArray.reverse();
实现对该数组的降序排列,输出[6,5,4,3,2,1]
numberArray.sort(function(a,b){returnb-a})
15. 闭包,闭包的作用?
闭包就是能够读取其他函数内部变量的函数。闭包是将函数内部和函数外部连接起来的桥梁。
作用 可以读取函数内部的变量 让这些变量的值始终保持在内存中。
缺点 1.闭包的缺点就是会增大内存使用量,并且使用不当容易造成内存泄漏。
2.如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
16. 作用域,作用域链?
作用域就是变量与函数的可访问范围。作用域分为 全局作用域(所声明的变量全局都可以访问),局部作用域(所声明的变量只在其内部可以访问)。
作用域链:当我们在一个函数内部访问当前作用域内不存在的变量时,就会逐层向外查找,如果一直找不到就会报错。当我们在局部作用域中,调用外部变量时,就产生了作用域链。
17. 描述一次完整的http请求?
1.查询NDS(域名解析),获取域名对应的IP地址
查询浏览器缓存
2.浏览器与服务器建立tcp链接(三次握手)。
第一次握手:客户端发送一个请求连接,服务器端只能确认自己可以接受客户端发送的报文段。
第二次握手: 服务端向客户端发送一个链接,确认客户端收到自己发送的报文段。
第三次握手: 服务器端确认客户端收到了自己发送的报文段。
3.浏览器向服务器发送http请求(请求和传输数据)。
4.服务器接受到这个请求后,根据路经参数,经过后端的一些处理生成html代码返回给浏览器。
5.浏览器拿到完整的html页面代码开始解析和渲染,如果遇到外部的css或者js,图片一样的步骤。
6.浏览器根据拿到的资源对页面进行渲染,把一个完整的页面呈现出来。
18. 浏览器是如何渲染页面的?
流程:解析html以及构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
1.构建DOM树: 渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树。
2.构建渲染树: 解析对应的css样式文件信息(包括js生成的样式和外部的css)。
3.布局渲染树:从根节点递归调用,计算每一个元素的大小,位置等。给出每个节点所在的屏幕的精准位置。
4.绘制渲染树:遍历渲染树,使用UI后端层来绘制每一个节点。
Vue.js 方面:
1.Vue的核心思想:
组件化开发和数据驱动。
2.什么是vuex?有什么作用?
vuex是vue的状态管理工具。
作用:项目数据状态的集中管理,复杂组件的数据通信问题。
3.Vuex的核心属性:
State:存储数据状态的地方,但是不可以直接修改里面的数据。
Mutations: mutations定义的方法动态修改vuex的store中的状态或数据。
Actions:简单的说就是异步操作数据,view层通过store.dispatch来分发action。
Getters:类似vue的计算属性,主要用来过滤数据。
Modules:模块化管理 项目特别复杂的时候,可以让每一个模块拥有自己的state,mutation,action,getters,使得结构非常清晰,方便管理。
4.vuex的工作流程?
在vue组件里面,通过dispatch来触发actions提交修改数据的操作
然后再通过actions的commit来触发mutations来修改数据
Mutations接收到commit的请求,就会自动通过mutate来修改state里面的数据
最后由store触发每一个调用它的组建的更新。
5.Vue的生命周期?
BeforeCreated(实例创建前):此时的vue实例还没有挂载元素$el,数据对象data也是undefined。
Created(实例创建完成):vue实例的数据对象data有了,但是$el还没有。
beforeMount(实例载入前):vue的实例的$el和data都初始化了,但还是挂载在之前的虚拟DOM节点上,data.message还没替换。
Mounted(实力载入完成):vue实例挂载完成,data.message成功渲染。
beforeUpdate(实例更新前):data发生变化前。
Updated(实例更新前):data发生变化后。
BeforeDestory(实例销毁前):在实例销毁之前调用,实例仍然可用。
Destory(实例销毁完成):所有的监听事件会被消除,所有的子实例也会被销毁。
6.vue常用的指令?
v-model v-html v-text v-for v-show v-if v-on
7.V-if和v-show的区别?
V-if是通过添加和删除DOM节点来控制显示隐藏,v-show是通过操作css的display属性来控制显示隐藏;
V-if拥有更高的切换成本,v-show拥有更高的渲染成本。
频繁切换的时候使用v-show,不经常切换就使用v-if.
8.V-for和v-if的优先级?
当它们处于同一节点,v-for的优先级比v-if更高,这意味着v-if将分别重复运行于每个v-for的循环中。
9.MVC MVP MVVM的区别?
**MVC: **MVC之间的数据通信都是单向的。View(视图层)发送指令到controller(控制层),完成业务逻辑后,要求Model(模型层)改变状态,匠心的数据发送到(view)视图层,用户得到反馈。
MVP:在MVP中,view和model之间没有任何通信关系,所有的通信和业务逻辑都放在presenter层中。View层发送指令到presenter层,presenter层处理业务逻辑,要求model层改变状态,完成状态修改之后,发送指令到presenter层,之后再通知view层做出改变。
**MVVM: **Model专门用来处理数据模型。View专门用来处理用户视图,ViewModel用来使view和model双向绑定,view的任何变化都会通知ViewModel,而model的任何变化也会通知ViewModel,无论哪一项发生改变,都会使对应的视图/数据模型同时发生改变。
11.什么是axios?
就是请求后台资源的模块,前台通过它获取后台数据,类似ajax交互。
13.路由传参的方法?路由传参方法
1. 字符串拼接 : 路由后面直接拼接要传递的参数,用this.$rote.params 接收。
2. path和query:path后面跟要跳转的路由,query后边跟要传递参数的对象 用 this.$route.query 接收。
3. name和params:name后面跟要跳转路由的名称,params后面跟传递参数的对象,用this.$route.params接收。
14.Vuex中actions和mutations的区别?
Mutations的更改是同步更改,用于用户执行直接数据更改,this.$store.commit(‘名’)触发。
Actions的更改是异步操作,用于需要与后端交互的数据更改,this.$store.dispath(“名”)触发。
注意:
1):定义actions方法创建一个更改函数时,这个函数必须携带一个context参数,用于触发mutations方法,context.commit(‘修改函数名’ , ’异步请求值’);
2):mutations第一个参数必须传入state,第二个参数是新值。
15.渐进式框架的理解?
主张最少,没有多做职责之外的事 我的理解就是 用什么就引入什么,没有硬性规定。
项目介绍1
项目介绍:
项目背景:
商品分类怎么实现的;
单选多选全选怎么实现的:
加入购物车怎么实现的;
登录注册怎么实现;
登录流程:
当我点击登录的时候,我先判断我输入的值是否符合规则,如果符合,就把参数拼接到接口上,然后请求,后台会返回一个token值,我把token放在本地存储中,在全局路由守卫中,当我要访问一个需要登录才可以进入的路由的时候,我就判断本地存储中有没有这个token值,如果有,就进入这个路由,如果没有,就返回登录页面登录。
Loading动画怎么实现:
用axios拦截器实现loading动画效果 首先新建一个loading组件,里面写一些动画效果,然后在vuex里面写一个状态来控制我的loading动画组件的显示隐藏,然后在全局main.js中配置axios拦截器,分别定义一个请求拦截器和响应拦截器,在请求数据时执行请求拦截器,改变我vuex里面定义的状态,让loading动画显示,反之,数据请求到之后,隐藏loading动画即可。
图片懒加载怎么实现:
我们先不给<img>设置src,把图片真正的URL放在另一个属性data-src中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到src中。
移动端的性能优化:
首屏加载和按需加载,懒加载 资源预加载 图片压缩处理,使用base64内嵌图片 合理缓存dom对象 使用touchstart代替click(click 300毫秒的延迟) 利用transform:translateZ(0),开启硬件GUP加速 不滥用web字体,不滥用float(布局计算消耗性能),减少font-size声明 使用viewport固定屏幕渲染,加速页面渲染内容 尽量使用事件代理,避免直接事件绑定。
项目介绍2
项目介绍:
项目背景:
后台权限管理是怎么实现的:
定义两张路由表,一张是静态路由表(无需权限的使用),另一张是权限路由表(和后台返回的权限进行匹配使用)。用户登录,判断登录是否成功,登录成功后判断是否获取用户权限列表,获取到后,将权限数据存储到Vuex中。用Vuex中的权限数据和定义好的需要访问权限的路由表进行比对。比对完后生成当前账户对应的权限路由表。通过addRouters方法动态添加路由规则,生成可访问的侧边栏菜单。
跨域问题,怎么解决的:
jsonp跨域原理: jsonp是请求之后 后台会封装好的一段json,并且把数据放在一个callback回调函数中,并返回一个js文件,动态的引入这个文件,调用这个callback回调函数,进行数据访问。
反向代理跨域:客户端发送请求时不直接到服务器而是先到代理的中间层在这里将localhost:8080的这个域名装换为www.njc.com,再将请求发送到服务器这样在服务器端收到的请求就是使用的www.njc.com域名同理,当服务器返回数据的时候,也是先到代理的中间层将www.njc.com转换成localhos:8080;这样在客户端也是在相同域名下访问的了。
前言
本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性、框架的原理以问题的形式进行整理汇总,意在帮助作者及读者自测下 Vue 掌握的程度。本文章节结构以从易到难进行组织,建议读者按章节顺序进行阅读,当然大佬级别的请随意。希望读者读完本文,有一定的启发思考,也能对自己的 Vue 掌握程度有一定的认识,对缺漏之处进行弥补,对 Vue 有更好的掌握。 文章最后一题,欢迎同学们积极回答,分享各自的经验 ~~~
1、说说你对 SPA 单页面的理解,它的优缺点分别是什么?
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
- 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
- 基于上面一点,SPA 相对对服务器压力小;
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
- 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
- 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
- SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
2、v-show 与 v-if 有什么区别?
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
3、Class 与 Style 如何动态绑定?
Class 可以通过对象语法和数组语法进行动态绑定:
- 对象语法:
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
isActive: true,
hasError: false
}
- 数组语法:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
Style 也可以通过对象语法和数组语法进行动态绑定:
- 对象语法:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
- 数组语法:
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
styleColor: {
color: 'red'
},
styleSize:{
fontSize:'23px'
}
}
4、怎样理解 Vue 的单向数据流?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个 prop 的情形 :
- 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- 这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
5、computed 和 watch 的区别和运用的场景?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
-
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
-
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
6、直接给一个数组项赋值,Vue 能检测到变化吗?
由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
为了解决第二个问题,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
7、谈谈你对 Vue 生命周期的理解?
(1)生命周期是什么?
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
(2)各个生命周期的作用
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activited | keep-alive 专属,组件被激活时调用 |
deadctivated | keep-alive 专属,组件被销毁时调用 |
beforeDestory | 组件销毁前调用 |
destoryed | 组件销毁后调用 |
(3)生命周期示意图
image8、Vue 的父组件和子组件生命周期钩子函数执行顺序?
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
-
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
-
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
-
父组件更新过程
父 beforeUpdate -> 父 updated
-
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
9、在哪个生命周期内调用异步请求?
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面 loading 时间;
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
10、在什么阶段才能访问操作DOM?
在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。vue 具体的生命周期示意图可以参见如下,理解了整个生命周期各个阶段的操作,关于生命周期相关的面试题就难不倒你了。
image11、父组件可以监听到子组件的生命周期吗?
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
12、谈谈你对 keep-alive 的了解?
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
- 一般结合路由和动态组件一起使用,用于缓存组件;
- 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
- 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
13、组件中 data 为什么是一个函数?
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
// data
data() {
return {
message: "子组件",
childName:this.name
}
}
// new Vue
new Vue({
el: '#app',
router,
template: '<App/>',
components: {App}
})
因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
14、v-model 的原理?
我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用 value 属性和 input 事件;
- checkbox 和 radio 使用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小红')
},
},
15、Vue 组件间通信有哪几种方式?
Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
(1)props / $emit
适用 父子组件通信
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref
与 $parent / $children
适用 父子组件通信
-
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 -
$parent
/$children
:访问父 / 子实例
(3)EventBus ($emit / $on)
适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)$attrs
/$listeners
适用于 隔代组件通信
-
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过v-bind="$attrs"
传入内部组件。通常配合 inheritAttrs 选项一起使用。 -
$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on="$listeners"
传入内部组件
(5)provide / inject
适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex 适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
16、你使用过 Vuex 吗?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
主要包括以下几个模块:
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
- Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
- Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
- Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
17、使用过 Vue SSR 吗?说说 SSR?
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
- 更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
- 更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
- 更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
- 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
如果没有 SSR 开发经验的同学,可以参考本文作者的另一篇 SSR 的实践文章《Vue SSR 踩坑之旅》,里面 SSR 项目搭建以及附有项目源码。
18、vue-router 路由模式有几种?
vue-router 有 3 种路由模式:hash、history、abstract,对应的源码如下所示:
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
其中,3 种路由模式的说明如下:
-
hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
-
history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
-
abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
19、能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':
https://www.word.com#search
hash 路由模式的实现主要是基于下面几个特性:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
- hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
- 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
- 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:
- pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
- 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
20、什么是 MVVM?
Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于2005年在他的博客上发表
MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。如下图所示:
image(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
我们以下通过一个 Vue 实例来说明 MVVM 的具体实现,有 Vue 开发经验的同学应该一目了然:
(1)View 层
<div id="app">
<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>
</div>
(2)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;
}
});
}
})
(3) Model 层
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "IoveC",
"domain": "www.cnblogs.com"
}
}
21、Vue 是如何实现数据双向绑定的?
Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据,如下图所示:
image即:
- 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
- Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。
其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
以上四个步骤的流程图表示如下,如果有同学理解不大清晰的,可以查看作者专门介绍数据双向绑定的文章《0 到 1 掌握:Vue 核心之数据双向绑定》,有进行详细的讲解、以及代码 demo 示例。
image22、Vue 框架怎么实现对象和数组的监听?
如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty() 对数据进行劫持,但是 Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持,同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // observe 功能为监测数据的变化
}
}
/**
* 对属性进行递归遍历
*/
let childOb = !shallow && observe(val) // observe 功能为监测数据的变化
通过以上 Vue 源码部分查看,我们就能知道 Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
23、Proxy 与 Object.defineProperty 优劣对比
Proxy 的优势如下:
- Proxy 可以直接监听对象而非属性;
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
- Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
- Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Object.defineProperty 的优势如下:
- 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
24、Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
来实现为对象添加响应式属性,那框架本身是如何实现的呢?
我们查看对应的 Vue 源码:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的splice变异方法触发响应式
target.splice(key, 1, val)
return val
}
// key 已经存在,直接修改属性值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// target 本身就不是响应式数据, 直接赋值
if (!ob) {
target[key] = val
return val
}
// 对属性进行响应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
我们阅读以上源码可知,vm.$set 的实现原理是:
-
如果目标是数组,直接使用数组的 splice 方法触发相应式;
-
如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)
25、虚拟 DOM 的优缺点?
优点:
- 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
- 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
- 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
缺点:
- 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
26、虚拟 DOM 实现原理?
虚拟 DOM 的实现原理主要包括以下 3 部分:
- 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
- diff 算法 — 比较两棵虚拟 DOM 树的差异;
- pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
如果对以上 3 个部分还不是很了解的同学,可以查看本文作者写的另一篇详解虚拟 DOM 的文章《深入剖析:Vue核心之虚拟DOM》
27、Vue 中的 key 有什么作用?
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。具体有无 key 的 diff 过程,可以查看作者写的另一篇详解虚拟 DOM 的文章《深入剖析:Vue核心之虚拟DOM》
所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key
对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
28、你有对 Vue 项目进行哪些优化?
如果没有对 Vue 项目没有进行过优化总结的同学,可以参考本文作者的另一篇文章《 Vue 项目性能优化 — 实践指南 》,文章主要介绍从 3 个大方面,22 个小方面详细讲解如何进行 Vue 项目的优化。
(1)代码层面的优化
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分使用场景
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化无限列表性能
- 服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
- Webpack 对图片进行压缩
- 减少 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建结果输出分析
- Vue 项目的编译优化
(3)基础的 Web 技术的优化
-
开启 gzip 压缩
-
浏览器缓存
-
CDN 的使用
-
使用 Chrome Performance 查找性能瓶颈
29、对于即将到来的 vue3.0 特性你有什么了解的吗?
Vue 3.0 正走在发布的路上,Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:
(1)监测机制的改变
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:
-
只能监测属性,不能监测对象
-
检测属性的添加和删除;
-
检测数组索引和长度的变更;
-
支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 还提供了以下特性:
- 用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。
- 默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。
- 更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。
- 不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。
- 更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。
(2)模板
模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
(3)对象式的组件声明方式
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
(4)其它方面的更改
vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:
- 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
- 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
- 基于 treeshaking 优化,提供了更多的内置功能。
网友评论