【1 需求分析】采用原生javascript搭建音乐播放器

盼望着,盼望着,终于放暑假了!放暑假,干点什么好呢,想来想去还是做个音乐播放器吧!接下来几天,我将运用所学,搭建一个音乐播放器~
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 包装后按照规则运行。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

007的米奇妙妙屋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值