文章目录

  • 前端安全
    • 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通常被用在第三方广告网站,为了跟踪用户的浏览记录,根据收集的用户浏览习惯,给用户推送相关广告。

XSS

  • XSS攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如cookie等。

    • XSS的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在了一起,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。
  • 攻击类型:存储型、反射型、DOM型。
    • 存储型:恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。存储型XSS攻击步骤:

      1. 攻击者将恶意代码提交到目标网站的数据库中。这种行为常见于带有用户保存数据的网站,如论坛发帖、商品评论等。
      2. 用户打开目标网站,网站服务器将恶意代码从数据库中取出后拼接成HTML返回给浏览器。
      3. 用户浏览器接收响应后执行解析,其中的恶意代码也被执行。恶意代码窃取用户数据并发送到攻击者的网站,或冒充用户调用目标网站接口等。
    • 反射型:攻击者诱导用户访问一个带有恶意代码的URL,服务器收到请求后把带有恶意代码的数据发送到浏览器端,浏览器解析这段带有XSS代码的数据后当作脚本执行,完成XSS攻击。反射型XSS的攻击步骤:
      1. 攻击者构造出特殊的URL,其中包含了恶意代码。
      2. 用户打开带有恶意代码的URL时,网站服务端将恶意代码从URL中取出,拼接在HTML中返回给浏览器。
      3. 后续步骤同存储型的步骤3。反射型和存储型的区别是:反射型的恶意代码存在URL里,存储型的恶意代码放在数据库中。
    • DOM型:修改页面DOM节点形成XSS。DOM型XSS的攻击步骤:
      1. 攻击者构造出特殊的URL,其中包含了恶意代码。用户打开带有恶意代码的URL,前端JS直接取出URL中的恶意代码进行执行。之后步骤同以上步骤3。DOM型和以上两种的区别是:DOM型取出和执行恶意代码是由浏览器完成的,属于前端JS自身的安全漏洞;其他两种是服务器端的安全漏洞。
  • 预防:
    • 方法一:限制浏览器的执行。

      • 使用纯前端的方式,不用服务器拼接后返回的内容,即不适用服务端渲染。
      • 对需要插入到HTML中的代码做好充分转义。
    • 方法二:使用CSP,建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码注入。
      • CSP:内容安全策略,本质建立一个白名单。开发者只需要配置规则,如何拦截由浏览器实现。有2种方式可以开启CSP:

        • 设置HTTP首部中的Content-Security-Policy
        • 设置meta标签的方式,http-equiv=“Content-Security-Plicy”
    • 方法三:对敏感信息进行保护,如cookie使用http-only,表示脚本无法获取;使用验证码,避免脚本伪装成用户执行一些操作。

网络劫持

  • 有2种网络劫持:

    • DNS劫持:输入url_A但被强制跳转到url_B

      • DNS强制解析:通过修改运营商的本地DNS记录,来引导用户流量到缓存服务器
      • 302跳转方式:通过监控网络出口的流量,分析判读哪些内容是可以进行劫持处理的,在对劫持的内容发起302跳转。(302 Found,临时重定位)
    • HTTP劫持:访问url_A但是一直有url_B的广告
      • 由于http明文传输,运营商可以修改http的响应内容,向其中加入广告。
  • DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持。但是http劫持非常盛行,目前最有效的办法就是全站HTTPS,对HTTP加密。

前端存储

缓存

  • 浏览器首先向浏览器缓存发出请求,根据强制缓存规则判断是否向服务器发起请求。
  • 缓存位置:
    • from memory:内存缓存,可快速读取,在进程关闭后会被清空。

      • 在浏览器中,JS文件、图片等解析后被放入内存。
    • from disk: 硬盘缓存,需要对硬盘进行I/O操作,读取复杂,
      • 在浏览器中,CSS文件会存入硬盘,每次渲染页面从硬盘中读取。
  • 如果有缓存内容,判断该缓存是否过期:
    • 没有过期,读取该缓存,加载内容。
    • 如果过期了,浏览器携带该缓存的相关标识向服务器发起请求,根据协商缓存规则进行处理。
      • 服务器返回结果为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

  • 过程:

    1. 首先解析收到的文档,根据文档定义,构建DOM树,由DOM元素和属性节点构成。
    2. 对CSS进行解析,生成CSSOM规则树。
    3. 根据DOM树和CSSOM树构建渲染树,其节点被称为渲染对象。渲染对象和DOM元素相对应,但不会构建不可见的DOM元素。
    4. 浏览器根据渲染树上的渲染对象,计算在页面上的具体的位置和大小。
    5. 最后,遍历渲染树,并调用相关绘制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)}}
    }
    

