- 继承的实现
在 JavaScript 中,继承是通过原型链来实现的。原型链是 JavaScript 中对象之间的一种关系,它允许一个对象继承另一个对象的属性和方法。
以下是一个简单的示例,展示了如何使用原型链来实现继承
function Parent() { this.name = 'Parent'; } Parent.prototype.sayHello = function() { console.log('Hello from Parent'); }; function Child() { Parent.call(this); // 调用父类的构造函数 } Child.prototype = Object.create(Parent.prototype); // 创建一个新的对象,继承自父类的原型对象 Child.prototype.constructor = Child; // 修复构造函数的指向 var child = new Child(); child.sayHello();
在上述示例中,我们定义了一个父类
Parent
和一个子类Child
。子类通过Parent.call(this)
调用父类的构造函数,从而继承了父类的属性。然后,我们使用Object.create()
方法创建一个新的对象,该对象继承自父类的原型对象。最后,我们将子类的构造函数指向Child
,以确保正确的构造函数调用。这样,子类就继承了父类的属性和方法,可以通过
child.sayHello()
来调用父类的方法。
2. this指向、call、apply、bind 区别与用法
this
:在 JavaScript 中,this
的指向在运行时根据函数的调用方式动态确定。一般来说,在普通函数调用中,this
指向全局对象(在浏览器中是window
),但在对象方法中,this
指向调用该方法的对象。
call
:它可以用来改变函数执行时的this
对象指向。用法是function.call(thisArg, arg1, arg2,...)
,其中thisArg
是新的this
对象,后面依次是传递给函数的参数。
apply
:和call
类似,区别在于传递参数的方式,它是function.apply(thisArg, [argsArray])
,即第二个参数是一个数组,包含了要传递给函数的参数。
bind
:它会创建一个新函数,这个新函数的this
被永久地绑定为指定的值。function.bind(thisArg, arg1, arg2,...)
,返回的新函数可以在后续合适的时候调用。
3. 回调函数,Promise封装、async、await的使用
回调函数:
回调函数是一种将一部分操作(通常是异步操作完成后的后续处理)作为参数传递给另一个函数的方式。当异步任务完成后,调用这个传递进去的回调函数来执行相应的后续动作。优点是简单直接,但可能会导致回调嵌套过多,形成所谓的“回调地狱”,代码可读性和维护性变差。
Promise 封装:
Promise 是对异步操作的一种封装,它代表了一个异步操作可能的最终完成(成功或失败)状态。通过
then
方法可以添加成功时的处理逻辑,通过catch
方法添加失败时的处理逻辑。它使得异步代码的结构更加清晰,能够更好地处理异步流程和错误情况。可以解决回调地狱问题。async/await:
这是基于 Promise 的进一步语法糖。使用
async
关键字标记一个函数为异步函数,在其中可以使用await
表达式等待一个 Promise 对象的结果。它使得异步代码看起来更像同步代码,极大地提高了异步代码的可读性和可理解性。可以更简洁、直观地编写异步逻辑,避免了 Promise 中复杂的链式调用。
4. Promise.all、Promise.race、Promise.allSettled、Promise.any
Promise.all:
它接受一个可迭代对象(通常是一个数组),其中每个元素都是一个 Promise。当所有这些 Promise 都成功完成(fulfilled)时,Promise.all 才会成功完成,并返回一个包含所有成功结果的数组。如果其中有任何一个 Promise 失败(rejected),则整个 Promise.all 立即失败,返回的 Promise 就会被拒绝,错误信息就是那个失败的 Promise 的错误信息。
Promise.race:
同样接受一个可迭代对象。它会返回一个 Promise,一旦可迭代对象中的任何一个 Promise 状态确定(无论是成功还是失败),这个返回的 Promise 就会立即跟着确定,其状态和结果与第一个确定状态的 Promise 相同。
Promise.allSettled:
与 Promise.all 类似,但不同的是,它会等待所有的 Promise 都有结果(不管是成功还是失败),然后返回一个数组,数组中的每个元素都包含对应 Promise 的状态(fulfilled 或 rejected)和结果(如果是 fulfilled则是值,如果是 rejected则是错误信息)。
Promise.any:
接受一个可迭代对象。它会返回一个 Promise,只要可迭代对象中有任何一个 Promise 成功,它就成功并返回那个成功的结果。如果所有的 Promise 都失败了,才会失败。
5. Promise并发控制
在 JavaScript 中,Promise 用于处理异步操作。当需要进行并发控制时,可以使用一些方法来限制同时进行的 Promise 数量,以避免过多的并发请求导致性能问题或其他异常。
以下是两种常见的实现方式:
- 使用
async
/await
和一个循环来逐个执行 Promise,并在每次迭代中等待一个 Promise 完成后再进行下一个:async function concurrentRequest(urls, limit) { const results = []; for (const url of urls) { if (results.length < limit) { const result = await fetch(url); results.push(result); } else { await Promise.race(results); results.shift(); const result = await fetch(url); results.push(result); } } return results; }
在上述代码中,定义了一个名为
concurrentRequest
的异步函数,它接受一个 URL 数组和并发限制数作为参数。通过一个循环遍历 URL 数组,使用fetch
发送请求并将结果添加到results
数组中。如果results
数组的长度小于并发限制,则直接发送请求;否则,使用Promise.race
等待其中一个 Promise 完成,然后移除数组的第一个元素,并发送下一个请求。
- 使用第三方库,如
async-pool
或p-limit
,它们提供了更方便的 API 来实现并发控制:const asyncPool = require('async-pool'); async function concurrentRequest(urls, limit) { return asyncPool(limit, urls, fetch); }
在上述代码中,使用
async-pool
库的asyncPool
函数来并发执行请求。将并发限制和 URL 数组作为参数传递给asyncPool
,并指定fetch
函数作为执行的任务。这些方法可以根据具体的需求来选择使用,以实现对 Promise 并发的控制。同时,还可以根据实际情况进行调整和优化,例如处理错误情况、设置超时等。
6. 任务队列宏任务与微任务
在 JavaScript 中,任务队列可以分为宏任务队列和微任务队列。宏任务队列主要包括 script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI 渲染等;微任务队列主要包括 Promise.then、process.nextTick(Node.js)、MutationObserver、Object.observe(已废弃,由 Proxy 对象替代)等。
宏任务和微任务的执行顺序是:先执行宏任务队列中的一个任务,然后执行微任务队列中的所有任务,接着再执行宏任务队列中的下一个任务,如此反复。这种执行机制被称为事件循环(Event Loop)。
具体来说,当 JavaScript 引擎执行代码时,会先执行同步任务,也就是在执行栈中按照顺序执行的任务。如果在执行同步任务的过程中遇到了异步任务,比如 setTimeout、setInterval、Promise.then 等,那么这些异步任务会被分别添加到宏任务队列或微任务队列中。
当执行栈中的同步任务执行完毕后,JavaScript 引擎会检查微任务队列是否为空。如果微任务队列不为空,那么会依次取出微任务队列中的任务并执行,直到微任务队列为空。
微任务队列中的任务执行完毕后,JavaScript 引擎会再次检查宏任务队列是否为空。如果宏任务队列不为空,那么会取出宏任务队列中的第一个任务并执行。
需要注意的是,宏任务队列和微任务队列的执行顺序是固定的,微任务队列的任务总是在宏任务队列的任务之前执行。
举例:
console.log('1'); setTimeout(function() { console.log('2'); }, 0); Promise.resolve().then(function() { console.log('3'); }); console.log('4');
在上述代码中,首先输出
1
,然后遇到setTimeout
,将其回调函数放入宏任务队列。接着遇到Promise
,将其回调函数放入微任务队列。最后输出4
。当执行栈中的同步任务执行完毕后,JavaScript 引擎会检查微任务队列是否为空。如果微任务队列不为空,那么会依次取出微任务队列中的任务并执行,直到微任务队列为空。在这个例子中,微任务队列中有一个任务,即
Promise
的回调函数,所以会先输出3
。微任务队列中的任务执行完毕后,JavaScript 引擎会再次检查宏任务队列是否为空。如果宏任务队列不为空,那么会取出宏任务队列中的第一个任务并执行。在这个例子中,宏任务队列中有一个任务,即
setTimeout
的回调函数,所以会输出2
。因此,最终的输出结果是
1
、4
、3
、2
,微任务先于宏任务执行
7. 了解V8垃圾回收
分代回收:
V8 将内存分为新生代和老生代。新生代通常存放生存时间较短的对象,采用复制算法进行回收。老生代存放生命周期较长的对象,主要使用标记-清除和标记-整理算法。
新生代回收:
把新生代空间分为两个等大小的区域,使用时一个区域处于使用状态,另一个为空。当进行回收时,将存活的对象复制到空闲区域,然后交换两个区域的角色。
老生代回收:
- 标记-清除:先标记出所有需要回收的对象,然后统一清理。
- 标记-整理:在标记之后,将存活对象向一端移动,然后清理掉边界外的空间。
对象晋升:
新生代中的对象经过多次回收仍然存活,会被晋升到老生代。
空闲列表:
用于管理可分配的内存空间。
V8 的垃圾回收机制不断优化和改进,以提高性能和效率,减少对程序运行的影响。同时,开发者也需要注意一些编程实践,以避免不必要的内存消耗和垃圾回收压力。例如,及时释放不再使用的对象引用等。
以下是一些可以用来优化 V8 垃圾回收性能的方法:
避免不必要的对象创建:
- 减少临时对象的频繁生成,例如重复使用对象而不是每次都创建新的。
及时释放不再使用的引用:
- 确保没有对不再需要的对象存在强引用,以便它们能被垃圾回收。
合理使用数据结构:
- 选择合适的数据结构,比如对于频繁插入和删除的场景,使用合适的链表结构可能比数组更高效。
避免大对象:
- 过大的对象可能会对垃圾回收造成较大压力。
优化循环:
- 在循环中避免创建不必要的对象或保留不必要的引用。
监控内存使用情况:
- 通过工具监测内存的增长和回收情况,以便及时发现问题并进行调整。
利用缓存:
- 对于一些可以缓存的数据,合理利用缓存可以减少重复对象的创建。
考虑使用对象池:
- 对于一些经常创建和销毁的相似对象,可以使用对象池来重复利用对象。
避免频繁触发垃圾回收:
- 保持代码的高效性,避免短时间内产生大量需要回收的对象。
8. JS动态DOM的获取、创建、删除、修改内容
获取元素:
const element = document.getElementById('myElement'); const elements = document.getElementsByTagName('div'); const elementByClass = document.getElementsByClassName('myClass'); const elementByQuery = document.querySelector('.myQuery'); const elementsByQueryAll = document.querySelectorAll('.myQueryAll');
创建元素:
const newDiv = document.createElement('div'); newDiv.textContent = '新创建的元素';
添加元素:
const parent = document.getElementById('parent'); parent.appendChild(newDiv);
删除元素:
const elementToRemove = document.getElementById('toRemove'); elementToRemove.parentNode.removeChild(elementToRemove);
修改内容:
const existingElement = document.getElementById('existing'); existingElement.textContent = '修改后的内容'; // 或修改 HTML 内容 existingElement.innerHTML = '<h2>新的 HTML 内容</h2>';
JavaScript 中动态 DOM 操作的最佳实践:
缓存元素引用:
- 多次使用的元素引用,避免重复查找,提升性能。
事件委托:
- 而不是为大量动态添加的元素逐个添加事件处理,将事件添加到它们的父元素上进行委托处理。
批量操作:
- 当有多个元素要创建或修改时,尽量集中在一起进行操作,减少频繁的 DOM 操作。
避免不必要的重绘和回流:
- 如尽量先修改元素样式在内存中,最后一次性应用到 DOM 上。
合理利用模板:
- 对于复杂的结构,可以使用模板来生成,而不是手动拼接大量 HTML 代码。
考虑性能影响:
- 尤其是在频繁操作 DOM 的场景下,要时刻关注性能开销。
代码可读性和维护性:
- 确保动态 DOM 操作的代码逻辑清晰,易于理解和维护。
使用成熟的库或框架:
- 如 React、Vue 等,它们在 DOM 操作方面有优化和良好的实践。
分离逻辑与视图:
- 不要让动态 DOM 操作的代码过于混乱地与业务逻辑交织在一起。
9. JS修改样式、切换class控制样式 以及属性的操作
修改样式:
const element = document.getElementById('myElement'); element.style.color ='red'; element.style.backgroundColor = 'blue';
切换 class 控制样式:
const target = document.getElementById('targetElement'); // 添加 class target.classList.add('newClass'); // 移除 class target.classList.remove('oldClass'); // 切换 class(有则移除,无则添加) target.classList.toggle('toggleClass');
操作属性:
const attrElement = document.getElementById('attrElement'); // 设置属性 attrElement.setAttribute('data-custom', 'value'); // 获取属性值 const customValue = attrElement.getAttribute('data-custom'); // 移除属性 attrElement.removeAttribute('data-custom');
10. 事件模型、冒泡与捕获、事件代理、阻止默认
- 事件模型:
- DOM 的事件操作都定义在
EventTarget
接口,所有节点对象都部署了该接口。EventTarget
接口主要提供了addEventListener
、removeEventListener
和dispatchEvent
三个实例方法。addEventListener
用于在当前节点或对象上定义特定事件的监听函数。removeEventListener
用于移除通过addEventListener
添加的事件监听函数。dispatchEvent
用于在当前节点上触发指定事件,从而触发监听函数的执行。- 冒泡与捕获:
- 事件流会经过捕获阶段、目标阶段和冒泡阶段。
- 冒泡型事件从内向外传播,捕获型事件则从外向内传播。
- 不是所有事件都能冒泡,如
blur
、focus
、load
和unload
等事件。- 事件代理:
- 事件代理又称事件委托,是把元素响应事件的函数委托给父元素或更外层元素。
- 利用事件冒泡机制,在父元素上绑定事件处理函数,通过判断事件目标来执行相应操作。
- 适用于列表项、动态添加或删除的元素等场景,可以减少内存消耗和重复工作。
- 阻止默认行为:
- 可以使用
event.preventDefault()
方法或在函数中返回false
来阻止元素的默认行为。- 在事件处理函数中,
event.preventDefault()
方法会阻止默认行为的发生。- 如果在事件冒泡过程中有一个处理函数阻止了默认行为,那么当事件冒泡到祖先元素时,也会被阻止默认行为。
11. localStorage、Cookie、Session、SessionStorage 区别
localStorage:
- 用于持久化地存储数据在客户端。
- 数据没有过期时间,除非手动清除,否则会一直存在。
- 存储容量一般较大。
- 可以跨页面、跨会话使用。
Cookie:
- 可用于存储少量数据。
- 可以设置过期时间。
- 服务器可以在响应中设置,浏览器在每次请求时会自动携带相应的 Cookie 到服务器。
- 常用于识别用户身份、保存用户偏好等。
Session:
- 主要在服务器端维护。
- 与特定用户的会话相关联。
- 当用户会话结束(如关闭浏览器),相关数据通常会被清除。
SessionStorage:
- 数据仅在当前会话有效,即关闭浏览器窗口后数据会丢失。
- 主要用于临时存储与当前会话相关的数据。
- 同样是在客户端存储。
对比项 localStorage Cookie Session SessionStorage 存储位置 客户端 客户端并随请求发送到服务器 服务器端 客户端 过期时间 无,除非手动清除 可设置 随会话结束 同会话 存储容量 较大 较小 - 较大 主要用途 长期本地数据存储 用户身份、偏好等 服务器端会话管理 临时会话数据存储
12. history、pushState、replaceState,不改变url刷新页面
history:
浏览器的历史记录对象,提供了操作浏览器历史记录的方法和属性。
pushState:
- 可以动态地添加新的历史记录状态。
- 接受三个参数:状态对象、标题(目前大多数浏览器忽略)、可选的新 URL。它可以在不触发页面刷新的情况下改变浏览器地址栏中的 URL。
replaceState:
- 类似 pushState,但它是替换当前的历史记录状态而不是添加。
不改变 URL 刷新页面:
可以使用
location.reload()
方法来实现不改变当前 URL 而刷新页面以下是一个示例代码展示
pushState
的使用const stateObj = { name: 'example' }; history.pushState(stateObj, '', '/newpage');
13. Ajax 的使用与Promsie封装
function ajax(url, method = 'GET', data = null) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.responseText); } else { reject(new Error(`请求失败,状态码: ${xhr.status}`)); } } }; xhr.send(data); }); } // 使用示例 ajax('https://example.com/data') .then(response => console.log(response)) .catch(error => console.error(error));
14. Fetch API的使用
Fetch API 是一种用于在网络中发送和接收请求的 JavaScript API。它提供了一种更简洁、直观的方法来处理 HTTP 请求和响应,相比传统的 XMLHttpRequest(XHR)具有以下特点:
- 更简洁的 API:只需要一个函数就可以完成网络请求;
- 基于 Promise:支持更直观的异步编程;
- 内置 Request 和 Response 类:方便进行请求和响应的处理;
- 支持跨域请求:允许在不同域名之间进行数据交互。
使用 Fetch API 发送请求的基本步骤如下:
- 创建一个 Request 对象,用于描述要发送的请求,包括请求的 URL、方法、标头(headers)等信息;
- 使用 fetch() 方法发送请求,并获取响应。fetch() 方法接收一个 Request 对象作为参数,并返回一个 Promise 对象;
- 在 Promise 对象的 then() 方法中处理响应数据。可以使用 response.json()、response.text() 等方法将响应数据转换为相应的格式进行处理;
- 在 catch() 方法中处理请求过程中可能出现的错误。
Fetch API 还支持发送 POST 请求、设置请求头、处理响应状态码等功能。可以根据具体的需求设置请求的参数和处理响应的数据。
fetch('https://example.com/data') .then(response => { if (!response.ok) { throw new Error(`HTTP 错误! 状态码: ${response.status}`); } return response.json(); }) .then(data => console.log(data)) .catch(error => console.error(error));
fetch('https://example.com/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'value' }) }) .then(response => response.json()) .then(result => console.log(result)) .catch(error => console.error(error));
15. Axios介绍
Axios 是一个基于 Promise 的 HTTP 客户端库,具有以下一些特点和优势:
特点:
- 可以在浏览器和 Node.js 环境中使用。
- 简洁易用的 API 设计。
- 支持请求和响应的拦截。
- 自动转换请求和响应的数据类型(如 JSON 转换)。
16. WebSocket
WebSocket 是一种在客户端和服务器之间,单个 TCP 连接上进行全双工通信的协议
主要特点和优势包括:
实时性强:能够实现近乎实时的数据交互,无需频繁轮询。
高效性:减少了不必要的网络请求和数据传输开销。
全双工通信:双方可以同时发送和接收信息。
与 HTTP 兼容性好:它基于 HTTP 协议进行初始握手,然后升级为 WebSocket 连接。
17. 跨域方案:CORS、JSONP、同源策略
同源策略:这是浏览器的一种安全机制,它限制了不同源(协议、域名、端口不同)的文档或脚本之间的交互,主要是为了防止恶意行为。
CORS(跨域资源共享):
- 服务器端通过设置特定的响应头来允许跨域请求。
- 比如设置
Access-Control-Allow-Origin
等头部信息来明确允许哪些源进行访问。JSONP(JSON with Padding):
- 利用
<script>
标签的跨域能力。- 服务器端返回一个包含数据的函数调用,前端通过预先定义好的函数来处理数据。
此外,还有一些其他跨域方案,比如:
- 代理服务器:在同域的服务器端进行请求转发。
- Websocket:其不受同源策略限制。
18. 浏览器渲染机制
浏览器的渲染机制是指浏览器将 HTML、CSS 和 JavaScript 等代码转换为可视化页面的过程。以下是一般情况下浏览器渲染页面的主要步骤:
- 请求和解析 HTML:浏览器发送 HTTP 请求获取 HTML 文件,并开始解析它,构建 DOM 树。
- 解析 CSS 和 JavaScript:浏览器同时请求并解析 CSS 文件,生成 CSSOM 树。如果 HTML 中包含 JavaScript,浏览器会执行这些脚本。
- 构建渲染树:DOM 树和 CSSOM 树合并成渲染树,其中包含了所有可见内容的节点及其计算样式。
- 布局(回流):计算每个节点在屏幕上的确切位置和大小。
- 绘制(重绘):按照从后向前的顺序,逐个绘制每个节点的内容。
- 合成和光栅化:对于复杂的动画或效果,使用 GPU 进行合成和光栅化操作,提高性能。
在这个过程中,回流和重绘是两个重要概念。
回流是指当 DOM 树中元素的几何属性发生变化或 CSS 样式改变影响到元素布局时,浏览器需要重新计算元素布局的过程。
重绘是指当元素的外观发生改变但不影响其几何属性时,浏览器需要更新该元素的像素信息并重新绘制它的过程。
为了提高浏览器的渲染性能,可以采取以下优化策略:
- 缓存元素引用:避免重复通过查询来获取相同的元素,将常用元素引用缓存起来。
- 批量处理操作:比如将多个样式修改或节点添加/删除等操作积攒起来一次性执行。
- 避免频繁读写类名:直接操作元素的样式属性可能更高效。
- 使用文档片段:先在文档片段上进行节点操作,最后再将其添加到 DOM 中。
- 使用虚拟 DOM:通过对比虚拟 DOM 和实际 DOM 的差异来最小化实际的 DOM 操作。
- 合理使用事件委托:减少大量事件绑定到具体元素上。
- 避免不必要的元素尺寸和位置计算:比如频繁获取元素的宽度、高度等。
- 对复杂动画使用 CSS 动画或 requestAnimationFrame:而不是通过频繁修改 DOM 来实现。
19. 加载JS、CSS区别和放置位置原因
区别:
- 功能不同:CSS 主要用于定义页面的样式表现,而 JS 主要用于实现页面的交互逻辑和动态行为。
- 加载影响:CSS 文件加载不会阻塞页面渲染,但会阻塞其后的 JS 执行;JS 文件加载和执行可能会阻塞页面渲染。
放置位置的原因:
对于 CSS:
- 通常放在
<head>
中,这样可以尽早让浏览器构建 CSSOM 树,使页面能尽快以正确的样式进行渲染。如果放在页面底部,可能导致页面先以无样式的状态短暂显示,然后再突然变成有样式,影响用户体验。对于 JS:
- 一般脚本放在页面底部接近
</body>
处,主要是为了减少对页面渲染的阻塞。尤其是一些非立即执行的脚本,放在底部可以让页面先完成主要内容的渲染,提升用户在视觉上的加载速度。- 但也有一些特殊情况,比如一些脚本需要在页面加载早期就执行来初始化某些功能或设置环境,可能就会放在
<head>
中,但需要注意可能带来的阻塞问题。
20. script的asnyc和defer的作用
async
和defer
主要用于控制<script>
标签中脚本的加载和执行方式,它们的作用如下:async:
- 表示该脚本会异步加载,不会阻塞页面渲染。
- 一旦该脚本加载完成,就会立即执行,不管此时页面是否已经完成渲染。
defer:
- 同样不会阻塞页面渲染,脚本会在页面解析完成后执行。
- 多个带有
defer
属性的脚本会按照它们在页面中出现的顺序依次执行。
21. px、em、rem、vw、rpx区别
px(像素):
- 绝对单位,在不同设备上显示的实际物理尺寸相对固定。
em:
- 相对单位,它的值等于当前元素的字体大小。如果父元素字体大小改变,它也会相应变化。
rem(根元素字体大小):
- 也是相对单位,它的值取决于根元素(通常是<html>)的字体大小,在整个页面中保持相对一致性。
vw(视口宽度的百分比):
- 视口相关单位,1vw 等于视口宽度的 1/100。
rpx:
- 是微信小程序中定义的单位。在不同屏幕宽度下会进行自适应转换。
22. 实现0.5px细线
- 使用 CSS3 的
transform
缩放:通过创建一个1px
的线条,然后使用transform: scale(0.5)
将其宽度缩小一半,可以实现0.5px
的细线效果。- 伪元素叠加:使用两个相邻的
1px
线条,并通过负margin
使它们重叠,从而形成视觉上的0.5px
效果。- 使用
box-shadow
模拟:将box-shadow
的垂直偏移量设置为0.5px
,可以模拟出0.5px
的细线。- 背景渐变法:通过设置一个极窄的背景渐变,可以实现
0.5px
的线条效果。
23. 移动端响应式高清图片解决方案
srcset 属性:可以在
<img>
标签中使用srcset
属性,指定不同分辨率的图片及对应的条件,让浏览器根据屏幕特性自动选择合适的图片。<img src="default.jpg" srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1024w">
- picture 元素:结合
<source>
元素,可以更精细地控制不同条件下显示的图片。<picture> <source media="(max-width: 480px)" srcset="small.jpg"> <source media="(max-width: 768px)" srcset="medium.jpg"> <img src="default.jpg"> </picture>
- 使用 CSS 媒体查询结合背景图片:根据不同屏幕宽度设置不同的背景图片。
- 图片 CDN:利用专业的图片内容分发网络,它们通常会自动进行图片的优化和适配。
- 懒加载:先只加载可视区域内的图片,当用户滚动到其他区域时再加载相应图片,节省带宽和提高加载速度。
- WebP 格式图片:使用 WebP 格式的图片,它能在保持相同质量的情况下减小图片文件大小。可以根据浏览器支持情况提供不同格式的图片。
- 图片压缩工具:在上传图片前,使用专业的图片压缩工具对图片进行压缩,减少图片大小。