欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > JS进阶01-异步编程、跨域、懒加载

JS进阶01-异步编程、跨域、懒加载

2025/6/9 20:53:13 来源:https://blog.csdn.net/m0_64455070/article/details/144216618  浏览:    关键词:JS进阶01-异步编程、跨域、懒加载

目录

一、异步编程

1.1.异步编程的基本概念与重要性

1.2.事件循环(Event Loop)机制

1.3.JavaScript异步编程的常见方式及详解

示例

1.4.异步编程的最佳实践

二、跨域

2.1.什么是跨域

2.2.怎么解决跨域

1. JSONP(JSON with Padding)

2. CORS(Cross-Origin Resource Sharing)

3. 反向代理/Nginx反向代理

4. WebSocket

5. postMessage

6. Node.js中间件代理

三、懒加载

1. 懒加载原理

2. 懒加载思路及实现

3. 图片的懒加载

案例


一、异步编程

1.1.异步编程的基本概念与重要性

        异步编程是一种编程方式,它允许程序在等待某些操作(如网络请求、文件读取、定时器等)完成的同时,继续执行其他任务。这种编程方式可以显著提高程序的执行效率,特别是在处理I/O密集型任务时表现出色。

        在JavaScript中,由于它是单线程语言,即同一时间只能执行一个任务,因此异步编程显得尤为重要。如果使用同步编程模型来处理需要大量时间的任务,会阻塞整个线程,导致页面或程序卡顿。而异步编程则可以让程序在执行这些长时间任务时,继续执行其他代码,从而优化用户体验。

1.2.事件循环(Event Loop)机制

        1.事件循环(Event Loop)

        JavaScript的执行环境是单线程的,这意味着它同一时间只能执行一个任务。

        事件循环是JavaScript实现异步编程的核心机制。它包含一个执行栈(Call Stack)和一个或多个任务队列(Task Queue)。

        执行栈用于执行同步代码,而任务队列则用于存放异步任务的回调函数。

        当执行栈为空时,事件循环会从任务队列中取出任务并执行。

        2.任务类型

        宏任务(Macro Task):如setTimeoutsetInterval、I/O操作等。

        微任务(Micro Task):如Promise的回调、process.nextTick等。

        微任务的优先级高于宏任务。在每一次事件循环中,执行栈中的任务执行完毕后,会先检查微任务队列,执行所有微任务,再执行下一个宏任务。

1.3.JavaScript异步编程的常见方式及详解

        1、回调函数(Callback Functions)

                回调函数是最早的异步处理方式。它通过将一个函数作为参数传递给另一个函数,在异步任务完成后调用该函数来处理结果。

        回调函数的优点是简单易懂,但在处理多个异步任务时,会导致回调函数嵌套过多,形成“回调地狱”,使代码难以维护和理解。

        2、Promise对象

        Promise是ES6引入的一种异步编程解决方案,它是一个表示未来某个事件(通常是一个异步操作)的结果的对象。

        Promise对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。状态一旦改变就不能再变。

        Promise对象提供了then()catch()方法来处理异步操作的结果和异常。then()方法用于处理成功的情况,catch()方法用于处理失败的情况。

        Promise支持链式调用,可以避免回调地狱,使代码更加简洁易读。

        Promise.all()和Promise.race()是Promise对象的两个静态方法。

        Promise.all()接受一个包含多个Promise的数组,并返回一个新的Promise。当所有Promise都完成时,这个Promise才会完成;如果其中有一个Promise失败,Promise.all()会立即失败。

        Promise.race()则只要数组中的任意一个Promise完成或失败,就会立即完成或失败。

        有关promise的更详细讲解在ES6基础知识icon-default.png?t=O83Ahttps://blog.csdn.net/m0_64455070/article/details/143714359

        3、async/await

        async/await是基于Promise的语法糖,是ES2017引入的用于处理异步代码的方式。

  async用来声明一个异步函数,该函数会隐式地返回一个Promise。

  await只能在async函数内部使用,用于等待一个Promise的完成。它返回的是Promise成功的结果值,如果Promise失败,则抛出异常。

        async/await使得异步代码看起来像是同步的,提高了代码的可读性和可维护性。它避免了回调地狱,并且错误处理更加方便。

        有关async/await更详细讲解在本文作者的ES6基础知识icon-default.png?t=O83Ahttps://blog.csdn.net/m0_64455070/article/details/143714359

示例

以下是一个使用async/await实现异步网络请求的示例代码:

