面试问题和解答简单整理

页面从加载到完成经历了什么

dom tree -> render tree

回流和重绘

回流:当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流
重绘:当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流
当页面布局和几何属性改变时就需要回流
比如:添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变——边距、填充、边框、宽度和高度,内容改变

对闭包的理解

外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象(实质是未被垃圾回收)

堆和栈

栈内存:直接定义的基本类型,如:字符串、数字、布尔值、null、undefined、指针
堆内存:Object、通过new实例化的类(new Boolean、new String等)

栈内存中的变量一般都是已知大小或者有范围上限的,算作一种简单存储。而堆内存存储的对象类型数据对于大小这方面,一般都是未知的。个人认为,这也是为什么null作为一个object类型的变量却存储在栈内存中的原因。
因此当我们定义一个const对象的时候,我们说的常量其实是指针,就是const对象对应的堆内存指向是不变的,但是堆内存中的数据本身的大小或者属性是可变的。而对于const定义的基础变量而言,这个值就相当于const对象的指针,是不可变。

内存分配和垃圾回收

一般来说栈内存线性有序存储,容量小,系统分配效率高。而堆内存首先要在堆内存新分配存储区域,之后又要把指针存储到栈内存中,效率相对就要低一些了。
垃圾回收方面,栈内存变量基本上用完就回收了,而堆内存中的变量因为存在很多不确定的引用,只有当所有调用的变量全部销毁之后才能回收。
es6中新增的 WeekMap 对象可以对大对象类型的垃圾回收有一些用处

JS数据结构的栈和队列操作

栈(堆栈):栈是一种后进先出的数据结构,也就是说最新添加的项最早被移出;它是一种运算受限的线性表,只能在表头/栈顶进行插入和删除操作;
打个比方:一队人马走入了一条死胡同,只有一个入口,无出口,想要出去就只能把尾变成首开始出去;
JS也为数组提供了方法可以实现类似入栈出栈功能,入栈push()、出栈pop()。

队列:是一种先进先出的数据结构;队列在列表的末端增加项,在首端移除项;
例如在银行窗口排队办理业务,最前面的第一个人开始办理,后面来的人只能在队伍末尾排队,直到排到他们为止;
JS为数组提供了方法可以实现队列功能, 入队unshift()、 出队pop();

sessionStorage 和 localStotage 区别

sessionStorage:sessionStorage的生命周期是在仅在当前会话下有效。sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。

localStotage:localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。

304的缓存原理

客户端请求一个页面(A)。 服务器返回页面A,并在给A加上一个ETag。 客户端展现该页面,并将页面连同ETag一起缓存。 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。

301重定向和302重定向的区别

302重定向只是暂时的重定向,搜索引擎会抓取新的内容而保留旧的地址,因为服务器返回302,所以,搜索搜索引擎认为新的网址是暂时的。比如未登陆的用户访问用户中心重定向到登录页面。

而301重定向是永久的重定向,搜索引擎在抓取新的内容的同时也将旧的网址替换为了重定向之后的网址。301比较常用的场景是使用域名跳转。

静态资源缓存的几种方式

Service Worker:
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的

Memory Cache:
也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。
内存缓存中有一块重要的缓存资源是preloader相关指令(例如 \)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。 需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。

Disk Cache:
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。 在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。 浏览器会把哪些文件丢进内存中?哪些丢进硬盘中? 关于这点,网上说法不一,不过以下观点比较靠得住:

对于大文件来说,大概率是不存储在内存中的,反之优先 当前系统内存使用率高的话,文件优先存储进硬盘

Push Cache:
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令

对原型链的理解

中所有的对象都是Object的实例,并继承Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸;
在对象创建时,就会有一些预定义的属性,其中定义函数的时候,这个预定义属性就是prototype,这个prototype是一个普通的对象;
而定义普通的对象的时候,就会生成一个proto,这个proto指向的是这个对象的构造函数的prototype

前端中常见的设计模式

观察订阅模式

代理模式

HTTP2相对于HTTP1的优缺点

HTTP/2主要是为了解决现HTTP 1.1性能不好的问题才出现的。当初Google为了提高HTTP性能,做出了SPDY,它就是HTTP/2的前身,后来也发展成为HTTP/2的标准。

HTTP Header的压缩,采用的是HPack算法。
HTTP/2的Server Push,非常重要的一个特性。
请求的pipeline(管道流,数据交错传输)。
修复在HTTP 1.x的队头阻塞问题。
在单个TCP连接里多工复用请求。
HTTP/2兼容HTTP 1.1,例如HTTP Method,Status code,URI以及大部分Header Fields。

HTTPS的优缺点

JS的线程机制以及 Event Loop

GUI渲染线程:
浏览器输入url,浏览器主进程接管,开一个下载线程,然后进行 http请求(略去DNS查询,IP寻址等等操作),然后等待响应,获取内容,随后将内容通过RendererHost接口转交给Renderer进程,浏览器渲染流程开始;
为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

JS引擎线程:
也称为JS内核,负责解析处理运行JS脚本程序(例如V8引擎);
JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序;
同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

事件触发线程(Event Loop):
事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。或者当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理

定时触发器线程:
传说中的setInterval与setTimeout所在线程,当使用setTimeout或setInterval时,它需要定时器线程计时,计时完成后就会将特定的事件推入事件队列中。

异步http请求线程:
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

移动端常见的布局方式(rem、vw)

