博客列表 >异步之xhr与fecth的优化与模块

异步之xhr与fecth的优化与模块

风车
风车原创
2022年08月12日 21:14:04513浏览

同步与异步

同步:顺序执行,优点是静态预判结果可控, 缺点就是如果某一步任务执行慢,那就会影响到后续的所有代码
异步:乱序执行,优点就是不会阻塞代码,但是缺点也很明显就是顺序不可控

以银行排队办业务为例

  1. * 1. 同步: 默认排队叫号, 依次办理
  2. * 2. 异步: 耗时任务(如修改密码忘带身份证)则离开队列, 后面任务继续
  3. * 3. 任务队列: 取了身份证回来了, 就待在"任务队列"中等待再次叫号

哪些是异步任务(耗时)?

  1. * 1. 定时任务: setTimeout, setInterval
  2. * 2. 事件监听: addEventListener
  3. * 3. 网络请求: ajax, promise,fetch
  4. * 4. 文件读写等涉及IO的操作

(二) 进程与线程

  1. * 1. 进程: 程序的运行状态, 执行实例
  2. * 2. 一个cpu同一时刻只能执行一个进程,通过上下文切换实现多任务,除非多核
  3. * 3. 线程: 进程中的某个任务,即一个进程,可以由多个线程完成
  4. * 4. js的特征,决定了它只能是单线程,例如dom操作中, 增删元素就不可能同时进行
  5. * 5. 单线程可确保js按用户要求的顺序执行,并确定业务逻辑正确,结果可控
  6. * 6. 但是单线程,也决定了所有任务必须在一个执行栈中完成,导致耗时任务必然会阻塞整个线程
  7. * 7. 解决文案: 任务队列与事件循环(事件轮询)

比较通俗的解释:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

(三) 单线程,任务队列,事件循环之间的关系与协同

  1. * 1. js所有任务都在主线程中"同步"执行
  2. * 2. 异步任务以回调的形式声明,并离开主线程,交给多线程的浏览器去执行
  3. * 3. 异步任务执行完毕,进入到"任务队列"中排队等待进入主线程执行
  4. * 4. 主线程同步任务全部完成后, 通过"事件循环"查询"任务队列"中是否有等待的任务
  5. * 5. 如果有,则该就绪任务进入主线程同步执行
  6. * 6. 该任务完成后, 事件循环会再次取出下一个就绪的异步任务进入主线程
  7. * 7. 以上过程不断重复, 直到主线程中的同步任务, 任务队列中的异步任务全部执行完毕
  8. */

下面的代码按正常顺序会输出100 .200 .300 但是因为输出200的地方是一个定时器,也就是异步任务,因为在JS中会先执行同步任务,在执行异步任务 所以会先输出100.300 最后再输出200.

  1. console.log(100);
  2. setTimeout('console.log(200)', 1000);
  3. console.log(300);

ajax-xhr编程步骤

传统方式,信息会在新页面中或者弹窗中查看,用户体验不好 —>
Ajax: 局部更新,用户不需要离开当前页面就可以看到新内容, 单页面应用SPA(基于异步任务)
传统XHR的编程步骤
**

  1. * 1. 创建对象: new XMLHttpRequest();
  2. * 2. 响应类型: xhr.responseType = "json";
  3. * 3. 配置参数: xhr.open("GET", url, true);
  4. * 4. 请求回调: xhr.onload = () => console.log(xhr.response);
  5. * 5. 失败回调: xhr.onerror = () => console.log("Error");
  6. * 6. 发起请求: xhr.send(null);
  7. *
  8. * xhr 至少监听2个事件: load,error, 调用2个函数: open,send
  9. * post请求,需要设置一下请求头与请求的数据,其它与get请求完全相同
  10. */

下面是实例演示
在页面创建两个按钮并创建点击事件

  1. <button onclick="getUser1(this)">查询用户信息: XHR</button>
  2. <button onclick="getUser2(this)">查询用户信息: Fetch</button>

