JavaScript到这里,我们的JS部分就已经结束了,接下来将会学习我们的新语言TypeScript。
TypeScript
TypeScript(简称 TS)是微软开发的一门开源编程语言,它是 JavaScript 的超集 (Superset)------ 这意味着所有合法的 JavaScript 代码都是合法的 TypeScript 代码,同时 TypeScript 在 JavaScript 基础上增加了静态类型系统,并扩展了更多特性。
因为浏览器只支持js语言,所以我们需要一些组件,将编写的TS代码转化为JS在浏览器上运行。
前后端交互使用xhr,后期采取node
回调地狱
"回调地狱"(Callback Hell)是 JavaScript 异步编程中因多层嵌套的回调函数导致的代码可读性差、维护困难的现象。它的核心问题是:当多个异步操作存在依赖关系(后一个操作需要前一个的结果)时,回调函数会层层嵌套,形成 "金字塔" 式的代码结构,最终变得难以理解和修改。
为什么会出现回调地狱?
JavaScript 是单线程语言,处理异步操作(如网络请求、文件读写、定时器)时,必须通过回调函数指定操作完成后的逻辑。如果异步操作存在依赖(比如 "先获取用户信息→再用用户 ID 获取订单→再用订单 ID 获取商品"),后一个操作的回调必须嵌套在前一个操作的回调中。随着依赖步骤增加,嵌套层级会越来越深,形成 "地狱"。
典型示例:回调地狱长什么样?
假设我们需要模拟三个依赖的异步操作,用嵌套回调实现会是这样:
javascript
// 模拟异步操作:获取用户
function getUser(userId, callback) {
setTimeout(() => {
console.log("1. 获取用户信息成功");
callback({ id: userId, name: "小明" }); // 回调传递用户数据
}, 1000);
}
// 模拟异步操作:用用户ID获取订单
function getOrder(userId, callback) {
setTimeout(() => {
console.log("2. 根据用户ID获取订单成功");
callback({ orderId: "order_001", userId }); // 回调传递订单数据
}, 1000);
}
// 模拟异步操作:用订单ID获取商品
function getProduct(orderId, callback) {
setTimeout(() => {
console.log("3. 根据订单ID获取商品成功");
callback({ productId: "prod_123", orderId }); // 回调传递商品数据
}, 1000);
}
// 多层嵌套的回调(回调地狱)
getUser(101, (user) => {
getOrder(user.id, (order) => {
getProduct(order.orderId, (product) => {
console.log("最终结果:", product);
// 如果还有更多步骤,这里会继续嵌套...
});
});
});
这段代码的问题:
- 层级过深(像 "金字塔" 一样向右延伸),阅读时需要逐层查找逻辑。
- 维护困难:修改某一步逻辑时,需要在多层嵌套中定位。
- 错误处理繁琐:每个回调都要单独处理错误,容易遗漏。
如何解决回调地狱?
核心思路是**避免嵌套,让代码 "纵向扁平",**用 Promise 链式调用替代嵌套。
Promise
Promise 代表一个尚未完成但最终会有结果的异步操作 (如网络请求、文件读写、定时器等)。它有且仅有三种状态,且状态一旦改变就不可逆:
- pending(等待中):初始状态,异步操作尚未完成。
- fulfilled(已成功) :异步操作完成,结果可用(由
resolve触发)。 - rejected(已失败) :异步操作出错,原因可用(由
reject触发)。
基本用法:创建与使用 Promise
通过 new Promise(executor) 创建实例,executor 是一个立即执行的函数,接收两个参数:
resolve(value):异步成功时调用,将状态转为fulfilled,并传递结果value。reject(error):异步失败时调用,将状态转为rejected,并传递错误error(通常是Error对象)。
javascript
// 模拟"读取文件"的异步操作(1秒后随机成功/失败)
const readFilePromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.3; // 70% 概率成功
if (success) {
resolve("文件内容:Hello World"); // 成功时传递结果
} else {
reject(new Error("文件不存在")); // 失败时传递错误
}
}, 1000);
});
处理异步结果
Promise 实例通过以下方法定义状态改变后的逻辑,且这些方法返回新的 Promise ,因此支持链式调用。
1. then(onFulfilled?, onRejected?)
- 作用:处理
fulfilled或rejected状态的结果。 - 参数:
onFulfilled:状态为fulfilled时执行,接收resolve传递的value。onRejected:状态为rejected时执行,接收reject传递的error(可选,通常用catch替代)。
- 返回值:新的 Promise,其状态由回调函数的返回值决定(若返回 Promise,则跟随其状态;否则直接
fulfilled)。
2. catch(onRejected)
- 作用:专门处理
rejected状态,等价于then(null, onRejected)。 - 优势:更清晰地集中捕获错误(包括
then回调中抛出的错误)。
3. finally(onFinally)
- 作用:无论状态是
fulfilled还是rejected,都会执行(如关闭加载动画、清理资源)。 - 特点:不接收参数(无法获取成功 / 失败的结果),返回新的 Promise(状态与原 Promise 一致)。
javascript
readFilePromise
.then((content) => {
console.log("读取成功:", content);
return content.toUpperCase(); // 返回处理后的值,传给下一个then
})
.then((upperContent) => {
console.log("转换为大写:", upperContent); // 输出:读取成功的内容的大写形式
})
.catch((error) => {
console.log("出错了:", error.message); // 捕获所有上游错误(如"文件不存在")
})
.finally(() => {
console.log("操作结束(无论成败)"); // 必然执行
});
async/await
async/await 是 ES2017(ES8)引入的语法糖,基于 Promise 实现,用于简化异步代码的编写。它让异步逻辑的写法更接近同步代码,解决了 Promise 链式调用可能带来的代码冗余问题,进一步提升了可读性和可维护性。
核心作用
async:修饰函数,表明该函数内部有异步操作,函数的返回值会自动包装为一个Promise(无论是否显式返回 Promise)。await:只能在async函数内部使用,用于 "等待" 一个 Promise 完成,暂停当前函数执行,直到 Promise 状态变为fulfilled(成功)或rejected(失败),再继续执行后续代码。
基本用法
1. async 函数的定义
在函数声明、函数表达式、箭头函数前添加 async 关键字,即可定义一个 async 函数:
javascript
// 函数声明
async function fn1() {}
// 函数表达式
const fn2 = async function () {};
// 箭头函数
const fn3 = async () => {};
async 函数的返回值规则:
- 若返回非 Promise 值(如
123、{a:1}),则自动包装为Promise.resolve(返回值)。 - 若返回 Promise,则直接返回该 Promise(状态由其自身决定)。
- 若抛出错误(
throw new Error(...)),则自动包装为Promise.reject(错误)。
示例:
javascript
async function test() {
return 100; // 等价于 return Promise.resolve(100)
}
test().then(res => console.log(res)); // 输出:100
async function test2() {
throw new Error("出错了"); // 等价于 return Promise.reject(new Error("出错了"))
}
test2().catch(err => console.log(err.message)); // 输出:出错了
2. await 的使用
await 后必须跟一个 Promise 对象 (若不是,会自动用 Promise.resolve() 包装),作用是 "等待" 该 Promise 完成:
- 若 Promise 成功(
fulfilled),await会返回 Promise 的结果(resolve的值)。 - 若 Promise 失败(
rejected),await会抛出错误(需用try/catch捕获)。
示例:用 async/await 处理异步操作
javascript
// 模拟一个异步请求(返回Promise)
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: "张三", age: 20 });
}, 1000);
});
}
// 用async/await调用异步函数
async function getUser() {
// 等待fetchData的结果(1秒后返回)
const user = await fetchData();
console.log(user); // 输出:{ name: "张三", age: 20 }(1秒后执行)
return user;
}
getUser();
错误处理
await 等待的 Promise 若失败(rejected),会抛出错误,需用 try/catch 捕获(替代 Promise 的 catch 方法):
示例:处理异步错误
javascript
function riskyTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("操作失败!")); // 模拟失败
}, 1000);
});
}
async function handleTask() {
try {
// 等待可能失败的Promise
const result = await riskyTask();
console.log("成功:", result); // 若成功则执行(此处不会执行)
} catch (error) {
// 捕获失败的错误
console.log("失败:", error.message); // 输出:失败:操作失败!
}
}
handleTask();
与 Promise 链式调用的对比
async/await 本质是 Promise 的语法糖,但比链式调用更直观。
Promise 链式调用:
javascript
运行
javascript
fetchUser()
.then(user => fetchOrder(user.id))
.then(order => fetchProduct(order.id))
.then(product => console.log(product))
.catch(err => console.log(err));
-
async/await写法:javascript
运行
javascriptasync function getResult() { try { const user = await fetchUser(); const order = await fetchOrder(user.id); const product = await fetchProduct(order.id); console.log(product); } catch (err) { console.log(err); } } getResult();
显然,async/await 的代码更接近同步逻辑,层级扁平,可读性更高。
node.js的安装
https://nodejs.org/zh-cn/download/
进入网址后,按照步骤一步步安装即可,不要下载最新版就行。

