浏览器相关问题

388 阅读12分钟

浏览器相关问题

target 和 currentTarget 区别

  1. target 返回触发事件的元素
  2. currentTarget 返回绑定事件的元素

如何自定义事件(原生 js 提供的事件)

  1. createEvent,设置事件类型,是 html 事件还是鼠标事件
  2. initEvent,初始化事件,事件名称,是否允许冒泡,是否阻止自定义事件
  3. dispatchEvent,触发事件

cookie、localStorage、sessionStorage 区别

特性cookielocalStoragesessionStorage
由谁初始化客户端或服务器,服务器可以使用 Set-Cookie 请求头。客户端客户端
数据的生命周期一般由服务器生成,可设置失效时间,如果在浏览器生成,默认是关闭浏览器之后失效永久保存,可清除仅在当前会话有效,关闭页面后清除
存放数据大小4KB5MB5MB
与服务器通信每次都会携带在 HTTP 头中,如果使用 cookie 保存过多数据会带来性能问题仅在客户端保存仅在客户端保存
用途一般由服务器生成,用于标识用户身份用于浏览器缓存数据用于浏览器缓存数据
访问权限任意窗口任意窗口当前页面窗口

事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。

对跨域的理解

跨域是因为浏览器的同源策略导致的,目的是为了保护浏览器的数据安全

解决跨域的方式一般分为生产环境和开发环境,

在生产环境:

  1. NGINX反向代理配置
  2. 服务端设置请求头,配置白名单
  3. jsonp请求,利用script可以请求第三方链接,不过jsonp只能进行get请求,且需要服务端数据传送到callback的参数

在开发环境:

  1. webpack的proxyTable
  2. chrome的跨域插件
  3. Charles等工具

那什么是同源策略呢

同源策略指的是:ip、端口、协议都相同则表示同源

事件循环

因为js是单线程的解释型语言且在执行过程中,分为同步任务和异步任务

同步任务:在主流程先执行

异步任务:分为宏任务和微任务,在同步任务执行完成之后先执行微任务队列,微任务队列执行完成之后,执行一个宏任务,然后再检查是否有微任务,如果有微任务,如果有则先把微任务执行完,这样的循环过程就叫做时间循环

js为什么是单线程

  1. 简化并发问题,不需要开发者去处理线程间的同步
  2. 减轻浏览器的复杂度和资源消耗(如线程之间的通信)
  3. js也提供的事件循环来实现异步编程

单线程的js有什么缺点

  1. 阻塞较大
  2. 难以利用多核CPU
  3. 难以处理大量的网络请求
  4. 虽然有时间循环,但是无法时间真正的并行

浏览器的渲染过程和微信小程序的有什么不同

浏览器的渲染过程

js是单线程的,网页开发渲染线程和脚本线程是互斥的

  1. DNS 的查询(也就是根据域名去找对应的 ip 地址)
  2. TCP 的连接(连接建立、数据传送以及连接释放)
  3. HTTP 请求即响应
  4. 服务端响应
  5. 客户端渲染

客户端渲染过程如下:

  1. 解析 html 生成 dom 树
  2. 解析 css 生成 cssom 树
  3. 将 dom 树和 cssom 树规则合并在一起生成渲染树
  4. 遍历渲染树开始布局,计算每一个节点的位置信息等
  5. 将渲染树每个节点绘制到屏幕

同时这个也可以回(输入url到渲染完成浏览器是怎么做的)这个问题

小程序渲染

小程序是双线程的,分为渲染层和逻辑层,其中wxml工作在渲染层,js工作在逻辑层,渲染层使用webview进行渲染,逻辑层采用jscore运行js脚本,两个线程的通信通过native来中转,网络请求也有native来转发

为什么小程序要采用双线程

小程序设计时,要求渲染快,加载快

  1. 如果是纯客户端,会有版本更新的问题
  2. 如果是纯web技术,回到了单线程的问题UI和JS资源问题
  3. 介于客户端和web之间的技术;webUI方面由渲染层完成,js由逻辑层完成,同时提供大量的api来使用原生能力,同时双线程防止开发者随意操作界面,也保证了用户的数据安全

浏览器的缓存机制

浏览器的缓存机制也就是我们说的 HTTP 缓存机制,其机制是根据 HTTP 抱文的缓存标识进行的,HTTP 抱文分为两种:

  1. HTTP 请求(Request)抱文
  2. HTTP 响应(Response)抱文