然后根据上面的步骤一步一步创建xhr

  1. function getUser1(btn) {
  2. //1. 创建对象
  3. const xhr = new XMLHttpRequest();
  4. //2. 响应类型
  5. xhr.responseType = 'json';
  6. //3. 配置参数
  7. // 不传get参数,默认返回全部用户, 用表格
  8. let url = 'http://website.io/users.php';
  9. // 如果有get参数,就是用户id,输入对应的id 获取对应的值
  10. url = 'http://website.io/users.php?id=1';
  11. //url:当前的服务器地址
  12. xhr.open('GET', url);
  13. //4. 请求回调
  14. xhr.onload = () => {
  15. console.log(xhr.response);
  16. // 将数组渲染到页面中
  17. render(xhr.response, btn);
  18. };
  19. //5. 失败回调
  20. xhr.onerror = () => console.log('Error');
  21. //6. 发起请求
  22. xhr.send(null);
  23. }

fetch请求语法
fetch: 基于Promise, 返回 Promise对象

  1. // fetch
  2. /**
  3. * fetch(url) //当前请求的地址
  4. * .then(res) //then(然后的意思)链式执行,一步一步的写
  5. * .then(...)
  6. * .catch(err)
  7. */
  8. function getUser2(btn) {
  9. let url = 'http://website.io/users.php';
  10. // fetch的所有操作都是异步的,但是通过then使用顺序可控
  11. fetch(url)
  12. .then(function (response) {
  13. return response.json();
  14. })
  15. .then(function (json) {
  16. // 控制台
  17. console.log(json);
  18. // 将数组渲染到页面中
  19. render(json, btn);
  20. });
  21. }

现在推荐使用fetch请求,更加简便明了

fetch 请求中的api(浏览器原生实现的异步技术,可以直接用)
fetch: 基于Promise, 返回 Promise对象
随机生成json数据的网站:http://jsonplaceholder.typicode.com/users
通过上方的网站可以生成一组假的JSON数据
然后通过上方网站提供的代码访问拿到其中的第一条数据(以列表的形式渲染到页面)
(路径最后的todos/1 表示第一条数据)
这里如果直接访问会出现跨域的错误,需要在php中进行处理

  1. fetch('https://jsonplaceholder.typicode.com/todos/1')
  2. .then(response => response.json())
  3. .then(json => console.log(json))

如果我们想拿到所有的一组数据,就将路径直接改为/users(如果请求一组数据,会以表格的形式渲染到页面)

  1. fetch('https://jsonplaceholder.typicode.com/users')
  2. .then(response => response.json())
  3. .then(json => console.log(json));

上方的代码就是请求数据接口的代码,后期可以直接将需要访问的接口网址直接替换进去

出现跨域请求是因为同源策略

同源策略要求:协议相同,域名相同,端口相同,否则就是跨域请求,这是禁止的(这个禁止是脚本禁止,标签的话一般不受影响)
js脚本禁止跨域是因为js脚本可以动态的进行dom操作,对网站进行增删改查等,这样又很大的安全隐患

  1. //写在PHP里面,表示允许所有跨域请求
  2. header("access-control-allow-origin: *");