async function fetchData(url) {try {const response = await fetch(url); // 等待fetch函数的Promise完成if (!response.ok) {throw new Error('Network response was not ok');}const data = await response.json(); // 等待响应的JSON数据解析完成return data;} catch (error) {console.error('Fetch error:', error);throw error; // 将错误抛出给调用者处理}
}// 使用 async/await 处理异步请求结果
(async () => {try {const data = await fetchData('https://api.example.com/data');console.log('Request succeeded:', data);} catch (error) {console.error('Request failed:', error);}
})();

在这个示例中,fetchData函数是一个异步函数,它使用await关键字等待fetch函数的Promise完成,并获取响应的JSON数据。然后,我们使用一个立即执行的异步函数来调用fetchData函数,并处理异步请求的结果。如果请求失败,则捕获异常并打印错误信息。

1.4.异步编程的最佳实践

避免回调地狱:使用Promise或async/await来避免回调地狱,使代码更加简洁易读。

错误处理:在异步代码中添加适当的错误处理逻辑,以确保程序的健壮性。

代码可读性:使用有意义的函数名和变量名,以及适当的注释来提高代码的可读性。

性能优化:避免不必要的异步操作,以减少资源消耗和提高程序性能。

二、跨域

2.1.什么是跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源。在Web开发中,由于同源策略(Same-Origin Policy)的限制,浏览器默认不允许跨域请求。如果尝试进行跨域请求,浏览器会抛出安全错误。

什么是同源策略?

同源策略是一种安全功能,它要求协议、域名和端口三者必须相同,才允许访问资源。

2.2.怎么解决跨域

1. JSONP(JSON with Padding)

原理:

JSONP是一种通过<script>标签实现跨域请求的技术。由于<script>标签不受同源策略的限制,可以加载其他域的JavaScript文件。

优点:

兼容性好,可以解决主流浏览器的跨域问题

缺点:

仅支持GET请求。

不安全,可能遭受XSS攻击。

实现步骤

JSONP的工作流程如下:

前端:动态创建一个<script>标签,并设置其src属性为跨域的URL,同时在URL中传入一个回调函数名(如callback=myFunction)。

后端:服务器接收到请求后,将返回的数据作为回调函数的参数,并包装成JavaScript代码返回(如myFunction({"key":"value"}))。

前端:当浏览器加载这段JavaScript代码时,会自动调用回调函数myFunction,从而实现跨域数据传输。

但JSONP只能发送GET请求,且存在安全风险,容易受到XSS攻击。

2. CORS(Cross-Origin Resource Sharing)

原理

CORS是一种由服务器设置响应头来允许跨域请求的机制。服务器通过设置特定的HTTP响应头,如Access-Control-Allow-Origin,来指定哪些域名或IP地址可以跨域访问资源。

优点

灵活,可以细粒度地控制哪些源可以访问资源。

前端无需配置,只需后端设置响应头。

缺点

需要服务器支持

实现步骤

前端:无需特殊配置,只需发送跨域请求即可。

后端:服务器需要在响应头中设置Access-Control-Allow-Origin等CORS相关的头部字段。

CORS支持所有类型的HTTP请求,且安全性较高,是跨域请求的推荐解决方案。但需要注意的是,CORS需要服务器和浏览器同时支持才能实现。

3. 反向代理/Nginx反向代理

原理

反向代理是一种在服务器端设置代理服务器,将前端的跨域请求转发到目标服务器,并将目标服务器的响应返回给前端的技术。Nginx是一种常见的反向代理服务器。

优点

解决跨域问题:通过反向代理,前端可以与目标服务器进行跨域通信,而无需修改前端代码。

安全性:反向代理可以作为一道安全屏障,隐藏目标服务器的真实地址,防止直接攻击。

负载均衡:在大型应用中,反向代理可以将请求分发到多个目标服务器,实现负载均衡,提高系统的可用性和性能。

缓存:反向代理可以缓存目标服务器的响应,减少对目标服务器的请求次数,提高响应速度。

缺点

配置复杂性:反向代理需要服务器端的配置和管理,这可能需要一定的技术知识和经验。

单点故障:如果反向代理服务器出现故障,整个系统可能会受到影响,导致服务中断。

性能瓶颈:虽然反向代理可以提高性能,但在高并发场景下,它也可能成为性能瓶颈。

实现步骤

前端:无需特殊配置,只需发送请求到反向代理服务器即可。

后端:需要在服务器端(如Nginx)配置反向代理规则,将请求转发到目标服务器。

通过反向代理,前端可以认为是在与同源服务器通信,从而绕过浏览器的同源策略限制。但需要注意的是,反向代理需要服务器端的配置和管理。

4. WebSocket

原理

WebSocket是一种在单个TCP连接上进行全双工通讯的协议。它默认支持跨域请求,可以在客户端和服务器之间建立持久的连接,并通过该连接进行实时数据传输。

优点

支持全双工通信,实时性好。

不受同源策略限制。

缺点

需要服务器支持WebSocket协议。

可能存在性能问题,如页面建立多个WebSocket连接。

实现步骤

前端:使用WebSocket API(如new WebSocket('ws://example.com/socket'))建立连接。

后端:服务器需要支持WebSocket协议,并处理连接和数据传输。

WebSocket适用于需要实时通信或双向通信的场景,如聊天室、实时数据监控等。

5. postMessage

原理

postMessage是HTML5中提供的一个API,用于在不同窗口、标签页或iframe之间进行跨域通信。

优点

安全,可以通过origin参数控制消息的来源。

支持复杂的通信场景,如跨域iframe之间的通信。

缺点

需要双方配合实现。

实现步骤

前端:使用window.postMessage方法发送消息到目标窗口或iframe。

接收端:目标窗口或iframe需要监听message事件,并处理接收到的消息。

postMessage适用于不同窗口之间的通信,如父子窗口、跨域iframe等。但需要在接收端进行安全验证以防止恶意攻击。

6. Node.js中间件代理

原理

当前端发送跨域请求到Node.js服务器时,Node.js服务器使用中间件拦截并处理这些请求。中间件可以检查请求的头信息、参数等,并根据配置将请求转发到目标服务器。目标服务器处理请求后,将响应返回给Node.js服务器,Node.js服务器再将响应转发给前端。通过这种方式,Node.js服务器作为中间层,实现了跨域通信。常见的中间件有corshttp-proxy-middleware等。

优点

灵活性:Node.js中间件代理可以灵活地处理跨域请求,并与其他Node.js应用集成。

易于配置:与Nginx等反向代理服务器相比,Node.js中间件代理的配置通常更加简单和直观。

可扩展性:Node.js中间件代理可以轻松地与其他中间件和路由集成,实现更复杂的功能。

缺点

性能:与Nginx等高性能反向代理服务器相比,Node.js中间件代理的性能可能稍逊一筹。

资源消耗:Node.js中间件代理需要额外的资源来运行和处理请求,这可能会增加服务器的负载。

维护成本:与Nginx等成熟的反向代理服务器相比,Node.js中间件代理可能需要更多的维护和支持

实现步骤

前端:发送请求到Node.js服务器。

后端:Node.js服务器使用中间件拦截并处理跨域请求,将请求转发到目标服务器,并将响应返回给前端。

Node.js中间件代理需要服务器端的配置和管理,但可以灵活地处理跨域请求,并与其他Node.js应用集成。

三、懒加载

1. 懒加载原理

        懒加载,即按需加载或延迟加载,是指当页面或应用中的某些资源(如图片、视频、数据等)在需要时才进行加载,而不是在页面初始化时一次性加载所有资源。这种技术的核心原理是减少初始加载时间和网络流量,提高页面响应速度和用户体验。

懒加载的原理主要基于以下几点:

按需加载:只加载用户当前需要或即将需要的资源,避免加载无用资源。

异步加载:将资源的加载推迟到用户需要使用时再进行,不阻塞用户界面的渲染。

动态加载:根据用户的行为和需求,动态地生成和加载页面内容或资源。

2. 懒加载思路及实现

  1. 确定需要懒加载的资源:根据页面或应用的需求,确定哪些资源可以或应该进行懒加载。
  2. 设置占位符:在资源实际加载之前,使用占位符(如低分辨率图片、默认图标等)来占据资源的位置。
  3. 监听用户行为:通过事件监听(如滚动事件、点击事件等)来检测用户何时需要加载资源。
  4. 加载资源:当用户需要资源时,触发加载逻辑,从服务器获取资源并替换占位符。

懒加载的实现方式有多种,包括但不限于:

  1. 图片懒加载:通过监听滚动事件和判断图片是否进入可视区域来实现图片的延迟加载。
  2. 视频懒加载:在视频播放器进入用户视野或用户点击播放按钮时才开始加载视频内容。
  3. 数据懒加载:在用户滚动到页面底部或进行分页操作时,加载更多的数据。

3. 图片的懒加载

图片的懒加载是懒加载技术中最常见的应用之一。

步骤

在HTML中为需要懒加载的图片设置占位符,并使用自定义属性(如data-src)存储真实图片的路径。

在JavaScript中监听滚动事件,判断图片是否进入可视区域。

当图片进入可视区域时,将占位符替换为真实图片

// 获取页面中的所有懒加载图片
var imgs = document.querySelectorAll('img[data-src]');// 监听滚动事件
window.addEventListener('scroll', function() {// 遍历所有懒加载图片imgs.forEach(function(img) {// 判断图片是否进入可视区域if (isImageInViewport(img)) {// 替换占位符为真实图片img.src = img.getAttribute('data-src');// 移除data-src属性,避免重复加载img.removeAttribute('data-src');}});
});// 判断图片是否进入可视区域的函数
function isImageInViewport(img) {var rect = img.getBoundingClientRect();var inViewport = (rect.top >= 0 &&rect.left >= 0 &&rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&rect.right <= (window.innerWidth || document.documentElement.clientWidth));return inViewport;
}

现代浏览器支持

现代浏览器(如Chrome、Firefox、Safari等)通常支持图片的懒加载属性。可以在<img>标签中使用loading="lazy"属性来告诉浏览器延迟加载图片,直到它们出现在视口中。

例如:<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="Description">

案例

1. 图片懒加载(使用JavaScript和Intersection Observer API)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Lazy Load Example</title>
<style>.lazy-load {opacity: 0;transition: opacity 0.3s;}.lazy-load.loaded {opacity: 1;}
</style>
</head>
<body><!-- 图片列表,使用 data-src 属性存储真实图片 URL -->
<img class="lazy-load" data-src="image1.jpg" alt="Image 1">
<img class="lazy-load" data-src="image2.jpg" alt="Image 2">
<img class="lazy-load" data-src="image3.jpg" alt="Image 3">
<!-- ...更多图片... --><script>// 获取所有需要懒加载的图片const lazyImages = document.querySelectorAll('.lazy-load');// 创建 Intersection Observer 实例const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {if (entry.isIntersecting) {// 当图片进入可视区域时,加载图片const img = entry.target;img.src = img.dataset.src;img.classList.add('loaded');// 停止观察这个图片observer.unobserve(img);}});});// 开始观察每个图片lazyImages.forEach(img => {observer.observe(img);});
</script></body>
</html>

2. 视频懒加载(使用JavaScript和事件监听)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Lazy Load Example</title>
</head>
<body><!-- 视频列表,使用 data-src 属性存储视频 URL -->
<div class="video-container"><video class="lazy-load" controls><source data-src="video1.mp4" type="video/mp4">Your browser does not support the video tag.</video>
</div>
<div class="video-container"><video class="lazy-load" controls><source data-src="video2.mp4" type="video/mp4">Your browser does not support the video tag.</video>
</div>
<!-- ...更多视频... --><script>// 获取所有需要懒加载的视频const lazyVideos = document.querySelectorAll('.lazy-load video');// 为每个视频容器添加点击事件监听器lazyVideos.forEach(videoContainer => {const video = videoContainer.querySelector('video');const source = video.querySelector('source');videoContainer.addEventListener('click', () => {// 当视频容器被点击时,加载视频video.src = source.dataset.src;video.load(); // 触发浏览器加载视频资源// 移除 data-src 属性,避免重复加载source.removeAttribute('data-src');// 移除点击事件监听器,避免重复操作videoContainer.removeEventListener('click', arguments.callee);});});
</script></body>
</html>

3. 数据懒加载(使用JavaScript和滚动事件监听)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Lazy Load Example</title>
<style>#content {max-height: 400px;overflow-y: auto;}.item {padding: 10px;border-bottom: 1px solid #ccc;}
</style>
</head>
<body><div id="content"><!-- 初始内容 --><div class="item">Item 1</div><div class="item">Item 2</div><!-- ...更多初始内容... --><!-- 占位符,用于加载更多内容 --><div id="load-more">Load More</div>
</div><script>let currentPage = 1; // 当前页码// 为“加载更多”按钮添加点击事件监听器document.getElementById('load-more').addEventListener('click', () => {loadMoreData(currentPage);currentPage++; // 更新页码});// 滚动事件监听器,当用户滚动到底部时自动加载更多数据window.addEventListener('scroll', () => {const content = document.getElementById('content');const loadMore = document.getElementById('load-more');const bottomOfWindow = window.scrollY + window.innerHeight === document.body.offsetHeight;const bottomOfContent = content.scrollTop + content.clientHeight >= content.scrollHeight - 10; // 减去10是为了避免因为滚动条或内容高度计算误差导致的重复加载if (bottomOfWindow || bottomOfContent) {loadMoreData(currentPage);currentPage++; // 更新页码// 可以选择隐藏或禁用“加载更多”按钮,直到新数据加载完成loadMore.style.display = 'none';}});// 模拟加载更多数据的函数function loadMoreData(page) {setTimeout(() => {const content = document.getElementById('content');const loadMore = document.getElementById('load-more');// 清除之前的占位符(如果存在)if (loadMore.style.display === 'none') {loadMore.style.display = 'block';}// 创建新的内容项并添加到页面中for (let i = 0; i < 5; i++) {const newItem = document.createElement('div');newItem.className = 'item';newItem.textContent = `Item ${(page - 1) * 5 + i + 3}`; // 假设每页加载5个项,从Item 3开始content.appendChild(newItem);}}, 1000); // 模拟网络延迟,设置为1秒}
</script></body>
</html>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词