一、js基本数据类型以及区别
- 栈 :基本数据类型 : String、Number、Undefined、Null、Boolean
- 堆 :引用型数据类型 : Object(Array、Function)
区别:
- 存储位置
- 原始数据类型直接存储在 “栈” 中的简单数据段,占据空间小、大小固定,因为它频繁使用 所以放入栈中存储;
- 引用型数据类型存储在 “堆” 中,占据空间大,大小不固定。
- 在数据结构中
- 栈数据存取得方式是 “先进后出”
- 堆是一个优先队列,按照优先级排序的 ,优先级可按照大小规定
- 在操作系统中
- 栈内存由编译器自动分配释放,存放函数的参数、局部变量的值等、操作方式类似于数据结构中的栈
- 堆内存(Object(函数,数组))一般由开发着分配释放、要是开发者不释放,程序结束时可能由垃圾回收机制回收
二、数据类型的判断方式
- typeOf: 数组、对象、null都会被判断为object,其他判断都正确。(null表示一个空对象指针所以 typeof(null) == object)

- instanceof:可以正确判断(引用型数据类型(object))对象的类型,运行机制是判断在其原型链中能否找到该类型的原型

- constructor 两个作用
- 一是判断数据的类型
- 二是对象实例通过
constrcutor对象访问它的构造函数。但是如果创建一个对象改变他的原型constrcutor就不能判断数据类型了
- Object.prototype.toString.call() 所有的都可以判断

三、数组的判断方法
- 通过Object.prototype.toString.call()做判断
- 通过原型链做判断
- 通过ES6的Array.isArray()做判断
- 通过instanceof做判断
- 通过Array.prototype.isPrototypeOf
四、this
是执行上下文中的一个属性,它指向最后一次调用这个方法的对象;
-
函数调用 : 直接作为函数调用时 this指向全局

-
方法调用:如果一个函数作为一个对象的方法来调用时,this 指向这个对象

-
构造器调用:如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
-
apply、call、bind调用
- call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call()方法时,传递给函数的参数必须逐个列举出来

- apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。

- bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变

五、箭头函数与普通函数有什么区别
- 箭头函数没有自己的 “this”,他的this是继承自己作用域的上一层,并且不会在次发生改变(call、apply、bind也不能更改他)
- 箭头函数不能作为构造函数使用 因为它没有自己的this
- 它没有自己的 “arguments” ,在它里面访问的 “arguments”是他外层函数的
- 箭头函数没有 protoType
六、let、var、const 的区别
- var 存在变量提升,let、const没有;
- var可以重复声明 后者覆盖前者
- var不存在块级作用域,内层变量可能覆盖外层、计数的时循环变量泄漏为全局变量
- var 和 let 声明变量可以不用设置初始值。 const必须设置
- let 变量可以改变指针(this)指向,const变量不允许改变指针指向
七、new的时候干了什么 (执行过程)
- 创建一个空对象
- 设置对象的原型为函数的 prototype 对象
- 让函数的this指向这个对象,执行构造函数的代码
- 判断函数的返回值类型(构造函数通常不返回任何值,返回(undefined)),值:返回创建的对象; 引用型(object):返回引用类型对象
八、数组常用的方法:
- 改变原数组:shift、pop、unshift、push、reverse、sort、splice
- 不改变原数组:map、every、filter、some、join、slice、find、reduce、flat、forEach
九、for...in 和 for...of的区别
- for...of遍历获取的是对象的健值 , for...in获取的是对象的健名
- for...of不会遍历原型链 , for...in会遍历对象的整个原型链
- 数组遍历:for...of只返回数组下标对应的属性值 , for...in会返回数组中所有可枚举属性(包括原型链上的)
- for...in适合遍历对象 for...of适合遍历数组
十、forEach和Map的区别
- forEach没有返回值、map有返回值(返回一个新的数组,不会改变原数组)
- forEach、map都不可以通过 break、return等方式提前终止循环
- forEach可以在回调函数中改变原数组,map不可以
十一、原型和原型链、实例
1. 原型 :
- 每一个构造函数内部都有一个 “prototype”属性,他的属性值是一个对象,这个对象中就包含了所有该构造函数的所有实例共享的属性和方法,当你使用构造函数新建一个对象之后,这个对象内部就会包含一个指针,这个指针指向构造函数的prototype属性对应的值,这个值就是对象的原型 可以通过 Object.getPrototypeOf()来获取对象的原型
2. 原型链:
- 当你访问一个对象的属性的时候如果这个对象里面不存在 他就会去他的原型对象里面去找,然后这个原型对象又会有自己的原型,找不到他就会一直找下去,直到找到尽头(原型链的尽头是 null)这就是原型链
- prototype 、proto 、 和 constructor之间的关系
- 构造函数有一个
prototype属性,它指向原型对象。 - 原型对象包含
constructor指向构造函数,并可存放公共属性和方法。 - 实例化对象时,该对象有一个
_proto_属性,指向其构造函数的原型,从而可访问其中的属性和方法。
- 实例
- 通过 “构造函数” 和 new 创建出来的一个对象,实例可以通过 _proto_指向原型,通过 “constryctor” 指向构造函数
十二、js事件流(事件机制)
主要分为三个阶段:
- 捕获阶段 事件会从最外层的元素开始沿着DOM树向下传递,直到到达目标元素(就是你添加点击事件啥的那个上)
- 目标阶段 当它到达目标元素的时候会触发目标元素上的这个事件
- 冒泡阶段 从目标元素开始沿着DOM树向上冒泡,触发所有祖先元素上的事件处理器
实际开发中 大多事件默认在冒泡阶段触发(如:点击、划过等),但是也有在捕获阶段触发的 如(监听:addEventListener)

