我的2023前端面试准备-浏览器篇
最近准备面试,把这个过程准备的常考面试题分享出来,也方便自己日后回顾,只适用于初级哦
1、输入URL到页面加载的过程
1、浏览器进程判断输入的是不是网页,不是的话跳转到搜索引擎实行搜索操作
2、判断请求的资源是否存在缓存,是的话直接使用缓存的数据,没有的话给服务器发送请求
3、DNS域名解析,浏览器会向DNS服务器发送一个DNS请求,DNS域名解析后拿到对应的IP地址,浏览器提供了DNS数据缓存的功能,会把解析的结果保存下来,在下次访问的时候就直接走缓存,不会经过解析
4、建立TCP连接,这个过程会经过三次握手,即判断客户端和服务端的接收能力能力和发送能力,以及同步TCP窗口大小等信息,为后续的可靠性传输做准备
5、发送http请求:建立TCP连接之后就可以发送http请求了,浏览器和服务端可以正常通信,即开始发送http请求
6、响应请求:这个响应会包括响应行,响应头和响应体,这个响应会包含服务器对请求的响应结果
7、断开TCP连接:这个过程需要判断一个Connection字段,如果响应头或者请求头设置 了Connect:Keep-alive,则说明建立了持久连接,之后请求统一站点都会复用这个连接,否则断开TCP连接,这个过程会实现四次挥手
8、解析页面:首先会解析html去生成一个DOM树,之后解析CSS生成一个CSS规则树,这两棵树会结合生成render树,CSS规则树不会阻塞dom树的构建,但会阻塞render树的构建,渲染树不会包含display:none等不会出现在页面上的节点。遇到script或其他需要加载的资源,浏览器会单独发起一个欸请求处理这些资源,之后按顺序执行他们。之后根据渲染树的布局,计算css样式,获取每一个节点的位置大小等几何信息,对页面进行布局,最后根据计算的布局结果,将每一个可见节点绘制在页面上,生成可让用户交互的界面
2、什么是同源策略,跨域怎么解决
什么是同源策略
实际上跨域问题就是由浏览器的同源策略造成的,浏览器的同源策略限制了从一个源加载的脚本和文档是否可以与进一个源的资源交互,这是浏览器用来防止一些潜在恶意文件的重要安全机制,同源一般指的是协议、域名和端口号必须一致,他是保证用户信息安全的重要手段,只对 js 脚本做限制,对script、img、link标签不做限制,因为这些操作不会通过响应结果来进行有可能出现安全问题的操作,同源策略只要限制以下三个方面:
①当前域下的js不能访问其他域下的localSession、sessionStorage、cookie和indexDB
②当前域下的js脚本不能访问其他域下的DOM
③当前域下的axjax不能发送跨域请求
跨域解决方法
① JSONP:利用JSONP解决跨域问题,实际上是利用了script标签没有跨域限制的特点,通过script标签的src属性,发送一个带有callback参数的GET请求,服务器将接口返回的数据拼接到callback函数,由浏览器解析执行,最终拿到callback函数返回的数据
缺点:只支持get请求,可能会出现 XSS 攻击
② CORS ( 跨域资源共享 ) :跨域资源共享是一种机制,使用额外的http头来告诉浏览器,让运行在一个origin上的web应用可以访问另一个源上的资源,CORS需要浏览器和服务器共同支持,设置的关键是后端,只要在服务端设置Access-control-allow-origin,这个属性是用来告诉浏览器哪些源可以访问请求,一般设置为通配符*,表示所有网站都可以访问这个请求,CORS在请求的时候有两种情况,简单请求和复杂请求
简单请求,请求的方法必须是get/post/head,content-type的值必须是①text/plain②mulitipart/form-data③application/x-www-form/urlencoded
复杂请求:简单请求外的都属于复杂请求,复杂请求会多增加一个http查询请求,这个请求是一个options请求,用来知道服务是否允许跨域
③postMessage:会接受两个参数(data,origin),data是h5规范支持的任意数据类型或对象,有些浏览器支支持字符串,因此可以通过JSON.stringify进行序列化,origin是指协议,端口,主机,一般可以是*,表示这个页面的资源可以传递给任意窗口,如果要限制当前同源的话,就配置一个‘/’
postMessage可以解决当前页面与其他窗口的数据传递,多窗口的消息传递,当前页面与其嵌套的iframe的消息传递,以上三种场景的跨域数据传递
④node中间件代理:同源策略是针对浏览器的,服务器与服务器之间没有跨域限制,通过一个代理服务器来实现数据的转发,客户端发送请求给代理服务器,代理服务器转发这个请求给服务端,服务端响应数据给代理服务器,代理服务器转发这个响应给客户端,浏览器向代理服务器发送请求也是遵循同源策略的
⑤nginx 反向代理:同node中间件代理类似,都只通过一个代理服务器实现数据的转发,只需要修改nginx中的配置即可实现代理,支持session,支持所有浏览器,不修改任何代码,不会影响服务器的性能,而且可以通过修改cookie的domian信息,方便当前域的cookie写入,实现跨域登录
⑥document.domain+iframe:这种方法用在主域相同子域不同的场景,通过js强制设置document.domian未基础主域实现跨域
⑦location.hash+iframe:如果a和b想跨域通信,需要借助一个C页面,不同域直接用iframe的location.hash来传值,相同域用js直接访问
⑧window.name+iframe: 由iframe的window.name从外域把数据传到本地域,躲过浏览器的跨域限制
⑨websocket:是h5新增的一种协议,实现了浏览器和服务器的全双工通信,也是跨域的一种解决方案,他是server push技术的一种很好的实现
(webSocket:是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输)
3、什么是浏览器缓存
浏览器的缓存机制: 先判断强缓存,如果命中的话使用强缓存中的数据,不会再发起请求,没有命中判断是否命中协商缓存,优先使用ETag进行判断,没有ETag则使用last-modifed进行判断,回向服务器发送请求判断是否修改过信息,没有修改的话命中协商缓存,服务器不会返回资源,直接使用本地缓存的数据,如果都不命中在服务器再返回新的数据
用自己的话描述:缓存分为强缓存和协商缓存。
强缓存的话是根据expires和cache-control里面的mag-age来进行判断的,expires是http1.0的缓存方式,是一个绝对时间,如果浏览器和服务器的时间不一致的话可能会导致缓存失效,所以http1.1就出来一个max-age,他是设置缓存有效的一个时间,一般来说max-age的优先级是高于expires的,但是max-age有些浏览器不兼容,这个时候就使用expires去判断
如果没有命中强缓存的话,就会去判断是否命中协商缓存,协商缓存是使用etag和last-modified两个字段判断的,优先使用etag进行判断,他能准确的判断文件是否修改,而last-modified可能对于短时间内多次修改会导致缓存失效,etag有强弱之分的,以w开头的是弱etag,表示若验证,比较容易生成,使用etag给浏览器发送请求,浏览器会返回一个etag字段,浏览器携带一个if-none-cache字段给服务器,服务器判断这两个字段是否一样,一样的话说明文件没有被修改,返回304继续使用旧的资源,否则返回200状态码,并把新的资源返回给浏览器,使用last-modified也是一样的过程知识浏览器是携带if-modifie-since字段给服务器,服务器是判断if-modife-since和last-modified字段是否相同
如果前面两种缓存都没有命中的话,就使用启发式 缓存,启发式缓存是通过响应头里面的Date字段,减去last-modified字段的10%作为缓存时间
一般缓存的位置是由浏览器决定的,分为memory cache和disk cache,优先使用memory cache
Memory cache是在内存中缓存的,缓存的速度快,一般适用于小的文件,而disk cache是在硬盘中缓存的,缓存的速度会比较慢,但是可以缓存一些比较大的文件,除了上面两种还有像service worker还有push canche,push canche在前面三种缓存都没有命中的情况下去使用的,一般存在session中,一旦会话结束就被释放,时间比较短且只能使用一次
4、什么是重排和重绘,怎么减少
重排:
浏览器第一次计算节点的大小、位置等信息称之为布局,之后修改样式导致需要重新计算称之为重排
造成重排的原因:修改了DOM结构(新增/删除节点),修改了DOM的样式(大小/位置等),窗口的resize(改变窗口大小)、通过getComputedStyle()方法获取尺寸、位置等样式信息
重绘:
浏览器第一次渲染内容称之为重绘,之后每一次重新渲染都会称之为重绘,比如修改背景颜色、边框颜色、文本颜色等非几何信息,会导致重绘
重排一定会造成重绘,重绘不一定会造成重排
怎么减少:
①尽量一次性修改样式,通过class等
②尽量不要操作DON
③不要使用getComputedStyle去获取样式
④使用position:absolute/fixed,减少重排重绘的成本
浏览器的优化:
在绘制的过程中,浏览器会将布局的内容绘制到不同的合成层,一般标准流中 的内容都是绘制到同一个合成层的,浏览器会讲一些特殊的内容绘制到另外一个图层并由GPU来加速绘制,因为每一个合成层都是单独渲染的,会被绘制到另一个合成层的元素:①设置了pisition:fixed②设置了3D tranform③video,canvas,iframe④动画opcaticy⑤animation或者transition设置了opcacity或tranform等
5、localStorage,sessionStorage,cookie
浏览器端常用的存储技术是 cookie 、localStorage 和 sessionStorage。
- cookie : 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。
- sessionStorage: html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。
- localStorage: html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享
webStorage与cookie的区别
- Web Storage是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽;
- cookie 需要指定作用域,不可以跨域调用;
- Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie;
- Cookie 也是不可以或缺的:Cookie 的作用是与服务器进行交互,作为 HTTP 规范的一部分而存在 ,而 Web Storage 仅仅是为了在本地“存储”数据而生。
6、BOM和DOM
DOM ****指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象、history对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
7、事件委托
事件委托(Event Delegation)是一种用于优化事件处理的技术。它的基本思想是:将原本需要绑定在子元素上的事件监听器委托给它们的父元素或某个祖先元素,从而减少事件监听器的数量。借助事件机制(如事件冒泡),父元素可以捕获到子元素触发的事件,并使用事件对象中的事件目标(event target)信息来判断实际触发事件的子元素,然后进行相应的处理。
优点:
- 减少内存占用:通过在父元素上绑定一个事件监听器来代替多个子元素事件监听器,降低了内存开销。
- 动态元素支持:使用事件委托,即使在页面上新增子元素,无需为新添加的子元素手动绑定事件监听器,新添加的子元素也可以触发事件。
demo:
假设有一个列表,其中包含若干个列表项。当点击单个列表项时,需要进行特定操作。不使用事件委托,可以为每个列表项单独绑定事件监听器:
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
const listItems = document.querySelectorAll("li");
listItems.forEach((item) => {
item.addEventListener("click", (event) => {
console.log("Clicked on list item:", event.target.textContent);
});
});
</script>
使用事件委托,将事件监听器绑定在父元素 ul
上,通过事件对象判断实际触发事件的列表项:
<ul id="itemList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
const list = document.querySelector("#itemList");
list.addEventListener("click", (event) => {
if (event.target.tagName.toLowerCase() === "li") {
console.log("Clicked on list item:", event.target.textContent);
}
});
</script>
请注意,使用事件委托时需要确保事件能够冒泡到父元素。如果事件被某个子元素的事件监听器拦截(通过event.stopPropagation()
),则事件委托将无法正常工作。
react
在 React 中,事件处理机制内部使用了事件委托。React 不会将事件监听器直接绑定到 DOM 元素上,而是在应用的根元素(例如 #root
元素)上统一监听所有事件。React 使用事件对象的信息,如事件类型和目标元素,来确定具体触发事件的组件,并执行相应的事件处理函数。
React 16:
在 React 16 及更早版本中,React 事件委托在 document
上完成。这意味着所有 React 应用的事件监听器都绑定在了 document
上。虽然这在大多数情况下都有效,但也可能会导致一些问题,尤其是在应用与非 React 应用共存时,亦或者在内嵌 React 应用时。例如,使用 event.stopPropagation()
阻止事件传播时可能会产生不可预知的行为,并影响到其他应用。
React 17:
为解决这些问题,从 React 17 开始,事件委托的绑定从 document
更改为当前 React 应用的根元素。这意味着在 React 17 中,React 应用的所有事件监听器将绑定在应用的根元素上,而非 document
。此更改提高了 React 应用与其他应用的兼容性,降低了事件处理中的潜在问题风险。
8、事件冒泡和事件捕获
事件冒泡(Event Bubbling)
事件冒泡是从内到外的事件传播方式。当事件触发时,首先触发目标元素(最内层元素),然后传播到其父元素、祖父元素,依此类推,直到根元素,即最外层元素。在冒泡的过程中,所有元素的事件处理程序都按顺序触发。
举例:假设有一个 div 元素,包括一个 button 元素。两个元素都监听了 click 事件。当用户点击 button 时,事件冒泡会先触发 button 的 click 处理程序,然后再触发父 div 的 click 处理程序。
事件捕获(Event Capturing)
与事件冒泡相反,事件捕获是从外到内的事件传播方式。当事件触发时,首先触发最外层元素(例如文档的根元素),然后逐层向内传播,直到触发目标元素(事件发生的最内层元素)。在这个过程中,所有元素的事件处理程序都按顺序触发。
以相同的例子来解释事件捕获:当用户点击 button 时,事件捕获会先触发父 div 的 click 处理程序,然后再触发 button 的 click 处理程序。
在实际应用中,可以利用这两种事件传播机制来优化事件处理,例如,使用事件委托(event delegation)技术减少事件监听器数量。对于普通事件监听器而言,默认采用事件冒泡机制。 若要使用事件捕获机制,可以在 addEventListener
方法的第三个参数中设置为 true
:
// 使用事件捕获
document.querySelector("#parent-div").addEventListener("click", (event) => {
console.log("Clicked on parent div");
}, true);
// 使用事件冒泡(默认行为)
document.querySelector("#child-button").addEventListener("click", (event) => {
console.log("Clicked on child button");
});
注意,在事件传播过程中,可以使用 event.stopPropagation()
方法阻止事件继续传播,无论是事件冒泡还是事件捕获。
转载自:https://juejin.cn/post/7270509784688164898