盼望着,盼望着,终于放暑假了!放暑假,干点什么好呢,想来想去还是做个音乐播放器吧!接下来几天,我将运用所学,搭建一个音乐播放器~
1.项目架构
2.技术架构
3.开发环境
开发环境约束:
开发工具:VSCode
开发语言:HTML5 、CSS3 、JavaScript
时间约束:建议开发周期控制在 2 周内,需要开发者合理规划好时间
技术约束:HTML5 、CSS3 、JavaScript(ES6)、Ajax
4.所用知识点
1.模块化
2.Ajax使用
3.Promise异步处理
为什么使用模块化?
1.页面交互逻辑以及页面数量增加,JS代码复杂性增加
2.更多的代码引发了全局变量污染、依赖关系混乱
本项目采用ESM作为成熟的模块化解决方案
ESM的使用
<script src="入口文件" type="module">
基本导出:类似于 exports.xxx = xxxx ,基本导出可以有多个,每个必须有名称。
export 声明表达式
或
export {具名符号}
基本导入:由于使用的是依赖预加载,因此,导入任何其他模块,导入代码必须放置到所有代码之前。对于基本导出,如果要进行导入,使用下面的代码。
import { 导入的符号列表 } from "模块路径";
细节:
通过关键字as对导入的符号进行重命名
导入时使用的符号是常量,不可修改
使用*号导入所有的基本导出
// 基本导出
// 1.js(表示一个 js 文件,该文件导出 name,age,sex,fn)
export var name = "aaa";
export function fn() {}
var age = 18;
var sex = 1;
export { age, sex };
//基本导入
// 2.js(新建 2.js 文件,从 1.js 文件中导入变量 name,age,sex,fn )
import { name, age, sex, fn } from "./1.js";
console.log(name, age, sex, fn); // aaa 18 1 ƒ fn(){}
默认导出
每个模块只允许有一个基本的默认导出,默认导出类似于CommonJS中的module.exports,因为只有一个,所以不需要具体的名字
export default 默认导出的数据
或
export {默认导出的数据 as default}
默认导入
import 接收变量名 from "模块路径";
如果希望同时导入某个模块的默认导出和基本导出,可以使用下面的语法
import 接收默认导出的变量, { 接收基本导出的变量 } from "模块路径";
如果使用*号,会将所有基本导出和默认导出聚合到一个对象中,默认导出会座位属性default存在
// 默认导入
//3.js(新建 3.js 的文件,该文件导出fn)
export default function fn(){};
//4.js(新建 4.js 的文件,该文件导出一个对象 {a,name} )
function a(){}
let name = '111';
export default {a,name}
//5.js(新建 5.js 的文件,该文件导出一个对象 {a,name} )
function b(){}
let nameB = '111';
export {b,nameB}
//默认导出
//6.js(新建 6.js 的文件,从 3.js/4.js 文件中的导入变量 fn)
import fn from './3.js';
import * as all from "./4.js";
import * as allB from "./5.js";
console.log(fn, all.default.a ,all.default.name,allB.b , all.nameB)//ƒ fn(){} ƒ a(){} '111' ƒ b(){} '111'
Ajax
用于创建快速动态网页的技术,在后台与服务器进行少量数据交换,AJAX使得网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
Ajax给服务器发送请求的代码如下:
// 1. 创建 xmlHttpRequest 对象
let xmlhttp;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2. 设置回调函数
xmlHttp.onreadystatechange = callback;
// 3. 使用 open 方法与服务器建立连接
xmlhttp.open(method, url, async);
/*
method:请求的类型;GET 或 POST
url:文件在服务器上的位置
async:true(异步)或 false(同步)
*/
// 添加请求头信息(可选)
xmlhttp.setRequestHeader(header, value);
// 4. 使用 send 方法发送请求
xmlhttp.send(string);
/*
string:仅用于 POST 请求,格式可以为 multipart/form-data,JSON,XML
*/
当请求发送出去之后,会得到一个响应结果,在回调函数中针对不同的响应状态进行处理。
function callback() {
if (xmlHttp.readyState == 4) {
//判断交互是否成功
/*
readyState属性:表示请求/响应过程的当前阶段
0:未初始化。尚未调用 open()方法。
1:启动。已经调用 open()方法,但尚未调用 send()方法。
2:发送。已经调用 send()方法,但尚未接收到响应。
3:接收。已经接收到部分响应数据。
4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。
只有在XMLHttpRequest对象完成了以上5个步骤之后,才可以获取从服务器端返回的数据。
*/
if (xmlHttp.status == 200) {
/*
status属性:响应的 HTTP 状态码,常见的状态码如下
200:响应成功
301:永久重定向/永久转移
302:临时重定向/临时转移
304:本次获取内容是读取缓存中的数据
400:请求参数错误
401:无权限访问
404:访问的资源不存在
*/
//服务器的响应,可使用 XMLHttpRequest 对象的 responseText(获得字符串形式的响应数据) 或
// responseXML (获得 XML 形式的响应数据) 属性获得
let responseText = xmlHttp.responseText;
} else {
// 失败,根据响应码判断失败原因
}
}
}
针对响应成功和响应失败,除了使用状态码进行判断外,XMLHttpRequest 提供了响应成功和失败的 api 使用
//将请求时候步骤2改为以下代码
xmlHttp.onload = function () {};
//等效于
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status == 200) {
}
}
};
//将请求时候步骤2改为以下代码
xmlHttp.onerror = function () {};
//等效于
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status !== 200) {
}
}
};
事件循环
明确三个概念:
栈、堆、事件队列
function foo(b) {
let a = 10;
return a + b + 11;
}
function bar(x) {
let y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
这段代码应该这么理解:
把7代入bar这个函数,返回foo(21)
把21代入foo则10+21+22=42
当调用 bar 时,第一个帧被创建并压入栈中,帧中包含了 bar 的参数和局部变量。 当 bar 调用 foo 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含 foo 的参数和局部变量。当 foo 执行完毕然后返回时,第二个帧就被弹出栈(剩下 bar 函数的调用帧 )。当 bar 也执行完毕然后返回时,第一个帧也被弹出,栈就被清空了。
堆:对象被分配在堆中
队列:JS运行时包含了一个待处理消息的消息队列。
队列分为两种:
1.宏任务 计时器结束的回调、事件回调、http 回调等等绝大部分异步函数进入宏队列
2.微任务 MO,Promise产生的回调进入微队列
Promise异步处理
Promise的异步模型是一套异步处理的通用模型
1.ES6将异步模型分为两个阶段
unsettled:还在前期处理
settled:已经有结果且无法逆转
2.ES6将事情划分为三种状态:
pending:挂起 未决
resolved:已处理,可以按正常逻辑下去
rejected:已拒绝,有错
无论是阶段还是状态 都是不可逆的
3.不同的已决状态,决定了不同的处理
resolved 状态:这是一个正常的已决状态,后续处理表示为 thenable
rejected 状态:这是一个非正常的已决状态,后续处理表示为 catchable
4.整件事称为promise
const pro = new Promise((resolve, reject) => {
// 未决阶段的处理
// 通过调用resolve函数将Promise推向已决阶段的resolved状态
// 通过调用reject函数将Promise推向已决阶段的rejected状态
// resolve和reject均可以传递最多一个参数,表示推向状态的数据
});
pro.then(
(data) => {
//这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
//如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
//data为状态数据
},
(err) => {
//这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
//如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
//err为状态数据
}
);
未决阶段的处理函数是同步的,会立即执行
thenable 和 catchable 函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
pro.then 可以只添加 thenable 函数,pro.catch 可以单独添加 catchable 函数
在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向 rejected,并会被 catchable 捕获
一旦状态推向了已决阶段,无法再对状态做任何更改
Promise 并没有消除回调,只是让回调变得可控
// 题目一
const promise1 = new Promise((resolve, reject) => {
console.log("promise1");
resolve("resolve1");
});
const promise2 = promise1.then((res) => {
console.log(res);
});
console.log("1", promise1);
console.log("2", promise2);
/*
'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
*/
// 题目二
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
/*
1
2
4
"timerStart"
"timerEnd"
"success"
*/
//题目三
Promise.resolve().then(() => {
console.log("promise1");
const timer2 = setTimeout(() => {
console.log("timer2");
}, 0);
});
const timer1 = setTimeout(() => {
console.log("timer1");
Promise.resolve().then(() => {
console.log("promise2");
});
}, 0);
console.log("start");
/*
'start'
'promise1'
'timer1'
'promise2'
'timer2'
*/
为了简化 Promise api 的使用,ES2016 中新增了 async 和 await 两个关键字,下面我们来看下其简写方式又是怎么替代 Promise api 的。
async 和 await
async:目的是简化在函数的返回值中对 Promise 的创建,用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
async function test() {
console.log(1);
return 2;
}
//等效于
function test() {
return new Promise((resolve, reject) => {
console.log(1);
resolve(2);
});
}
await:await 关键字必须出现在 async 函数中,用在某个表达式之前,如果表达式是一个 Promise,则得到的是 thenable 中的状态数据
async function test1() {
console.log(1);
return 2;
}
async function test2() {
const result = await test1();
console.log(result);
}
test2();
function test1() {
return new Promise((resolve, reject) => {
console.log(1);
resolve(2);
});
}
function test2() {
return new Promise((resolve, reject) => {
test1().then((data) => {
const result = data;
console.log(result);
resolve();
});
});
}
test2();
如果 await 的表达式不是 Promise,则会将其使用 Promise.resolve 包装后按照规则运行。