目录
18. webpack的module、bundle、chunk代码块分别是什么
20. script 标签中的 async 和 defer 属性
1. 观察者模式、发布订阅区别
(1)观察者模式
被观察者Subject将某个观察者添加到自己的观察者列表
后,建立关联。(一对多)Subject被观察者触发“通知观察者”
方法,通知出去。
核心·:被观察者
需要在每次自身改变后都绑定式地触发对观察者
的通知
// 被观察者
class Subject {
constructor() {
this.observerList = [];//建立观察者列表
}
addObserver(observer) {//添加观察者方法
this.observerList.push(observer);
}
removeObserver(observer) {//删除观察值方法
const index = this.observerList.findIndex(o => o.name === observer.name);
this.observerList.splice(index, 1);
}
notifyObservers(message) {//通知观察者方法
const observers = this.observerList;
observers.forEach(observer => observer.notified(message));
}
}
// 观察者
class Observer {
constructor(name, subject) {
this.name = name;
if (subject) {
subject.addObserver(this);
}
}
notified(message) {
console.log(this.name, 'got message', message);
}
}
// 使用
const subject = new Subject();// 被观察者
const observerA = new Observer('observerA', subject);// 观察者主动申请加入被观察者的列表
const observerB = new Observer('observerB');
subject.addObserver(observerB);// 被观察者主动将观察者加入列表
subject.notifyObservers('Hello from subject');
(2)发布订阅模式
发布订阅核心基于一个中心来建立整个体系。其中发布者
和订阅者
不直接进行通信,而是发布者将要发布的消息交由中心管理,订阅者按需订阅消息。
举例:通过邮件系统订阅某个网站通知(用户:订阅者 邮件系统:订阅中心 网站:发布者 )
// 发布订阅中心
class PubSub {
constructor() {
this.messages = {};
this.listeners = {};
}
publish(type, content) {
const existContent = this.messages[type];
if (!existContent) {
this.messages[type] = [];
}
this.messages[type].push(content);
}
subscribe(type, cb) {
const existListener = this.listeners[type];
if (!existListener) {
this.listeners[type] = [];
}
this.listeners[type].push(cb);
}
notify(type) {
const messages = this.messages[type];
const subscribers = this.listeners[type] || [];
subscribers.forEach((cb) => cb(messages));
}
}
// 发布者
class Publisher {
constructor(name, context) {
this.name = name;
this.context = context;
}
publish(type, content) {
this.context.publish(type, content);
}
}
// 订阅者
class Subscriber {
constructor(name, context) {
this.name = name;
this.context = context;
}
subscribe(type, cb) {
this.context.subscribe(type, cb);
}
}
// 使用
const TYPE_A = 'music';
const pubsub = new PubSub();//发布订阅中心
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');// 发布
const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, (res) => {// 订阅
console.log('subscriberA received', res);
});
pubsub.notify(TYPE_A);// 通知
2. 强缓存与协商缓存
- 浏览器缓存:浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问时,浏览器就可以直接加载。
- 强缓存(优先级高):浏览器二次请求资源时,根据响应头中的Cache-Control判断本地缓存资源是否过期,决定是否从服务器拿资源。
- 协商缓存:浏览器二次请求资源时,请求头中携带If-None-Match,If-Modified-Since(分别对应响应头中的ETag、Last-Modified的值),服务器与最新的ETag做对比,判断资源是否修改,决定是否从服务器拿资源。
(1)如果一致返回304,使用本地资源。如果不一致返回200,向服务器请求新的资源并返回新资源标识。
(2)Last-Modified 标识即使文件内容相同,但是只要修改时间发生变化,都会再次返回该资源,而 ETag 可以判断出文件内容是否相同,相同则直接返回304,缓存策略更佳。
总结过程:
当浏览器请求一个资源时,浏览器会先判断本地有没有缓存;
没有缓存则直接发送请求,拿到最新的资源;如果有缓存,就判断是否过期;
如果没过期就直接用本地缓存的资源,如果过期了就再看有没有 Last-Modified或 ETag;
没有的话就直接请求资源,有的话就带上该标识去往服务端,服务端会根据该资源的修改情况返回200或304;
最后拿到数据,渲染页面。
3. 同源策略
同源:协议、域名、端口号相同
要求同源场景:AJAX通信、DOM操作、Cookie、localStorage、IndexDB
跨域实现:
(1)CORS实现
简单请求:浏览器请求头设置origin,服务器响应头设置Access-Control-Allow-Origin实现。
非简单请求:浏览器通过OPTIONS方法发送预检请求给服务器,获知跨域请求是否被允许。被允许后,请求头同样设置origin字段请求。
(2)JSONP实现
<script>
标签没有跨域限制,动态创建<script>
标签向服务器发送 GET 请求,服务器收到请求后将数据放在一个指定名字的回调函数中并传送回来,返回给浏览器解析执行,从而前端拿到callback函数返回的数据。
// 原生JS实现
// 浏览器
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
//服务端
handleCallback({"success": true, "user": "admin"})
// vue的axios实现
this.$http = axios;
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
(3)postMessage 实现
(4)WebSocket协议实现:通过socket.io接口实现通信
4. Nginx反向代理
Nginx 是一款轻量级的 Web 服务器,也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求
5. 事件流和事件模型
阻止事件冒泡:event.stopPropagation();
阻止冒泡以及其他注册事件:event.stopImmediatePropagation();
6. React合成事件
是React基于浏览器事件机制自身实现的事件机制。
事件代理函数(如onclick)
看似绑定到真实DOM
元素上,实际上是把所有的事件绑定到结构的最外层,使用一个统一的事件去监听。
这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象
当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升。
7.合成事件和原生事件执行顺序
原生事件:子元素 DOM 事件监听!
原生事件:父元素 DOM 事件监听!
React 事件:子元素事件监听!
React 事件:父元素事件监听!
原生事件:document DOM 事件监听!
8. 事件代理(委托)
事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件代理就是在冒泡阶段完成。
把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。
执行事件的时候再去匹配目标元素。
// 来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上:
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
// 判断是否匹配目标元素
if (target.nodeName.toLocaleLowerCase === 'li') {
console.log('the content is: ', target.innerHTML);
}
});
- 优点:减少内存消耗、动态绑定事件、
- 缺点:事件委托的次数、DOM层数可能会影响页面性能(减少绑定的层级,不在
body
元素上,进行绑定) - 适合场景:给页面所有a标签添加click事件、
ajax
的局部刷新区域
9. 宏任务、微任务
- 宏任务: script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。
- 微任务包括: promise.then()、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
10. 执行上下文、执行栈
执行上下文是Javascript
代码执行环境的抽象概念,包括创建阶段 → 执行阶段 → 回收阶段。创建阶段会确定this值、创建词法环境(全局环境和函数环境)、变量环境
执行栈是一个存储函数调用的栈结构,遵循先进后出的原则,用于存储在代码执行期间创建的所有执行上下文。
11. 垃圾回收机制
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。
- 常见内存泄漏情况:未声明创建全局变量、定时器中存在对外部变量的引用、闭包函数访问外层函数变量(外层作用域)、引用DOM元素后再删除
(1)标记清除:垃圾回收机制会标记上下文中所有变量,去除被引用的变量的标记,剩余变量为待删除的。
(2)引用计数:引用表中存储各变量引用次数,当变量引用次数为0时会被清除。
12. Web常见攻击方式
(1)XSS跨站脚本攻击
通过注入恶意脚本在浏览器上运行,盗取cookie等用户信息。
分类:
i)存储型XSS攻击:(服务器安全漏洞)
攻击者将恶意代码提交到目标网站的数据库中,用户打开网站时,服务端取出恶意代码并拼接在 HTML 中,浏览器解析执行恶意代码,用户数据被发送到攻击者网站。
场景:常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等
ii)反射型XSS攻击:(服务器安全漏洞)
攻击者构造出特殊的 URL,用户打开时服务端从 URL 中取出恶意代码,拼接在 HTML 中,浏览器解析执行恶意代码,用户数据被发送到攻击者网站。
场景:常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
iii)DOM型:(前端安全漏洞)
攻击者构造出特殊的 URL,用户打开后浏览器直接解析执行,窃取用户数据。
预防:
XSS
攻击的两大要素:
攻击者提交而恶意代码
在用户输入的过程中,过滤掉用户输入的恶劣代码,然后提交给后端,
浏览器执行恶意代码。
避免使用.innerHTML
、.outerHTML
、document.write()等能解析字符串中代码的属性,尽量使用.textContent
、.setAttribute()
等。
DOM 中的内联事件监听器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作为代码运行。要避免!
(2)CSRF跨站请求伪造
凭借已有的用户凭证,绕过后台用户验证,诱导受害者进入第三方网站向被攻击网站发送跨站请求。
受害者登录a.com,并保留了登录凭证(Cookie)。引导受害者访问b.com拿到Cookie,由b.com携带凭证在a.com进行操作。
方式:图片URL、超链接、CORS、Form提交
预防:阻止外域访问(同源检测)、携带token访问
(3)SQL注入攻击
将恶意的 Sql
查询或添加语句插入到应用的输入参数中,再在后台 Sql
服务器上解析执行,进行攻击。
13. HTTPS更安全
HTTPS = HTTP + SSL
- 机密性:混合算法(对称加密+非对称加密)
- 完整性:摘要算法(Hash函数生成摘要,对比前后摘要判断数据是否被修改)
- 身份认证:数字签名(服务器公钥被认证机构加密,用户解密后可通过公钥加密数据,服务器解密)
- 不可否定:数字签名
14. 浏览器和服务器的安全通信
客户端建立SSL连接,服务端返回证书和公钥,客户端产生会话密钥,用公钥加密会话密钥,发给服务端,服务端用自己的私钥解密出会话密钥。进行通信。
实际数据传输使用对称加密
密钥协商时使用非对称加密
15. webpack理解
webpack
是一个用于现代JavaScript
应用程序的静态模块打包工具,通过分析模块间的依赖关系,在其内部构建出一个依赖图,最终编绎输出模块为 HTML、JS、CSS 以及各种静态文件(图片、字体等),让我们的开发过程更加高效。
- 主要作用
(1)模块打包
(2)编译兼容:通过webpack
的Loader
机制进行编译转换
(3)能力扩展:通过webpack
的Plugin
机制,实现诸如按需加载,代码压缩等功能。
- 构建流程
(1)初始化
- 从配置文件和Shell语句中读取与合并参数,激活webpack加载项和插件Plugin。
(2)编译:从 entry 出发,针对每个 Module 串行调用对应的 loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
- 执行对象的run方法开始编译
- 从 entry 出发,找出所有入口文件,调用所有模块对应的loader对模块进行翻译(递归)
(3)输出:将编译后的 Module 组合成 Chunk,再 转换成文件,输出到文件系统中。
- 得出模块之间依赖关系,组装成Chunk,转换为文件加入到输出列表
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development', // 模式
entry: './src/index.js', // 打包入口地址
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist') // 输出文件目录
},
module: {
rules: [
{
test: /\.css$/, //匹配所有的 css 文件
use: 'css-loader' // use: 对应的 Loader 名称
}
]
},
plugins:[ // 配置插件
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
- 常见的loader
image-loader
:加载并且压缩图片文件。
less-loader
: 加载并编译 LESS 文件。
sass-loader
:加载并编译 SASS/SCSS 文件。
css-loader
:加载 CSS,支持模块化、压缩、文件导入等特性(要配合style-loader
)
style-loader
:将 CSS 样式挂载到style 标签上。需要注意 loader
执行顺序,style-loader
要放在第一位,loader
都是从后往前执行。
babel-loader
:把 ES6 转换成 ES5
eslint-loader
:通过 ESLint 检查 JavaScript 代码。
vue-loader
:加载并编译 Vue 组件。
file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
url-loader
:与 file-loader
类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader
处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
- 常见plugin
HtmlWebpackPlugin:打包结束后,⾃动生成⼀个 html
⽂文件,并把打包生成的js
模块引⼊到该 html
中。
clean-webpack-plugin:删除(清理)构建目录。
mini-css-extract-plugin:提取css到单独文件中。
copy-webpack-plugin:复制文件或目录到执行区域,
16. Loader和Plugin的区别
Loader
本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
Plugin
就是插件,基于事件流框架 Tapable
,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
Loader
在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
Plugin
在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入
16. 编写Loader和Plugin思路
Loader:创建Loader函数,确定输入输出,函数内部实现一些具体的转换和处理逻辑。
Plugin:定义Plugin类,内部实现apply方法可以访问compiler对象,注册相应的钩子函数,在内部进行逻辑处理。
16. webpack模块热更新HMR
webpack-dev-server
创建express和socket两个服务器,分别提供静态资源的服务和webSocket长连接服务。
socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)。
socket server 通过长连接将这两个文件发给客户端(浏览器)。
客户端通过HMR runtime机制,加载文件进行模块更新。
webpack会在开发服务器上启动一个HTTP服务器,用于提供静态资源和构建后的文件。启动时该服务器与浏览器之间建立一个WebSocket连接,用于实时通信。本地修改某个文件时候webpack就会重新编译构建该模块(携带hash),浏览器会依据构建时的路由hash进行新旧资源的对比,向服务器发请求进行增量更新。
16. webpack抽象语法树
(1)模块解析:Webpack使用抽象语法树来解析模块之间的依赖关系。当Webpack遇到import
、require
等模块引入语句时,它会使用抽象语法树解析相应的模块路径,并构建模块之间的依赖关系图,用于后续的模块打包和构建。
(2)代码转换:Webpack中的加载器(Loaders)和插件(Plugins)可以使用抽象语法树对代码进行转换和处理。Loader可以将源代码解析为抽象语法树,在此基础上进行各种转换操作,例如转译ES6+语法、处理样式文件、压缩代码等。
17. webpack proxy工作原理
利用http-proxy-middleware
这个http
代理中间件,实现请求转发给其他服务器。
注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制。
在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题。
通过 webpack-dev-server,
设置webpack proxy
实现代理请求后,相当于浏览器与服务端中添加一个代理者。
当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地。
18. webpack优化前端性能
JS代码压缩:配置TerserPlugin插件
CSS代码压缩:css-minimizer-webpack-plugin插件
Html文件代码压缩:HtmlwebpackPlugin插件
文件大小压缩:compression-webpack-plugin
图片压缩:image-webpack-loader装载器
Tree Shaking消除死代码:
代码分离:配置splitChunksPlugin,实现代码分离到不同的bundle
中
内联Chunk:配置InlineChunkHtmlPlugin
插件,将一些chunk
的模块内联到html
// 使用示例
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true // 电脑cpu核数-1
})
]
}
}
18. 如何提高webpack打包速度
(1)优化依赖模块解析时间(多进程打包)
HappyPack 可利用多进程对文件进行打包,让 Webpack 同一时间处理多个任务,将任务分解给多个子进程去并发的执行,子进程处理完后,再把结果发送给主进程。
由于webpack以往是单线程模式,只能逐个去打包文件。
使用thread-loader会创建一个worker池,提供并行处理环境,将指定的多个loader线程任务并行执行,加快构建过程。
(2)利用缓存缩短重新运行的构造时间
配置cache-loader,HardSourceWebpackPlugin。会将构建结果缓存到磁盘里,提升二次构建速度。
(3)缩小文件搜索范围
通过指定模块搜索路径、扩展名等方式缩小文件搜索范围。
(4)外部扩展
将不怎么需要更新的第三方库脱离webpack打包,不被打入bundle中,从而减少打包时间,比如jQuery用script标签引入
(5)DLL动态链接库
采用webpack的 DllPlugin 和 DllReferencePlugin 引入dll,让一些基本不会改动的代码先打包成静态资源,之后的构建中若资源不变,Webpack会直接引用已经打包好的DLL文件,避免反复编译浪费时间
18. webpack的module、bundle、chunk代码块分别是什么
module
是Webpack处理的单个文件,代表了应用程序的组成部分。bundle
是由Webpack生成的最终输出文件,它包含了所有模块的代码和资源。chunk
是逻辑上的代码块,表示一组相互依赖的模块。它可以根据需要进行拆分和加载。
19. 模块化工具
(1)Rollup:代码效率更简洁、效率更高,体积更小;默认支持 Tree-shaking。rollup适用于基础库的打包,如vue、d3等: Rollup 就是将各个模块打包进一个文件中,并且通过 Tree-shaking 来删除无用的代码,可以最大程度上降低代码体积,但是rollup没有webpack如此多的的如代码分割、按需加载等高级功能,其更聚焦于库的打包,因此更适合库的开发.
(2)Parcel: parcel适用于简单的实验性项目: 他可以满足低门槛的快速看到效果,但是生态差、报错信息不够全面都是他的硬伤,除了一些玩具项目或者实验项目不建议使用。
(3)SnowPack:
(4)Vite:无需打包快速启动;即时模块热更新;按需编译
注意: Vite修改一个模块的时候,仅需让浏览器重新请求该模块即可。
无须像webpack
那样需要把该模块的相关依赖模块全部编译一次,效率更高。
(5)Webpack:一切皆模块,按需加载。webpack适用于大型复杂的前端站点构建: webpack有强大的loader和插件生态,打包后的文件实际上就是一个立即执行函数,这个立即执行函数接收一个参数,这个参数是模块对象,键为各个模块的路径,值为模块内容。立即执行函数内部则处理模块之间的引用,执行模块等,这种情况更适合文件依赖复杂的应用开发.
20. script 标签中的 async 和 defer 属性
async和defer的异同主要是因为他们的加载和执行时机。区别如下:
- 普通的script加载完会立即执行,会阻塞script标签下面的资源加载和dom的解析
- 使用async后,script加载完后会立即执行。(网络)资源的加载过程是异步的,不会阻塞后续资源的加载dom和解析。
- 使用defer后,script异步加载,html解析之后执行、DomContentLoaded之前执行。
async和defer的script都是异步加载,但async加载后立即执行,执行时阻塞后续资源加载dome解析;而defer的script加载完会等待html解析后才执行,不阻塞。
同时多个 async 之间的执行顺序也不确定,而defer是依据出现次序顺序执行。
21. 浏览器存储相关
- cookie
作用:由于HTTP无状态,可携带cookie访问服务器实现认证。
内容:(1)作用域:通过Domain和path指定cookie所属域名、路径。浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie。(ps:cookie在浏览器存储)
(2)过期时间:Expires截止日期,Max-Age失效时间。(默认关闭浏览器cookie被清除)
(3)属性:在 JS 脚本里可以用 document.cookie 来读写 Cookie 数据,带来安全隐患,有可能会导致“跨站脚本”(XSS)攻击窃取数据。
HttpOnly | 此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问 |
SameSite | 设置成“SameSite=Strict”可以严格限定 Cookie 不能随着跳转链接跨站发送,防止“跨站请求伪造”(CSRF)攻击 |
Secure | Cookie 仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送 |
场景:广告跟踪,自动登录
cookie-session服务端存储数据技术
session配合cookie,存储服务端会话数据,例如用户的个人信息。
在Redis内存型数据库中以 key-value 的形式存,正合 sessionId-sessionData 的场景。
- sessionStorage
窗口/标签页关闭,浏览器清楚数据
- localStorage
本地存储,受同源策略限制
- IndexDB
用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对数据的高性能搜索。
22. 闭包
闭包是指有权访问另一个函数作用域中变量的函数。
(1)形成原因:内部的函数存在外部作用域的引用就会导致闭包。
(2)闭包中的变量存储的位置是堆内存。
假如闭包中的变量存储在栈内存中,那么栈的回收 会把处于栈顶的变量自动回收。所以闭包中的变量如果处于栈中那么变量被销毁后,闭包中的变量就没有了。所以闭包引用的变量是处于堆内存中的。
(3)闭包作用:
保护函数的私有变量不受外部的干扰。形成不销毁的栈内存。
保存,把一些函数内的值保存下来。闭包可以实现方法和属性的私有化。(即使函数执行完毕,闭包仍然可以访问和操作函数内部的变量。这对于实现状态保持、缓存计算结果、延迟执行等场景非常有用。)
(4)使用场景:i)返回一个可以返回一个函数,拿到它上层的变量引用。
var n = 10
function fn(){
var n =20
function f() {
n++;
console.log(n)
}
return f
}
var x = fn()
x() // 21
// 这里的 return f, f()就是一个闭包,存在上级作用域的引用。
ii)可以通过自执行函数,拿到外层函数的值,实现循环赋值
for(var i = 0; i<10; i++){
(function(j){
setTimeout(function(){
console.log(j)
}, 1000)
})(i)
}
/* 因为存在闭包的原因上面能依次输出1~10,闭包形成了10个互不干扰的私有作用域。将外层的自执行函数去掉后就不存在外部作用域的引用了,输出的结果就是连续的 10。为什么会连续输出10,因为 JS 是单线程的遇到异步的SetTImeout代码不会先执行(会入栈),等到同步的代码执行完 i++ 到 10时,异步代码才开始执行此时的 i=10 输出的都是 10。 */
iii)回调函数、函数柯里化都会访问外层变量,都是闭包。
iv)节流防抖
23. 防抖节流
(1)防抖:事件频繁触发,就延迟一定时间后再执行,如果在延迟时间内又触发,那么会重新计时。当事件停止触发后执行一次。(参考:王者多次点击回城操作,会重新倒计时回城时间)
理解:频繁点击,只在最后一次+隔一段时间生效
应用场景:i)输入框搜索:用户输入时...
ii) 按钮提交时防重复点击:避免多次点击重复提交,发送多次请求,只让最后一次有效
iii)窗口大小调整:当窗口大小调整时,、避免频繁操作导致页面抖动。(不确定!个人感觉是为了避免缩放过程中太多操作,如果每次都是一点一点放大,页面会一卡一卡的)
function debounce(fn, wait) {
let timer = null;
return function () {
let context = this, args = arguments;
if (timer) {
clearTimeout(timer);
timer = null;
}
//多次触发,则重新计时
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
(2)节流
事件持续触发时,限制一定时间间隔只执行一次回调函数。比如,设置每1000毫秒最多执行一次回调函数,不论事件触发的频率有多高。(参考:王者里面的普通攻击,在限制一定时间点击多次,也只能攻击一次。)
理解:频繁点击,并不是点击次数越多加载越多,因为点击次数会被控制
应用场景:i)加载长页面时的滚动操作:当用户滚动页面时,节流限制加载事件的触发频率,避免过多的加载请求。(短时间内不停的刷刷刷,并不会请求多次)
ii)频繁点击按钮:当用户频繁点击按钮时,节流可以用于限制按钮点击的频率,避免过于频繁的操作。
function throttle(fn, delay) {
let currentTime = Date.now();
return function () {
let context = this, args = arguments, nowTime = Date.now();
// 超过指定时间才执行
if (nowTime - currentTime >= delay) {
nowTime = currentTime;
return fn.apply(context, args);
}
}
}
24. 请求头响应头
25.Babel原理
(1)解析:Babel 首先将源代码解析为抽象语法树(AST)。即词法分析与语法分析的过程。AST 将代码分解为语法树节点,每个节点代表代码的一个部分,例如函数、变量、语句等。
(2)转换:遍历抽象语法树AST,依据转换规则进行语法树的修改更新。
(3)生成:将更新的抽象语法树转为目标代码。
26. Tree shaking原理
- ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
- 静态分析程序流,删除未使用的模块和变量代码
27. 前端工程化
将前端开发中的设计、开发、测试和部署等环节进行标准化和自动化,以提高开发效率和代码质量,并降低维护成本。
具体而言,前端工程化包括以下方面:
-
模块化:将复杂的代码拆分成小的可重用的模块,并使得不同模块之间的依赖关系更清晰。
-
自动化构建:通过使用构建工具(如 Gulp、Webpack、Rollup 等),可以自动化地完成代码编译、压缩、打包、转换、优化等任务,从而提高开发效率。
-
自动化测试:通过使用自动化测试框架和工具(如 Jest、Mocha、Chai、Selenium 等),可以自动化地完成单元测试、集成测试、UI 测试等任务,从而提高代码质量并减少故障。
-
自动化部署:通过使用自动化部署工具(如 Jenkins、Travis CI、GitLab CI/CD 等),可以自动化地完成代码上传、服务器部署、数据库更新等任务,从而减少手动操作产生的错误和漏洞。
-
规范化管理:通过使用代码规范(如 ESLint、Stylelint、Prettier 等)和版本控制系统(如 Git),可以规范开发流程和代码风格,提高代码可读性和可维护性。
28. 浏览器有哪些进程
(1)浏览器进程:负责控制浏览器除标签页外的界面,包括地址栏、书签、前进后退按钮等,以及负责与其他进程的协调工作,同时提供存储功能
(2)GPU进程:负责整个浏览器界面的渲染,实现3D CSS效果,UI界面。
(3)网络进程:负责发起和接受网络请求
(4)插件进程:负责插件的运行,通过隔离保证插件崩溃不会对页面造成影响。
(5)渲染进程:负责控制显示tab标签页内的所有内容,核心任务是将HTML、CSS、JS转为用户可以与之交互的网页,排版引擎Blink和JS引擎V8都是运行在该进程中,默认情况下Chrome会为每个Tab标签页创建一个渲染进程。
- 渲染进程中的线程
(1)GUI渲染线程
:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行
(2)JS引擎线程
:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS。它GUI渲染进程不能同时执行,只能一个一个来,如果JS执行过长就会导致阻塞掉帧
(3)计时器线程
:指setInterval和setTimeout,因为JS引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来负责计时器工作
(4)异步http请求线程
: XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入事件队列,等待JS引擎空闲执行
(5)事件触发线程
:主要用来控制事件循环,比如JS执行遇到计时器,AJAX异步请求等,就会将对应任务添加到事件触发线程中,在对应事件符合触发条件触发时,就把事件添加到待处理队列的队尾,等JS引擎处理。
29.进程间通信方式
管道通信、消息队列通信、共享内存通信、信号量通信、socket通信(不同主机之间)
30.多标签之间如何通信(中介者)
localStorage(同源)
:在一个标签页监听localStorage的变化,然后当另一个标签页修改的时候,可以通过监听获取新数据WebSocket
:因为websocket可以实现实时服务器推送,所以服务器就可以来当这个中介者。标签页通过向服务器发送数据,然后服务器再向其他标签推送转发ShareWorker
:会在页面的生命周期内创建一个唯一的线程,并开启多个页面也只会使用同一个线程,标签页共享一个线程postMessage
:
// 发送方
window.parent().pastMessage('发送的数据','http://接收的址')
// 接收方
window.addEventListener('message',(e)=>{ let data = e.data })
31.异步
异步:代码执行过程中遇到一些无法立即处理的任务。
(1)setTimeout:计时完成后需要执行的任务。
(2)XHR.fetch:网络通信后
(3)addEventListener:用户通信后
渲染主进程从消息队列中依次取出任务执行。
32.websocket协议
一文吃透 WebSocket 原理 刚面试完,趁热赶紧整理 - 掘金
WebSocket 是一种在单个TCP连接上进行全双工通信的协议,是应用层协议,使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
因为 HTTP 协议有一个缺陷:通信只能由客户端发起,不具备服务器推送能力。
- 特点:i)全双工通信:数据在客户端-服务端两个方向同时通信传输。
ii)二进制帧结构:WebSocket
更侧重于“实时通信”,而HTTP/2
更侧重于提高传输效率。
iii)协议名:ws
和wss
分别代表明文和密文,默认端口使用80或443。
iv)握手建立连接:WebSocket复用了HTTP的握手通道。具体指的是,客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。// 客户端发送数据 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket // 表示要升级到websocket协议 Connection: Upgrade // 表示要升级协议 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // base64编码的密文,用于简单的认证秘钥,与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。 Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 // 表示websocket的版本 // 服务端返回的数据格式 HTTP/1.1 101 Switching Protocols // 状态代码101表示协议切换 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat // 验证客户端请求报文,同样也是为了防止误连接。具体做法是把请求头里“Sec-WebSocket-Key”的值,拼接上一个专用的 UUID,再计算摘要并转成base64字符串