前端面试八股—浏览器(一)
文章目录
- 前端安全
- CSRF
- XSS
- 网络劫持
- 前端存储
- 缓存
- 强制缓存
- 协商缓存
- 点击刷新或按F5、按Ctrl+F5强制刷新、地址栏回车的区别
- 存储方式
- cookie
- cookie、session、token
- localStorage
- sessionStorage
- 页面渲染
- 浏览器渲染进程的线程
- GUI渲染线程
- JS引擎线程
- 事件触发线程
- 定时器触发线程
- 异步http请求线程
- 浏览器的渲染过程
- JS文件对渲染的影响
- 回流与重绘
- 回流
- 重绘
- 避免回流与重绘
- 图片懒加载
- data urls
- 事件循环
- 进程、线程、协程
- 事件模型
- DOM0级事件模型
- DOM2级事件模型(标准事件模型)
- IE事件模型
- 事件对象Event
- Even Loop
- 浏览器Even Loop
- node Even Loop
- 跨域
- 同源策略
- 解决方案
- JSON
- CORS
- Nginx
- 正向代理与反向代理
- 反向代理解决跨域
- Proxy代理
- document.domain
- postMessage+iframe
- 节流和防抖
- 防抖
- 节流
前端安全
CSRF
- CSRF攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,攻击者可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。
- CSRF攻击的本质是利用cookie在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
- 攻击类型:GET、POST、链接
- GET类型:比如在网站的一个img标签里构建一个请求,当用户打开这个网站时会自动发起提交。
- POST类型:比如构建一个表单,然后隐藏它;当用户进入页面时,自动提交这个表单。
- 链接类型:在a标签的href属性中构建一个请求,诱导用户去点击。
- 防御:
- 进行同源检测。服务器根据http请求头中的origin或referer信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当origin或referer信息都不存在时,直接阻止请求。
- host:描述请求将被发送的目的地,只包括域名和端口号。
- origin:表明请求来源于哪个站点,只包括协议、域名、端口,不包含任何路径信息。
- referer:告知服务器请求的原始资源的URI,包括协议、域名、路径和查询参数。
- 使用Token进行验证。服务器向用户返回一个随机数token,当网站再次发起请求时,在请求参数中加入这个token,服务器会对这个token进行验证。这种方法解决了使用cookie单一验证的问题。但token存在负载均衡的问题,如果请求经过负载均衡转移到了其他服务器,但这个服务器没有保留这个token,也无法进行验证
- 对cookie进行双重验证。服务器在用户访问页面时,向请求域名注入一个cookie,内容为随机字符串,当用户再次请求服务器时,从cookie中取出这个字符串添加到URL中。服务器通过对cookie中的数据和URL中的参数进行比较来验证。这种方式利用了攻击者只能利用cookie但是不能访问获取cookie的特点,但如果网站存在XSS漏洞,也会失败。所以,在设置http-only限制cookie时,也要设置cookie的samesite属性,限制第三方cookie。
- 属性:
- httpOnly:通过程序(如JS脚本、Applet等)将无法读取到cookie信息。
- secure:true表示创建的cookie只能在https链接中传到服务器,如果是http连接则不会传递该消息。
- Samesite:限制第三方cookie,用来防止CSRF攻击和用户追踪。取值:
- strict:最为严格,完全禁止第三方cookie。跨站点时,任何情况都不会发送cookie,即只有当前网页的URL与请求目标一致时,才会带上cookie。
- lax:规则稍宽,大多数情况都不发送第三方cookie,当导航到目标网址的get请求除外。
- none:将在所有情况下发送,允许跨域。
- 第三方cookie:cookie的域和地址栏中的域不匹配,这个cookie通常被用在第三方广告网站,为了跟踪用户的浏览记录,根据收集的用户浏览习惯,给用户推送相关广告。
- 属性:
- 进行同源检测。服务器根据http请求头中的origin或referer信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当origin或referer信息都不存在时,直接阻止请求。
XSS
- XSS攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如cookie等。
- XSS的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在了一起,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。
- 攻击类型:存储型、反射型、DOM型。
- 存储型:恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。存储型XSS攻击步骤:
- 攻击者将恶意代码提交到目标网站的数据库中。这种行为常见于带有用户保存数据的网站,如论坛发帖、商品评论等。
- 用户打开目标网站,网站服务器将恶意代码从数据库中取出后拼接成HTML返回给浏览器。
- 用户浏览器接收响应后执行解析,其中的恶意代码也被执行。恶意代码窃取用户数据并发送到攻击者的网站,或冒充用户调用目标网站接口等。
- 反射型:攻击者诱导用户访问一个带有恶意代码的URL,服务器收到请求后把带有恶意代码的数据发送到浏览器端,浏览器解析这段带有XSS代码的数据后当作脚本执行,完成XSS攻击。反射型XSS的攻击步骤:
- 攻击者构造出特殊的URL,其中包含了恶意代码。
- 用户打开带有恶意代码的URL时,网站服务端将恶意代码从URL中取出,拼接在HTML中返回给浏览器。
- 后续步骤同存储型的步骤3。反射型和存储型的区别是:反射型的恶意代码存在URL里,存储型的恶意代码放在数据库中。
- DOM型:修改页面DOM节点形成XSS。DOM型XSS的攻击步骤:
- 攻击者构造出特殊的URL,其中包含了恶意代码。用户打开带有恶意代码的URL,前端JS直接取出URL中的恶意代码进行执行。之后步骤同以上步骤3。DOM型和以上两种的区别是:DOM型取出和执行恶意代码是由浏览器完成的,属于前端JS自身的安全漏洞;其他两种是服务器端的安全漏洞。
- 存储型:恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。存储型XSS攻击步骤:
- 预防:
- 方法一:限制浏览器的执行。
- 使用纯前端的方式,不用服务器拼接后返回的内容,即不适用服务端渲染。
- 对需要插入到HTML中的代码做好充分转义。
- 方法二:使用CSP,建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码注入。
- CSP:内容安全策略,本质建立一个白名单。开发者只需要配置规则,如何拦截由浏览器实现。有2种方式可以开启CSP:
- 设置HTTP首部中的Content-Security-Policy
- 设置meta标签的方式,http-equiv=“Content-Security-Plicy”
- CSP:内容安全策略,本质建立一个白名单。开发者只需要配置规则,如何拦截由浏览器实现。有2种方式可以开启CSP:
- 方法三:对敏感信息进行保护,如cookie使用http-only,表示脚本无法获取;使用验证码,避免脚本伪装成用户执行一些操作。
- 方法一:限制浏览器的执行。
网络劫持
- 有2种网络劫持:
- DNS劫持:输入url_A但被强制跳转到url_B
- DNS强制解析:通过修改运营商的本地DNS记录,来引导用户流量到缓存服务器
- 302跳转方式:通过监控网络出口的流量,分析判读哪些内容是可以进行劫持处理的,在对劫持的内容发起302跳转。(302 Found,临时重定位)
- HTTP劫持:访问url_A但是一直有url_B的广告
- 由于http明文传输,运营商可以修改http的响应内容,向其中加入广告。
- DNS劫持:输入url_A但被强制跳转到url_B
- DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持。但是http劫持非常盛行,目前最有效的办法就是全站HTTPS,对HTTP加密。
前端存储
缓存
- 浏览器首先向浏览器缓存发出请求,根据强制缓存规则判断是否向服务器发起请求。
- 缓存位置:
- from memory:内存缓存,可快速读取,在进程关闭后会被清空。
- 在浏览器中,JS文件、图片等解析后被放入内存。
- from disk: 硬盘缓存,需要对硬盘进行I/O操作,读取复杂,
- 在浏览器中,CSS文件会存入硬盘,每次渲染页面从硬盘中读取。
- from memory:内存缓存,可快速读取,在进程关闭后会被清空。
- 如果有缓存内容,判断该缓存是否过期:
- 没有过期,读取该缓存,加载内容。
- 如果过期了,浏览器携带该缓存的相关标识向服务器发起请求,根据协商缓存规则进行处理。
- 服务器返回结果为304表示可以继续使用该缓存,浏览器会读取该缓存,加载内容。
- 服务器返回结果为200表示资源过期了,同时返回新的资源和缓存标识,浏览器更新缓存内容和标识,并加载新内容。
- 如果没有缓存,会直接向服务器发起请求。服务器返回200和资源与标识,浏览器根据相关Cache-Control判断是否要缓存,加载内容。
强制缓存
规则:浏览器第一次请求服务器时,服务器会把缓存规则放在响应头里,和资源一起返回给浏览器。强制缓存的控制字段为Expires和Cache-Control,其中Cache-Control优先级高于Expires。
字段 取值 HTTP版本 说明 Expires 服务器返回给浏览器缓存到期的绝对时间
当浏览器再次发起请求时,如果时间小于该字段,可以使用缓存。1.0 该方法有缺陷,客户端的时间和服务器的时间无法保证完全同步,可能会因为时区等问题导致缓存直接失效 Cache-Control public - 所有内容被浏览器和服务器缓存
private - 默认值,内容只会被浏览器缓存
no-cache - 浏览器缓存,但是否可用要看协商缓存的结果
no-store - 所有内容均不缓存
max-age=xxx - 缓存将在xxx秒后失效1.1+
协商缓存
在强制缓存失效后,浏览器携带缓存标识向服务器发起请求,查询该缓存是否可用。服务器返回的结果:
- 304:资源无更新,浏览器可以继续使用缓存中的资源。
- 200:资源更新了,同时返回新的资源和资源标识,浏览器需要更新缓存。
规则:依据reponse / request头中的Last-Modified / If-Modified-Since 和 Etag / If-None-Match字段
字段对 说明 Last-Modified / If-Modified-Since 服务器会把资源最后一次更新时间放在Last-Modified中返回。
浏览器在If-Modified-Since中携带该值请求服务器。Etag / If-None-Match Etag是服务器根据该资源内容生成唯一标识符,浏览器在If-None-Match中携带该值。 - Etag的优先级高于Last-Modified,因为Last-Modified单位是s,如果1s内资源被修改多次,该值可能没有变化。Etag是根据内容散列、最后的修改时间、版本号的hash值,只要文件被修改,Etag一定发生变化。
点击刷新或按F5、按Ctrl+F5强制刷新、地址栏回车的区别
- 点击刷新或按F5:强制缓存直接失效,浏览器直接对本地的缓存文件过期,携带If-Modified-Since、If-None-Match进行协商缓存。
- 按Ctrl+F5强制刷新:浏览器对本地文件过期,并认为本地没有任何缓存文件,相当于第一个请求服务器。
- 地址栏回车:按正常流程,先判断是否有缓存、在强制缓存、最后协商缓存。
存储方式
cookie
- html5标准前本地存储的主要方式,请求头会自带cookie,大小只有4K;每个domain限制20个cookie。
- cookie字段设置:name、value、domain、path、secure、httponly、max-age/expires
// 设置cookie
const isHttps = 'https:' === document.location.protocol ? true : falseconst setCookie = (name, value, timeout) => {const options = {'domain': '*', // 可以访问该cookie的域名'path': '*', // 可以访问该cookie的页面路径'secure': isHttps, // true-cookie只会在https连接中发送'max-age': timeout,'samesit': 'strict', // 禁止第三方cookie}let cookies = `${name}=${value}`for(let key in options){const value = options[key]if (value) {cookies += `;${key}=${value}`} }document.cookie = cookies
}// 获取cookie
const getCookie = (name) => {const cookieStr = document.cookieconst index = cookieStr.indexOf(`${name}=`)if (index === -1) { return null }const valueStartIndex = index + name.length + 1// indexOf(subStr, start)const valueEndIndex = cookieStr.indexOf(';', valueStartIndex)return cookieStr.substring(valueEndIndex, valueEndIndex)return
}// 删除cookie
const deleteCookie = (name) => {setCookie(name, '', -1)
}
cookie、session、token
- session:
- 在服务器端记录,每一个会话会产生一个sessionId。当用户打开某个web应用时,便与web服务器产生一次session。服务器使用 sessionId 把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。所以服务器根据sessionId来区分用户。
- 缺点:
- 单服务器时,用户量过大导致存放session的消耗过大。
- 分布式服务器,如果登录的请求和后续使用的请求经负载均衡后打到不同的服务器,后续服务器没有存储之前的会话信息,会认为用户没有登录。
- 由于服务端存储session比较麻烦,所以产生了cookie。
- cookie:
- 是服务端保存在客户端的临时的少量的数据, 由服务器生成,发送给浏览器。浏览器把cookie以键值对的形式保存到当前目录的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。
- 缺点:由于cookie存在客户端上,容易被攻击,所以产生了token。
- token:服务端生成的一串字符串,当作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token并将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
localStorage
- HTML5加入的以键值对的方式存储,永久性存储,大小为5M。
- 给localStorage增加过期机制
export const setLocalStorage = (key, value, maxAge) => {localStorage.setItem(key, JSON.stringify(value))if (maxAge) {localStorage.setItem(`${key}_expires`, Date.now() + maxAge)} }export const removeLocalStorage = (key) => {localStorage.removeItem(key)localStorage.removeItem(`${key}_expires`) }export const getLocalStorage = (key) => {const value = localStorage.getItem(key)if (value) {const expires = localStorage.getItem(`${key}_expires`)if (expires > Date().now()) { // 过期了, 移除removeLocalStorage(key)console.log('已过期')return undefined}return JSON.parse(value)}return value }
sessionStorage
- 与localStorage类似,但是当页面关闭后会被清理,且不能再所有同源窗口中共享,是会话级别的存储方式。
- 生命周期比较
内容 生命周期 cookie 会话cookie默认到浏览器关闭(注意不是页面/窗口关闭),但是有些浏览器提供了会话恢复功能,这种情况即使关闭了浏览器,会话cookie也能被保存下来。
持久cookie取决于expires或max-age的值。localStorage 除非手动清除,否则一直保存 sessionStorage 窗口/标签页关闭后清空
页面渲染
浏览器渲染进程的线程
- 浏览器渲染进程的线程一共有5种,分别是:GUI渲染线程、JS引擎线程、事件触发线程、定时器触发线程、异步http请求线程。
GUI渲染线程
- 负责渲染浏览器页面,解析HTML、CSS,构建DOM树、CSSOM树,构建render树并绘制页面。
- 当界面需要重绘或者引发回流时,该线程会执行。
JS引擎线程
- JS引擎线程也称为JS内核,负责处理JS脚本程序,解析JS脚本并运行代码。
- JS引擎线程一直等待任务队列中任务的到来,然后进行处理。一个Tab页无论什么时候,都只有一个JS引擎线程在运行JS程序。
- GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,当JS引擎空闲时会立即被执行。所以,如果JS执行时间过长,会造成页面渲染的不连贯,导致页面渲染加载阻塞。
事件触发线程
- 用来控制事件循环。当JS引擎执行代码块如setTimeout,或来自浏览器内核的其他线程,如鼠标点击、Ajax异步请求时,会将对应的任务添加到事件触发线程中,当对应的事件收到处理结果被触发时,将该事件添加到待处理队列中,等到JS引擎的处理。
定时器触发线程
- 即setTimeout、setInterval所在的线程。
- 浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞状态会影响计时的准确性,因此使用单独的线程计时并处罚定时器。
- 计时完毕后将事件添加到队列中,等待JS引擎空闲后执行。所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间把任务添加到事件队列中。
异步http请求线程
- XHR连接后通过浏览器新开的一个线程。检测到状态变更后,如果设置有回调函数,异步线程会产生状态变更的事件,将回调函数放入事件队列中,等待JS引擎空闲后执行。
浏览器的渲染过程
具体内容可见:浏览器解析渲染HTML
- 过程:
- 首先解析收到的文档,根据文档定义,构建DOM树,由DOM元素和属性节点构成。
- 对CSS进行解析,生成CSSOM规则树。
- 根据DOM树和CSSOM树构建渲染树,其节点被称为渲染对象。渲染对象和DOM元素相对应,但不会构建不可见的DOM元素。
- 浏览器根据渲染树上的渲染对象,计算在页面上的具体的位置和大小。
- 最后,遍历渲染树,并调用相关绘制api,将内容显示在屏幕上。
- 这个过程是逐步完成的,为了更好的用户体验,渲染引擎会尽可能早的将内容呈现在屏幕上,并不会等到所有html都解析完成后再去构建和布局。它是解析完一部分就显示一部分,同时,可能还在通过网络下载其余内容。
JS文件对渲染的影响
- 在构建DOM时,如果HTML解析器遇到了JS,会暂停构建DOM,将控制权转移给JS引擎。等到JS执行完成后,浏览器从中断的地方恢复构建DOM。
- 由于JS既可以修改DOM,也能修改CSS,所以JS能导致CSSOM阻塞DO的构建。
- 由于不完整的CSS是无法使用的,所以必须要等到CSSOM构建完成后才能进行下一步操作。
- 所以,浏览器一般会先构建CSSOM,在运行脚本,最后恢复DOM构建。
回流与重绘
回流
- 对DOM的修改引发DOM几何尺寸的变化,导致浏览器需要重新计算并绘制部分或全部文档的过程。
- 导致回流的操作:页面的首次渲染、浏览器窗口大小发生变化、元素的尺寸或位置发生变化、激活CSS伪类、添加或删除可见DOM元素等。
- 回流会更新render树,代价是高昂的,会破坏用户体验。
重绘
- 对DOM的修改只导致了样式的变化,而没有影响其在文档流中的位置时,浏览器只需要为元素绘制新样式的过程。
- 导致重绘的操作:color、background、border-radius、box-shadow、visibility等属性改变。
- 回流一定导致重绘,但是重绘不一定导致回流。
避免回流与重绘
- 减少措施:
- 操作DOM时,尽量在低层级DOM节点进行操作。
- 定位时使用absolute或者fixed,是元素脱离文档流,这样他们的变化就不会影响其他元素。 ’
- … …
- 浏览器针对页面的回流与重绘,通过渲染队列进行了优化:
- 浏览器会将所有的回流、重绘操作放在一个队列中,当队列中的操作到了一定的数量或一定的时间间隔,浏览器对队列进行批处理。
图片懒加载
- 在长网页中延迟加载图片数据,是一种较好的网页性能优化方式。在长网页或应用中,如果所有图片都被加载出来,而用户只看了可视窗口中的一部分图片,会浪费性能。
- 图片懒加载:在滚动屏幕之前,可视化区域之外的图片不会进行加载。只有滚动屏幕时才加载。
- 图片的加载是由src引起的,当src赋值时,浏览器会请求图片资源。
- 使用data-xxx属性存储图片的真实路径,在需要加载图片时,把data-xxx中的属性赋值给src属性。
- 在浏览器中,可视区域内的资源就是用户需要的资源,当图片出现在可视区域时,进行赋值操作。
- 实现:
const imgs = document.querySelectorAll('img') // 监听滚动事件 window.onscroll = function () {const scrollTop = document.body.scrollTop || document.documentElement.scrollTop // 浏览器滚动的高度const innerHeight = window.innerHeightimgs.forEach(item => {if (item.offsetTop - scrollTop <= innerHeight) {item.src = item['data-src']}}) }
data urls
- 前缀为 data: 的url协议,向文档中嵌入小文件。
- 语法:
// data: 协议前缀 // mediatype 数据类型,如text/plain,image/png等 // base64 非文本的base64编码 // data 数据本身 data:[<mediatype>][;base64],data
- 通过data urls可以把图片嵌入到html中。
- 传统src属性指定了服务器上的资源,每次加载图片都会发起请求。如果图片过多,会影响页面的加载。
- data url使得图片以base64字符串嵌入到页面中,把图片二进制数据转换为字符串存储,该图片可以随着html下载的同时下载到本地,而不用再次发起请求。
- svg格式图片不支持base64编码,因为svg是纯文本。
事件循环
进程、线程、协程
- 进程是指具有一定功能的程序,是系统分配资源的独立单位。进程具有独立的内存。
- 线程是进程的一个实体,是CPU进行任务调度的基本单位。线程没有独立的资源空间,只是暂存在计数器、寄存器、栈中。同一个进程间的线程可以共享资源。
- 协程是一种程序运行方式,协作的线程、协作的函数。和线程的区别是:
- 同一时间内可以有多个线程处于运行状态,但运行的协程只能有一个,其余协程都处于暂停状态。
- 普通线程对于执行控制权是抢占式的,由环境分配。但是协程是合作式的,控制权有协程自己分配。
- JS是单线程的,只能维护一个调用栈。引入Generator后,每个任务都可以维护自己的调用栈,这样当抛出错误时,可以找到原始调用栈。
- Generator是半协程,因为只有Generator调用者可以将控制权还给它,其内部通过yield命令交换控制权。
事件模型
- 事件流会处于3个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
- 事件委托:利用事件冒泡,在父级节点上绑定一个事件处理函数,统一处理该节点及其子节点出发的这一类型的所有事件,通过e.target事件源进行区分。
- 好处:在JS中添加到页面的事件处理程序数量将直接影响页面的整体运行性能,同时事件处理函数对象也会占用内存。只对父级绑定函数,只需要一个函数对象的内存空间,且只有一个dom进行交互。
DOM0级事件模型
- 直接在dom对象上注册事件名。
- 这种模型不会传播,没有事件流的概念,在现在的浏览器种通过冒泡的方式实现。
- 特点:
- 只支持冒泡,不支持捕获。
- 同一类型的事件只能绑定一次。同类型事件,后绑定的会覆盖之前的。
// 方法一:在html代码中直接绑定
<div onclick="clickHandler()"></div>// 方法二:通过js代码绑定
const dom = document.getElementById('btn')
btn.onclick = clickHandler// 解绑事件
btn.onclick = null
DOM2级事件模型(标准事件模型)
- 一次事件共有三个过程:
- 事件捕获阶段:事件从document一直向下传播到目标元素,如果useCapture = true,则在捕获阶段会依次检查节点是否绑定了事件监听,如果有就触发该函数。
- 目标阶段:事件到达目标元素,触发目标元素的监听函数。
- 事件冒泡阶段:事件从目标元素一直向传播到document,如果useCapture = false,则在冒泡阶段依次检查节点是否绑定了事件监听,如果有就触发该函数。
- 可以在一个DOM上对同类型事件绑定多个处理函数,且各自不会冲突。
// 事件绑定监听函数
addEventListener(eventType: string, hander: () => {}, useCapture: boolean)
// 事件移除监听函数
removeEventListener(eventType, hander, useCapture)// eventType: 事件类型,不要加on
// hander: 处理函数
// useCapture: true - 在捕获阶段执行;false - 在冒泡阶段执行
IE事件模型
- 有两个过程:目标阶段、事件冒泡阶段。
// 事件绑定
attachEvent(eventType, handler)
// 事件移除
detachEvent(eventType, handler)
事件对象Event
- 阻止默认事件,如a标签的跳转等:event.preventDefault()
- 阻止冒泡:event.stopPropagation()
- event.target:返回事件的目标节点,即触发该事件的节点。
- event.currentTarget:返回触发事件监听器的节点。
- currentTarget只能存在于事件被处理过程中,当事件处理完成后,currentTarget被重新赋值。
- target和currentTarget的区别:target始终指向事件发生时的节点,currentTarget指向事件监听的元素。
<div id="box"><div id="btn1">1</div><div id="btn2">2</div><div id="btn3">3</div>
</div>
const dom = document.getElementById('box')
box.addEventListener('click', (e) => {// target - 触发事件的节点,currentTarget - 注册事件监听的节点console.log(e) // e.target = div#btn2, e.currentTarget = null// alert(e.currentTarget) // [object HTMLDivElement]// 事件委托const {target: {id}} = eswitch (id) {case 'btn1': console.log('btn1')breakcase 'btn2': console.log('btn2')breakcase 'btn3': console.log('btn3')breakdefault:console.log('default')}
})
Even Loop
浏览器Even Loop
- JS是单线程,一次只能执行一个任务;如果有多个任务,就得排队,等待前一个任务完成后再执行下一个。这样可能造成"假死",导致无法响应用户行为。当这种等到机制运行时,会造成组成,这就是同步机制,Even Loop的作用就是解决这种问题。
- 在程序中设置两个两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程之间的通信,称为"消息线程(Even Loop线程)"。
- 异步模式/非阻塞模式:每当遇到I/O时,主线程让消息线程通知相应的I/O程序,然后接着往后执行。等到I/O程序完成操作后,消息线程把结果返回给主线程,主线程调用回调函数,完成整个任务。
- JS在执行过程中会产生执行环境,这些执行环境会被顺序加入到执行栈中。如果遇到异步代码,会被挂起并加入任务队列中。一旦执行栈为空,会从任务队列中取出需要执行的代码放到执行栈中执行。
- 不同的任务源会被分配到不同的任务队列中,任务源分配微任务(microtask)和宏任务(macrotask),es6规范中微任务称为jobs,宏任务称为task。
- 微任务:process.nextTick、promise、Object.observe、MutationObserver
- 宏任务:script、setTimeout、setInterval、I/O、UI rendering
- 一次Even Loop顺序如下:
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有的微任务
- 必要的话渲染UI
- 开始下一轮的Event Loop,执行宏任务中的异步代码。
node Even Loop
- 说明:
- timers:处理setTimeout、setInterval的回调
- I/O callbacks:处理系统级别的回调
- idle, prepare:仅node内部使用
- poll:轮询队列,处除了times、check,大部分回调会被加到这个队列中,如文件的读取、监听用户请求等。
- 如果poll中有回调,依次执行回调,直到请求队列。
- 如果poll中没有回调,等待其他队列中出现回调,结束该阶段;否则一直等待。
- check:使用setImmediate的回调会直接进入这个队列。
- close callbacks:执行关闭请求的回调。
- nextTick和promise:事件循环中,每次执行一个回调前,必须清空nextTick和promise队列。
- 其中,nextTick优先级大于promise.then
- setTimeout(0)和setImmediate()执行顺序不一定:
- 如果node初始化事件循环的事件大于10ms,会按顺序执行setTimeout、setImmediate
- 如果小于10ms,会先执行setImmediate
async function async1(){console.log("1")await async2();console.log("2")
}async function async2(){console.log("3")
}console.log("4")setTimeout(()=>{console.log(5)
},0)setTimeout(()=>{console.log("6")
},3)setImmediate(()=> console.log("7"))process.nextTick(()=> console.log("8"))async1()new Promise(function(resolve){console.log("9")resolve()console.log("10")
}).then(function(){console.log("11")
})console.log("12")
- 打印顺序:4 1 3 9 10 12 8 2 11 5 7 6
跨域
同源策略
- 同源是指域名、协议、端口号完全相同。
- 同源策略分为2种:
- DOM同源策略:禁止对不同源的页面的DOM进行操作。
- XHR同源策略:禁止使用XHR对不同源的服务器发起HTTP请求。
- 解决方案:JSON、CORS、Nginx代理、document.domian、window.name、postMessage+iframe。
解决方案
JSON
- 利用了script标签不受同源策略限制的特点。
- 原理:
- 在客户端创建一个scrpit标签,其src是服务器接口,并携带参数callback接收服务器返回的数据。
- 服务器收到请求,处理完成后返回一个字符串,‘callback(JSON.stringfy(data))’
- 客户端收到响应后,把字符串转成表达式进行执行。
- 缺点:
- 只能进行get请求,一旦有恶意代码返回,前端也无法阻止。
- 无法检测请求是否成功。
CORS
- Cross-Origin Resource Sharing,跨域资源共享。
- 想要利用这个技术的关键在于服务端,设置响应头种Access-Control-Allow-Origin允许跨域操作,发送请求有两种情况:简单请求、复杂请求。
- 简单请求:使用Get/Post/Head方法,Content-Type取值为text.plain、multipart/form-data、application/x-www/urlencoded时,发起简单请求。浏览器判断是简单请求时,会在请求头中添加origin字段,表示发起请求所在的源。服务器收到请求后判断origin是否在自己的许可范围。如果不在,会拒绝请求;如果在,设置以下响应头:
- Access-Control-Allow-Origin:*表示允许所有源请求。
- Access-Control-Allow-Credentials:告知浏览器请求时是否需要携带cookie,取值为true/false。
- 复杂请求:请求2次,第一次发送一个预检请求,使用Options方法询问服务器是否允许我进行跨域请求。服务器会进行验证,在响应头中进行说明。
- Access-Control-Allow-Origin:告诉浏览器允许这个源的请求
- Access-Control-Allow-Methods:告诉浏览器允许请求的类型
- Access-Control-Allow-Headers:告诉浏览器允许发送请求时自定义的头部
- Access-Control-Max-Age:告诉浏览器预检请求的有效期。即在xxx时间内,可以直接发送Ajax请求,不用再询问。
请求头 OPTIONS /cors HTTP/1.1 Origin: http://test.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Custom-Header1,Custom-Header2 Host: target.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61(Unix) Access-Control-Allow-Origin: http://test.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: Custom-Header1,Custom-Header2 Access-Control-Max-Age: 1728000 Content-type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
Nginx
正向代理与反向代理
- 正向代理:一个位于客户端和服务器之间的服务器,客户端向代理发送一个请求并指定了原始服务器,然后代理向原始服务器转交请求并将获得的内容返回给浏览器。
- 反向代理:对于客户端来说,代理相当于原始服务器,并且客户端不需要进行任何设置。客户端向反向代理的命名空间中的内容发送普通请求,反向代理判断向哪个服务器转交请求。
反向代理解决跨域
配置文档见:nginx官方文档
前端不需要做什么,服务器部署nginx配置
前端把请求都发给代理服务器,由代理服务器请求后端服务器。
配置:
server{listen 80;# server_name 代理服务器ipserver_name 129.168.110.120;# 设置代理location [ = | ~ | ~* | ^~ ] uri {# proxy_pass 转发ipproxy_pass http://127.0.0.1:8080;add_header Access-Control-Allow-Originadd_header Access-Control-Allow-Credential} }
- location指令说明:该指令用于匹配URL
通配符 说明 = 用于不含正则表达式的uri前,要求请求字符串与uri严格匹配
如果匹配成功,不再向下搜索,并立即处理该请求~ 用于表示uri包含正则表达式,且区分大小写 ~* 用于表示uri包含正则表达式,且不区分大小写 ^~ 用于不含正则表达式的uri前,要求Nginx服务器找到和uri匹配度最高的location后,立即使用location做处理,而不再使用location块中的正则uri和请求字符串做匹配。
server{listen 80;server_name localhost;location ~ /edu/ {proxy_pass http://124.192.224.226:8080;}location ~ /vod/ {proxy_pass http://124.192.224.226:8081;}
}
Proxy代理
详细内容这位大大的博文:Webpack Proxy工作原理(本地跨域)
- 前端配置webpack,webpack-dev-server
- webpack proxy只能用于开发阶段,临时解决本地请求服务器产生的跨域问题。
- webpack中的proxy只是一层代理,用于把指定的path代理到后端提供的地址,使用node来做server。该技术只在webpack打包阶段在本地临时生成了node server,来实现类似nginx中的proxy_pass反向代理的效果。
- proxy工作原理实质上利用了 http-proxy-middleware这个http代理中间件,实现请求转发给其他服务器。
devServer{port: 3000,proxy: {'/api': {target: 'http://124.192.224.226:8080', // 请求将转发到http://124.192.224.226:8080/api上changeOrigin: true}}
}
document.domain
- 该方法只能用于二级域名相同的情况。
- 如a.demo.com和b.demo.com,给页面添加document.domain = 'demo.com’表示二级域名相同,实现跨域。
postMessage+iframe
- 用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息。
<div><iframe id="iframe" src='http://b.com/b.html'></iframe>
</div>// 发送消息
window.onload = function () {// postMessage(data, origin)// data - 要传递的数据,一般通过JSON.stringfy进行序列化// origin: 指明目标窗口的源window.iframes[0].postMessage('from a.com', 'http://b.com')
}
// 接收消息
window.addEventListener('message', (e) => {console.log(e)
}, false)
节流和防抖
防抖
- 策略:当事件被触发时,设定一个周期延迟执行动作。若周期内又被触发,则重新设定周期,直到周期结束,执行动作。
- 应用场景:按钮提交
- 实现
- 延迟执行:触发事件后不会立即执行,而是在n秒后执行,如果n秒内又触发了事件,则会重新计算函数执行的时间。
function debounce (fn, wait) {let timeout = nullreturn function () {const self = thisconst args = arguments// 如果有timeout,说明还在计时中,那就中止当前计时,重新计时if (timeout) { clearTimeout(timeout) }// wait后执行函数// timeout = setTimout 这是同步代码timeout = setTimeout(() => {fn.apply(self, args)}, wait)} } // immediate = false, 来跑一遍上述代码 // 一开始,timeout = null, 不执行clearTimeout // timeout被赋值,wait时间后执行fn // 假设在wait时间内再次触发,此时timeout是有值的 // 执行clearTimeout,中止当前计时 // timeout被赋值,开始重新计时,在wait时间后执行 // 假设在wait时间内没有被触发,计时到点后执行fn
- 立即执行:触发事件后后函数会立即执行,然后在n秒内如果没有触发事件,才会继续执行函数的效果。
function debounce (fn, wait) {let timeout = nullreturn function () {const self = thisconst args = argumentsif (timeout) { clearTimeout(timeout) }const callNow = !timeoutif (callNow) {fn.apply(self, args)}timeout = setTimeout(() => {timeout = null}, wait)} } // immediate = true,来跑一遍上述代码 // 一开始,timeout = null,不会执行clearTimeout那 // immediate = true, callNow = true, 执行fn // timeout被立刻赋值, 且在wait时间后在变为null // 假设wait时间内,又被触发 // timeout有值,执行clearTimeout中止当前计时 // callNow = !timeout = false, 就不会执行fn // timeout再次被赋值,开启新一轮计时 // wait时间内都没有被触发,timeout = null,一切回到原点
function debounce (fn, wait, immediate) {let timeoutreturn function () {const self = thisconst args = argumentsif (timeout) { clearTimeout(timeout) }if (immediate) {const callNow = !timeoutif (callNow) {fn.apply(self, args)}timeout = setTimeout(() => {timeout = null}, wait)} else {timeout = setTimeout(() => {fn.apply(self, args)}, wait)}} }
- 延迟执行:触发事件后不会立即执行,而是在n秒后执行,如果n秒内又触发了事件,则会重新计算函数执行的时间。
节流
- 策略:当持续触发事件时,保证每个间隔内执行一次。固定周期内,只执行一次动作;若又被触发,则不执行,直到周期结束。
- 应用场景:拖拽(防止高频次触发位置移动)、缩放(监控浏览器resize)
- 实现
const ThrottleType = {TIMESTAMP: 1, // 时间戳TIMEOUT: 2 // 定时器 } function throttle (fn, wait, type = ThrottleType.TIMESTAMP) {if (type === ThrottleType.TIMESTAMP) {var pre = 0} else {var timeout = null}return function () {const self = thisconst args = argumentsif (type === ThrottleType.TIMESTAMP) {let now = Date.now()if (now - pre >= wait) {fn.apply(self, args)}pre = Date.now()} else {if (!timeout) {timeout = setTimeout(() => {fn.apply(self, args)timeout = null}, wait)}}} }
前端面试八股—浏览器(一)相关推荐
- 前端面试中浏览器相关问题(二):回流与重绘
前端面试中浏览器相关问题(二):回流与重绘 文章目录 前端面试中浏览器相关问题(二):回流与重绘 浏览器的渲染过程 生成渲染树 回流 重绘 何时发生回流重绘 浏览器的优化机制 减少回流和重绘 最小化重 ...
- 前端面试-主流浏览器以及其内核
前端开发当然要了解你做测试的浏览器,而且在面试的时候也会经常问到这方面的问题. 现在国内常见的浏览器有:IE.Firefox.Safari.Opera.Google Chome.QQ浏览器.搜狗浏览器 ...
- 前端面试准备---浏览器和网络篇(一)
本文主要内容: AJAX GET和POST请求的区别 同源策略.JSONP.跨域方式 浏览器架构 输入一个Url到加载网页的全过程,发生了什么? 浏览器渲染的步骤 重绘和回流 页面渲染优化 AJAX ...
- 前端面试之浏览器原理篇
一.浏览器安全 1. 什么是 XSS 攻击? (1)概念 XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击.攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie ...
- 前端面试之浏览器内核
目前主流浏览器内核主要有以下几种: 1. Chromium/Blink:由谷歌开发的内核,是目前最为流行的浏览器内核,包括谷歌Chrome.Microsoft Edge.Opera等. 2. Geck ...
- 前端面试系列-浏览器缓存机制
缓存位置 从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络 Service Worker Memory Cache Disk Cache Push Cach ...
- 前端对所有文件请求添加header_【前端面试必问】浏览器缓存原理?送你满分答案...
(本文适合所1-3年的前端阅读) 原文链接: http://blog.poetries.top/2019/01/02/browser-cache/ 一.浏览器缓存基本认识 分为强缓存和协商缓存 浏览器 ...
- java 重定向到某个页面并弹出消息_前端面试100问之浏览器从输入URL到页面展示发生了什么...
点击蓝字,关注我们 『浏览器从输入URL到页面渲染发生了什么』作为一个经典题目,在前端面试中高频出现,很多大厂的面试都会从这个面试题出发,考察候选人对知识的掌握程度,这其中涉及到了网络.操作系统.We ...
- 前端面试知识点大全——浏览器篇
总纲:前端面试知识点大全 目录 1.浏览器工作原理 2.浏览器如何解析css,如何渲染css的 2.1 构建DOM树 2.2 构建CSSOM规则树(就是css规则树) 2.3 渲染阻塞 2.4 构建渲 ...
最新文章
- 有哪些可以免登录的视频会议软件/服务?
- CSS之定位布局(position,relative定位布局技巧)
- LRU原理及其实现(C++)
- 【PAT甲级 多项式相乘】1009 Product of Polynomials (25 分) C++ 全部AC
- jquery实现多行滚动效果
- js将数字转成大写中文
- word中公式和文字不在一行的设置方法
- 搜索引擎优化系统知名乐云seo_北京网络优化知名乐云seo
- 格式化代码_格式化代码是什么意思​
- 《活出生命的意义》节选
- 【渗透测试】VulnHub-Lord Of The Root: 1.0.1
- 基于反馈的动态补偿模型
- 我深爱的Java,对不起,我出轨了!!!呸!渣男!
- poe交换机归类有什么?
- python xlsx文件与csv文件转换
- 新颖的 USB HUB快充方案助您无忧!!(兼容PD、QC、AFC等快充协议)
- 携程编程大赛预赛第二场
- LaTeX 插入PDF图片,该用哪个命令?
- Installing OpenCV 2.4.9 in Ubuntu 14.04 LTS(好文章)
- 阿里巴巴项目P8技术咖总结的Java心得,完整版PDF可下载