浏览器,原文请移步 Browser | InterviewMap
回流和重绘
回流和重绘 - 掘金
存储
cookie,localStorage,sessionStorage,indexDB
从上表可以看到,cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。
对于 cookie,我们还需要注意安全性。
渲染机制
浏览器渲染一般分为以下几个步骤
1.处理html生成DOM树。
2.处理css生成CSSOM树。
3.将DOM和CSSOM合并成一个渲染树。
4.根据渲染树来布局,计算每个节点的位置。
5.调用GPU绘制,合成图层,显示在屏幕上。
构建CSSOM树时,会阻塞渲染,直到CSSOM树渲染完成。构建CSSOM树是十分消耗性能的过程,所以尽量保持层级扁平,减少过度层叠。由于css渲染是从右往左的,即 div span 是先找到所有的span标签渲染,然后再找div下span标签再次渲染。所以,扁平化我的理解是尽量用class,id 来代替,尽量不用多级层叠。例如项目中会有一个css的标准,class 为fs14的 为font-size:14px;btn-red为红色按钮等等。这样一来是做成一个标准,统一样式,二来是加快css渲染,一般这样的类,不会有多级层叠,就单独一个。
当html解析到script标签时,就会暂停DOM树的构建,转而执行js,直到js执行完毕后才会从暂停的地方从新构建DOM树
。也就是说,想要首屏渲染时间够快,就尽量不要首屏加载js文件。关于首屏加载js文件,我们可以通过webpack打包实现按需加载,将一个大的js文件打包成多个js文件。多个js文件之间相互依赖,但是在首屏加载的时候,只加载那些首屏执行的js文件,这样就能加快首屏的渲染速度。具体可看webpack配置,这里不做描述。webpack 中文文档(@印记中文) https://docschina.org/
onload,$.ready,DOMContentLoaded 区别
原生onload事件表示html,css,js,img所有文件加载完成。而jq的ready表示html加载完成。DOMContentLoaded 顾名思义,就是DOM结构加载完成 ,和jq的ready事件一致。更加详细的请移步DOMContentLoaded与load的区别 - CaiBoBo - 博客园
事件覆盖
多个原生onload事件会覆盖,只会执行最后一个onload事件。ready事件则会全部执行,按照顺序由上而下。jq中有load事件,是等全部资源加载完成后,再去执行,但是多个load事件不会覆盖。突然觉得jq慢慢的不用了,好像没什么用了。==!
图层
一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用。
通过以下几个常用属性可以生成新图层
3D 变换:translate3d、translateZ
will-change
video、iframe 标签
通过动画实现的 opacity 动画转换
position: fixed
事件流
事件流3个阶段
1.事件捕获阶段
2.处于目标阶段
3.事件冒泡阶段
事件流的两种形式
1.事件捕获:通俗的讲就是从外向内触发事件,从html>body>parent>self
2.事件冒泡:通俗的讲就是从内向外触发事件,从self>parent>body>html。一般都是使用事件冒泡的形式,不想事件冒泡,可以使用阻止事件冒泡。e.stopPropagation();
当一个dom元素有事件捕获和事件冒泡捕获时,优先执行事件捕获的方法。
这里要讲一下事件委托,事件委托是把事件绑定在父级元素上,通过点击子元素,事件冒泡到父元素的事件上,然后通过判断target对象,从而实现点击事件。为什么要这么做,在通常的交互中,往往子元素是通过接口请求而来,然后再渲染。这时候不方便对未知的子元素添加事件,就可以通过事件委托完美解决这个问题。
优点:1.减少事件注册,节省内存。2.减少dom节点更新的操作,处理逻辑只需在委托元素上进行。3.不需要给子节点注销事件。
缺点:1.根据事件冒泡而来,对不能冒泡的事件(onfocus,onblur)无法实现。2.层级过多,可能会被某一层阻止掉。
跨域
了解一下同源策略,同协议,同域名,同端口,三同有一个不同就是跨域,为了安全考虑。ajax请求中经常会碰到。通常会有以下几种解决方法。
JSONP
利用了script标签没有跨域的限制,指向一个需要访问的地址并提供一个回调函数来接收数据。工作中都是闻其声,不见其影,而且仅限于get请求。估计我遇见的少吧。
CORS 后端设置跨域
CORS需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
浏览器会自动进行 CORS 通信,实现CORS通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
document.domain
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域
postMessage
这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
项目开发过程中,一般都会采用代理。跨域只存在于浏览器中,我们可以让服务器去帮我们访问这个api,然后转发给前端。可以用到 proxyMiddleware 插件,webpack中进行设置。在vue的脚手架中,我们可以直接通过 proxyTable进行代理,比较方便。
Event loop 事件循环
有一份更加详细的,请移步大佬的专栏 详解JavaScript中的Event Loop(事件循环)机制
JS 单线程语言
众所周知,js是一门单线程语言,诞生之初,就是给浏览器服务的,操作DOM,与用户交互。这也决定了js是门单线程语言,否则一个线程在操作dom,另一个线程直接把dom删除了,就会发生意想不到的情况。
堆,栈,执行栈,事件队列
js在执行代码时,会将不同的变量存在在不同的地方:堆(heap)和栈(stack);其中,堆里存在着一些对象,而栈保留基本类型的变量和对象指针。执行栈,当js执行一个方法时,就会生成这个方法的执行环境,环境中有方法的私有作用域,内部定义的变量,this指向的对象等等。执行完这个方法后,就会销毁这个环境,再去执行下一个方法。由于js是单线程语言,同一时间只能执行一个方法,那么其他的方法就得排队,排队的地方就是执行栈。
当js执行一个脚本时,js会将脚本中的代码依次加入到执行栈中,从上而下开始执行。如果执行的是一个方法,那么就将该方法的执行环境添加到执行栈中,执行方法中的代码,直到执行完毕,然后销毁这个执行环境,并退出。然后接着执行后面的代码,一直循环,直到代码执行结束。这个是同步代码的执行顺序。
那么碰到异步的事件呢,异步事件不会马上得到结果,比如settimeout,ajax请求,promise等等。当代码执行遇到这种事件,就会将事件挂起,转而去执行执行栈后面的代码。等到异步事件返回结果时,就会将该异步事件加入与当前执行栈不同的一个队列且不会立刻执行回调,这个队列我们叫事件队列。当执行栈中的代码执行完毕,就会去检查事件队列中是否含有任务,如果存在任务,就将排在第一位的任务拿出来放到执行栈执行,直到该任务执行完毕。这个时候,执行栈会扫描事件队列并从中取出排在第一位的任务来执行,循环往复,直到全部执行完毕。
微任务和宏任务
微任务: promise process.nextTick
宏任务: 包裹所有代码的script标签 setTimeout setInterval
事件队列中的任务分为宏任务和微任务。微任务的优先级会比宏任务的优先级高。show me code!
刚开始执行时,首先会执行script这个宏任务,然后遇到第一个setTimeout,异步,挂起,加入宏任务,叫它 timer1吧,然后是 第二个setTimeout2,异步,挂起,加入宏任务,叫它 timer2吧。执行到 new Promise,立刻执行,这个时候会输出 4,生成一个promise 实例,并执行resolve() 方法,这里是异步,挂起,加入微任务。最后到 console.log,输出 6。好了,现在主线程所有代码执行完毕,已经输出 4 和 6。这个时候扫描事件队列,发现里面有 2个宏任务,timer1,timer2。1个微任务 promise.then()。由于微任务优先级高,所以将为微任务先取出并执行,输出了 5。再次扫描 事件队列,发现timer1,timer2。timer1先加入事件队列,执行timer1。在执行timer1时,发现了 timer3(setTimeout),异步,挂起,加入宏任务。timer1 执行完毕,再次扫描事件队列,发现有 timer2,timer3。timer2 比 timer3 早,执行 timer2 ,结束后再执行 timer3。这个时候就全部执行完毕。
首先明确一点,setTimeout 和 setInterval 中的运行机制。js代码执行的时候,遇到这两个,首先会立刻挂起,进入事件队列,然后根据给定的时间加入宏任务中。不给的话默认是4ms,哪怕是给了 0 ,也是 4ms的时间。那么从上图来看,timer1 过了 500ms才加入宏任务,timer2过了 1000ms才加入宏任务。在这期间,假设主线程执行的时间忽略不计,那么主线程执行完毕,扫描事件队列,发现 先加进来的 timer1,执行timer1的内容,遇到 timer3,挂起,加入事件队列,400ms后加入宏任务。timer1执行完毕,再次扫描宏任务,由于 timer3是400ms后加入,timer2是(1000 - 500)即 500 ms 后加入,所以 timer3 会比 timer2 早执行。故整个的输出顺序是 4 6 5 1 2 3。
js的执行机制,游泳,健身,了解一下,大佬讲的很仔细 这一次,彻底弄懂 JavaScript 执行机制 - 掘金