十三、闭包
就是有权访问另一个函数作用域中变量的函数
- 闭包的作用:
- 数据封装:实现变量的封装 保护函数的私有变量不受外部的干扰
- 保留状态 : 可以访问父函数里面的变量把值保存下来。即使父函数执行完毕后闭包仍然可以保存对这些变量的引用
- 延长生命周期 :延长生命周期 让函数在别的地方被调用,而不是局限于定义时的上下文
- 闭包的优点: 增加代码复用、实现私有变量和方法
- 闭包的缺点
- 内存占用: 如果没有被及时释放且频繁创建可能导致内存占用过大影响性能
- 使用场景
- 回调函数
- 封装
- 函数式编程
- 定时器和事件处理
十四、对 async 和 await的理解
其实就是 Generator(生成器) 的语法糖,它能实现的效果都可以使用promise的then链来实现,它是为了优化then链而开发出来的
- async返回的是一个 Promise对象,所以在处理它的返回值的时候应该用: 成功用then(),失败用catch();
- 如果没有返回值 就会返回Promise.resolve(undefined)
- async/await对比promise的优势:
- 避免过度使用promise的then链,使代码可读性增强;
- 对于错误捕获 async/await可用成熟的try...catch;promise使用catch可能导致错误处理逻辑与主逻辑分离增加理解难度
- 断点调试的时候比较好,async/await调试工具通常更擅长处理同步代码

十五、对promise的理解
promise对象是异步编程的一种,Promise是一个构造函数,接收一个函数做为参数返回一个Promise实例,有三种状态分别是 pending、resolved、rejected、并且这个状态一旦 改变就定型了不会在变了
- promise有三种状态 :
- pending(等待中)、resolved(成功)、rejected(失败)
- promise的特点:
- 对象的状态不受外界条件的影响,promise对象代表一个异步操作,只有三种状态,只有异步操作的结果可以决定当前是哪种状态,其他任何操作都改变不了这个状态。
- 一旦状态改变后就不能再变了,只有两种可能 pending——>resolved ; pending——>reject
- promise的缺点
- 无法中途取消,一旦创建就会立即执行;
- 如果不设置回调函数,Promise抛出的错误不会反应到外部;
- 当处于pending状态的时候无法得知目前进展到了哪一个阶段
- Promise怎么中断
- 使用外部标志:

- 常用方法:
then()、catch()、all()、race()、finally()
- then() :可以接收两个回调函数作为参数(第二个可以省略),第一个是成功的时候调用(resolved),第二个是失败的时候调用(rejected);因为他会返回一个新的promise实例所以then()还可以链式调用 ( promise.then().then()这么写很繁琐 es6 的 async/await可以解决)
- catch() :相当于then()的第二个参数,指向reject的回调函数;如果执行resolve回调函数的时候出现错误不会停止而是会进入 catch中
- all():一般并发请求数据的时候用,接收一个数组 如果全是resolved(成功)他就会返回成功,如果有一个失败了 他就会返回rejected(失败)
- race():返回最先执行完的那个promise对象的值,最先执行完的是resolved(成功)就是resolved 反之 则是rejected(失败);一般用于 你要做一个东西超出一定时间就不做了
- finally():不管返回的结果是什么都会执行操作

