1、浏览器的渲染过程
- 首先获取html,然后构建dom树
- 其次根据css构建render树,render树中不包含定位和几何信息
- 最后构建布局树,布局含有元素的定位和几何信息
2、重构、回流
浏览器的重构指的是改变每个元素外观时所触发的浏览器行为,比如颜色,背景等样式发生了改变而进行的重写构造新外观的过程。重构不会引发页面的重新布局,不一定伴随着回流。
回流指的是浏览器为了重新渲染页面的需要而进行的重新计算元素的集合大小和位置。他的开销是非常大的,回流可以理解为渲染树需要重新进行计算,一般最好触发元素的重构,避免元素的回流;比如通过添加类来添加css样式,而不是直接在DOM上设置,当需要操作某一块元素的时候,最好使其脱离文档流,这样就不会引起回流了,比如设置position:absolute或者fixed,或者display:none。等操作结束后再显示。
欢聚时代(虎牙、yy)
3、谈谈 event loop(事件循环机制)
在讲 Event Loop 之前,我们来了解点 node 的东西,来帮助我们更加明白事件循环是干什么的
Node是什么?
NodeJS 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,Node不是一门语言,是让js运行在后端的,运行时不包括 js 全集,因为在服务端不包含 DOM 和 BOM,Node也提供了一些新的模块,例如 http、fs等
Node解决了什么?
Node的首要目标是提供一种简单的,用于创建高性能服务器的开发工具
Web服务器的瓶颈在于并发的用户量,对比 Java 和 Php 的实现方式
Node在处理高并发,I/O 密集场景有明显的性能优势
- 高并发,是指在同一时间并发访问服务器
- I/O密集,指的是文件操作、网络操作、数据库,相对的有 CPU密集,CPU密集指的是逻辑处理运算、压缩、解压、加密、解密
- Web主要场景就是接收客户端的请求读取静态资源和渲染界面,所以Node非常适合Web应用的开发。
进程与线程
进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。
1、浏览器线程
- 用户界面-包括地址栏、前进/后退按钮、书签菜单等
- 浏览器引擎-在用户界面和呈现引擎之间传送指令(浏览器的主进程)
- 渲染引擎,也被称为浏览器内核(浏览器渲染进程)
- 一个插件对应一个进程(第三方插件进程)
- GPU提高网页浏览的体验(GPU进程)
2、浏览器渲染引擎
- 渲染引擎内部是多线程的,内部包含 ui 线程和 js 线程
- js 线程 ui 线程这两个线程是互斥的,目的就是为了保证不产生冲突
- ui 线程会把更改的放到队列中,当 js 线程空闲下来的时候,ui 线程在继续渲染
3、js单线程
- js 是单线程,为什么呢?如果多个线程同时操作DOM,那页面就会很混乱。这里所谓的单线程指的是主线程是单线程的,所以在Node中主线程依旧是单线程的
4、webworker 多线程
- 它和js主线程不是平级的,主线程可以控制 webworker,但是webworker不能操作DOM,不能获取 document,window
5、其他线程
- 浏览器事件触发线程(用来控制事件循环,存放 setTimeout、浏览器事件、ajax 的回调函数)
- 定时触发器线程(setTimeout定时器所在线程)
- 异步HTTP请求线程(ajax 请求线程)
单线程特点是节约了内存,并且不需要在切换执行上下文。而且单线程不需要管锁的问题
浏览器中的 Event Loop
- 同步任务都在主线程上执行,形成了一个执行栈
- 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行
- 主线程从任务队列中读取事件,这个过程是循环不断的
整个的这种运行机制又称为 Event Loop (事件循环)
node 中的 Event Loop
- 我们写的代码会交给 V8 引擎去进行处理
- 代码中可能会调用 nodeApi,node会交给 LIBUV 库处理
- LIBUV 通过阻塞 i/o 和多线程实现异步 io
- 通过事件驱动的方式,将结果放到事件队列中,最终交给我们的应用
同步,异步 阻塞和非阻塞
- 阻塞和非阻塞指的是调用者的状态,关注的是程序在等待调用结果时的状态
- 同步和异步指的是被调用者是如何通知的,关注的是消息通知机制
宏任务和微任务
- macro-task(宏任务)
- script(主代码块)、setTimeout,setInterval,setImmediate,I/O、UI rendering
- MessageChannel
- postMessage
- micro-task(微任务)
- process.nextTick(Nodejs)
- 原生 Promise
- Object.observe(已废弃)
- MutationObserver
同步代码先执行,执行是在栈中执行的,微任务大于宏任务,微任务会先执行(栈),宏任务后执行(队列)
4、事件的捕获冒泡机制,如果在父元素上加click事件,那么在点击子元素的时候,事件捕获会触发父元素的click吗,事件冒泡会触发父元素的click吗?
<div id="parent"> <div id="child"></div> </div>
var parent = document.getElementById('parent') var child = document.getElementById('child') // 设置为 false 就是事件冒泡:点击child后,输出为: child parent parent.addEventListener('click', function(){ console.log('parent') }, false) child.addEventListener('click', function(){ console.log('child') }, false)
var parent = document.getElementById('parent') var child = document.getElementById('child') // 设置为 true 就是事件捕获:点击child后,输出为: parent child parent.addEventListener('click', function(){ console.log('parent') }, true) child.addEventListener('click', function(){ console.log('child') }, true)
// 如果不想冒泡,则加入 e.stopPropagation() child.addEventListener('click', function(e){ console.log('child') e.stopPropagation() }, false)
addEventListener 的第三个参数默认是 false(事件冒泡)
DOM事件流(event flow)存在三个阶段:事件捕获阶段、目标阶段、事件冒泡阶段
事件捕获(event capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。
dom标准事件流的触发先后顺序为:先捕获再冒泡,即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。
5、谈谈 doctype 的作用
DOCTYPE 是 document type(文档类型)的缩写。<!DOCTYPE>声明位于文档的最前面,处于标签之前,它不是html标签。主要作用的告诉浏览器的解析器使用哪种HTML规范或者XHTML规范来解析页面。
6、前端对于过多的 if else 嵌套,有什么好的解决方法?
(1)使用职责链模式
(2)事件驱动
7、谈谈标准盒模型、怪异盒模型。标准盒模型和怪异盒模型如何相互转换?
(1)标准盒模型:
width 等于 content
一个块的总宽度 = width + padding + border + margin
(2)怪异盒模型
width 等于 content + padding + border
一个块的总宽度 = width + margin
来说说为什么要将标准盒模型转换为怪异盒模型(IE盒模型)
IE盒模型更容易开发。标准盒模型的 padding,border 是不算在宽度之内的,所以当你要在一个容器里并排显示两个相同的盒子。
你用标准盒模型时肯定会
- 设置每个盒子 50%
- 发现两个盒子紧紧挨在一起不美观
- 然后设置padding: 0 5%
- 这时盒子宽度又超过父容器的总宽度了,所以又得去跳转盒子的 40%
- 但是使用怪异盒模型的话,就将两个盒子的宽度设置为 50%,然后再怎么去调整padding都会在两个盒子的内部去调整,不会影响布局
那么怎么将标准盒模型转换成怪异盒模型呢?
在css3中有这样一个熟悉: box-sizing
box-sizing: content-box;(默认、标准盒模型)
box-sizing: border-box; (怪异盒模型)
8、谈谈BFC。哪些方式会触发BFC?实际工作中有遇到什么地方需要用到BFC的吗?
概念:块级格式化上下文(Block Formatting Context,BFC)是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。
- BFC是一个独立的布局环境,可以理解为一个容器,在这个容器中安装一定规则进行物品摆放,并且不会影响其他环境中的物品。
- 如果一个元素符合触发BFC的条件,则BFC中的元素布局不受外部影响。
- 浮动元素会创建BFC,则浮动元素内部子元素主要受该浮动元素影响,所以两个浮动元素之间互不影响。
创建BFC:
- 根元素或包含根元素的元素
- 浮动元素 float = left | right 或 inherit(不等于 none)
- 绝对定位元素 position = absolute | fixed
- display = inline-block | flex | inline-flex | table-cell | table-caption
- overflow = hidden | auto | scroll (不等于visible)
BFC的特性:
- BFC是一个独立的容器,容器内子元素不会影响容器外的元素。反之亦如此。
- 盒子从顶端开始垂直地一个接一个地排列,盒子之间垂直地间距是由margin决定的。
- 在同一个BFC中,相邻的块级盒子的垂直外边距会发生重叠。
- BFC区域不会和 float box 发生重叠。
- BFC能够识别并包含浮动元素,当计算其高度时,浮动元素也可以参与计算了。
BFC的作用:
- 包含浮动元素(清除浮动)
- 浮动元素会脱离文档流(绝对定位元素也会脱离文档流),导致无法计算准确的高度,这种问题称为高度塌陷。
- 解决高度塌陷问题的前提是能够识别并包含浮动元素,也就是清除浮动。
- 通过 overflow:hidden 创建 BFC,固然可以解决高度坍塌的问题,但是大范围应用在布局上肯定不是最合适的,毕竟 overflow:hidden 会造成溢出隐藏的问题,尤其是与JS的交互效果会有影响。
- 我们可以使用 clearfix 实现清除浮动。
.clearfix:after { content: ""; display: block; clear: both; }
- 导致外边距折叠
- 相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的垂直边距相遇时,它们将形成一个外边距。这个外边距的高度等于两个发生折叠的外边距高度中的较大者。
- 外边距的折叠条件是 margin 必须相邻
- 避免外边距折叠
- 外边距折叠只会发生在同一BFC的块级元素之间。如果它们属于不同的 BFC,它们之间的外边距则不会折叠。所以通过创建不同的BFC,就可以避免外边距折叠。
9、如果在浏览器不支持的情况下,如何使用 async await?(不包括webpack的loader)
使用babel进行es6转码
10、webpack 中 loader和plugin有什么区别?
什么是loader?
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。
- 处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行。
- 第一个执行的loader接收源文件内容作为参数,其他loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码。
什么是plugin?
在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在何时的时机通过webpack提供的API改变输出结果。
loader和plugin的区别
对于loader,它是一个转换器,将A文件进行编译成B文件,这里操作的是文件,比如将 A.scss 转换为 A.css,单纯的文件转换过程。
plugin是一个扩展器,它丰富了webpack本身,针对的是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,进行广泛的任务。
11、有哪些伪类?
- :active --- 正在被激活的元素
- :focus --- 获得焦点的元素
- :hover --- 鼠标悬浮的元素
- :link --- 未被访问过的链接
- :visited --- 已经访问过的链接
- :first-child --- 第一个子元素
- :lang --- 指定lang语言的标签
12、h5新增了哪些内容?
video
audio
canvas
SVG
地理位置 :navigator.geolocation.getCurrentPosition
web存储:localStorage 和 sessionStorage
应用缓存:Application Cache --- web应用可进行缓存,并可在没有因特网连接时进行访问
Web Worker:js线程,由于web worker 位于外部文件中,它们无法访问以下js对象:window对象、document对象、parent对象
新增的表单类型:email、url、number、range、Date picker、search、color
新增的表单属性:
form属性:autocomplete、novalidate
input属性:autocomplete、autofocus、form、height和width、list、min max step、multiple、pattern、placeholder、required
13、Vue中 computed、methods 和 watch有什么区别?
watch适合的场景:一个数据影响多个数据
computed适合的场景:一个数据受多个数据影响
数据量大,需要换成的时候用computed,每次需要重新加载,不需要缓存时用methods
14、谈谈 script 标签的 defer 和 async属性。
直接使用 script 脚本的话,html会按照顺序来加载并执行脚本,在脚本加载&执行的过程中,会阻塞后续的DOM渲染。
现在大家习惯于在页面中引入各种第三方脚本,如果第三方服务商出现了一些小问题,比如延迟之类的,就会使得页面白屏。
script提供了两种方式来解决上述问题,async 和 defer,这两个属性使得script不会阻塞DOM的渲染。
defer
如果script标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续DOM的渲染;
如果有多个设置了defer的script标签存在,则会按照顺序执行所有的script;
defer脚本会在文档渲染完毕后,DOMContentLoaded 事件调用前执行。
<body> <h1>hello</h1> <script src="./script2.js"></script> <script src="./script1.js"></script> <p>after script tags</p> <script> console.log('DOMscript') window.addEventListener('DOMContentLoaded', function(){ console.log('DOMContentLoaded') }) </script> </body>
上述代码的执行为:script2、script1、DOMscript、DOMContentLoaded
如果加上 defer后:
<body> <h1>hello</h1> <script defer src="./script2.js"></script> <script defer src="./script1.js"></script> <p>after script tags</p> <script> console.log('DOMscript') window.addEventListener('DOMContentLoaded', function(){ console.log('DOMContentLoaded') }) </script> </body>
上述代码的执行为:DOMscript、script2、script1、DOMContentLoaded
async
async的设置,会使得script脚本异步的加载并在允许的情况下执行
async的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行。
DOMContentLoader事件的触发并不受async脚本加载的影响,在脚本加载完成之前,就已经触发了DOMContentLoaded
普通的script
文档解析 --> 脚本加载 --> 脚本执行 --> DOMContentLoaded
文档解析的过程中,如果遇到script脚本,就会停止页面的解析进行下载。资源的下载是在解析过程中进行的,虽说script1脚本很快的加载完毕,但是它前面的script2并没有加载&执行,所以他只能处于一个挂起的状态,等待script2执行完毕后再执行。
当这两个脚本都执行完毕后,才会继续解析页面。
defer
文档解析时,遇到设置了defer的脚本,就会在后台进行下载,但是并不会阻止文档的渲染,当页面解析&渲染完毕后,会等到所有的defer脚本加载完毕并按照顺序执行,执行完毕后会触发DOMContentLoaded事件。
async
async脚本会在加载完毕后执行。
async脚本的加载不计入DOMContentLoaded事件统计。
推荐的应用场景
defer:如果你的脚本代码依赖于页面中的DOM元素(文档是否解析完毕),或者被其他脚本文件依赖。 例如:评论框、代码语法高亮、polyfill.js
async:如果你的脚本并不关心页面中的DOM元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据。例如:百度统计
如果不太能确定的话,用defer总是会比async稳定
15、如果我DNS服务器上的脚本被篡改了,我有什么发现的方法?
16、说一说有哪些微任务。
先说一下js的执行机制:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
注意:宏任务需要多次事件循环才能执行完,微任务是一次性执行完的;
宏任务macrotask
事件队列中的每一个事件都是一个 macrotask
优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval
比如:setImmediate指定的回调函数,总是排在setTimeout前面
微任务
优先级:process.nextTick > Promise > MutationObserve
17、谈一谈浏览器渲染整个页面的过程。
1、解析HTML文件,创建DOM树
2、解析CSS,生成CSS Rule Tree
3、将CSS与DOM合并,构建渲染树 (render tree)
4、布局(layout)计算渲染树节点大小
5、绘制(paint)
18、CSS是从左往右还是从右往左读取的?有什么好处?
假如我们有这样一颗dom树:
我们定义这样一段css:
.main .desc p { }
如果CSS是从左往右解析的:
- 遍历dom树
- .main -> section -> h1 发现没有 .desc 回溯
- .main -> section -> .content -> p 发现没有.desc 回溯
- .main -> aside -> .desc -> p 成功找到
如果从右到左解析:
- 先找出所有p节点
- 向上遍历 p -> .content -> section -> .main 发现没有 .desc 换一个p
- p -> .desc -> aside -> .main 成功找到
当dom树比较复杂的时候,可以发现从右到左解析能够有效减少回溯次数提升性能。
不过当从右往左解析时需要增加的性能消耗是如何在一棵树中找出所有的 p 节点,不过这一步增加的性能远小于回溯带来的性能损耗。
19、JS有哪些基础类型?
基本数据类型:Undefined、Null、Boolean、Number、String、Symbol(es6新增)
复杂数据类型:Object
20、说说有哪些块级元素,有哪些行级元素
行级元素:
- a
- b
- br
- code
- em
- i
- img
- label
- input
- select
- span
- strong
- sub
- sup
- textarea
块级元素:
- h1~h7
- div
- form
- ul、li、ol
- hr
- p
- table、tbody、tr、th、td、tfoot
晓教育
21、原生js点击url下面的 li 显示其索引值
采用事件委托实现:利用 Array.prototype.slice.call(nodeList) 的作用将具有length属性的元素转为数组,这样可以利用 indexOf 方法获取 li 的索引值。
var nodeList = document.getElementsByTagName('li') var arrNodes = Array.prototype.slice.call(nodeList) var nodeULs = document.getElementsByTagName('ul') nodeULs[0].addEventListener("click", function(e){ var target = e.target alert(arrNodes.indexOf(target)) }, false)
22、谈一谈重绘和回流
什么是回流
当 render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就成为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建 render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。
什么是重绘
当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color。这就成为重绘。
区别
回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流
当页面布局和几何属性改变时就需要回流
比如:添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变---边距、填充、边框、宽度和高度,内容改变
23、前端有哪些缓存?
http缓存:
- 强缓存:Cache-Control、Expires
- 协商缓存:Last-modified、ETag
浏览器缓存:
cookie、localStorage、sessionStorage、ApplicationCache
三级缓存原理
1、先去内存看,如果有,直接加载
2、如果内存没有,则去硬盘获取,如果有直接加载
3、如果硬盘也没有,那么就进行网络请求
4、加载到的资源缓存到硬盘和内存
24、谈谈进程和线程
- 一个程序可以有多个进程
- 一个进程可以有多个线程
- 多个进程之间可以相互通信
- 多个线程之间可以相互通信
- 不同进程之间的线程不能相互通信
在chrome中每打开一个标签页,每一个扩展程序都是一个进程
对于一个进程来说,又有哪些线程呢?
- GUI渲染线程
- JavaScript引擎线程
- 定时触发器线程
- 事件触发线程
- 异步http请求线程
那么当你打开一个页面,就启动了一个进程,那么这个进程有上面几个线程。这几个线程之间相互合作,但是GUI和JavaScript之间是相互独立的,而且JavaScript是单线程的。原因很简单,涉及UI操作的不能同时处理,不然给用户呈现的页面就会乱掉。
既然知道了浏览器作为应用程序具备的基本要素。那么我们想看看浏览器内部具体的工作方式。了解一个程序,首先了解程序的模块划分和工作流程。
浏览器主要划分这几个模块浏览器部件:这几个部件相互耦合,为用户提供页面
- 用户界面
- 浏览器引擎
- 渲染引擎
- 网络
- 用户界面后端
- JavaScript解释器
- 数据持久化存储
25、有了解过前端监控吗?
https://www.cnblogs.com/haishen/p/12018095.html
滴普科技
1、M个人相互传球,由甲开始传球,经过N次传球后,球仍回到甲手中,则不同的传球方式共有多少种?
(m-2)^(n-1)
球在传递过程中是不会回到A手,并且不能传给自己!
可以想象它是一棵树
这是面试官问的是,甲乙丙三个人传球,传5次,最后球在甲手中的传球方式
// 甲乙丙三个人传球,由甲先传,传5次,列出最后球落在甲手上的所有传球方式,这里0,1,2分别代表甲乙丙 function getBool(persons, total){ let res = [] find(0) return res // num:当前球在谁手上 // temp:之前传球的记录 function find(bool, temp=[]){ if(temp.length === total){ if(bool===0){ res.push([...temp, bool]) // 最后补上当前球在谁手上 } return } for(let i=0; i<persons.length; i++){ if(i===bool) continue find(i, [...temp, i]) } } } console.log(getBool([0,1,2], 5))
2、如何计算input框中内容的实际宽度?
定一个span标签,span中的内容和input中的内容一致,通过获取span的宽度就是input内容的宽度
<style> input{ width: 200px; } span{ visibility: hidden; position: absolute; } </style> <body> <input type="text" name="" id="inp"> <span id="content"></span> <button id="btn">打印contentWidth</button> <script> var btn = document.getElementById('btn') var input = document.getElementById('inp') var content = document.getElementById('content') input.addEventListener('input', function(e){ content.innerHTML = e.target.value console.log(content.offsetWidth); }) </script> </body>
3、现在我有三个请求,当第一个请求报错后,如何阻断后面的两个请求?
使用axios中的 cancelToken方法
4、const 中的 object 和 array 为什么还能修改?
因为对象和数组是引用类型的,const参数中保存的仅仅是对象的指针,这就意味着,const仅能保证指针不发生改变。修改对象的属性不会改变对象的指针,所有是被允许的。
也就是说,const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。