REM布局原理:根据CSS的媒体查询功能,更改html根字体大小,实现字体大小随屏幕尺寸变化。
REM布局中用到了JS来动态设置html的font-size,可能造成页面的抖动。
可以考虑比较新的VW布局,无需使用JS,虽说在移动端 iOS 8 以上以及 Android 4.4 以上才获得支持
视窗 viewport:简单的理解,viewport是严格等于浏览器的窗口。在桌面浏览器中,viewport就是浏览器窗口的宽度高度。但在移动端设备上就有点复杂。移动端的viewport太窄,为了能更好为CSS布局服务,所以提供了两个viewport:虚拟的visualviewport和布局的layoutviewport。

Promise是为了解决什么而产生的

Promise就是为了解决callback的问题而产生的。Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被Promise.resolve() 包装

缺点:无法取消 Promise ,错误需要通过回调函数来捕获

callback的优缺点

缺点:回调地狱,不能用 try catch 捕获错误,不能 return`;回调地狱导致的调试困难,和大脑的思维方式不符,嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转,嵌套函数过多的多话,很难处理错误

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

webpack中hash、chunkhash、contenthash区别

hash:每次修改任何一个文件,所有文件名的hash至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

chunkhash:chunkhash根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。并且webpack4中支持了异步import功能,固,chunkhash也作用于此。

contenthash:contenthash是针对文件内容级别的,只有你自己模块的内容变了,那么hash值才改变

什么是前端模块规范AMD,CMD,CommonJS和UMD

AMD 规范的实现代表是require.js,AMD是异步加载规范

CMD规范的实现代表是sea.js,对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行

CommonJS 是服务器端模块的规范,Node.js采用了这个规范,加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象

UMD是AMD和CommonJS的糅合,UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

CMD推崇依赖就近,AMD推崇依赖前置;简单来说,就是sea.js属于懒加载,require.js属于预加载
预加载和懒加载的优缺点:
预加载:当第一次访问时将所有的文件加载出来。优点:第一次访问完成以后, 再次访问的速度会很快;缺点:第一次加载页面要等待很久
懒加载:使用的时候才会加载对应的文件。优点:第一次访问速度相对快点;缺点:再访问其他新的模块时速度会变慢

commomJS模块:
1、获得的是缓存值,是对模块的拷贝
2、可以对commomJS模块重新赋值
3、可以对对象内部的值进行改变

es6模块:
1、获得的是时时的值,是对模块的引用
2、对es6模块重新赋值会报错
3、可以对对象内部的值进行改变

webpack 中 loader 和 plugin 的主要区别

loader 用于加载某些资源文件。
因为 webpack 只能理解 JavaScript 和 JSON 文件,对于其他资源例如 css,图片,或者其他的语法集,比如 jsx, coffee,是没有办法加载的。 这就需要对应的loader将资源转化,加载进来。从字面意思也能看出,loader是用于加载的,它作用于一个个文件上。

plugin 用于扩展webpack的功能。
它直接作用于 webpack,扩展了它的功能。当然loader也是变相的扩展了 webpack ,但是它只专注于转化文件(transform)这一个领域。而plugin的功能更加的丰富,而不仅局限于资源的加载。

Vue生命周期:A > B,B是A的子组件,他们两个的生命周期的顺序

关于Vue父子组件间的生命周期类似洋葱圈:
beforeCreated(A) -> created(A) -> beforeCreated(B) -> created(B) -> beforeMounted(B) -> mounted(B) -> beforeMounted(A) -> mounted(C)

Vue的数据响应式原理

Vue.js的响应式原理依赖于 Object.defineProperty,尤大大在Vue.js文档中就已经提到过,这也是Vue.js不支持IE8 以及更低版本浏览器的原因。Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过 getter 进行依赖收集(Vue里面真正做数据响应式处理的是defineReactive()。 defineReactive方法就是把对象的数据属性转为访问器属性),而每个 setter 方法就是一个观察者,在数据变更的时候通知订阅者更新视图。
但是这是针对Object实现的,如果是数组呢? 通过 set/get 方式是不行的。关于数组,Vue作者使用了一个方式来实现Array类型的监测: 拦截器,其核心思想是通过创建一个拦截器来覆盖数组本身的原型对象Array.prototype。

vue组件间通信六种方式(6种)

1、props/$emit:父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

2、$emit/$on:这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。

3、vuex

4、$attrs/$listeners:$attrs包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用;$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件

5、provide/inject:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

6、$parent / $children 与 $ref:ref如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例;$parent / $children 是 访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。

详细解答

简要介绍Vuex原理

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

Vuex与localStorage

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

Mpx的特性

  • 数据响应特性(watch / computed)
  • 增强的模板语法(动态组件 / 样式绑定 / 类名绑定 / 内联事件函数 / 双向绑定 等)
  • 深度性能优化(原生自定义组件/基于依赖收集和数据变化的setData)
  • Webpack编译(npm/循环依赖/Babel/ESLint/css预编译/代码优化等)
  • 单文件组件开发
  • 渐进接入 / 原生组件支持
  • 状态管理(Vuex规范/多实例/可合并)
  • 跨团队合作(packages)
  • 逻辑复用能力(mixins)
  • 脚手架支持
  • 小程序自身规范的完全支持
  • 多平台支持(微信、支付宝、百度、qq、头条)
  • 跨平台编译(支持将微信小程序转换为支付宝、百度、qq、头条小程序)
  • TypeScript支持(完善的类型推导)

微信小程序的架构原理

微信提供的环境为宿主环境,小程序可以直接调用宿主环境提供的相关能力,完成许多普通web应用无法完成的事情,小程序分渲染层和逻辑层分别右两个线程管理:渲染层使用WebView进行渲染;逻辑层采用JsCore线程解析允许JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由Native(微信客户端)做中转,逻辑层发送网络请求也经由Native转发。