浏览器与服务器通信的方式为应答模式,即为:TCP请求,那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中 HTTP 头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中

  1. 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
  2. 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

缓存分为强制缓存和协商缓存

强制缓存

强制缓存的返回状态码为: 200

  • HTTP 响应报文中的 expires--expires: Wed, 21 Oct 2020 03:25:41 GMT,这个是一个绝对时间,在指定的时间前请求缓存生效
  • HTTP 响应报文中的 Cache-Control--cache-control: max-age=600,这是一个相对值,单位 s,也就是 600s 之内再次请求会直接使用缓存,强制缓存生效

    在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control 相比于 expires 是更好的选择,所以同时存在时,只有 Cache-Control 生效

但是看返回值中的 size 栏中有from memory cachefrom disk cache:分别是内存中的缓存和硬盘中的缓存,浏览器对于两者的读取顺序是: memory -> disk

  1. 内存缓存:快速读取(直接写入进程的内存中,方便下次使用和快速读取)和时效性(也就是一旦关闭进程关闭,内存就会清空)
  2. 直接将缓存写入硬盘文件中,读取缓存需要对硬盘文件进行 I/O 操作,读取是复杂的,速度也是比内存慢的

协商缓存

协商缓存的返回状态码为: 304

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

  • 协商缓存生效,返回 304
  • 协商缓存失效,返回 200 和请求结果结果

协商缓存的表示在响应的报文中为:Last-Modified / If-Modified-SinceEtag / If-None-Match, 其中 Etag / If-None-Match > Last-Modified / If-Modified-Since

  • 也就是说: 强制缓存优先于协商缓存进行,若强制缓存(Expires 和 Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since 和 Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回 304,继续使用缓存

浏览器的垃圾回收机制与内存泄漏

浏览器对 js 具有自动垃圾回收机制(GC: Garbage COllecation),也就是说,执行环境会负责管理代码执行过程中使用的内存,其原理:垃圾收集器会定期(周期性)找出那些不再继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且 GC 时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

两种实现方式:标记清除引用计数。引用计数不太常用(在一些老的浏览器上面使用),标记清除较为常用。

1. 标记清除

js 中最常用的垃圾回收方式就是标记清除。当变量进入环境时,变量“进入环境”,被标记,离开环境时,被回收

垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记,然后它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包),而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 到目前为止,IE9+、Firefox、Opera、Chrome、Safari 的 js 实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

2. 引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。

function test() {
  var a = {}; //a的引用次数为0
  var b = a; //a的引用次数加1,为1
  var c = a; //a的引用次数再加1,为2
  var b = {}; //a的引用次数减1,为1
}

使用这种方法看起来好像很好理解,但当遇到循环引用的时候就很难受了

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
window.onload = function outerFunction() {
  var obj = document.getElementById("element");
  obj.onclick = function innerFunction() {};
};

第二个例子:obj 引用了 document.getElementById('element'),onclick 方法会引用外部环境中的变量,自然也包括 obj,这种隐蔽的循环引用很难发现的

遇到这样的情况我们需要手动去解除

myObject.element = null;
element.o = null;

window.onload = function outerFunction() {
  var obj = document.getElementById("element");
  obj.onclick = function innerFunction() {};
  obj = null;
};

IE8-格外需要注意这些事情,不过现在我们开发需要兼容 IE8-的情况正在一步步减少

内存管理

  1. 何时触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6 的垃圾回收是根据内存分配量运行的,当环境中存在 256 个变量、4096 个对象、64k 的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。

微软在 IE7 中做了调整,触发条件不再是固定的,而是动态修改的,初始值和 IE6 相同,如果垃圾回收器回收的内存分配量低于程序占用内存的 15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于 85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多

  1. 合理的 GC 方案
  • 基本方案 Javascript 引擎基础 GC 方案是(simple GC):mark and sweep(标记清除),即:
    1. 遍历所有可访问的对象。
    2. 回收已不可访问的对象。
  • GC 缺陷 和其他语言一样,javascript 的 GC 策略也无法避免一个问题:GC 时,停止响应其他操作,这是为了安全考虑。而 Javascript 的 GC 在 100ms 甚至以上,对一般的应用还好,但对于 JS 游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免 GC 造成的长时间停止响应。
  • GC 优化策略
    1. 分代回收:区分“临时”与“持久”对象,多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需要遍历的对象
    2. 增量 GC:增加回收次数,牺牲的是中断次数