前端知识多而杂乱,本文主要用于总结输出自己学习的知识,顺带分享,如果有错,还请谅解,欢迎纠正。
本文试图用异步串联起一部分杂乱的知识,帮助大家更好了解前端的发展也避免被乱七八糟的名词给吓到。包括回调地狱、Promise、Fetch API XMLHttpRequest XML JSON Ajax axios 等,如果你想了解这些内容以及他们的联系,这篇文章应该能帮到你。
所谓异步,就是事件 A 和时间 B 在两条不同是线上,不起冲突,举个生活中的例子。两个人打电话,A 在 B 表达时不能说话,反之同理,这就是不是异步,A、B 的沟通在同一条线上,互相阻碍。而信息沟通就是异步,因为 A 在发表观点并不影响 B 的输出。因此,在工作中用邮件,信息沟通也在逐步替代会议的形式,因为会议是多事件在同一条线上运行,造成了多的资源浪费。
接下来,详细讲讲前端中的异步。最初的异步是通过回调的方式实现,前端的异步场景主要有以下几个,setTimeout(定时器)、Fetch API和 XMLHttpRequest(网络请求)、用户的交互操作(输入文本等 )、文件操作、动画与过渡。这些都不会影响主任务的运行,举其中一个例子说明,若在主函数中,先读取一个文件,我们假设其耗时 3 秒,在读取文件下方放一个输入框,如果我们不采取异步的方式,那么,在这三秒钟,输入框是无法输入的,这就是在程序中异步的效果,如果不理解异步存在的意义,可以仔细体会这一点。
紧接着我们来讨论异步中一个臭名昭著的问题,“回调地狱”,实例如下
// 模拟异步API调用
function fetchUserData(userId, callback) {
setTimeout(() => {
console.log(`Fetched user data for ID: ${userId}`);
callback({ id: userId, name: 'John Doe' });
}, 1000);
}
function fetchUserPosts(userId, callback) {
setTimeout(() => {
console.log(`Fetched posts for user ID: ${userId}`);
callback([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
}, 800);
}
function fetchPostComments(postId, callback) {
setTimeout(() => {
console.log(`Fetched comments for post ID: ${postId}`);
callback([{ id: 1, text: 'Great post!' }]);
}, 500);
}
// 回调地狱示例
fetchUserData(123, (user) => {
console.log('User:', user);
fetchUserPosts(user.id, (posts) => {
console.log('Posts:', posts);
posts.forEach(post => {
fetchPostComments(post.id, (comments) => {
console.log(`Comments for post "${post.title}":`, comments);
// 更多嵌套...
if (comments.length > 0) {
// 继续嵌套其他异步操作
setTimeout(() => {
console.log('Final operation completed');
}, 300);
}
});
});
});
});
不难发现,在多层逻辑关系的嵌套下,函数的缩进成了一件非常恶心的事情,导致可维护性差、可读性差、并且难以错误分析。所以,聪明的程序员发明了一个东西,Promise。
// 模拟异步API调用(返回Promise)
function fetchUserData(userId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Fetched user data for ID: ${userId}`);
resolve({ id: userId, name: 'John Doe' });
}, 1000);
});
}
function fetchUserPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Fetched posts for user ID: ${userId}`);
resolve([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
}, 800);
});
}
function fetchPostComments(postId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Fetched comments for post ID: ${postId}`);
resolve([{ id: 1, text: 'Great post!' }]);
}, 500);
});
}
// 使用Promise链式调用
fetchUserData(123)
.then(user => {
console.log('User:', user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log('Posts:', posts);
const commentPromises = posts.map(post =>
fetchPostComments(post.id)
.then(comments => {
console.log(`Comments for post "${post.title}":`, comments);
return { post, comments };
})
);
return Promise.all(commentPromises);
})
.then(results => {
console.log('All comments fetched');
return new Promise(resolve => {
setTimeout(() => {
console.log('Final operation completed');
resolve(results);
}, 300);
});
})
.catch(error => {
console.error('Error occurred:', error);
});
这是使用 Promise 实现的和上文中同样的功能,发现了什么?缩进不见了,对吧?这就是 Promise 方法的神奇之处。如果你是刚刚开始了解异步,你不用详细看上面的代码,只需要知道“回调地狱”的问题被解决了,就是这么神奇。
接下来我们回到前面讲的常见异步场景,可能有人已经注意到,网络请求有两种实现方式,XMLHttpRequest 和 Fetch API,这里我们先讨论一下很多人可能会有的一个误区,XML。
XML 和 JSON 一样,都是一种数据交换格式,XML 采用的是类似于 HTML 的闭合标签的标记方式,JSON 采用的是键值对的方式。而 JSON 因其键值对的实现形式更加简单,成为当下更加主流的数据交换格式。那么,前面所说的误区是什么呢?就是XMLHttpRequest虽然名字中带有 XML 但不意味着他只能使用 XML 的数据交换格式。
好,回到XMLHttpRequest 和 Fetch API,那么这二者的区别究竟是什么呢?就是我们前面花了大篇幅介绍了 Promise,Fetch API 是基于 Promise 实现的,避免了“回调地狱”的问题,所以代替了 XMLHttpRequest,成为了主流的网络请求方式。
接下来我们继续探讨两个同样很像的东西,Ajax 和 axios,这二者都是用于实现同一个功能,即不刷新整个页面,与服务器交换数据、更新部分内容。上述功能的实现主要有以下三个要点,网络请求数据、数据格式、DOM 操作。而这两种技术的区别主要在于 axios 在便捷性和兼容性以及功能丰富度上有着很大的优势。因此,也是目前更加主流的实现方式。
到此,我们大概了解由“异步”引申出来的一大串东西,希望能以这种方式更好的把知识“串起来”,更加系统化的学习前端。