节流

  • 策略:当持续触发事件时,保证每个间隔内执行一次。固定周期内,只执行一次动作;若又被触发,则不执行,直到周期结束。
  • 应用场景:拖拽(防止高频次触发位置移动)、缩放(监控浏览器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)}}}
    }
    

前端面试八股—浏览器(一)相关推荐

  1. 前端面试中浏览器相关问题(二):回流与重绘

    前端面试中浏览器相关问题(二):回流与重绘 文章目录 前端面试中浏览器相关问题(二):回流与重绘 浏览器的渲染过程 生成渲染树 回流 重绘 何时发生回流重绘 浏览器的优化机制 减少回流和重绘 最小化重 ...

  2. 前端面试-主流浏览器以及其内核

    前端开发当然要了解你做测试的浏览器,而且在面试的时候也会经常问到这方面的问题. 现在国内常见的浏览器有:IE.Firefox.Safari.Opera.Google Chome.QQ浏览器.搜狗浏览器 ...

  3. 前端面试准备---浏览器和网络篇(一)

    本文主要内容: AJAX GET和POST请求的区别 同源策略.JSONP.跨域方式 浏览器架构 输入一个Url到加载网页的全过程,发生了什么? 浏览器渲染的步骤 重绘和回流 页面渲染优化 AJAX ...

  4. 前端面试之浏览器原理篇

    一.浏览器安全 1. 什么是 XSS 攻击? (1)概念 XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击.攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie ...

  5. 前端面试之浏览器内核

    目前主流浏览器内核主要有以下几种: 1. Chromium/Blink:由谷歌开发的内核,是目前最为流行的浏览器内核,包括谷歌Chrome.Microsoft Edge.Opera等. 2. Geck ...

  6. 前端面试系列-浏览器缓存机制

    缓存位置 从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络 Service Worker Memory Cache Disk Cache Push Cach ...

  7. 前端对所有文件请求添加header_【前端面试必问】浏览器缓存原理?送你满分答案...

    (本文适合所1-3年的前端阅读) 原文链接: http://blog.poetries.top/2019/01/02/browser-cache/ 一.浏览器缓存基本认识 分为强缓存和协商缓存 浏览器 ...

  8. java 重定向到某个页面并弹出消息_前端面试100问之浏览器从输入URL到页面展示发生了什么...

    点击蓝字,关注我们 『浏览器从输入URL到页面渲染发生了什么』作为一个经典题目,在前端面试中高频出现,很多大厂的面试都会从这个面试题出发,考察候选人对知识的掌握程度,这其中涉及到了网络.操作系统.We ...

  9. 前端面试知识点大全——浏览器篇

    总纲:前端面试知识点大全 目录 1.浏览器工作原理 2.浏览器如何解析css,如何渲染css的 2.1 构建DOM树 2.2 构建CSSOM规则树(就是css规则树) 2.3 渲染阻塞 2.4 构建渲 ...

最新文章

  1. 有哪些可以免登录的视频会议软件/服务?
  2. CSS之定位布局(position,relative定位布局技巧)
  3. LRU原理及其实现(C++)
  4. 【PAT甲级 多项式相乘】1009 Product of Polynomials (25 分) C++ 全部AC
  5. jquery实现多行滚动效果
  6. js将数字转成大写中文
  7. word中公式和文字不在一行的设置方法
  8. 搜索引擎优化系统知名乐云seo_北京网络优化知名乐云seo
  9. 格式化代码_格式化代码是什么意思​
  10. 《活出生命的意义》节选
  11. 【渗透测试】VulnHub-Lord Of The Root: 1.0.1
  12. 基于反馈的动态补偿模型
  13. 我深爱的Java,对不起,我出轨了!!!呸!渣男!
  14. poe交换机归类有什么?
  15. python xlsx文件与csv文件转换
  16. 新颖的 USB HUB快充方案助您无忧!!(兼容PD、QC、AFC等快充协议)
  17. 携程编程大赛预赛第二场
  18. LaTeX 插入PDF图片,该用哪个命令?
  19. Installing OpenCV 2.4.9 in Ubuntu 14.04 LTS(好文章)
  20. 阿里巴巴项目P8技术咖总结的Java心得,完整版PDF可下载

热门文章

  1. mac 已删除程序提示更新但是无法更新
  2. application/json 和 application/x-www-form-urlencoded 有什么区别?
  3. [gtalk]gtalk机器人
  4. Android开发背景
  5. Unix/Linux编程:socketpair
  6. python行业发展前景好_python就业前景怎么样
  7. vue飘窗效果(css飘窗效果,跟随页面滚动)
  8. origin画三个曲线图的方法
  9. 河狸家创始人孟醒:10亿估值仅用半年 刷新雕爷牛腩记录
  10. pymysql安装过程