具体内容结构(可作为回答思路)为:简略回答,详细回答
1、什么是XSS攻击
- 简略回答
xss攻击是跨站脚本攻击,通过在网站上注入恶意脚本,让它在用户的浏览器上运行,从而盗取用户的信息,比如cookie等内容。
- 详细回答
xss有多种攻击方式
- 获取页面的数据,如DOM、cookie、localStorage;
- DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器;
- 流量劫持(将链接指向某网站);
xss有多种攻击类型
-
存储型xss
- 恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。
- 这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。
-
反射型xss
- 攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。
- 反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。
- 反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。
-
DOM型xss
- 通过修改页面的 DOM 节点形成的 XSS。
- DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。
如何防御xss攻击
-
从浏览器的执行来进行预防
- 一种是使用纯前端的方式,不用服务器端拼接后返回(不使用服务端渲染)。
- 另一种是对需要插入到 HTML 中的代码做好充分的转义。
- 对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。
-
使用 CSP-内容安全策略 ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。
-
对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。
2、什么是CSRF攻击
- 简略回答
CSRF 攻击指的是跨站请求伪造攻击,利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
- 详细回答
攻击方式
- 攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。
- 如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,
- 绕过后台的用户验证,冒充用户向服务器执行一些操作。
攻击类型
- GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。
- POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。
- 链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。
如何防御 CSRF 攻击?
-
进行同源检测
- 服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止请求。
-
使用 CSRF Token 进行验证
- 服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加入服务器端返回的 token ,然后服务器对这个 token 进行验证。
-
对 Cookie 进行双重验证
- 服务器在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较,来进行验证。
-
在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用,从而可以避免被攻击者利用。
-
Samesite 一共有两种模式,
- 一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,
- 在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。
-
3、什么是中间人攻击
- 简略回答
中间⼈ (Man-in-the-middle attack, MITM) 是指攻击者与通讯的两端分别创建独⽴的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过⼀个私密的连接与对⽅直接对话, 但事实上整个会话都被攻击者完全控制。
- 详细回答
攻击步骤
- 客户端发送请求到服务端,请求被中间⼈截获
- 服务器向客户端发送公钥
- 中间⼈截获公钥,保留在⾃⼰⼿上。然后⾃⼰⽣成⼀个伪造的公钥,发给客户端
- 客户端收到伪造的公钥后,⽣成加密hash值发给服务器
- 中间⼈获得加密hash值,⽤⾃⼰的私钥解密获得真秘钥,同时⽣成假的加密hash值,发给服务器
- 服务器⽤私钥解密获得假密钥,然后加密数据传输给客户端
4、引起前端安全的问题有哪些
- 简略回答
- 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 区分所以被称作 XSS。
- 跨站点请求伪造(Cross-Site Request Forgeries,CSRF).
- iframe的滥⽤
- 恶意第三⽅库
- http劫持
- dns劫持
- 中间人攻击
- 详细回答
5、网络劫持有哪几种,如何防范
- 简略回答
有2种,DNS劫持(输⼊京东被强制跳转到淘宝这就属于dns劫持),HTTP劫持(输⼊京东被强制跳转到淘宝这就属于dns劫持)
- 详细回答
如何防范:
- DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持。
- http劫持的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。
6、浏览器中存在的进程有哪些
- 简略回答
-
浏览器主进程:
- 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
-
渲染进程:
- 核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
-
GPU 进程:
- 其实, GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
-
网络进程:
- 主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
-
插件进程:
- 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
- 详细回答
7、进程与线程概念
- 简略回答
进程 描述了CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。
线程 描述了执行一段指令所需的时间。
- 详细回答
进程和线程之间的关系
-
进程中的任意一线程执行出错,都会导致整个进程的崩溃。
-
线程之间共享进程中的数据。
-
当一个进程退出时,操作系统会回收该进程所申请的所有资源;即使其中任意线程因为操作不当导致内存泄漏,当进程退出时,这些内存也会被正确回收。
-
进程之间的内容相互隔离。
- 进程隔离就是为了使操作系统中的进程互不干扰,每一个进程只能访问自己占有的数据,也就避免出现进程 A 写入数据到进程 B 的情况。
8、进程与线程的区别
- 简略回答
-
进程可以看做独立应用,线程不能
-
资源方面:
- 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位);
- 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。
-
通信方面:
- 线程间可以通过直接共享同一进程中的资源
- 而进程通信需要借助 进程间通信。
-
调度:
- 进程切换比线程切换的开销要大。线程是CPU调度的基本单位,线程的切换不会引起进程切换,但某个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
-
系统开销:
- 由于创建或撤销进程时,系统都要为之分配或回收资源,如内存、I/O 等,其开销远大于创建或撤销线程时的开销。
- 同理,在进行进程切换时,涉及当前执行进程 CPU 环境还有各种各样状态的保存及新调度进程状态的设置,而线程切换时只需保存和设置少量寄存器内容,开销较小。
- 详细回答
9、浏览器渲染进程的线程有哪些
- 简略回答
-
GUI渲染线程
- 负责渲染浏览器页面,解析HTML、CSS,构建DOM树、构建CSSOM树、构建渲染树和绘制页面;
- 当界面需要重绘或由于某种操作引发回流时,该线程就会执行。
-
JS引擎线程
- JS引擎线程也称为JS内核,负责处理Javascript脚本程序,解析Javascript脚本,运行代码;
- JS引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个Tab页中无论什么时候都只有一个JS引擎线程在运行JS程序;
-
事件触发线程
- 事件触发线程属于浏览器而不是JS引擎,用来控制事件循环;
- 当JS引擎执行代码块如setTimeOut时(也可是来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件触发线程中;当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理;
-
定时器触发线程
- 定时器触发线程即setInterval与setTimeout所在线程;
- 浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中;
-
异步http请求线程
- XMLHttpRequest连接后通过浏览器新开一个线程请求;
- 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行;
- 详细回答
10、进程之间的通信方式
- 简略回答
-
管道通信
- 管道是一种最基本的进程间通信机制。管道就是操作系统在内核中开辟的一段缓冲区,进程1可以将需要交互的数据拷贝到这段缓冲区,进程2就可以读取了。
-
消息队列通信
- 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
-
信号量通信
- 共享内存最大的问题就是多进程竞争内存的问题,就像类似于线程安全问题。我们可以使用信号量来解决这个问题。
- 信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。
- 例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。
-
套接字通信
- 上面我们说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?答是必须的,这个时候 Socket 这家伙就派上用场了
- 例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。
- 详细回答
11、僵尸进程和孤儿进程是什么
- 简略回答
-
僵尸进程
- 子进程比父进程先结束,而父进程又没有释放子进程占用的资源,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵死进程。
-
孤儿进程
- 父进程退出了,而它的一个或多个子进程还在运行,那这些子进程都会成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
- 详细回答
12、死锁产生的原因
- 简略回答
死锁 是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
- 详细回答
产生死锁的原因
-
首先理解 系统中的资源可分为两类:
- 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
- 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
-
竞争资源
-
产生死锁中的竞争资源之一指的是竞争不可剥夺资源
- 例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞
-
产生死锁中的竞争资源另外一种资源指的是竞争临时资源
- 临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
-
-
进程间 推进顺序 非法
- 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
产生死锁的必要条件
-
互斥条件:
- 进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
-
请求和保持条件:
- 当进程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:
- 进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
-
环路等待条件:
- 在发生死锁时,必然存在一个进程——资源的环形链。
预防死锁的方法
-
资源一次性分配:
- 一次性分配所有资源,这样就不会再有请求了(破坏请求条件)
-
只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏请保持条件)
-
可剥夺资源:
- 即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
-
资源有序分配法:
- 系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
13、如何实现浏览器内多个标签页之间的通信
- 简略回答
实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信。
- 详细回答
实现方法
-
使用websocket协议
- 因为 websocket 协议可以实现服务器推送,所以服务器就可以用来当做这个中介者。
- 标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。
-
使用 SharedWorker 的方式
- sharedWorker 会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。
- 标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。
-
使用 localStorage 的方式
- 我们可以在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
-
使用 postMessage 方法
- 如果我们能够获得对应标签页的引用,就可以使用postMessage 方法,进行通信。
14、对Service Worker的理解
- 简略回答
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。
- 详细回答
实现步骤
- 首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件
- 那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存
- 存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
- 打开页面,可以在开发者工具中的 Application 看到 Service Worker 已经启动了,在Cache中也可以发现所需的文件已被缓存
15、对浏览器的缓存机制的理解(缓存流程)
- 简略回答
缓存流程
-
浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用;
-
下一次加载资源时,由于 强制缓存 优先级较高,先比较当前时间与上一次返回 200 时的时间差,
- 如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。
- 如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;
-
如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;
-
服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,
- Etag 值一致则没有修改,命中协商缓存,返回 304;
- 如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
-
如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,
- 一致则命中协商缓存,返回 304;
- 不一致则返回新的 last-modified 和文件并返回 200;
- 详细回答
16、浏览器资源缓存的位置有哪些
- 简略回答
资源缓存的位置一共有 3 种,按优先级从高到低分别是
-
Service Worker:
- 当 Service Worker 没有命中缓存的时候,需要去调用 fetch 函数获取 数据。
- 也就是说,如果没有在 Service Worker 命中缓存,会根据缓存查找优先级去查找数据。但是不管是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示是从 Service Worker 中获取的内容。
-
Memory Cache:
- Memory Cache 就是内存缓存,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
-
Disk Cache:
- Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但什么都能存储到磁盘。
-
Push Cache:
- Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。
- 详细回答
17、协商缓存和强缓存的区别
- 简略回答
-
强缓存
- 使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。
- 设置方式:分别是 http 响应头信息中的 Expires 属性和 Cache-Control 属性。
-
协商缓存
- 如果命中强制缓存,就无需发起新的请求,直接使用缓存资源,如果没有命中强制缓存,且设置了协商缓存,这个时候协商缓存就会发挥作用了。
- 设置方式:分别是 http请求头中if-modified-since和if-none-match 响应 头信息中的 Etag 和 Last-Modified 属性。
- 详细回答
18、为什么需要浏览器缓存
- 简略回答
浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。
- 详细回答
优点
- 减少了服务器的负担,提高了网站的性能
- 加快了客户端网页的加载速度
- 减少了多余网络数据传输
19、点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别
- 简略回答
- 点击刷新按钮或者按 F5:浏览器直接对本地的缓存文件过期,但是会带上If-Modifed-Since,If-None-Match,这就意味着服务器会对文件检查新鲜度,返回结果可能是 304,也有可能是 200。
- 用户按 Ctrl+F5(强制刷新) :浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是 200。
- 地址栏回车: 浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。
- 详细回答
20、什么情况会阻塞渲染
- 简略回答
-
首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。
-
当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。
- 如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。
- script 标签加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 script 标签放在任意位置。
- 对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。
- 详细回答
21、浏览器渲染优化
- 简略回答
-
针对 JavaScript 进行优化,原因是JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。
-
因此我们可以对JavaScript的加载方式进行改变,来进行优化
-
尽量将JavaScript文件放在body的最后
-
body中间尽量不要写
-
-
针对CSS:
-
使用CSS有三种方式:使用link、@import、内联样式,其中link和@import都是导入外部样式。
-
它们之间的区别:
- 浏览器会派发一个新等线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码
- @import :GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)
- style:GUI直接渲染
- 在开发过程中,导入外部样式使用link,而不用@import。如果css少,尽可能采用内嵌样式,直接写在style标签中。
-
-
针对DOM树、CSSOM树:
- HTML文件的代码层级尽量不要太深
- 使用语义化的标签,来避免不标准语义化的特殊处理
- 减少CSS代码的层级,因为选择器是从左向右进行解析的
-
减少回流与重绘
-
操作DOM时,尽量在低层级的DOM节点进行操作
-
不要使用table布局, 一个小的改动可能会使整个table进行重新布局
-
使用CSS的表达式
-
不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
-
使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
-
避免频繁操作DOM,可以创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
-
将元素先设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
-
将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
- 浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列
- 浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
-
- 详细回答
22、浏览器的渲染过程
- 简略回答
- 首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。
- 然后对 CSS 进行解析,生成 CSSOM 规则树。
- 根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。
- 当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
- 布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件
这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
- 详细回答
23、浏览器本地存储方式及使用场景
- 简略回答
存储方式
-
Cookie
-
Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。
-
域名之间跨域共享Cookie,有两种方法
- 使用Nginx反向代理
- 在一个站点登陆之后,往其他网站写Cookie。服务端的Session存储到一个节点,Cookie存储sessionId
-
使用场景
- Cookie和session结合使用,我们将sessionId存储到Cookie中,每次发请求都会携带这个sessionId,这样服务端就知道是谁发起的请求,从而响应相应的信息。
- 可以用来统计页面的点击次数
-
特性
- Cookie一旦创建成功,名称就无法修改
- Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie
- 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb
- 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的
- Cookie在请求一个新的页面的时候都会被发送过去
-
-
LocalStorage
-
LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。
-
使用场景
- 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
- 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中
-
优势
- 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
- LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
- 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带
-
缺点
- 存在浏览器兼容问题,IE8以下版本的浏览器不支持
- 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
- LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问
-
-
SessionStorage
-
SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。
-
使用场景
- 由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。
-
SessionStorage与LocalStorage对比
- SessionStorage和LocalStorage都在本地进行数据存储;
- SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;
- LocalStorage和SessionStorage都不能被爬虫爬取;
-
-
indexDB
- 当需要在本地存储大量数据的时候,我们可以使用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库,它内部采用对象仓库的形式存储数据,它更接近 NoSQL 数据库
- 详细回答
24、Cookie有哪些字段,作用分别是什么
- 简略回答
-
Name:cookie的名称
-
Value:cookie的值,对于认证cookie,value值包括web服务器所提供的访问令牌;
-
Size: cookie的大小
-
Path:可以访问此cookie的页面路径。 比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。
-
Secure:
- 指定是否使用HTTPS安全协议发送Cookie。
- 使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。
-
Domain:
- 可以访问该cookie的域名。
- Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。
- 当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围。
-
HTTP:
- 该字段包含HTTPOnly 属性 ,该属性用来设置cookie能否通过脚本来访问,默认为空,即可以通过脚本访问。
- 在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。
- 该属性用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。
- 但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。
-
Expires/Max-size:cookie的超时时间。
- 若设置其值为一个时间,那么当到达此时间后,此cookie失效。
- 不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。
- 详细回答
cookie使用概述
-
服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。
-
一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。
- expires 指定了 cookie 失效的时间,
- domain 是域名、
- path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。
- secure 规定了 cookie 只能在确保安全的情况下传输,
- HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。
25、Cookie、LocalStorage、SessionStorage区别
- 简略回答
-
cookie:
- 最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。
- cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,
- cookie 只能被同源的页面访问共享。
-
sessionStorage:
- html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。
- sessionStorage一般能够存储 5M 或者更大的数据,
- 它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。
-
localStorage:
- html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。
- 它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效。
- 并且 localStorage 也只能被同源页面所访问共享。
- 详细回答
26、前端储存的⽅式有哪些?
- 简略回答
-
cookies: 在HTML5标准前本地储存的主要⽅式
- 优点是兼容性好,请求头⾃带cookie⽅便,
- 缺点是⼤⼩只有4k,⾃动请求头加⼊cookie浪费流量,每个domain限制20个cookie,使⽤起来麻烦,需要⾃⾏封装;
-
localStorage:HTML5加⼊的以键值对(Key-Value)为标准的⽅式
- 优点是操作⽅便,永久性储存(除⾮⼿动删除),⼤⼩为5M,兼容IE8+ ;
-
sessionStorage:与localStorage基本类似
- 区别是sessionStorage当⻚⾯关闭后会被清理
- ⽽且与cookie、localStorage不同,他不能在所有同源窗⼝中共享,是会话级别的储存⽅式;
-
IndexedDB:
- 是被正式纳⼊HTML5标准的数据库储存⽅案
- 它是NoSQL数据库,⽤键值对进⾏储存,可以进⾏快速读取操作
- ⾮常适合web场景,同时⽤JavaScript进⾏操作会⾮常便
- 详细回答
27、什么是同源策略
- 简略回答
同源策略 限制了从同一个源加载的文档或脚本 如何 与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议、端口号、域名必须一致。
- 详细回答
作用
- 跨域问题其实就是浏览器的同源策略造成的。
- 当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。
- 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。
- 当前域下 ajax 无法发送跨域请求。
同源政策的目的
- 主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,
- 对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。
28、如何解决跨越问题
- 简略回答
跨域的根源:浏览器基于安全考虑实施同源策略,要求脚本只能访问与它同源(协议、域名、端口相同)的资源。
-
CORS 跨域资源共享机制
-
当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP 请求。
-
实现要求
- CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。
- 因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。
-
浏览器将CORS分为简单请求和非简单请求:
-
简单请求
-
简单请求过程
-
对于简单请求,浏览器会直接发出CORS请求,它会在请求的头信息中增加一个Orign字段,该字段用来说明本次请求来自哪个源(协议+端口+域名)
-
服务器会根据这个值来决定是否同意这次请求。如果Orign指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头
- Access-Control-Allow-Origin: api.bob.com // 和Orign一直
- Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
- Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
- Content-Type: text/html; charset=utf-8 // 表示文档类型
-
如果Orign指定的域名不在许可范围之内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin头部信息,就知道出错了。这个错误无法通过状态码识别,因为返回的状态码可能是200。
-
简单请求不会触发CORS预检请求。若该请求满足以下两个条件,就可以看作是简单请求
- 请求方法是以下三种方法之一:HEAD,GET,POST
- HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type: 只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
- 不满足以上条件,就属于非简单请求
- 在简单请求中,在服务器内,至少需要设置字段:Access-Control-Allow-Origin
-
-
-
非简单请求
-
非简单请求过程
- 非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等。
- 非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求。
- 浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错。
- 服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。
- 只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
-
-
-
-
JSONP
- Jsonp的原理就是利用script标签没有跨域限制,通过script标签src属性,发送带有callback参数的GET请求
-
nginx代理跨域
1. Nginx 作为反向代理服务器,位于客户端和真实服务器之间。它接收客户端的请求,然后将请求转发到真实服务器,最后把真实服务器的响应返回给客户端。在这个过程中,Nginx 可以修改请求和响应的头部信息,使得客户端和真实服务器之间的交互看起来像是同源的,从而绕过浏览器的跨域限制。
- 详细回答
29、事件是什么
- 简略回答
事件是用户操作网页时发生的交互动作,事件被封装成一个 event 对象,包含了该事件发生时的所有相关信息( event 的属性)以及可以对事件进行的操作( event 的方法)。
- 详细回答
30、对事件委托的理解
- 简略回答
把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)。
- 详细回答
实现原理
事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点。
使用场景
给页面的所有的a标签添加click事件:
-
a标签可能包含一些像span、img等元素,如果点击到了这些a标签中的元素,就不会触发click事件,因为事件绑定上在a标签元素上,而触发这些内部的元素时,e.target指向的是触发click事件的元素(span、img等其他元素)。
-
这种情况下就可以使用事件委托来处理,将事件绑定在a标签的内部元素上,当点击它的时候,就会逐级向上查找,直到找到a标签为止
实现效果
使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定,比如说新增了一个子节点,并不需要单独地为它添加一个监听事件,它绑定的事件会交给父元素中的监听函数来处理。
优势
-
减少内存消耗
-
动态绑定事件
-
局限性
-
事件委托也是有局限的。比如 focus、blur 之类的事件没有事件冒泡机制,所以无法实现事件委托;
-
mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。
-
事件委托会影响页面性能,主要影响因素有:
- 元素中,绑定事件委托的次数;
- 点击的最底层元素,到绑定事件元素之间的DOM层数;
-
在必须使用事件委托的地方,可以进行如下的处理:
- 只在必须的地方,使用事件委托,比如:ajax的局部刷新区域
- 尽量的减少绑定的层级,不在body元素上,进行绑定
- 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发
-
31、对事件循环的理解
- 简略回答
事件循环(Event Loop)是 JavaScript 的一种运行机制,用于处理异步代码。
原因是因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
- 详细回答
事件循环执行流程
-
在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。
-
当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。
-
任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,
-
js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
-
当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。
执行栈
可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。
-
微任务包括:
- promise 的回调、
- node 中的 process.nextTick 、
- 对 Dom 变化监听的 MutationObserver。
-
宏任务包括:
- script 脚本的执行、
- setTimeout ,
- setInterval ,
- setImmediate 一类的定时事件,
- 还有如 I/O 操作、UI 渲染等。
32、Node 中的 事件循环 和浏览器中的事件循环有什么区别
- 简略回答
node中的事件循环和浏览器中事件循环是完全不相同的东西。
- 详细回答
node的事件循环 Event Loop
- 分为 6 个阶段,它们会按照顺序反复运行。
- 每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。
- 当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
循环流程
-
Timers(计时器阶段):
-
初次进入事件循环,会从计时器阶段开始。
-
此阶段会判断是否存在过期的计时器回调(包含 setTimeout 和 setInterval),
- 如果存在则会执行所有过期的计时器回调,执行完毕后,
- 如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Pending callbacks 阶段。
-
-
Pending callbacks:
- 执行推迟到下一个循环迭代的I / O回调(系统调用相关的回调)。
-
Idle/Prepare:仅供内部使用。
-
Poll(轮询阶段):
-
当回调队列不为空时:
- 会执行回调,若回调中触发了相应的微任务,这里的微任务执行时机和其他地方有所不同,
- 不会等到所有回调执行完毕后才执行,而是针对每一个回调执行完毕后,就执行相应微任务。
- 执行完所有的回调后,变为下面的情况。
-
当回调队列为空时(没有回调或所有回调执行完毕):
- 但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会结束轮询阶段,进入 Check 阶段。
- 否则会阻塞并等待任何正在执行的I/O操作完成,并马上执行相应的回调,直到所有回调执行完毕。
-
-
Check(查询阶段):
- 会检查是否存在 setImmediate 相关的回调,如果存在则执行所有回调,
- 执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,
- 执行完微任务后再进入 Close callbacks 阶段。
-
Close callbacks:
- 执行一些关闭回调,比如socket.on('close', ...)等。
33、垃圾回收机制
- 简略回答
垃圾回收过程:通过 GC Root 标记空间中活动对象和⾮活动对象。
⽬前 V8 使用了两个垃圾回收器:主垃圾回收器和副垃圾回收器。
- 副垃圾回收器主要负责新⽣代的垃圾回收。大多数的对象最开始都会被分配在新生代,该存储空间相对较小
- 主垃圾回收器主要 负责⽼⽣代中的垃圾回收。除了新⽣代中晋升的对象,⼀些⼤的对象会直接被分配到⽼⽣代⾥。
- 详细回答
- 在 V8 中,会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放生存时间久的对象:
新生代
-
分为两个空间:from 空间(对象区)和 to 空间(空闲区)。
-
新加⼊的对象都会存放到对象FROM区域,当对象区域快被写满时,就需要执⾏⼀次垃圾清理操作:
- 首先要对对象区域中的垃圾做标记,标记完成之后,就进入垃圾清理阶段。
- 副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来。
- 这个复制过程就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了
- 完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域,这种算法称之为 Scavenge(清除) 算法,这样就完成了垃圾对象的回收操作。
- 同时,这种角色翻转的操作还能让 新生代中的这两块区域无限重复使用下去
-
为了执⾏效率,⼀般新⽣区的空间会被设置得⽐较⼩。
-
副垃圾回收器还会采⽤对象晋升策略,也就是移动那些经过两次垃圾回收依然还存活的对象到⽼⽣代中。
-
老生代
-
⽼⽣代中的对象有两个特点:
- 对象占⽤空间⼤;
- 对象存活时间⻓
-
主垃圾回收器采⽤标记清除的算法进⾏垃圾回收。
-
标记阶段:
- 从一组根元素开始,递归遍历这组根元素,
- 在这个遍历过程中,能到达的元素称为活动对象,
- 没有到达的元素就可以判断为垃圾数据。
-
清除阶段:
- 主垃圾回收器会直接将标记为垃圾的数据清理掉。
-
对⼀块内存多次执⾏标记清除算法后,会产⽣⼤量不连续的内存碎⽚。⽽碎⽚过多会导致⼤对象⽆法分配到⾜够的连续内存,于是⼜引⼊了另外⼀种算法——标记整理。
-
这个算法的标记过程仍然与标记清除算法⾥的是⼀样的,先标记可回收对象,但后续步骤不是直接对可回收对象进⾏清理,⽽是让所有存活的对象都向⼀端移动,然后直接清理掉这⼀端之外的内存
-
-
垃圾回收优化
-
为了降低老生代的垃圾回收而造成的卡顿
-
V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,这个算法称为增量标记算法
-
使用增量标记算法可以把一个完整的垃圾回收任务拆分为很多小的任务,
- 这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样当执行代码时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。
-
-
-
全停顿
- javaScript 是单行线语言,运行在主线程上。一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。这种行为叫做全停顿。
- V8 新生代的垃圾回收中,因其空间较小,且存活对象较少,所以全停顿的影响不大。
- 老生代中,如果在执行垃圾回收的过程中,占用主线程时间过久,主线程是不能做其他事情的,需要等待执行完垃圾回收操作才能做其他事情,这将就可能会造成页面的卡顿现象。
-
自动垃圾回收算法
- 标记-清除法
- 复制算法
- 引用计数
- 增量标记算法
34、哪些操作会造成内存泄漏
- 简略回答
- 第一种情况是由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 第二种情况是设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 第三种情况是获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
- 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。
- 详细回答
PS.未完待续,文中答案有误也欢迎评论指出!
另外作者也在找工作,欢迎公司有HC的同学内推,base地:上海、北京或杭州。