创建一个按钮。点击之后会返回对应的数据,其实就是创建一个点击事件,点击之后会通过上面的方法,访问php数据,然后传回并渲染到页面

  1. function getUser(btn) {
  2. let url = 'http://website.io/users.php?id=1';
  3. fetch(url)
  4. .then(response => response.json())
  5. .then(json => {
  6. console.log(json);
  7. // 将数组渲染到页面中
  8. render(json, btn);
  9. });

ECMA2017中, 有async,await,用来简化异步回调方法
首先在上方对象的前面加上一个async关键字(这个关键字的意思是,这是一个异步操作)
然后在下面创建一个常量接收到数据,在fetch之前加上 await关键字(这个关键字只能在异步操作中使用)

  1. async function getUser(btn) {
  2. let url = 'http://website.io/users.php';
  3. // 这是一个异步耗时操作,需要等待结果才可进入下一步
  4. const response = await fetch(url);
  5. // 获取到结果之后,再转为json
  6. const result = await response.json();
  7. console.log(result);
  8. // 将数组渲染到页面中
  9. render(result, btn);
  10. }

模块

模块成员的导出与导入
js中, 所有代码共用一个全局作用域: window,有时候会产生一些冲突,在ES6中,可以通过模块的来解决这个问题
1.一个模块就是一个JS文档
2.模块有自己独立的作用域
3.模块内的成员,默认全部私有,只有导出才能被外部使用
导出与导入
1.逐个导出:

  1. //第一个方法:在变量前面加上export
  2. export let username = '猪老师';
  3. //第二种方法:也是添加export 只是这是导出一个函数
  4. export function hello(username) {
  5. return `Hello, ${username}`;
  6. }

导入模块
通过import关键字导入
默认script在浏览器环境下不支持模块,需要指定type

  1. <script type="module">
  2. // js中, 所有代码共用一个全局作用域: window
  3. // 导入模块
  4. //import 导入模板中的变量或函数,需要用模板字面量来接收,这个模板字面量的名称要与模板中定义的变量或者函数名一致
  5. import { username, hello } from './modules/m1.js';
  6. console.log(username);
  7. console.log(hello(username));
  8. </script>

2.逐个导出

  1. // (1) 声明
  2. let username = '猪老师';
  3. function hello(username) {
  4. return `Hello, ${username}`;
  5. }
  6. // (2) 导出: 对象字面量 {...}
  7. export { username, hello };

在导出或者导入时,可能会出现命名重复,这时候可以使用别名导出

  1. let username = '欧阳老师';
  2. function hello(username) {
  3. return `Hello, ${username}`;
  4. }
  5. // (2) 别名导出: 隐藏模块成员的真实名称, as
  6. export { username as myname, hello as getName };

在导出的时候通过as关键字,为需要到导出的数据定义一个别名,同样的,在使用别名后,导入使用时也需要使用别名导入

  1. <script type="module">
  2. import { myname,getName } from './modules/m2.js';
  3. console.log(myname);
  4. console.log(getName(myname));
  5. </script>

如果导入时 导出时定义的别名还是已经被定义了,还是可以在导入时使用as关键字再次重命名,然后调用时需要用最新的名字

  1. <script type="module">
  2. import { myname as name,getName as get } from './modules/m2.js';
  3. console.log(name);
  4. console.log(get(name));
  5. </script>

默认导出
一个模块,只能有一个默认导出
默认导出,导出的是一个值,没有命名,由导出的时候再命名

  1. 定义一个类,然后直接导出
  2. export default class {
  3. constructor(username) {
  4. this.username = username;
  5. }
  6. getUsername() {
  7. return `Hello, ${this.username}`;
  8. }
  9. }

默认导入

  1. <script type="module">
  2. // 导入模块的默认成员不要用对象字面量,用标识符
  3. // 只要不用对象字面量接收模块成员,就是默认导出
  4. import User from './modules/m3.js';
  5. //因为默认导出的是一个类,所以需要new一个 再赋值输出
  6. const user = new User('王老师');
  7. console.log(user.username);
  8. console.log(user.getUsername(user.username));
  9. </script>

混合导出(默认成员与非默认成员的导出)

  1. // 默认成员与非默认成员的导出
  2. let username = '牛老师';
  3. function hello(username) {
  4. return `Hello, ${username}`;
  5. }
  6. let useremail = 'admin@php.cn';
  7. // 私有成员, 不对外
  8. let sex = 'male';
  9. // 将邮箱 useremail 做为默认导出 , 起一个特殊的别名 default
  10. export { username, hello, useremail as default };

混合导入

  1. <script type="module">
  2. // 接收混合导入成员,默认成员的命名,必须放在非默认的前面
  3. //默认导入是可以随便命名,因为 默认导出时没有命名
  4. import email, { username, hello } from './modules/m4.js';
  5. console.log(email);
  6. console.log(hello(username));
  7. </script>

命名空间
导入的成员,过多时,使用{…}会很长的
可以将所有的导入成员,全部挂载到一个对象上
此时, 导入模块的成员,自动成为该对象上的属性
这个对象就是: 命名空间
注释:在导入时候 用 * (命名空间名) 将所有的对象全部挂载到同一个新的命名空间里面

  1. import * as user from './modules/m4.js';
  2. console.log(user);

如何访问这个新命名空间里面的成员
在访问命名空间的模块成员是,直接添加命名空间前缀

  1. console.log(user.username);
  2. console.log(user.hello(user.username));
声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议