- Promise解决了什么问题
- 回调地狱的问题
- 回调地狱:一个异步操作依赖于另一个异步操作的结果时,我们就会在第一个操作的回调函数中启动第二个操作,以此类推。如果这样的依赖关系有很多层,就会形成所谓的“回调地狱”。
十六、什么是DOM和BOM他们的区别是什么
-
DOM:文档对象模型 :DOM会把文档(HTML、XML)的东西看做是一个对象,就可以通过js来“找到”他们做一些操作修改;
-
BOM:浏览器对象模型:BOM主要关注浏览器窗口和它里面的对象,他提供了很多方法和接口让我们可以通过js来控制浏览器的行为
区别:
- 关注点不同:DOM主要关注文档内容、结构、样式而BOM主要关注浏览器窗口和窗口中的对象
- 功能不同:
- DOM提供了操作网页内容和接口的方法 如:element.innerHTML
- BOM提供了与浏览器交互的接口和方法如:window.close()、window.Open()
- 使用场景不同:需要操作网页元素修改样式、改变内容时用DOM ;与浏览器进行交互的时候用BOM,如打开新窗口、获取浏览器窗口大小等
十七、执行上下文、执行上下栈
- 执行上下文定义了变量和函数在当前执行环境中的可见性和行为 有三个重要属性
- 变量对象就是执行上下文中存储数据的地方,他里面存储了执行上下文中定义的所有变量、函数声明和函数参数,需要用哪个的时候就会到里面去找
- 作用域链:他会先在当前执行上下文的变量对象中查找,如果没找到会沿着作用域继续向上查找,直到找到为止
- this值:用于引用当前执行上下文的对象,不同用函数调用会导致this的指向也不同
- 执行上下栈就是管理执行上下文用的它里面有所有的正在执行的或者等待执行的上下文,代码开始执行的时候 会先创建一个全局的执行上下文放在栈底,然后每次调用一个函数都会为这个函数创建一个新的执行上下文放在栈顶部,执行完毕后就就会从栈顶移走接着执行下一个; 当一个函数内部调用了另外一个函数的时候会先执行内部的函数
十八、null和undefined的区别
- null
- 表示对象没设置
- 是字面量属性不是全局对象的属性
- 数值计算中null会转为0
- undefined
- 表示缺失值(应该有个值但是还未定义)
- 是全局对象的一个属性
- 数字计算中会转为NAN
十九、垃圾回收机制
概念: js运行代码的时候要分配空间来存储变量和值,当这些变量和值不再参与运行的时候,就得系统收回被他们占用的空间,就是垃圾回收 回收方式:
- 标记清除法: 分为标记和清除两个步骤
- 标记 : 垃圾回收器会从根对象(指的是直接被引用的对象)开始,递归访问这些对象的引用,把被引用的标记为 “活动” 状态 就是还是在用
- 清除:垃圾回收器会遍历整个堆内存,找出没有被标记的(也就是没有使用的)然后释放
- 引用计数法: 它会给每个对象配一个 “计数器” 用来记录这个对象被引用的次数
- 计数增加:当有新的引用指向某个对象的时候 计数就会加一
- 减少与回收:当某个引用被删除的时候 就会减一,如果减到 0 就表示没有引用这个对象,垃圾回收器就会释放这个对象占用的内存
- 缺点 : 如果有两个对象相互引用对方,即使其他地方不再引用他们的计数也永远不会变为 0
- 如何减少垃圾回收
- 减少全局变量的使用
- 数据解除引用:当你不再需要某个对象时确保将他的引用设为null
- 对象优化:尽量重用对象而不是不断创建新的
- 避免过多使用闭包
二十、内存泄漏
- 什么情况会造成内存泄漏
- 意外的全局变量
- 计数器或者回调函数没有及时解除
- 脱离DOM使用:获取一个DOM引用但是后面这个元素被删了,由于一直保留了对这个元素的引用所以他也不会被回收
- 闭包
二十一、js继承的方式
- 原型链继承 :首先每个对象都会有一个原型(prototype),当我们试图访问一个对象上不存在的属性的时候他就会沿着原型链向上去查找,直到找到或者到达原型链的顶端((Object.prototype)通常是null)
- ES6的类继承 :使用class和 extends 关键字
二十二、深浅拷贝
- 深拷贝 :复制 并且 创建 一个一模一样的对象,独立开辟一个空间,不共享内存,修改新对象,旧对象保持不变
- 方法:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
- JSON.parse() 和 JSON.stringify():不能复制函数和循环引用的对象,也不能复制Date对象和RegExp对象等特殊对象。
- 使用递归
- 使用 lodash 的 _.cloneDeep() 方法
- 浅拷贝:新旧对象共享一块内存
- Object.assign()
- 展开运算符(...)
- Array.from()
- for...in循环或者Object.keys
二十三、作用域 作用域链
- 作用域:就是一个有效区域 在这个区域内定义的变量只能在这个区域内看见和使用 别的地方看不到用不了
- 全局作用域:程序里面所有的地方都能看到、能用到的地方
- 局部作用域:只在某个特定区域内有效比如函数内部别的地方用不到、看不到
- 作用域链 在当前 作用域中去查找 如找不到就去父级去找,等 依次往上找 直到访问到 window 则停止 这个查找过程就是作用域链
二十四、防抖,节流
- 防抖 :是当事件被触发后,延迟n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时
- 使用场景 :
- 调整浏览器窗口大小时,resize次数过多;
- 防止用户在短时间内多次点击按钮,导致重复提交