数据请求
在编程中,"数据请求" 通常指通过网络(如 HTTP/HTTPS)从服务器获取或提交数据(如 API 接口调用)。不同环境(浏览器 / Node.js)有不同的实现方式。
数据请求的核心目的
前端发起数据请求,本质是通过 HTTP/HTTPS 协议与后端接口通信,获取后端存储的数据(如你的商品列表、用户信息),或提交前端数据(如表单、订单),最终服务于业务逻辑(如渲染商品、展示用户信息)。
核心特点:异步性(请求需要时间,不会阻塞前端代码执行,必须通过特定方式处理结果)。
我们使用一个京东案例,采用原生Ajax实现异步。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
* {
padding: 0;
margin: 0;
}
html,
body {
background-color: #F5F6FA;
/* height: 100%; */
width: 100%;
display: flex;
/* align-items: center; */
/* border: 1px solid black; */
display: flex;
flex-direction: column;
}
.divtop {
-webkit-font-smoothing: antialiased;
font: 12px/1.5 PingFang SC, Source Han Sans CN, Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
margin: 0;
padding: 0;
color: #666;
text-decoration: none;
width: 100%;
/* cursor: pointer; */
display: block;
background-color: rgb(255, 63, 75);
height: 60px;
text-align: center;
}
.app {
display: flex;
flex-wrap: wrap;
justify-content: center;
/* align-items: center; */
background-color: white;
}
.just {
display: flex;
flex-direction: column;
/* justify-content: center; */
/* align-items: center; */
/* height: 100%; */
width: 15%;
padding: 5px;
}
.just:hover {
border: 2px red solid;
border-radius: 10px;
cursor: pointer;
}
.content {
width: 100%;
overflow: hidden;
/* 必须:隐藏超出的行 */
text-overflow: ellipsis;
/* 最后一行末尾显示省略号 */
display: -webkit-box;
/* 开启弹性盒模型(用于多行控制) */
-webkit-line-clamp: 2;
/* 限制最大显示行数(关键) */
-webkit-box-orient: vertical;
/* 垂直排列文本(关键) */
padding: 0 2px;
/* 可选:长单词/长数字换行 */
word-break: break-all;
font-size: 16px;
}
.t,
.s {
font-size: 14px;
color: gray;
}
.p {
font-weight: bold;
font-size: 18px;
color: red;
}
.item {
position: relative;
}
.i2 {
position: absolute;
right: 0;
top: 0;
}
.s:hover {
color: red;
}
ul {
display: flex;
list-style: none;
}
li {
padding: 5px;
}
.divtop2 {
height: 30px;
font-size: 14px;
color: #666;
display: flex;
justify-content: space-between;
}
ul :hover {
color: red;
cursor: pointer;
/* text-decoration: underline; */
}
.divtop2 ul li:nth-child(1) {
position: relative;
}
.divtop2 ul li:nth-child(1):hover .city {
display: block;
}
.city {
position: absolute;
/* background-color: yellow; */
background-color: #fff;
height: 100px;
width: 100px;
z-index: 1;
font-size: 14px;
color: #000;
display: none;
}
.city span:hover {
background-color: red;
color: #fff;
width: 100%;
}
</style>
<body>
<div class="divtop">
<img width="10%"
src="https://img20.360buyimg.com/img/jfs/t1/339448/9/18317/1603/68e931c0F85a2f55c/6d0a0e2999849996.png"
alt="">
<img width="65%" src="./img/2.png" alt="">
</div>
<div class="divtop2">
<div class="left">
<ul>
<li>
<div>中国大陆-北京</div>
<div class="city">
<span>中国大陆版</span><br>
<span>中國港澳版</span><br>
<span>中國台灣版</span><br>
<span>京东全球版</span>
</div>
</li>
<li>你好,先登录<span style="margin-left: 10px;color: red;">免费注册</span></li>
<li>切换企业版</li>
</ul>
</div>
<div class="right">
<ul>
<li>购物车</li>
<li>我的订单</li>
<li>我的京东</li>
<li>企业采购</li>
<li>商家服务</li>
<li>网页导航</li>
<li>手机京东</li>
<li>网站无障碍</li>
</ul>
</div>
</div>
<div class="app">
<!-- <div class="just"> -->
<!-- <div class="item">
<img width="100%" src="https://ts1.tc.mm.bing.net/th/id/OIP-C.P1sp8dyDEIRwTuSVCbReiQAAAA?rs=1&pid=ImgDetMain&o=7&rm=3" alt="">
</div>
<div class="item">
描述
</div>
<div class="item">
标签
</div>
<div class="item">
价格
</div>
<div class="item">
店铺
</div>
</div> -->
<!-- </div> -->
</body>
<script>
//
let xhr = new XMLHttpRequest()
xhr.open('get', "http://localhost:8889/data", true)
xhr.send()
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
// console.log(xhr.responseText);
let jds = JSON.parse(xhr.responseText)
// console.log(jds);
jds.myData.jds.forEach(element => {
document.querySelector('.app').innerHTML += `
<div class="just">
<div class="item i">
<img width="100%" src="${element.img}" alt="">
<img class="i2" width="20%" src="https://m.360buyimg.com/umm/jfs/t1/344271/40/7695/1784/68d666cfF0a0873a3/2666ed87fae11ac8.png" alt="">
</div>
<div class="item content">
${element.content}
</div>
<div class="item t">
${element.title}
</div>
<div class="item p">
¥${element.price}
</div>
<div class="item s">
${element.store}
</div>
</div>
</div>
`
});
}
}
</script>
</html>