- 节流:在指定的时间内,只响应第一次;
- 实现思想:接受一个函数
func和一个等待时间wait作为参数。它返回一个新的函数,这个新函数在被调用时会清除之前的定时器(如果有的话),并设置一个新的定时器。当定时器到期时,才会执行传入的func函数。 - 使用场景
- 监听滚动事件

二十五、事件循环(EventLoop)
事件循环就是单线程的js在处理异步事件时的一种循环过程,当有异步任务出现的时候js会把他们分为 “微任务” 和 “宏任务”,当所有的 “同步任务” 全都执行完毕后,异步会优先执行所有已经存在的 “微任务”,之后在去执行 “宏任务”,只执行一个;之后再去找有没有新的 “微任务”, 有的话全部执行,没有的话再回到宏任务队列接着执行宏任务;
- 同步任务:被扫描到就会马上执行(优于所有异步)
- 异步任务:等同步执行完再去执行;
- 微任务,宏任务
- 微任务:Promise、async/await、process.nextTick(会在所有同步代码和I/O操作完成后,但在任何定时器或Promise之前执行)、MutationObserver
- 宏任务:script、setTimeout、setInterval、I/O操作、DOM事件(点击,滚动等)
二十六、单线程、多线程、进程
- 单线程: JavaScript中,一次只能执行一个任务,其他任务必须等待。
- js为什么是单线程:JavaScript主要用于与用户互动、操作DOM,如果不是单线程,那么可能发生同时对同一元素执行两个操作的情况
- 单线程与多线程的区别:
- 单线程一次只能处理一个事件
- 多线程可以同时执行多个任务
- 线程与进程的区别:线程是CPU调度和执行的最小单位。一个进程可以包含多个线程。一个浏览器标签页是一个进程,这个标签页里面运行的js是就是线程
二十七、GET 、POST的区别
- get
- 一般用于获取数据;
- 安全性低
- 如果需要传递参数会默认将参数拼接到URL后面传给服务端
- 请求传递参数的大小有限制,是浏览器地址栏的大小
- 一般会走缓存,为了防止缓存,给url后拼接的参数每次都不同
- post
- 一般用于发短信,发数据
- 传递参数,需要把参数放入请求体中发给服务端
- 请求参数放进请求体中对大小没有太大限制
- 安全性较高,请求不会走缓存
二十八、强缓存、协商缓存
- 强缓存:通过
Expires或Cache-Control字段控制,(Cache-Control:max-age=3600表示资源在本地缓存中有效时间为3600秒)资源在本地缓存的有效期内,浏览器直接使用缓存中的资源,不向服务器发起请求。 - 协商缓存:当强缓存失效后,浏览器通过
Last-Modified或ETag字段与服务器进行协商,以验证缓存是否仍然有效。如果服务器确认缓存有效,则返回304状态码,浏览器继续使用缓存中的资源
二十九、CDN缓存
就像一个全球性的内容 “储物柜”,这个“储物柜”有跟多小柜子,当你在网上浏览网页、看视频、的时候,CDN会根据你的位置,选择离你最近的那个小柜子然后给你;
- 工作流程
- 内容缓存:CDN会把网站的静态资源、动态内容,复制到选定的分发节点上,并对这些资源压缩优化
- 用户请求分发:用户请求访问的时候,CDN会根据用户位置选择一个最近性能最佳的节点分发
- 缓存命中与回源处理:CDN会检查 “储物柜” 有没有这个;有的话(缓存命中)直接将内容反过来;没有的话(缓存未命中)就会从源服务器调过来;以便下次使用
三十、地址栏输入 URL 后,按下回车发生了什么
- DNS域名解析;
- 与服务器创建TCP链接;
- 第一次握手:浏览器 发送一个带有 SYN 标志的数据包给 服务器 表示他想连
- 第二次:服务器收到后响应一个 SYN + ACK 的数据包给浏览器 表示它同意,并且已收到请求
- 第三次:浏览器收到后再发一个 带 ACK 标志的数据包给服务器 确认建立
- 发起HTTP请求
- 服务端响应HTTP请求;
- 浏览器解析资源并渲染页面;
- TCP四次握手断开HTTP链接
- 第一次:浏览器发一个带 “FIN” 的数据包给服务器表示它想断;
- 第二次:服务器收到后,回复一个带有 “ACK”的数据包给浏览器,表示已收到;
- 第三次:服务器发一个带有 “FIN” 的数据包给浏览器,表示他也想断;
- 第四次:浏览器收到后,回复一个带有 “ACK”标志的数据包给 “服务器”确认已断开
三十一、webSocket
- 原理: 主要是基于 “HTTP协议” 和 “TCP”连接,在 “HTTP”协议的基础上做了一次升级(通过在HTTP请求和响应中添加特定的头部信息来实现的); WebSocket 的通信是持续的、实时的,不需要每次发送数据都重新建立连接。
- 使用方法
- 建立连接:客户端发一个HTTP请求到服务端,包含WebSocket协议的升级头部,表示当前链接想升级,服务器收到后如果同意就会返回相应的相应;
- 数据传输:连接建立后 客户端 和 服务端就可以双向通信了;
- 关闭连接:任何一方都可以发起关闭的请求,对方收到后就会关闭
- WebSocket心跳 用于确保客户端和服务器之间的连接仍然活跃,避免因为长时间没有通信而导致连接丢失
- 具体操作:
- 发送心跳包:客户端(或者服务端)都可能会定期向对方发送心跳数据包检测对方状态
- 响应与反馈:对方在接收的同时会返回一个表示接收到心跳数据包的响应
- 连接状态检测:如果在一段时间内没有收到对方的响应表示链接可能断开了,就会尝试重新连接
三十二、hash(哈希路由)和 history 区别
- Hash : 主要是利用URL中的hash值(#后面的部分)实现页面的跳转,hash的变化不会引发页面的重新加载,它会触发hashchange事件来实现页面跳转
- 优点 : 兼容性好
- 缺点:URL中带 “#” 不美观
- History :利用HTML5提供的 History Api实现页面的跳转,通过pushState 或 replaceState方法改变当前的历史记录条目而不会触发页面重加载。同时也可以通过监听popstate事件当用户点击浏览器的前进或后退的时候根据当前历史记录条目显示相应内容
- 优点 : 美观
- 缺点: 兼容性差
三十三、面向对象的理解
就是把所有的东西都看做是一个对象然后由对象之间分工合作,以对象划分问题,而不是步骤;
- 特征:
- 封装;
- 继承;
- 多态
- 优点: 易维护、复用、扩展;根据他的特性(封装、继承、多态)可设计出低耦合的系统
- 缺点:性能比面向过程低
三十四、 回流(重排)与重绘
- 重绘:重绘是指元素的外观发生了改变,但不影响其在文档流中的位置。这些改变包括颜色、背景色、透明度、文本方向、阴影等非几何属性的变化
- 回流:回流是指当渲染树中部分或全部元素的尺寸、结构、布局、隐藏状态等发生变化时,浏览器需要重新计算元素的几何属性(例如位置、大小、布局)并重新构建渲染树的过程
- 重绘不一定回流 回流(重排)一定重绘
三十五、script标签里的async和defer有什么区别
当没有async和defer这两个属性的时候,浏览器会立刻加载并执行指定的脚本
- 有async
- 加载和渲染后面元素的过程将和script的加载和执行并行进行(异步)(不会保证执行顺序谁先加载好就执行谁)
- 有defer
- 加载和渲染后面元素的过程将和script的加载并行进行(异步),但是它的执行事件要等 所有元素解析完成之后才会执行 (会保证执行顺序)
三十六、浏览器的存储方式有哪些
- cookies:
- 兼容性好,请求头自带cookie
- 存储量小,资源浪费,使用麻烦
- localstorage:
- 操作方便,永久存储,兼容性好;
- 保存值的类型被限定,浏览器在隐私模式下不可读取;
- seesionstorage:
- 当前页面关闭后就会被清理
- indexDB:
- 以键值对进行存储,可以快速读取,适合 WEB 场景
三十七、、HTTP 和 HTTPS
- HTTP:超文本传输协议;
- HTTPS:安全超文本传输协议
- 区别
- HTTPS需要 CA证书,费用较高;HTTP不需要;
- HTTP信息是明文传输,HTTPS具有更加安全的 SSL加密传输协议
- HTTP端口号是 80 ; HTTPS是 443
三十八、toString() 和 valueOf()
1. toString()是返回对象的字符串

valueOf()方法在JavaScript中通常用于返回对象的基本值
三十九、cookie、localStorage 和 sessionStorage的区别
- 用途:Cookie通常用于在客户端和服务器之间传递少量数据(如会话ID、用户首选项等)。localStorage和sessionStorage则用于在客户端存储大量数据,以便在多个页面或浏览器会话之间共享。
- 安全性:由于cookie数据会随HTTP请求发送,因此它们可能会受到攻击。localStorage和sessionStorage仅在本地存储数据,因此相对更安全。但请注意,如果浏览器存在安全漏洞或恶意软件,则这些存储机制也可能受到威胁。
- 大小和数量限制:Cookie的大小和数量都受到浏览器和服务器的限制。localStorage和sessionStorage的存储大小通常比cookie大得多,但具体大小取决于浏览器。
- 有效期:Cookie可以设置过期时间,而localStorage中的数据没有过期时间(除非显式删除)。sessionStorage中的数据仅在当前浏览器会话期间有效。
浏览器的最大并发条数(promise.all并发请求数据)
浏览器类型、版本、操作系统、网络条件以及服务器端的配置。不同的浏览器和浏览器版本可能会有不同的默认并发请求限制 如现在的 Chrome4 最大限制数是 6 条
布局响应式和自适应的区别
- 自适应布局 : 如 多媒体查询 :布局的变化是 达到某个点的时候突然发生的 ;
- 响应式布局:根据屏幕、设备(包括桌面电脑、平板、手机)的不同自适应,不会是达到某个点才发生变化
webpack
- 入口(Entry) :
- 入口是Webpack构建模块的起点。Webpack会从指定的入口文件开始,递归地构建其依赖的模块图。
- 你可以通过配置
entry属性来指定一个或多个入口文件。
- 输出(Output) :
- 输出属性告诉Webpack在哪里输出它所创建的打包文件(bundles),以及如何命名这些文件。
- 你可以通过配置
output属性来指定输出文件的名称、路径和公共路径(publicPath)等。
- Loader:
- Loader让Webpack能够处理那些非JavaScript文件(因为Webpack自身只理解JavaScript)。
- Loader本质上是一个函数,接收源文件作为输入,并返回转换后的结果。
- 你可以通过配置
module.rules来指定不同类型的文件应该使用哪些Loader进行处理。
- 插件(Plugins) :
- 插件用于执行范围更广的任务,从打包优化和压缩,到重新定义环境的变量等。
- 插件基于事件流框架Tapable,可以监听Webpack生命周期中的事件,并在合适的时机通过Webpack提供的API改变输出结果。
- 你可以通过在Webpack配置中添加
plugins数组来使用多个插件。
- 模式(Mode) :
- 模式用于指示Webpack使用相应模式的配置。Webpack为不同模式提供了一些默认值,如
development和production。 - 在
development模式下,Webpack会启用源代码映射(source maps)并优化构建速度。 - 在
production模式下,Webpack会启用代码压缩和树摇(tree shaking)等优化措施。 - 你可以通过配置
mode属性来指定Webpack的构建模式。
- 模式用于指示Webpack使用相应模式的配置。Webpack为不同模式提供了一些默认值,如
一、提升开发体验
Source Map类型:可以使用eval-cheap-module-source-map类型的source map,它打包编译速度快,只包含行映射,便于快速定位错误代码。而在生产环境中,为了更详细的错误信息,可以使用source-map,但需要注意它会稍微降低打包速度。
二、提升打包构建速度
- 使用thread-loader:通过多进程打包,可以显著提高构建速度。将
thread-loader放在耗时的loader之前,如babel-loader,可以利用多核CPU的优势,并行处理文件。 - 使用cache-loader:缓存资源可以加快二次构建速度。同样,将
cache-loader放在耗时的loader之前,可以缓存之前的处理结果,避免重复处理。 - Hot Module Replacement(HMR) :在开发环境中,HMR可以只刷新修改的文件,而不是整个项目,从而加快代码重新构建速度。需要在
webpack.dev.js中配置HotModuleReplacementPlugin插件,并设置devServer的hot选项为true。 - oneOf规则:在配置loader时,使用
oneOf规则可以使得文件只能匹配其中的一个loader,从而避免不必要的匹配和处理。 - 利用include/exclude属性:在loader配置中,使用
include和exclude属性可以指定需要处理或排除的文件目录,从而减少不必要的文件扫描和处理。 - 开启多进程编译:使用HappyPack等插件可以将模块的转译任务分解给多个子进程并行处理,以加快编译速度。但请注意,每个进程启动会有一定的开销,因此请在特别耗时的操作中使用。
三、减少代码体积
- CSS代码压缩:使用
css-minimizer-webpack-plugin插件可以压缩CSS代码,包括压缩和去重。 - JS代码压缩:使用
terser-webpack-plugin插件可以压缩JS代码,减少文件体积。 - Tree Shaking:只打包用到的代码,不打包未使用的代码。Webpack 5默认开启Tree Shaking,当打包模式为production时自动开启。确保你的代码是ES6模块语法,并且没有使用CommonJS语法,以便Webpack能够正确地进行Tree Shaking。
- 图片优化:使用
url-loader和image-webpack-loader插件可以优化和压缩图片,减小图片的体积。对于小图片,可以将其转换为base64编码,减少HTTP请求次数。
四、优化代码运行性能
- 模块懒加载:将大文件拆分成多个小文件,按需加载,可以提升首屏加载速度。在Vue或React等框架中,可以使用路由懒加载或组件懒加载来实现。
- Gzip压缩:开启Gzip压缩可以减少页面加载时间。在
webpack.prod.js中配置compression-webpack-plugin插件可以实现Gzip压缩。 - 合理配置hash:确保修改过的文件更新hash值,未修改的文件保持原hash值,以便利用浏览器缓存。在
webpack.base.js的output配置中添加contenthash可以实现这一点。
五、其他优化策略
- 使用Webpack Bundle Analyzer:使用
webpack-bundle-analyzer插件可以分析打包后的代码体积和模块依赖关系,找出体积较大的模块,并进行优化。 - 使用DLLPlugin:对于稳定的第三方库或框架,可以使用
DLLPlugin将它们提前打包成单独的文件,避免每次构建都重新打包这些库,提高打包速度。 - 使用DefinePlugin进行环境变量注入:使用
DefinePlugin插件可以将环境变量注入到Webpack的构建过程中,以消除开发和生产环境下的无用代码。
判断元素是否在可视区;滚动条是否到了页面底部
- 是否在可视区: 如果元素的顶部在视口的顶部以下,且元素的底部在视口的底部以上,则认为元素在可视区内。
- 是否到了页面底部: 如果当前滚动高度加上视口的高度等于或大于页面的总高度,则认为滚动条已经到达页面底部。
纯函数
- 定义:纯函数是对给定的输入返还相同的输出的函数,并且要求所有的数据都是不可变的;
- 优势
- 通过纯函数可以产生可测试的代码
- 不依赖外部环境计算,不会产生副作用,复用性高
- 可读性高,不管是不是纯函数,都会有一个语义化的名称,便于阅读
- 符合模块化概念及单一职责原则
- 特性
- 函数内部传入指定的值,就会返回唯一确定的值
- 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数