1、变量、声明、传递 (值、引用)
javascript:void(0) 含义
javascript:void(0) 中最关键的是 void 关键字, void 是 JavaScript 中非常紧张的关键字,该操作符指定要盘算一个表达式但是不返回值。void() 仅仅是代表不返回任何值,但是括号内的表达式还是要运行,如:void(alert("Warnning!"))
href="#"与href="javascript:void(0)"的区别
- #包含了一个位置信息,默认的锚是#top也就是网页的上端。在页面很长的时候会使用#来定位页面的具体位置,格式为:# + id。
- 而 javascript:void(0), 仅仅表示一个死链接。
JavaScript 变量
JavaScript 变量均为 对象。当声明一个变量时,就创建了一个新的对象。 JavaScript 一切皆对象。typeof 操作符可以检测变量的类型。
- 值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
- 引用数据类型:对象(Object)、数组(Array)、函数(Function)、Date 等有多个值构成的可变长度的复杂类型。
- console.log(typeof "John") // 返回 string
- console.log(typeof 3.14) // 返回 number
- console.log(typeof false) // 返回 boolean
- console.log(typeof [1,2,3,4]) // 返回 object
- console.log(typeof {name:'John', age:34}) // 返回 object
复制代码 null 和 undefined 的值相称,但类型不等
- null 是一个只有一个值的特殊类型。表示一个空对象引用。用 typeof 检测 null 返回是object。可以设置为 null 来清空对象。var person = null; // 值为 null(空), 但类型为对象
- undefined 是一个没有设置值的变量。typeof 一个没有值的变量会返回 undefined。任何变量都可以通过设置值为 undefined 来清空。 类型为 undefined
var person; // 值为 undefined(空), 类型是undefined
person = undefined; // 值为 undefined, 类型是undefined
undefined:是所有没有赋值变量的默认值,主动赋值。
null:主动释放一个变量引用的对象,表示一个变量不再指向任何对象地址。当使用完一个比力大的对象时,需要对其举行释放内存时,设置为 null。
声明 (创建):var、let、const
- 使用 var 声明的全局作用域变量属于window 对象。
- 使用 let 声明的全局作用域变量不属于 window 对象。
- 使用 var 声明的变量在任何地方都可以修改。使用 var 可以重新声明已经存在的变量
- 在相同的作用域或块级作用域中,不能使用 let 重新声明已经存在的变量,
- let 声明的变量只在 let 命令所在的代码块内有效。
- 在相同的作用域或块级作用域中,不能使用const关键字来重置var和let关键字声明的变量。
- 在相同的作用域或块级作用域中,不能使用const关键字来重置const关键字声明的变量
- const 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
- var 关键字定义的变量可以先使用后声明。
- let 关键字定义的变量需要先声明再使用。
- const 用来声明一个只读的常量,声明时必须举行初始化,且初始化后不可再修改。
使用const 声明的数组,不能重新赋值给 变量arr,但可以修改数组中的元素:
const arr = []
arr.push('one');
arr.push('two');
arr.push('three');
console.log(arr)
在 JavaScript 中创建变量通常称为"声明"变量。使用 var 关键词来声明变量:var carname;
变量声明之后,该变量是空的(它没有值)。如需向变量赋值,使用等号:carname="Volvo";
也可以在声明变量时对其赋值:var carname="Volvo"; 示例:创建名为 carname 的变量,并向其赋值 "Volvo",然后把它放入 id="demo" 的 HTML 段落中:
var carname="Volvo";
document.getElementById("demo").innerHTML=carname;
let 变量 = ""; //当前代码块见效
const 常量 = "123456"; //被const创建出来的东西不可以被修改
var 和 let 区别。在JavaScript中,var 和 let 都用于声明变量,但它们之间存在几个关键的区别。
作用域
- var 声明的变量 作用域是分情况的:
假如在一个函数内部声明,则具有函数作用域,它仅在该函数整个内部可见。
假如在函数外部声明,它就在全局作用域内可见。
- let 声明的变量:
具有块级作用域,即它们只在包含它们的代码块(例如:if语句、循环体)内有效。
提升 (Hoisting)
- var声明的变量会被提升到它们所在作用域的顶部,也就是说在声明之前就可以访问这些变量,但是访问的效果是undefined。
- let声明的变量同样会在它们的作用域内被提升,但是不答应在声明之前访问它们(这会导致一个ReferenceError),这种行为被称为暂时性死区(Temporal Dead Zone,TDZ)。
重复声明
- 在相同的作用域内,用var声明的变量可以被重新声明。
- let声明的变量则不答应在相同的作用域内重复声明,实验这样做会引发错误。
全局对象属性
- 在全局作用域中,用var声明的变量会成为全局对象的属性(在欣赏器中是window对象,在Node.js中是global对象)。
- let声明的变量不会成为全局对象的属性。
考虑到var的一些特性(如变量提升和较宽松的作用域)大概引发意外的行为,今世JavaScript开发中优先使用let(和const,对于不需要重新赋值的变量)来声明变量,以促进更加可靠和可预测的代码编写风格。为什么发起使用let?
- 减少错误:let的块级作用域和暂时性死区特性有助于减少因变量提升或不小心的变量重复声明导致的错误。
- 提高代码可读性:let的块级作用域使得代码布局更加清晰,易于理解,特别是在循环和条件语句中。
- 更安全的作用域管理:使用let可以克制在不应该访问某个变量的作用域中误访问到它,从而提高代码的健壮性和安全性。
- 克制全局污染:由于let声明的变量不会成为全局对象的属性,这有助于克制不须要的全局命名空间污染。
一条语句,多个变量
可以在一条语句中声明很多变量。该语句以 var 开头,并使用逗号分隔变量即可:var lastname="Doe", age=30, job="carpenter";
声明也可高出多行:
- var lastname="Doe",
- age=30,
- job="carpenter";
复制代码 一条语句中声明的多个变量不可以同时赋同一个值:
- var x,y,z=1; // x,y 为 undefined, z 为 1。
复制代码
Value = undefined
在盘算机程序中,常常会声明无值的变量。未使用值来声明的变量,其值现实上是 undefined。
在执行过以下语句后,变量 carname 的值将是 undefined:var carname;
var 可以重新声明 变量
var 可以重新声明 JavaScript 变量,该变量的值不会丢失。
在以下两条语句执行后,变量 carname 的值依然是 "Volvo":
- var carname="Volvo";
- var carname;
复制代码
变量提升
示例:
- function fn(){
- console.log(name); // name 先使用,后声明并初始化
- var name = '变量提升';
- }
- fn()
复制代码
- 在其他编程语言里,变量必须是先声明然后才气使用,不答应先使用后声明。
- 但是在 js 里 "答应先使用后声明"。 由于在js执行的时候。它会起首检测你的代码,发现在代码中会有 name 使用,那么运行时就会变成如下的逻辑:
- function fn(){
- var name; // 直接把 name 的声明提到作用域的最前面,
- console.log(name); // 这里使用name, 但是 name 只是声明,没有初始化,所以打印 undefined
- name = '变量提升'; // 这里进行初始化
- }
- fn()
复制代码 这种把变量声明,提前到代码块作用域最前面运行的逻辑,被称为变量提升。
- 在JavaScript中,变量提升(Hoisting)是指 "变量、函数" 的声明,在编译阶段被移动到它们所在作用域的顶部的行为。变量提升 只适用于通过var关键字声明的变量和函数声明,不适用于通过let和const声明的变量,以及函数表达式。JavaScript 严格模式 (strict mode) 不答应使用未声明的变量。
- 变量提升 在其他语言里是绝对没有的,而且也不是什么好事变。在ES6中就明确了这样使用变量是不美满的,es6 提出使用 let 来声明变量,就不会出现该问题了。
JavaScript 只有声明的变量会提升,初始化的不会。示例:
- function test_1() {
- /*只有声明的变量会提升*/
- var x1 = 10; // 初始化 x
- y1 = 20;
- console.log(x1 + "" + y1); // y 是先使用,后面声明
- var y1; // 声明 变量 y
- console.log(x1 + "" + y1);
- }
- function test_2() {
- /*初始化的变量不会提升*/
- var x2 = 5; // 初始化 x
- console.log(x2 + "" + y2); // y 是先使用,后面声明和初始化
- var y2 = 7; // 初始化 y
- }
- test_1();
- test_2();
复制代码 函数声明的提升:
- sayHello();
- function sayHello() {
- console.log("Hello!");
- }
复制代码 例子中的函数调用可以正常工作,并打印出"Hello!",由于函数声明被提升到了作用域的顶部。
总结:理解变量提升对于编写可预测和易于维护的JavaScript代码至关紧张。保举的做法是总是在作用域的顶部声明变量,以克制由于变量提升导致的潜在错误。此外,鉴于let和const提供的额外安全性及其遵照块级作用域的行为,保举使用这两者而非var举行变量声明。
值传递,引用传递,共享传递
- 基本类型 是 不可变类型,传递时是传递的值。传值的意思就是:传内存拷贝。值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。基本类型参数的传递与基本类型的复制一样,传递的是变量值。场景:JavaScript 中假如传递给函数的形参是不可变类型,则在函数中改变形参的值不会影响外部实参的值。示例:JS的基本类型,是按值传递的。
- var a = 1;
- function foo(x) {
- x = 2;
- }
- foo(a);
- console.log(a); // 仍为1, 未受x = 2赋值所影响
复制代码 - 对象类型 是 可变类型,引用类型参数的传递与引用类型的复制一样,传递时是传递的内存地址。传引用的意思就是:传内存指针。引用数据类型:对象(Object)、数组(Array)、函数(Function)、Date 等有多个值构成的可变长度的复杂类型。场景:JavaScript 中假如传递给形参是对象类型,由于对象是可变(mutable),当修改形参对象的属性值,也会影响到实参的属性值。这意味着在函数内部对对象或数组的修改会反映到外部,由于它们都指向同一个内存地址。示例:
- var obj = {x : 1};
- function foo(o) {
- o.x = 3;
- }
- foo(obj);
- console.log(obj.x); // 3, 被修改了!
复制代码 说明 o 和 obj 是同一个对象,o 不是 obj 的副本。以是不是按值传递。 但这样是否说明 JS 的对象是按引用传递的呢?我们再看下面的例子:
- var obj = {x : 1};
- function foo(o) {
- o = 100;
- }
- foo(obj);
- console.log(obj.x); // 仍然是1, obj并未被修改为100.
复制代码 假如是按引用传递,修改形参o的值,应该影响到实参才对。但这里修改o的值并未影响obj。 因此JS中的对象并不是按引用传递。那么究竟对象的值在JS中怎样传递的呢?
- 共享传递:该求值策略被用于Python、Java、Ruby、JS等多种语言中。该策略中,基本类型按值传递,对象类型按共享传递的(call by sharing,也叫按对象传递、按对象共享传递)。共享传递和引用传递的区别在于:在共享传递中对函数形参的赋值,不会影响实参的值。如下面例子中,不可以通过修改形参o的值,来修改obj的值。
- var obj = {x : 1};
- function foo(o) {
- o = 100;
- }
- foo(obj);
- console.log(obj.x); // 仍然是1, obj并未被修改为100.
复制代码 "按值传递、按引用传递" 区别
- 按值传递(call by value) 是最常用的求值策略:函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低。
- 按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值假如被修改,实参也会被修改。同时两者指向相同的值。按引用传递会使函数调用的追踪更加困难,偶然也会引起一些微妙的BUG。
2、this 关键字
this 关键字对象详解:http://blog.csdn.net/chenchunlin526/article/details/78889301
this 是面向对象语言中的一个紧张概念,在 C++、JAVA,C#等大型语言中,this 指向运行时的当前对象。但是在 JavaScript中,由于 JavaScript 的动态性(表明执行,固然也有简朴的预编译过程),this 的指向在运行时才确定。也就是 JavaScript 中 this 会随着执行情况的改变而改变。这个特性带来了编程上的自由和机动,联合 apply、call、bind 方法,可以使 JS 变得异常强大。
this 的值通常是由所在函数的执行情况决定,也就是说要看函数是怎样被调用的。同一个函数每一次调用,this 都大概指向不同的对象;
- 没有绑定到任何对象的 变量、函数、属性 等,都是 绑定到 全局对象。
- this 永远指向末了调用它的那个对象
- 匿名函数的 this 永远指向 window
- 使用 call() 大概 apply() 的函数会直接执行
- bing() 是创建一个新的函数,需要手动调用才会执行
- 假如 call、appy、bind 接收到的第一个参数是空大概 null,undefine 的话,则会忽略这个参数
- forEach、map、filter 函数的第二个参数也是能显式绑定 this 的
谁调用大概哪个对象调用this所在的函数,this 就指向谁。this 永远指向 末了调用它 的 那个对象。但是 箭头函数 的 this 是由 外层作用域决定的
- 假如单独使用则 this 表示全局(Global)对象。在欣赏器中 window 就是该全局对象
- 在对象的方法中 this 表示该方法所属的对象
- var person = {
- firstName: "John",
- lastName : "Doe",
- id : 5566,
- fullName : function() {
- console.log(this.firstName + " " + this.lastName;);
- return this;
- }
- };
- console.log(person.fullName());
复制代码 - 在函数中 this 表示全局对象。但是在严格模式下 this 是未定义的(undefined)
- 在事件中,this 表示接收事件的元素。在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素。示例:事件中的 this
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>菜鸟教程(runoob.com)</title>
- </head>
- <body>
- <h2>JavaScript <b>this</b> 关键字</h2>
- <button onclick="this.style.display='none'">点我后我就消失了</button>
- </body>
- </html>
复制代码 - 改变 this 指向 (apply、call、bind)。在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们答应切换函数执行的上下文情况(context),即 this 绑定的对象。示例:使用 person2 作为参数来调用 person1.fullName 方法时, this 将指向 person2, 即便它是 person1 的方法:
- var person1 = {
- fullName: function() {
- return this.firstName + " " + this.lastName;
- }
- }
- var person2 = {
- firstName:"John",
- lastName: "Doe",
- }
- person1.fullName.call(person2); // 返回 "John Doe"
复制代码
new 绑定
对象构造函数中的 this,使用 new 创建 实例,this 会绑定到这个新实例对象。
- <script type="text/javascript">
- var name = "chunlynn";
- //构造函数
- function Foo(){
- this.name = 'linda';
- this.foo = function(){
- console.log(this.name);
- }
- }
- var obj = new Foo(); // 实例化对象
- obj.foo(); // 输出 linda
- </script>
复制代码 构造函数 和 普通函数 的区别:
- 构造的属性用 this.name="chunlynn",然后使用 new 创建 实例。
- 普通函数用 var name = 'linda'。
普通函数中的 this
在普通函数直接调用 this 时,则指向 window对象
- <script type="text/javascript">
- var name = "chunlynn";
- //普通函数
- function foo(){
- var name = 'linda';
- console.log(this.name);
- }
- foo(); //输出 chunlynn
- </script>
复制代码 下面再来看一个有些难度的:
- <script type="text/javascript">
- var name = "chunlynn";
- //构造函数
- function Foo (){
- this.name = 'linda';
- this.foo = function(){
- var name = 'doudou';
- return function(){
- console.log(this.name);
- };
- }
- }
-
- var obj = new Foo(); // 实例化对象
- obj.foo()(); //输出 chunlynn
- // 拆开分步执行如下:
- // var temp = obj.foo(); // obj.foo() 执行完成后返回一个函数
- // temp(); ---> window.temp(); 函数没有显示的赋值给一个变量,所以window对象接收这个函数
- // 所以在 window 对象的方法中,this.name 指的就是 window.name,所以输出 chunlynn
- </script>
复制代码
onclick(this) 中的 this
对于一个onclick属性,由于它是所属的HTML元素所拥有,this 指向该HTML元素。
- <a href="javascript:void(0);" title="a标签标题" onclick="test(this);">A标签测试</a>
- <script type="text/javascript">
- function test(obj){
- var value = obj.title;
- console.log(value); //这是一个标题!!
- }
- </script>
复制代码 onclick 为它所属的 <a> 元素所有,this 指向 <a> 元素。
定时器 setTimeout 中 this
定时器中的this指向window对象。由于定时器就是全局函数,由window调用。
示例:
- <script type="text/javascript">
- var name = "chunlynn";
- //普通函数
- function foo(){
- var name = 'linda';
- console.log(this.name);
- }
- setTimeout(foo, 5000); //output:chunlynn
- // this指向:谁调用或者哪个对象调用this所在的函数,this就指向谁。
- // 定时器为全局函数,由window调用,因此定时器中的this指向window对象。
- </script>
复制代码
函数嵌套中 this
箭头函数内部的 this 由 箭头函数所在的上下文确定。
- <script type="text/javascript">
- var name = "chunlynn";
- var obj = {
- name:'linda',
- fn: function(){
- console.log(this.name);
- },
- foo:function(){
- setTimeout(this.fn, 5000);
- }
- }
- obj.foo(); //output:chunlynn
- // this指向:谁调用或者哪个对象调用this所在的函数,this就指向谁。
- // 嵌套带入进去最终执行的就是
- // window.setTimeout(function (){console.log(this.name)}, 5000); this指向的就是window对象
- </script>
复制代码 换个思路,上面最终执行的代码为:
- <script type="text/javascript">
- window.setTimeout(
- function (){
- // this指向的就是window对象,直接包含this.name的为setTimeout函数
- console.log(this.name);
- }
- , 5000);
- </script>
复制代码 这样答案一览无余了吧,this 绑定到了 window 对象。
箭头函数内部的 this 由 箭头函数所在的上下文确定。以是,用call()大概apply()调用箭头函数时,无法对this举行绑定,即传入的第一个参数被忽略:
- var obj = {
- birth: 1990,
- getAge: function (year) {
- var b = this.birth; // 1990
- var fn = (y) => y - this.birth; // this.birth仍是1990
- return fn.call({birth:2000}, year);
- }
- };
- obj.getAge(2015); // 25
复制代码
改变 this 指向:apply、call、bind
使用 apply 、call 或 bind方法时都可以改变 this 指向。
使用 call() 大概 apply() 的函数是会直接执行的。
bind() 是创建一个新的函数,需要手动调用才会执行
- <script type="text/javascript">
- var name = "chunlynn";
- var obj = {
- name:'linda',
- foo:function(){
- console.log(this.name);
- }
- }
- var f = {};
- f.name = "doudou";
- f.fun = obj.foo; //将obj对象的foo函数赋值给f对象的fun属性。本质为复制。
- f.fun(); //output:doudou
- f.fun.apply(obj); //output:linda
- f.fun.apply(window); //output:doudou
- f.fun.apply(); //output:chunlynn
- // this指向:谁调用或者哪个对象调用this所在的函数,this就指向谁。
- </script>
复制代码
call 、bind 、 apply 这三个函数的参数:
- 第一个参数:在函数内部,this 指向第一个参数,
- 第二个参数差别就来了:
call 的参数是直接按原函数传递,参数之间全都用逗号分隔。
apply 的所有参数都必须放在一个数组里面。
bind 除了返回是函数以外,它的参数和 call 一样。
- var name = '小王', age = 17;
- var obj = {
- name: '小张',
- objAge:this.age,
- myFun:function () {
- console.log(this.name + "年龄" + this.age);
- }
- }
- var db = {
- name: '德玛',
- age: 99
- }
- obj.myFun.call(db,'成都','上海'); // 德玛 年龄 99 来自 成都去往上海
- obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海
- obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海
- obj.myFun.bind(db,['成都','上海'])(); // 德玛 年龄 99 来自 成都, 上海去往 undefined
复制代码 call 函数有(call的调用者表示函数的参数个数+1)个参数
- 参数1:会被赋值给 call 的调用者,在函数内部 this 指向第一个参数
- 参数 2,3,4 ...
a.call(n, i) 等同于:a(i), 同时 a函数的内部 this 指向 n
- // JavaScript 中一切皆对象,所以函数也是一个对象
- // 所以每个函数内部都会有一个this指针,该this就好比是python中的self
- function sign(n) {
- //this = 123
- console.log(`n ---> ${n}`)
- console.log(`this ---> ${this}`)
- }
- //基于call机制的函数调用
- //参数123会被赋值给sign函数内部的this,456会被赋值给sing的参数n。
- sign.call(123, 456)
- /*
- n ---> 456
- this ---> 123
- */
复制代码 apply() 的参数为空时,默认调用全局对象 window。
查看 chrome 欣赏器控制台:
"匿名函数" 中的 this
匿名函数的 this 永远指向 全局对象
"箭头函数" 中的 this
- 当创建一个函数,并将其赋值给一个变量时,这便是函数表达式。该函数是匿名的,这意味着它没有名字。
- 箭头函数在ES6被引入。是函数表达式的紧凑替换品,而且总是匿名的。语法:(params) => { <function body> }
ES6中的 "箭头函数" 相当于 "匿名函数" 以是没有自己的 this。但是和匿名函数不同的是 this 指向
- 匿名函数的 this 永远指向 全局对象
- 箭头函数中的 this 继续自外层代码块的 this。以是箭头函数定义在哪,this 就指向那。即箭头函数中:this的指向由外层作用域决定,而且指向函数定义时的this,而不是执行时的this。
示例:箭头函数定义在一个对象里,那箭头函数里的 this 就指向该对象。
- //es6中的双箭头操作符,类似于java中的lamada表达式
- <script type="text/javascript">
- var author = "chunlynn";
- var book = {
- name: 'linda',
- init: function(){
- setTimeout(ev=>{
- console.log(this.name);
- },3000);
- }
- }
- book.init(); //output:linda
- // this指向:谁调用或者哪个对象调用this所在的函数,this就指向谁。
- </script>
复制代码
总结
- ① 谁调用 大概 哪个对象调用 this 所在的函数,this 就指向谁。 假如有嵌套调用,则其值会被绑定到调用 this 所在函数的近来的父对象 ,岂论这个 this 出现在什么样的函数中,层次有多深,布局多复杂,只要看直接包含它的函数即可。
- ② “this” always refers to the “owner” of the function we're executing。
- ③ this 通常指向的是我们正在执行的函数自己,大概是,指向该函数所属的对象。
- ④ this 是 Javascript 语言的一个关键字,它代表函数运行时主动天生的一个内部对象,只能在函数内部使用。
3、JavaScript 异步编程
异步的概念
异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。
- 传统单线程编程中,程序的运行是同步的(同步不意味着所有步调同时运行,而是指步调在一个控制流序列中按次序执行)。
- 而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有次序关系。
同步按你的代码次序执行,异步不按照代码次序执行,异步的执行效率更高。如图通俗地表明一下异步:异步就是从主线程发射一个子线程来完成使命。
什么时候用异步编程
在前端编程中(以致后端偶然也是这样),在处置惩罚一些简短快速的操作时,例如盘算 1 + 1 的效果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时担当多方面的请求。以是,当一个事件没有竣事时,界面将无法处置惩罚其他请求。
现在有一个按钮,假如我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。为了克制这种情况的发生,我们常常用子线程来完成一些大概消耗时间足够长以至于被用户察觉的事变,好比读取一个大文件大概发出一个网络请求。由于子线程独立于主线程,以是纵然出现阻塞也不会影响主线程的运行。但是子线程有一个范围:一旦创建并执行了子线程后就会与主线程失去同步,我们无法确定它的竣事,假如竣事之后需要处置惩罚一些事变,好比处置惩罚来自服务器的信息,我们是无法将它归并到主线程中去的。为了办理这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步使命的效果处置惩罚。
回调函数
回调函数就是一个函数,它是在我们启动一个异步使命的时候就告诉它:等你完成了这个使命之后要干什么。这样一来主线程几乎不用关心异步使命的状态了,他自己会善始善终。
示例:
- function print() {
- console.log('test callback')
- }
- // setTimeout 会在子线程中等待 3 秒,在 setTimeout 函数执行之后主线程并没有停止
- setTimeout(print, 3000);
- console.log('main thread continue')
复制代码 这段程序中的 setTimeout 就是一个消耗时间较长(3 秒)的过程,它的第一个参数是个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待 3 秒,然后执行回调函数 "print",在命令行输出 "Time out"。固然,JavaScript 语法非常友好,不必单独定义一个函数 print ,通常将上面的程序写成:
- function print() {
-
- }
- setTimeout(print, 3000);
- console.log('main thread continue')
- setTimeout(function () {
- console.log('test callback');
- }, 3000);
复制代码
异步 AJAX
除了 setTimeout 函数以外,异步回调广泛应用于 AJAX 编程。见:AJAX 教程 | 菜鸟教程
XMLHttpRequest 常常用于请求来自长途服务器上的 XML 或 JSON 数据。一个标准的 XMLHttpRequest 对象往往包含多个回调:
示例:( 实验一下 )
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>菜鸟教程(runoob.com)</title>
- </head>
- <body>
- <p><strong>以下内容是通过异步请求获取的:</strong></p>
- <p id="demo"></p>
- <script>
- var xhr = new XMLHttpRequest();
-
- xhr.onload = function () {
- // 输出接收到的文字数据
- document.getElementById("demo").innerHTML=xhr.responseText;
- }
-
- xhr.onerror = function () {
- document.getElementById("demo").innerHTML="请求出错";
- }
-
- // 发送异步 GET 请求
- xhr.open("GET", "https://www.runoob.com/try/ajax/ajax_info.txt", true);
- xhr.send();
- </script>
- </body>
- </html>
复制代码 XMLHttpRequest 的 onload 和 onerror 属性都是函数,分别在它请求成功和请求失败时被调用。
假如你使用完整的 jQuery 库,也可以更加优雅的使用异步 AJAX:
示例:( 实验一下 )
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>菜鸟教程(runoob.com)</title>
- <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
- </script>
- <script>
- $(document).ready(function(){
- $("button").click(function(){
- $.get("/try/ajax/demo_test.php",function(data,status){
- alert("数据: " + data + "\n状态: " + status);
- });
- });
- });
- </script>
- </head>
- <body>
- <button>发送一个 HTTP GET 请求并获取返回结果</button>
- </body>
- </html>
复制代码
AJAX原理(含常见口试题)
:https://juejin.cn/post/6844904114896240647
AJAX 即 Asynchronous Javascript And XML(异步JavaScript和XML)是指一种创建交互式网页应用的网页开发技术,用于创建快速动态网页。它可以令开发者只向服务器获取数据(而不是图片,HTML文档等资源),互联网资源的传输变得亘古未有的轻量级和纯粹,这激发了广大开发者的创造力,使各式各样功能强大的网络站点,和互联网应用如雨后春笋一样平常冒出,不断带给人惊喜。
一、什么是 AJAX
Ajax 是一种异步请求数据的web开发技术,对于改善用户的体验和页面性能很有帮助。简朴地说,在不需要重新刷新页面的情况下,Ajax 通过异步请求加载背景数据,并在网页上出现出来。常见运用场景有表单验证是否登入成功、百度搜索下拉框提示和快递单号查询等等。
Ajax的目的是提高用户体验,较少网络数据的传输量。同时,由于AJAX请求获取的是数据而不是HTML文档,因此它也节省了网络带宽,让互联网用户的网络冲浪体验变得更加顺畅。
二、AJAX原理是什么
Ajax相当于在用户和服务器之间加了一个中间层,使用户操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,像一些数据验证和数据处置惩罚等都交给Ajax引擎自己来做,只有确定需要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。
Ajax的原理简朴来说通过XmlHttpRequest对象来向服务器发送异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。要清晰这个过程和原理,我们必须对 XMLHttpRequest有所相识。
XMLHttpRequest 是 ajax 的核心机制,它是在IE5中起首引入的,是一种支持异步请求的技术。简朴的说就是JavaScript可以向服务器提出请求和处置惩罚响应,而不阻塞用户。到达无刷新的效果。
发送 ajax 请求
- from flask import Flask, render_template, request # pip install Flask
- app = Flask(__name__)
- @app.route("/")
- def index():
- # 跳转到首页
- print("你曾经来过服务器")
- name = "alex"
- # 数据是在这里渲染后, 返回个客户端的html
- return render_template("index.html", name=name)
- if __name__ == '__main__':
- app.run()
复制代码 起首,用 Flask 创建一个背景服务器
html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <script src="/static/js/jquery.js"></script>
- </head>
- <body>
- 我就是一个传统的html页面, 我的名字是{{name}}
- </body>
复制代码 使用 jquery 来发送 ajax 的 get 请求
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <script src="/static/js/jquery.js"></script>
- <script>
- function setCookie(name, value) {
- let Days = 30;
- let exp = new Date();
- exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
- document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
- }
- $(function(){
- // 可以在js任意位置设置cookie信息
- setCookie("name", "i-have-a-dream")
- $(".get_btn").click(function(){
- $.ajax({
- url:"/ajax_get", // 服务器地址: 域名+url
- method:'get', // 发送get请求
- headers:{ // 添加请求头信息
- "token":"mememmememe",
- },
- data:{ // 传递参数
- name:'alex',
- _: new Date().getTime()
- },
- contentType:'application/json;charset=utf8',
- beforeSend: function(req){ // 也可以这样添加请求头信息
- req.setRequestHeader("tokken", "i-can-do-it-haha");
- },
- success: function(back){ // 请求成功后. 返回数据了. 要做什么?
- console.log(back);
- }
- });
- })
- })
- </script>
- </head>
- <body>
- 我就是一个传统的html页面, 我的名字是{{name}}
- <br/>
- <input type="button" class="get_btn" value="点我发送get_请求">
- <hr/>
- <a href="javascript:void(0);" class="post_btn" >点我发送post_请求</a>
- </body>
- </html>
复制代码 服务器处置惩罚ajax_get请求
- @app.route("/ajax_get")
- def ajax_get_req():
- # 接收cookie中的信息
- n = request.cookies.get('name')
- if not n:
- return "没有cookie就不要来了."
- # 接收header中的信息
- token = request.headers.get('token')
- if not token:
- return "没token还想来?"
- # Flask接收get请求的参数
- name = request.args.get('name')
- _ = request.args.get('_')
- if name and _:
- # 返回json
- return {"name":'alex', "id": 10086, "isMen": True}
- else:
- return "回家去吧"
复制代码 发送post请求(json)
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <script src="/static/js/jquery.js"></script>
- <style>
- #mask{
- position:fixed;
- top:0;
- left:0;
- right:0;
- bottom: 0;
- background-color: rgba(0,0,0, .3);
- color: #fff;
- font-size:30px;
- text-align: center;
- padding-top:300px;
- display:none;
- }
- </style>
- <script>
- $(function(){
- $(".post_btn").click(function(){
- $('#mask').css("display","block");
- $("#data_tbody").remove();
- $.ajax({
- url:'/ajax_post',
- method:'post',
- data: JSON.stringify({
- name:'alex',
- id:'10086'
- }),
- headers: { // 发送json数据. 要换这个头, 否则服务器收不到数据
- "Content-Type": "application/json;charset=utf-8"
- },
- dataType:"text",
- success:function(d){
- $('#mask').css("display","none"); // 设置不遮罩
- let data = JSON.parse(d);
- let tbody = $("<tbody id='data_tbody'></tbody>")
- data.forEach(function(item){
- let tr = `<tr><td>${item.id}</td><td>${item.name}</td><td>${item.age}</td></tr>`;
- tbody.append(tr);
- });
- $('table').append(tbody);
- }
- })
- });
- })
- </script>
- </head>
- <body>
- 我就是一个传统的html页面, 我的名字是{{name}}
- <br/>
- <input type="button" class="get_btn" value="点我发送get_请求">
- <hr/>
- <a href="javascript:void(0);" class="post_btn" >点我发送post_请求_加载一个表格试试</a>
- <hr/>
- <table width="80%" border="1">
- <thead>
- <tr>
- <td>id</td>
- <td>name</td>
- <td>age</td>
- </tr>
- </thead>
- </table>
- <div id="mask"><span>正在加载中......</span></div>
- </body>
- </html>
复制代码 Python 服务器
- from flask import Flask, render_template, request # pip install Flask
- import time
- import json
- app = Flask(__name__)
- @app.route("/")
- def index():
- # 跳转到首页
- print("你曾经来过服务器")
- name = "alex"
- return render_template("index.html", name=name) # 数据是在这里渲染后, 返回个客户端的html
- @app.route("/ajax_post", methods=['POST'])
- def ajax_get_post():
- # time.sleep(3)
- # 接收JSON数据
- print(request.json)
- lst = [
- {"id": 1, "name": "张飞", "age": 16},
- {"id": 2, "name": "孙斌", "age": 16},
- {"id": 3, "name": "樵夫", "age": 16},
- {"id": 4, "name": "大佬", "age": 16},
- ]
- return json.dumps(lst)
- if __name__ == '__main__':
- app.run()
复制代码
JavaScript 的 Promise
3 种 异步 用法
什么是异步编程:https://www.runoob.com/js/javascript-async.html
JavaScript Promise 对象:https://www.runoob.com/w3cnote/javascript-promise-object.html
明白话透彻讲解 Promise 的使用:https://juejin.cn/post/7011755708496478215
JavaScript 高级深入浅出:Promise 详解:https://juejin.cn/post/7063377198014529572
js 执行的时候,一次只能执行一个使命,这就会导致其他使命阻塞而无法运行。由于这个缺陷导致 js 的所有网络操作,欣赏器事件,都必须是异步执行。异步执行可以使用回调函数执行。
常见的异步模式有以下几种:
- 定时器。定时器是在固定时间触发某个回调函数。
- // setTimeout 示例
- function callBack(){
- console.log('执行完成')
- }
- console.log('before setTimeout')
- setTimeout(callBack,1000)// 1秒后调用callBack函数
- console.log('after setTimeout')
复制代码 但是,对于 ajax 网络请求就没有这么简朴了,大概有多个网络请求是关联的,先执行某个请求返回效果后,第一个返回效果作为第二个请求的参数,调用第二个网络请求。如此,假如业务复杂,网络请求太多时,回调也很多,容易出现回调地狱。如下示例:回调地狱。这还只是 3 个回调,假如几十个回调,那看起来相当酸爽!!!- setTimeout(function () {
- console.log("First");
- setTimeout(function () {
- console.log("Second");
- setTimeout(function () {
- console.log("Third");
- }, 3000);
- }, 4000);
- }, 1000);
复制代码 以是 Promise 出现了,专门办理异步回调地狱问题。JavaScript 的 Promise 是一个对象,代表异步操作是成功还是失败。Promise 答应对异步操作举行更优雅的管理,包括成功情况和错误处置惩罚。在已往,异步操作常常依靠回调函数,但是这会导致所谓的"回调地狱",特别是在处置惩罚多个异步操作时。Promise提供了一种更好的办理方案,答应链式调用和更简便的错误处置惩罚。
- 接口调用
- 事件函数
Promise 基本概念
Promise 翻译成中文:承诺、保证。
resolve 办理
reject 拒绝
通俗地讲 Promise 就像一个容器,里面存放着将来才会竣事,返回效果的容器(resolve、reject),返回的效果只需要在出口处接收就好了。从语法上讲,Promise 是一个对象,从它可以获取异步操作的消息。Promise 对象代表一个异步操作,有三种状态:
- Pending(挂起、待定):初始状态,既不是成功,也不是失败。
- Fulfilled(已实现):意味着操作成功完成。
- Rejected(已拒绝):意味着操作失败。
Promise 也有一些缺点。起首,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,假如不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目进步展到哪一个阶段(刚刚开始还是即将完成)。
Promise 构造函数
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地誊写复杂的异步使命。创建 Promise时,Promise 构造函数只有一个参数而且是一个函数,该函数是同步的而且会被立即执行,以是这个函数被称为起始函数。起始函数包含两个参数 resolve 和 reject。起始函数执行成功时可以调用resolve函数传递成功的效果。当起始函数执行失败时可以调用reject函数传递失败的缘故原由。
起始函数的两个参数通常命名为 resolve(用于成功时调用)和 reject(用于失败时调用)。
- resolve 和 reject 的作用域只有起始函数,不包括 then 以及其他序列;
- resolve 和 reject 并不能够使起始函数克制运行,别忘了 return。
Promise 还提供了.then(), .catch(), 和 .finally()方法来处置惩罚成功、失败和无论成功还是失败都要执行的代码。假如不需要给 then 中的函数传递返回值,则参数 resolve 和 reject 都可以省略
- then:用于处置惩罚 Promise 成功状态的回调函数。
- catch:用于处置惩罚 Promise 失败状态的回调函数。
- finally:无论 Promise 是成功还是失败,都会执行的回调函数。
Promise 使用示例:
- function send(arg_test) {
- return new Promise(function(resolve, reject) {
- resolve(`[成功 ---> ${arg_test}]`);
- reject(`[失败 ---> ${arg_test}]`);
- });
- }
- send("测试Promise").then(function (arg_success){
- console.log(`arg_success ---> ${arg_success}`);
- }, (arg_fail)=>{
- console.log(`arg_fail ---> ${arg_fail}`);
- }).catch(function (error) {})
复制代码 Promise 使用示例:
- // resolve 和 reject 都是函数。
- new Promise(function (resolve, reject) {
- console.log("Run");
- });
- // 这段程序会直接输出 Run。
- const p = new Promise((resolve, reject) => {
- setTimeout(() => {resolve('123')}, 1000)
- }).then(res => {
- console.log(res) //1秒后打印123
- })
复制代码 Promise 使用示例:
- let promise = new Promise(function (resolve, reject) {
- // 异步操作
- let isSuccess = true; // 假设这是根据异步操作结果得来的
- if (isSuccess) {
- resolve('操作成功');
- } else {
- reject('操作失败');
- }
- });
- promise.then((result) => {
- console.log(result); // "操作成功"
- }).catch((error) => {
- console.error(error);
- }).finally(() => {
- console.log("无论成功还是失败,都会执行");
- });
复制代码 示例:使用 Promise 构造函数创建了一个 Promise 对象,并使用 setTimeout 模仿了一个异步操作。假如异步操作成功,则调用 resolve 函数并传递成功的效果;假如异步操作失败,则调用 reject 函数并传递失败的缘故原由。然后我们使用 then 方法处置惩罚 Promise 成功状态的回调函数,使用 catch 方法处置惩罚 Promise 失败状态的回调函数。这段程序会直接输出 error 或 success。
- const promise = new Promise((resolve, reject) => {
- // 异步操作
- setTimeout(() => {
- if (Math.random() < 0.5) {
- resolve('success');
- } else {
- reject('error');
- }
- }, 1000);
- });
-
- promise.then(result => {
- console.log(result);
- }).catch(error => {
- console.log(error);
- });
复制代码 resolve 和 reject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的:
- new Promise(function (resolve, reject) {
- var a = 0;
- var b = 1;
- if (b == 0) reject("Divide zero");
- else resolve(a / b);
- }).then(function (value) {
- console.log("a / b = " + value);
- }).catch(function (err) {
- console.log(err);
- }).finally(function () {
- console.log("End");
- });
复制代码 Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处置惩罚序列,.finally() 是在 Promise 执行的末了一定会执行的序列。 .then() 传入的函数会按次序依次执行,有任何异常都会直接跳到 catch 序列:
- new Promise(function (resolve, reject) {
- console.log(1111);
- resolve(2222);
- }).then(function (value) {
- console.log(value);
- return 3333;
- }).then(function (value) {
- console.log(value);
- throw "An error";
- }).catch(function (err) {
- console.log(err);
- });
复制代码
Promise 静态方法
- Promise.all():接收一个promise数组,只有当所有promise都完成时,才会通过resolve返回所有效果,假如有一个失败,则立即通过reject返回。
- Promise.race():也是接收一个promise数组,但是只返回第一个完成(无论是成功还是失败)的promise的效果。
- Promise.resolve()、Promise.reject():快速创建一个状态为fulfilled或rejected的promise。
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.all([p1,p2,p3]);
代码中,Promise.all 方法担当一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
p 的状态由 p1、p2、p3 决定,分成两种情况。
- (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值构成一个数组,传递给p的回调函数。
- (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
下面是一个具体的例子。
- // 生成一个Promise对象的数组
- var promises = [2, 3, 5, 7, 11, 13].map(function(id){
- return getJSON("/post/" + id + ".json");
- });
-
- Promise.all(promises).then(function(posts) {
- // ...
- }).catch(function(reason){
- // ...
- });
复制代码 Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.race([p1,p2,p3]);
代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
假如Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处置惩罚。
Promise.resolve 方法,Promise.reject 方法
偶然需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
代码将 jQuery 天生 deferred 对象,转为一个新的 ES6 的 Promise 对象。
假如 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为fulfilled。
- var p = Promise.resolve('Hello');
-
- p.then(function (s){
- console.log(s)
- });
- // Hello
复制代码 代码天生一个新的Promise对象的实例p,它的状态为fulfilled,以是回调函数会立即执行,Promise.resolve方法的参数就是回调函数的参数。
假如Promise.resolve方法的参数是一个Promise对象的实例,则会被原封不动地返回。
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。
- var p = Promise.reject('出错了');
-
- p.then(null, function (s){
- console.log(s)
- });
- // 出错了
复制代码 代码天生一个Promise对象的实例,状态为rejected,回调函数会立即执行。
链式调用 实现 回调地狱
Promise 的一个紧张特性是可以链式调用,每个.then()或.catch()现实上也都会返回一个新的 Promise,让链式调用成为大概。链式调用可以直接执行一系列异步操作。
现在用 Promise 来实现上面 setTimeout 回调地狱的功能:
- new Promise(function (resolve, reject) {
- setTimeout(function () {
- console.log("First");
- resolve();
- }, 1000);
- }).then(function () {
- return new Promise(function (resolve, reject) {
- setTimeout(function () {
- console.log("Second");
- resolve();
- }, 4000);
- });
- }).then(function () {
- setTimeout(function () {
- console.log("Third");
- }, 3000);
- });
复制代码 使用 Promise 将嵌套格式的代码变成了次序格式的代码。代码看着更清晰。也可以将核心部分写成一个 Promise 函数。这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。
- function print(delay, message) {
- return new Promise(function (resolve, reject) {
- setTimeout(function () {
- console.log(message);
- resolve();
- }, delay);
- });
- }
- print(1000, "First").then(function () {
- return print(4000, "Second");
- }).then(function () {
- print(3000, "Third");
- });
复制代码 Promise.prototype.then方法:链式操作
Promise.prototype.then 方法返回的是一个新的 Promise 对象,因此可以接纳链式写法。
- getJSON("/posts.json").then(function(json) {
- return json.post;
- }).then(function(post) {
- // proceed
- });
复制代码 代码使用 then 方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回效果作为参数,传入第二个回调函数。假如前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行效果,才会进一步调用。
- getJSON("/post/1.json").then(function(post) {
- return getJSON(post.commentURL);
- }).then(function(comments) {
- // 对comments进行处理
- });
复制代码 种设计使得嵌套的异步操作,可以被很容易得改写,从回调函数的"横向发展"改为"向下发展"。
Promise.prototype.catch方法:捕捉错误
Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。
- getJSON("/posts.json").then(function(posts) {
- // some code
- }).catch(function(error) {
- // 处理前一个回调函数运行时发生的错误
- console.log('发生错误!', error);
- });
复制代码 Promise 对象的错误具有"冒泡"性质,会不停向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。
async / await
在JavaScript中,一些操作是异步的。这意味着它们产生的效果大概值不会立即奏效。
- function fetchDataFromApi() {
- fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
- .then(res => res.json())
- .then(json => console.log(json.joke));
- }
- fetchDataFromApi();
- console.log('Finished fetching data');
复制代码 JavaScript 表明器不会等待异步fetchDataFromApi函数完成后再表明下一条语句。因此,在打印API 返回的真实数据之前,它就会打印 Finished fetching data。大多数情况下,这并不是我们想要的行为。可以使用async和await关键字,使程序在继续进步之前等待异步操作的完成。
async/await 是在 JavaScript 中用于处置惩罚异步操作的一种语法布局,它基于 Promise 机制,使异步代码看起来更像同步代码,从而提高代码的可读性和可维护性。
- async 关键字用于声明一个异步函数,该函数返回一个 Promise 对象。
- await 关键字只能在 async 函数内部使用,用于暂停异步函数的执行,等待一个 Promise 对象的办理,并返回办理的值。
async 关键字
定义函数时,在 function 前面加上 async 关键字,函数就标志为异步函数。
- async function fetchDataFromApi() {
- fetch('https://v2.jokeapi.dev/joke/Programming?type=single')
- .then(res => res.json())
- .then(json => console.log(json.joke));
- }
复制代码 异步函数总是返回一个promise
- async function echo(arg) {
- return arg;
- }
- const res = echo(5);
- console.log(res);
复制代码 以是可以通过在函数调用上链接一个then()来获得正确的执行次序:
- fetchDataFromApi()
- .then(() => {
- console.log('Finished fetching data');
- });
复制代码 但是这种写法还是不太优雅,以是就出现了 async/await,它能够用一种看起来更像同步代码的语法来编写异步代码,而且更容易阅读。
await 关键字
在任何异步操作前面加上 await 关键字。这将迫使 JavaScript 表明器"暂停"执行并等待效果。可以将这些操作的效果分配给变量:
- async function fetchDataFromApi() {
- const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
- const json = await res.json();
- console.log(json.joke);
- }
复制代码 等待调用fetchDataFromApi函数的效果:
- async function fetchDataFromApi() {
- const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
- const json = await res.json();
- console.log(json.joke);
- }async function init() { await fetchDataFromApi(); console.log('Finished fetching data');}init();
复制代码 现在这个代码比基于 promise 的版本更容易阅读。
"链式调用 实现 回调地狱" 代码可以改写如下:
- async function asyncFunc() {
- await print(1000, "First");
- await print(4000, "Second");
- await print(3000, "Third");
- }
- asyncFunc();
复制代码 可以看到通过 async / await 将异步操作变得像同步操作一样。异步函数 async function 中可以使用 await 指令,await 指令后必须跟着一个 Promise,异步函数会在这个 Promise 运行中暂停,直到其运行竣事再继续运行。异步函数现实上原理与 Promise 原生 API 的机制是一模一样的,只不外更便于程序员阅读。
处置惩罚异常的机制将用 try-catch 块实现:
- async function asyncFunc() {
- try {
- await new Promise(function (resolve, reject) {
- throw "Some error"; // 或者 reject("Some error")
- });
- } catch (err) {
- console.log(err);
- // 会输出 Some error
- }
- }
- asyncFunc();
复制代码 假如 Promise 有一个正常的返回值,await 语句也会返回它
- async function asyncFunc() {
- let value = await new Promise(
- function (resolve, reject) {
- resolve("Return value");
- }
- );
- console.log(value);
- }
- asyncFunc();
复制代码 声明 异步函数
上面例子中,使用了两个具名函数声明(function关键字后跟着函数名字),也可以把函数表达式、箭头函数和匿名函数标志为async。
异步函数表达式
当创建一个函数,并将其赋值给一个变量时,这便是函数表达式。该函数是匿名的,这意味着它没有名字。好比:
- const fetchDataFromApi = async function() {
- const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
- const json = await res.json();
- console.log(json.joke);
- }
复制代码 异步箭头函数
箭头函数在ES6被引入。它们是函数表达式的紧凑替换品,而且总是匿名的。
- (async () => {
- async function fetchDataFromApi() {
- const res = await fetch('https://v2.jokeapi.dev/joke/Programming?type=single');
- const json = await res.json();
- console.log(json.joke);
- }
- await fetchDataFromApi();
- console.log('Finished fetching data');
- })();
复制代码 使用函数表达式或函数声明并没有什么大的区别:大部分情况下,这只是一个使用偏好的问题。但有几件事变需要注意,好比变量提升,大概箭头函数无法绑定this的事实。
Await / Async 内部机制
async / await 在很大水平上是 promise 的语法糖。
promise大概会是三种状态之一:pending、fulfilled、大概rejected。一个promise开始时处于pending状态。假如与该promise有关的行为成功了,该promise就被称为fulfilled。假如行为不成功,该promise就被称为rejected。一旦promise是fulfilled大概rejected,但不是pending,它也被以为是settled。
当我们在async函数中使用 await 关键字来"暂停"函数执行时,真正发生的是我们在等待一个promise(无论是显式还是隐式)进入resolved或rejected状态。
- async function echo(arg) {
- return arg;
- }
- async function getValue() {
- const res = await echo(5);
- console.log(res);
- }
- getValue();
- // 5
复制代码 由于echo函数返回一个promise,而getValue函数中的await关键字在继续程序之前等待这个promise完成,以是我们能够将所需的值打印到控制台。
promise是对JavaScript中流程控制的一大改进,而且被一些较新的欣赏器API所使用。好比Battery status API、Clipboard API、Fetch API、MediaDevices API等等。
Node还在其内置的util模块中添加了一个promise函数,可以将使用回调函数的代码转换为返回promise。而从v10开始,Node的fs模块中的函数可以直接返回promise。
promise 转成 async/await
任何返回promise的函数都可以使用async/await。但并不是应该对所有的事变都使用async/await(该语法确实有其缺点,将在讨论错误处置惩罚时看到)。
看另一个例子。这里有一个小的实用函数,使用Node基于promise的API和它的readFile方法来获取一个文件的内容。
使用Promise.then():
- const { promises: fs } = require('fs');
- const getFileContents = function(fileName) {
- return fs.readFile(fileName, enc)
- }
- getFileContents('myFile.md', 'utf-8')
- .then((contents) => {
- console.log(contents);
- });
复制代码 有了async/await就会变成:
- import { readFile } from 'node:fs/promises';
- const getFileContents = function(fileName, enc) {
- return readFile(fileName, enc)
- }
- const contents = await getFileContents('myFile.md', 'utf-8');
- console.log(contents);
复制代码 注意:这是在使用一个叫做top-level await的功能,它只在ES模块中可用。要运行这段代码,请将文件生存为index.mjs并使用Node>=14.8的版本。
虽然这些都是简朴的例子,但可以发现async/await的语法更容易理解。当处置惩罚多个then()语句和错误处置惩罚时,这一点变得尤其真实。
错误处置惩罚
在处置惩罚异步函数时,有几种方法来处置惩罚错误。最常见的大概是使用try...catch块,可以把它包在异步操作中并捕捉任何发生的错误。
- async function fetchDataFromApi() {
- try {
- const res = await fetch('https://non-existent-url.dev');
- const json = await res.json();
- console.log(json.joke);
- } catch (error) {
- // Handle the error here in whichever way you like
- console.log('Something went wrong!');
- console.warn(error)
- }
- }
- await fetchDataFromApi();
- console.log('Finished fetching data');
复制代码 fetch 返回一个promise。当fetch操作失败时,promise的reject方法被调用,await关键字将这种reject转换为一个可捕捉的错误。然而,这种方法有几个问题。主要的问题是它很啰嗦,而且相当丢脸。假如正在构建一个CRUD应用程序,每个CRUD方法(创建、读取、更新、销毁)都有一个单独的函数。假如这些方法中的每一个都举行了异步API调用,就必须把每个调用包在自己的try...catch块中。这是相当多的额外代码。
另一个问题是,假如不使用await关键字,这将导致一个未处置惩罚的拒绝的promise:
- import { readFile } from 'node:fs/promises';
- const getFileContents = function(fileName, enc) {
- try {
- return readFile(fileName, enc)
- } catch (error) {
- console.log('Something went wrong!');
- console.warn(error)
- }
- }
- const contents = await getFileContents('this-file-does-not-exist.md', 'utf-8');
- console.log(contents);
复制代码 与await不同,return关键字不会将拒绝的promise转化为可捕捉的错误。
在函数调用中使用catch(),上例中的代码将优雅地处置惩罚错误:
- const contents = await getFileContents('this-file-does-not-exist.md', 'utf-8')
- .catch((error) => {
- console.log('Something went wrong!');
- console.warn(error);
- });
- console.log(contents);
复制代码 至于使用哪种策略,可以使用try/catch来规复async函数内部的预期错误,但通过在调用函数中添加catch()来处置惩罚意外错误。
并行运行异步命令
使用await关键字来等待一个异步操作完成时,JavaScript表明器会相应地暂停执行。虽然这很方便,但这大概并不总是我们想要的。考虑一下下面的代码:
- (async () => {
- async function getStarCount(repo){
- const repoData = await fetch(repo);
- const repoJson = await repoData.json()
- return repoJson.stargazers_count;
- }
- const reactStars = await getStarCount('https://api.github.com/repos/facebook/react');
- const vueStars = await getStarCount('https://api.github.com/repos/vuejs/core');
- console.log(`React has ${reactStars} stars, whereas Vue has ${vueStars} stars`)
- })();
复制代码 这里我们正在举行两次API调用,分别获取React和Vue的GitHub star数。虽然这样可以正常运转,但我们没有来由在发出第二个fetch请求之前等待第一个promise完成。假如我们要发出很多请求,这将是一个相当大的瓶颈。
为了办理这个问题,我们可以使用Promise.all,它接收一个promise数组,并等待所有promise被办理或其中任何一个Promise 调用 reject:
- (async () => {
- async function getStarCount(repo){
- // As before
- }
- const reactPromise = getStarCount('https://api.github.com/repos/facebook/react');
- const vuePromise = getStarCount('https://api.github.com/repos/vuejs/core');
- const [reactStars, vueStars] = await Promise.all([reactPromise, vuePromise]);
- console.log(`React has ${reactStars} stars, whereas Vue has ${vueStars} stars`);
- })();
复制代码
同步代码 调用 异步await
在一个同步循环中调用一个异步函数。好比说:
- // Return promise which resolves after specified no. of milliseconds
- const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
- async function process(array) {
- array.forEach(async (el) => {
- await sleep(el); // we cannot await promise here
- console.log(el);
- });
- }
- const arr = [3000, 1000, 2000];
- process(arr);
复制代码 这不会像预期的那样奏效,由于forEach只会调用函数而不等待它完成,以下内容将被打印到控制台:
1000
2000
3000
同样的事变也适用于其他许多数组方法,如map、filter和reduce。
荣幸的是,ES2018引入了异步迭代器,除了它们的next()方法会返回一个promise外,它们就像普通的迭代器。这意味着我们可以在其中使用 await。让我们使用for...of重写上面的代码:
- async function process(array) {
- for (el of array) {
- await sleep(el);
- console.log(el);
- };
- }
复制代码 现在,process函数的输出就是正确的次序:
3000
1000
2000
就像之前等待异步fetch请求的例子一样,这也会带来性能上的代价。for循环中的每个await都会阻塞事件循环,通常应该重构代码,一次性创建所有的promise,然后使用Promise.all()来获取效果。以致有一条ESLint规则,假如它检测到这种行为就会警告。
顶层 await
顶层await 是ES2022中引入的语言,从14.8版开始在Node中可用。
当试图在一个async函数之外使用await时,就会发生这种情况。
Uncaught SyntaxError: await is only valid in async functions, async generators and modules
例如,在代码的顶层:
- const ms = await Promise.resolve('Hello, World!');
- console.log(msg);
复制代码 顶层await办理了这个问题,使上述代码有效,但只在ES模块中奏效。假如我们在欣赏器中工作,我们可以把这段代码添加到一个叫做index.js的文件中,然后像这样把它加载到我们的页面中:
- <script src="index.js" type="module"></script>
复制代码
同步代码 获取 异步返回值
当返回的是Promise时,可以通过在函数调用上链接一个then()来获得正确的执行次序:
- async function getData(url) {
- try {
- const res = await new Promise((resolve, reject) => {
- uni.request({
- url: url,
- success: (result) => {
- resolve(result);
- },
- fail: (error) => {
- reject(error);
- }
- });
- });
- return res;
- } catch (err) {
- console.error(err);
- }
- }
- // 使用该函数
- getData('https://example.com/api')
- .then((res) => {
- console.log(res);
- })
- .catch((err) => {
- console.error(err);
- });
复制代码 用 Promise 对象实现的 Ajax 操作的例子。
- function ajax(URL) {
- return new Promise(function (resolve, reject) {
- var req = new XMLHttpRequest();
- req.open('GET', URL, true);
- req.onload = function () {
- if (req.status === 200) {
- resolve(req.responseText);
- } else {
- reject(new Error(req.statusText));
- }
- };
- req.onerror = function () {
- reject(new Error(req.statusText));
- };
- req.send();
- });
- }
- var URL = "/try/ajax/testpromise.php";
- ajax(URL).then(function onFulfilled(value){
- document.write('内容是:' + value);
- }).catch(function onRejected(error){
- document.write('错误:' + error);
- });
复制代码
常见的问题 (FAQ)
Q: then、catch 和 finally 序列可否次序颠倒?
A: 可以,效果完全一样。但不发起这样做,最好按 then-catch-finally 的次序编写程序。
Q: 除了 then 块以外,别的两种块可否多次使用?
A: 可以,finally 与 then 一样会按次序执行,但是 catch 块只会执行第一个,除非 catch 块里有异常。以是最好只安排一个 catch 和 finally 块。
Q: then 块怎样中断?
A: then 块默认会向下次序执行,return 是不能中断的,可以通过 throw 来跳转至 catch 实现中断。
Q: 什么时候适合用 Promise 而不是传统回调函数?
A: 当需要多次次序执行异步操作的时候,例如,假如想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下就很适合 Promise。
Q: Promise 是一种将异步转换为同步的方法吗?
A: 完全不是。Promise 只不外是一种更良好的编程风格。
Q: 什么时候我们需要再写一个 then 而不是在当前的 then 接着编程?
A: 当你又需要调用一个异步使命的时候。
4、JavaScript HTTP 库 Axios
官方文档:起步 | Axios中文文档 | Axios中文网
Axios 是一个基于 promise 的 HTTP 库,可以用在欣赏器和 node.js 中。Axios 不是一种新的技术。axios 是一个基于Promise 用于欣赏器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不外它是 Promise 的实现版本,符合最新的ES规范。特点
- 从欣赏器创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 超时处置惩罚
- 查询参数序列化支持嵌套项处置惩罚
- 主动将请求体序列化为:
- JSON (application/json)
- Multipart / FormData (multipart/form-data)
- URL encoded form (application/x-www-form-urlencoded)
- 将 HTML Form 转换成 JSON 举行请求
- 主动转换JSON数据
- 获取欣赏器和 node.js 的请求进度,并提供额外的信息(速率、剩余时间)
- 为 node.js 设置带脱期定
- 兼容符合规范的 FormData 和 Blob(包括 node.js)
- 客户端支持防御XSRF
axios长处:
- 1.支持 node 端和欣赏器端。同样的API,node和欣赏器全支持,平台切换无压力
- 2.支持 Promise。使用Promise管理异步,告别传统callback方式
- 3.丰富的设置项。支持拦截器等高级设置
- 4.社区支持。axios相干的npm包数目不停在增长
安装 axios
使用 npm npm install axios
使用 bower bower install axios
使用 yarn yarn add axios
使用 jsDelivr CDN:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
使用 unpkg CDN:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
使用 require 导入预构建的 CommonJS 模块:
const axios = require('axios/dist/browser/axios.cjs'); // browser
const axios = require('axios/dist/node/axios.cjs'); // node
axios 依靠原生的 ES6 Promise 实现而被支持,假如你的情况不支持 ES6 Promise,可以使用 polyfill。
axios 包括 TypeScript 定义:
- import axios from 'axios';
- axios.get('/user?ID=12345');
复制代码 CommonJS 用法
为了在CommonJS中使用 require() 导入时获得TypeScript类型推断(智能感知/主动完成),可以使用以下方法:
- const axios = require('axios').default;
- // axios.<method> 能够提供自动完成和参数类型推断功能
复制代码
案 例
- <script src="/static/axios.min.js"></script>
- <script>
- window.onload = function(){
- axios.post("/movies", {"page": 10086}).then(function(resp){
- console.log(resp.data);
- })
- }
- </script>
复制代码 axios 默认发送和接收的数据就是json. 以是在欣赏器抓包时可以看到
执行 GET 请求
- import axios from "axios";
- // 为给定 ID 的 user 创建请求
- axios.get('/user?ID=12345').then(function (response) {
- console.log(response);
- }).catch(function (error) {
- console.log(error);
- });
- // 上面的请求也可以这样做
- axios.get('/user', {
- params: {
- ID: 12345
- }
- }).then(function (response) {
- console.log(response);
- }).catch(function (error) {
- console.log(error);
- });
复制代码 - const axios = require('axios');
- // 向给定ID的用户发起请求
- axios.get('/user?ID=12345')
- .then(function (response) {
- // 处理成功情况
- console.log(response);
- })
- .catch(function (error) {
- // 处理错误情况
- console.log(error);
- })
- .finally(function () {
- // 总是会执行
- });
- // 上述请求也可以按以下方式完成(可选)
- axios.get('/user', {
- params: {
- ID: 12345
- }
- })
- .then(function (response) {
- console.log(response);
- })
- .catch(function (error) {
- console.log(error);
- })
- .finally(function () {
- // 总是会执行
- });
- // 支持async/await用法
- async function getUser() {
- try {
- const response = await axios.get('/user?ID=12345');
- console.log(response);
- } catch (error) {
- console.error(error);
- }
- }
复制代码 注意: 由于async/await 是ECMAScript 2017中的一部分,而且在IE和一些旧的欣赏器中不支持,以是使用时务须要小心。
执行 POST 请求
- import axios from "axios";
- axios.post('/user', {
- firstName: 'Fred',
- lastName: 'Flintstone'
- }).then(function (response) {
- console.log(response);
- }).catch(function (error) {
- console.log(error);
- });
复制代码
并发 请求
- function getUserAccount() {
- return axios.get('/user/12345');
- }
- function getUserPermissions() {
- return axios.get('/user/12345/permissions');
- }
- const [acct, perm] = await Promise.all([getUserAccount(), getUserPermissions()]);
- // OR
- Promise.all([getUserAccount(), getUserPermissions()])
- .then(function ([acct, perm]) {
- // 两个请求都执行完成
- });
复制代码
将 HTML Form 转换成 JSON 举行请求
- const axios = require('axios');
- const {data} = await axios.post('/user', document.querySelector('#my-form'), {
- headers: {
- 'Content-Type': 'application/json'
- }
- })
复制代码
Forms
- Multipart (multipart/form-data)
- const {data} = await axios.post('https://httpbin.org/post', {
- firstName: 'Fred',
- lastName: 'Flintstone',
- orders: [1, 2, 3],
- photo: document.querySelector('#fileInput').files
- }, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- }
- )
复制代码
- URL encoded form (application/x-www-form-urlencoded)
- const {data} = await axios.post('https://httpbin.org/post', {
- firstName: 'Fred',
- lastName: 'Flintstone',
- orders: [1, 2, 3]
- }, {
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- }
- })
复制代码
Axios API
可以通过向 axios 传递相干设置来创建请求:
axios(config)
- import axios from "axios";
- // 发送 POST 请求
- axios({
- method: 'post',
- url: '/user/12345',
- data: {
- firstName: 'Fred',
- lastName: 'Flintstone'
- }
- })
- // 获取远端图片
- axios({
- method: 'get',
- url: 'http://bit.ly/2mTM3nY',
- responseType: 'stream'
- }).then(function (response) {
- response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'));
- })
复制代码 axios(url [, config])
- // 发送 GET 请求(默认的方法)
- axios('/user/12345');
复制代码
请求方法的别名
为方便起见,为所有支持的请求方法提供了别名
- axios.request(config)
- axios.get(url [config])
- axios.delete(url [config])
- axios.head(url [config])
- axios.options(url [config])
- axios.post(url [ data[ config]])
- axios.put(url [data[config]])
- axios.patch(url [ data[ config]])
注意:在使用别名方法时, url、method、data 这些属性都不必在设置中指定。
axios基本使用及封装:https://juejin.cn/post/7084163923552780319
并 发
处置惩罚并发请求的助手函数:
- axios.all(iterable)
- axios.spread(callback)
创建 实例
可以使用自定义设置创建一个 axios 实例:axios.create([config])
- import axios from "axios";
- const instance = axios.create({
- baseURL: 'https://some-domain.com/api/',
- timeout: 1000,
- headers: {'X-Custom-Header': 'foobar'}
- })
复制代码 注意:使用创建的 axios 实例请求时,请求的设置项将与实例的设置归并。
请求 设置
这些是创建请求时可以用的设置选项。只有 url 是必需的,假如没有指定 method,请求将默认使用 get方法。
- {
- // `url` 是用于请求的服务器 URL
- url: '/user',
- // `method` 是创建请求时使用的方法
- method: 'get', // 默认值
- // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
- // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
- baseURL: 'https://some-domain.com/api/',
- // `transformRequest` 允许在向服务器发送前,修改请求数据
- // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
- // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
- // 你可以修改请求头。
- transformRequest: [function (data, headers) {
- // 对发送的 data 进行任意转换处理
- return data;
- }],
- // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
- transformResponse: [function (data) {
- // 对接收的 data 进行任意转换处理
- return data;
- }],
- // 自定义请求头
- headers: {'X-Requested-With': 'XMLHttpRequest'},
- // `params` 是与请求一起发送的 URL 参数
- // 必须是一个简单对象或 URLSearchParams 对象
- params: {
- ID: 12345
- },
- // `paramsSerializer`是可选方法,主要用于序列化`params`
- // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
- paramsSerializer: function (params) {
- return Qs.stringify(params, {arrayFormat: 'brackets'})
- },
- // `data` 是作为请求体被发送的数据
- // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
- // 在没有设置 `transformRequest` 时,则必须是以下类型之一:
- // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
- // - 浏览器专属: FormData, File, Blob
- // - Node 专属: Stream, Buffer
- data: {
- firstName: 'Fred'
- },
-
- // 发送请求体数据的可选语法
- // 请求方式 post
- // 只有 value 会被发送,key 则不会
- data: 'Country=Brasil&City=Belo Horizonte',
- // `timeout` 指定请求超时的毫秒数。
- // 如果请求时间超过 `timeout` 的值,则请求会被中断
- timeout: 1000, // 默认值是 `0` (永不超时)
- // `withCredentials` 表示跨域请求时是否需要使用凭证
- withCredentials: false, // default
- // `adapter` 允许自定义处理请求,这使测试更加容易。
- // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
- adapter: function (config) {
- /* ... */
- },
- // `auth` HTTP Basic Auth
- auth: {
- username: 'janedoe',
- password: 's00pers3cret'
- },
- // `responseType` 表示浏览器将要响应的数据类型
- // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
- // 浏览器专属:'blob'
- responseType: 'json', // 默认值
- // `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
- // 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
- // Note: Ignored for `responseType` of 'stream' or client-side requests
- responseEncoding: 'utf8', // 默认值
- // `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
- xsrfCookieName: 'XSRF-TOKEN', // 默认值
- // `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
- xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
- // `onUploadProgress` 允许为上传处理进度事件
- // 浏览器专属
- onUploadProgress: function (progressEvent) {
- // 处理原生进度事件
- },
- // `onDownloadProgress` 允许为下载处理进度事件
- // 浏览器专属
- onDownloadProgress: function (progressEvent) {
- // 处理原生进度事件
- },
- // `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
- maxContentLength: 2000,
- // `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
- maxBodyLength: 2000,
- // `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
- // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
- // 则promise 将会 resolved,否则是 rejected。
- validateStatus: function (status) {
- return status >= 200 && status < 300; // 默认值
- },
- // `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
- // 如果设置为0,则不会进行重定向
- maxRedirects: 5, // 默认值
- // `socketPath` 定义了在node.js中使用的UNIX套接字。
- // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
- // 只能指定 `socketPath` 或 `proxy` 。
- // 若都指定,这使用 `socketPath` 。
- socketPath: null, // default
- // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
- // and https requests, respectively, in node.js. This allows options to be added like
- // `keepAlive` that are not enabled by default.
- httpAgent: new http.Agent({ keepAlive: true }),
- httpsAgent: new https.Agent({ keepAlive: true }),
- // `proxy` 定义了代理服务器的主机名,端口和协议。
- // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
- // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
- // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
- // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
- // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
- proxy: {
- protocol: 'https',
- host: '127.0.0.1',
- port: 9000,
- auth: {
- username: 'mikeymike',
- password: 'rapunz3l'
- }
- },
- // see https://axios-http.com/zh/docs/cancellation
- cancelToken: new CancelToken(function (cancel) {
- }),
- // `decompress` indicates whether or not the response body should be decompressed
- // automatically. If set to `true` will also remove the 'content-encoding' header
- // from the responses objects of all decompressed responses
- // - Node only (XHR cannot turn off decompression)
- decompress: true // 默认值
- }
复制代码
响应 布局
某个请求的响应包含以下信息
- {
- // data 由服务器提供的响应
- data: {},
-
- // status 来自服务器响应的 HTTP 状态码
- status: 200,
- // statusText 来自服务器响应的 HTTP 状态信息
- statusText: 'OK',
- // headers 服务器响应的头
- headers: {},
- // config 是为请求提供的配置信息
- config: {},
- // request 是生成当前响应的请求
- // 在 node.js 中是最后一个 ClientRequest 实例 (在重定向中)
- // 在浏览器中是 XMLHttpRequest 实例
- request: {}
- }
复制代码 使用 then 时,你将接收下面这样的响应 :
- axios.get('/user/12345')
- .then(function (response) {
- console.log(response.data);
- console.log(response.status);
- console.log(response.statusText);
- console.log(response.headers);
- console.log(response.config);
- })
复制代码 在使用 catch 、或传递 rejection callback 作为 then 的第二个参数时,响应可以通过 error 对象被使用,可参考后面的篇章 —— 错误处置惩罚。
设置 默认值
全局的 axios 默认值
- axios.defaults.baseURL = 'http://api.example.com';
- axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
- axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
复制代码 自定义实例默认值
- // 创建实例时设置配置默认值
- const instance = axios.create({
- baseURL: 'https://api.example.com'
- });
- // 实例创建之后可修改默认配置
- instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
复制代码 设置的优先次序
设置会以一个优先次序举行归并。这个次序是:在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,末了是请求的 config 参数。后者将优先于前者。这里是一个例子:
- // 使用由库提供的配置默认值来创建实例
- // 此时超时配置的默认值是 0
- const instance = axios.create();
- // 覆写库的超时默认值
- // 现在,在超时前,所有请求都会等待 2.5 秒
- instance.defaults.timeout = 2500;
- // 为已知需要花费很长时间的请求覆写超时设置
- instance.get('/longRequest', {
- timeout: 5000
- });
复制代码
拦截器
场景:有些网站会对每次请求都添加加密信息. 大概每次返回数据的时候, 都有解密逻辑. 那此时. 你思考. 不大概每次请求都要程序员去手动写加密逻辑. 例如
- window.onload = function(){
- // 加密数据
- axios.post("/movies", {"page": 10086}).then(function(resp){
- 明文 = 解密(resp.data);
- console.log(明文);
- })
- // 加密数据
- axios.post("/movies", {"page": 10086}).then(function(resp){
- 明文 = 解密(resp.data);
- console.log(明文);
- })
- }
复制代码 这样很麻烦. 也很蛋疼. axios想到过类似的问题. 它提供了拦截器. 一次性处置惩罚好这种问题
- axios.interceptors.request.use(function(config){ // 拦截所有请求
- console.log("我是拦截器. 我可以对数据进行加密");
- console.log(config)
- return config;
- }, function(error){
- return Promise.reject(error);
- });
- axios.interceptors.response.use(function(response){ // 拦截所有响应
- console.log("我是响应回来之后拦截器. 我可以对数据进行解密")
- return response.data;
- }, function(error){
- return Promise.reject(error);
- });
复制代码 这样. 对于业务层的代码而言就简朴很多了
- window.onload = function(){
- // 加密的逻辑拦截器帮我完成了
- axios.post("/movies", {"page": 10086}).then(function(data){
- // 解密的逻辑拦截器帮我完成了
- console.log(data);
- })
- // 加密的逻辑拦截器帮我完成了
- axios.post("/movies", {"page": 10086}).then(function(data){
- // 解密的逻辑拦截器帮我完成了
- console.log(data);
- })
- }
复制代码 拦截器作用就是:在请求或响应被 then 或 catch 处置惩罚前拦截它们。
- // 添加请求拦截器
- axios.interceptors.request.use(
- function (config) {
- // 在发送请求之前做些什么
- return config;
- },
- function (error) {
- // 对请求错误做些什么
- return Promise.reject(error);
- }
- );
- // 添加响应拦截器
- axios.interceptors.response.use(
- function (response) {
- // 对响应数据做点什么
- return response;
- },
- function (error) {
- // 对响应错误做点什么
- return Promise.reject(error);
- }
- );
复制代码 假如你想在稍后移除拦截器,可以这样:
- const myInterceptor = axios.interceptors.request.use(function () { /* ... */ });
- axios.interceptors.request.eject(myInterceptor);
复制代码 可以为自定义 axios 实例添加拦截器:
- const instance = axios.create();
- instance.interceptors.request.use(function () { /* ... */ });
复制代码
错误 处置惩罚
- axios.get('/user/12345')
- .catch(function (error) {
- if (error.response) {
- // 请求已发出,且服务器的响应状态码超出了 2xx 范围
- console.log(error.response.data);
- console.log(error.response.status);
- console.log(error.response.headers);
- } else if (error.request) {
- // 请求已发出,但没有接收到任何响应
- // 在浏览器中,error.request 是 XMLHttpRequest 实例
- // 在 node.js 中,error.request 是 http.ClientRequest 实例
- console.log(error.request);
- } else {
- // 引发请求错误的错误信息
- console.log('Error', error.message);
- }
- console.log(error.config);
- });
复制代码 你可以使用 validateStatus 设置选项定义一个自定义 HTTP 状态码的错误范围:
- axios.get('/user/12345', {
- validateStatus: function (status) {
- // 当且仅当 status 大于等于 500 时 Promise 才被 reject
- return status < 500;
- }
- });
复制代码
取消 请求
AbortController
从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求:
- const controller = new AbortController();
- axios.get('/foo/bar', {
- signal: controller.signal
- }).then(function(response) {
- //...
- });
- // 取消请求
- controller.abort()
复制代码 CancelToken deprecated
已经
请求体 编码
默认情况下,axios将 JavaScript 对象序列化为 JSON 。 要以application/x-www-form-urlencoded格式发送数据,您可以使用以下选项之一。
欣赏器
在欣赏器中,可以使用URLSearchParams API,如下所示:
- const params = new URLSearchParams();
- params.append('param1', 'value1');
- params.append('param2', 'value2');
- axios.post('/foo', params);
复制代码 请注意,不是所有的欣赏器(拜见 caniuse.com)都支持 URLSearchParams ,但是可以使用polyfill (确保 polyfill 全局情况)
大概, 您可以使用qs 库编码数据:
- const qs = require('qs');
- axios.post('/foo', qs.stringify({ 'bar': 123 }));
复制代码 大概用另一种方式 (ES6),
- import qs from 'qs';
- const data = { 'bar': 123 };
- const options = {
- method: 'POST',
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
- data: qs.stringify(data),
- url,
- };
- axios(options);
复制代码 Node.js
Query string
在 node.js 中, 可以使用 querystring 模块,如下所示:
- const querystring = require('querystring');
- axios.post('http://something.com/', querystring.stringify({ foo: 'bar' }));
复制代码 大概从'url module'中使用'URLSearchParams',如下所示:
- const url = require('url');
- const params = new url.URLSearchParams({ foo: 'bar' });
- axios.post('http://something.com/', params.toString());
复制代码 您也可以使用 qs 库。
注意:假如需要对嵌套对象举行字符串化处置惩罚,则最好使用 qs 库,由于 querystring 方法在该用例中存在已知问题(querystring.stringify can't deal with nested objects · Issue #1665 · nodejs/node-v0.x-archive · GitHub)。
Form data
在 node.js, 您可以使用 form-data 库,如下所示:
- const FormData = require('form-data');
-
- const form = new FormData();
- form.append('my_field', 'my value');
- form.append('my_buffer', new Buffer(10));
- form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
- axios.post('https://example.com', form, { headers: form.getHeaders() })
复制代码 大概, 使用一个拦截器:
- axios.interceptors.request.use(config => {
- if (config.data instanceof FormData) {
- Object.assign(config.headers, config.data.getHeaders());
- }
- return config;
- });
复制代码 主动序列化
当请求头中的 content-type 是 application/x-www-form-urlencoded 时,Axios 将主动地将普通对象序列化成 urlencoded 的格式。
在欣赏器和 node.js 情况中都适用:
- const data = {
- x: 1,
- arr: [1, 2, 3],
- arr2: [1, [2], 3],
- users: [{name: 'Peter', surname: 'Griffin'}, {name: 'Thomas', surname: 'Anderson'}],
- };
- await axios.post('https://postman-echo.com/post', data,
- {headers: {'content-type': 'application/x-www-form-urlencoded'}}
- );
复制代码 服务器接收到的数据就像是这样:
- {
- x: '1',
- 'arr[]': [ '1', '2', '3' ],
- 'arr2[0]': '1',
- 'arr2[1][0]': '2',
- 'arr2[2]': '3',
- 'arr3[]': [ '1', '2', '3' ],
- 'users[0][name]': 'Peter',
- 'users[0][surname]': 'griffin',
- 'users[1][name]': 'Thomas',
- 'users[1][surname]': 'Anderson'
- }
复制代码 假如您的服务器框架的请求体剖析器(例如express.js的body-parser)支持嵌套对象解码,则其接收到的数据将与您提交的数据一样。
以下是一个express.js的服务器示例,它将会把接收到的数据作为响应返回:
- var app = express();
-
- app.use(bodyParser.urlencoded({ extended: true })); // support url-encoded bodies
-
- app.post('/', function (req, res, next) {
- res.send(JSON.stringify(req.body));
- });
- server = app.listen(3000);
复制代码
Multipart 实体 请求
使用 multipart/form-data 类型发起 POST 请求
使用 FormData API
欣赏器
- const form = new FormData();
- form.append('my_field', 'my value');
- form.append('my_buffer', new Blob([1,2,3]));
- form.append('my_file', fileInput.files[0]);
- axios.post('https://example.com', form)
复制代码 Axios 会将传入数据序列化,因此使用 Axios 提供的 API 可以无需手动处置惩罚 FormData 的数据并实现一样的效果:
- axios.postForm('https://httpbin.org/post', {
- my_field: 'my value',
- my_buffer: new Blob([1,2,3]),
- my_file: fileInput.files // FileList will be unwrapped as sepate fields
- });
复制代码 HTML 表单可以直接作为请求内容来举行传输。
Node.js
- import axios from 'axios';
- const form = new FormData();
- form.append('my_field', 'my value');
- form.append('my_buffer', new Blob(['some content']));
- axios.post('https://example.com', form)
复制代码 由于 node.js 当前不支持从文件创建 Blob,因此您可以使用第三方软件包来实现该目的。
- import {fileFromPath} from 'formdata-node/file-from-path'
- form.append('my_field', 'my value');
- form.append('my_file', await fileFromPath('/foo/bar.jpg'));
- axios.post('https://example.com', form)
复制代码 当 Axios 版本小于 v1.3.0 时您必须引入 form-data 包。
- const FormData = require('form-data');
- const form = new FormData();
- form.append('my_field', 'my value');
- form.append('my_buffer', new Buffer(10));
- form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
- axios.post('https://example.com', form)
复制代码 主动序列化
从 v0.27.0 版本开始,当请求头中的 Content-Type 是 multipart/form-data 时,Axios 支持主动地将普通对象序列化成一个 FormData 对象。
这个示例请求演示了怎样将一个数据通过 FormData 格式举行提交(欣赏器与 Node.js 情况):
- import axios from 'axios';
- axios.post('https://httpbin.org/post', {
- user: {
- name: 'Dmitriy'
- },
- file: fs.createReadStream('/foo/bar.jpg')
- }, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- }).then(({data})=> console.log(data));
复制代码 Axios FormData 序列化器支持一些特殊的结尾,以执行以下操作:
- {} - 通过 JSON.stringify 序列化数据
- [] - 将 array-like 的对象使用相同的键值来展开为单独的字段
提示:默认情况下,展开、扩展操作将在数组和 FileList 对象上使用。
FormData 序列化器支持通过 config.formSerializer: object 这个参数来传递一些额外的选项,以支持一些特殊的情况:
- visitor: Function - 用户定义的处置惩罚函数,将递归调用以按照自定义规则将数据对象序列化为FormData对象。
- dots: boolean = false - 使用点符号而不是括号来序列化数组和对象;
- metaTokens: boolean = true - 在 FormData 键值中添加特殊结尾(例如user{}: '{"name": "John"}')。后端的 body-parser 大概会使用此元信息主动将值剖析为 JSON。
- indexes: null|false|true = false - 控制怎样添加索引到打平的 array-like 对象的展开键值中
- null - 不添加中括号(arr: 1,arr: 2,arr: 3)
- false(默认值)- 添加空中括号(arr[]: 1,arr[]: 2,arr[]: 3)
- true - 添加带有索引的中括号(arr[0]: 1,arr[1]: 2,arr[2]: 3)
假设说我们有一个这样的示例对象:
- const obj = {
- x: 1,
- arr: [1, 2, 3],
- arr2: [1, [2], 3],
- users: [{name: 'Peter', surname: 'Griffin'}, {name: 'Thomas', surname: 'Anderson'}],
- 'obj2{}': [{x:1}]
- };
复制代码 接下来这些序列化的步调将会由 Axios 内置的序列化器主动执行:
- const formData= new FormData();
- formData.append('x', '1');
- formData.append('arr[]', '1');
- formData.append('arr[]', '2');
- formData.append('arr[]', '3');
- formData.append('arr2[0]', '1');
- formData.append('arr2[1][0]', '2');
- formData.append('arr2[2]', '3');
- formData.append('users[0][name]', 'Peter');
- formData.append('users[0][surname]', 'Griffin');
- formData.append('users[1][name]', 'Thomas');
- formData.append('users[1][surname]', 'Anderson');
- formData.append('obj2{}', '[{"x":1}]');
复制代码 - import axios from 'axios';
- axios.post('https://httpbin.org/post', {
- 'myObj{}': {x: 1, s: "foo"},
- 'files[]': document.querySelector('#fileInput').files
- }, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- }).then(({data})=> console.log(data));
复制代码 Axios支持以下别名方法:postForm,putForm,patchForm,这些方法只是对应的 HTTP 方法,其 content-type 头部默认设为multipart/form-data。
FileList 对象可以被直接传递:
- await axios.postForm('https://httpbin.org/post', document.querySelector('#fileInput').files)
复制代码 所有文件将使用相同的字段名files[]发送。
框架 整合
- vue-axios
- react-axios
- nuxtjs-axios
插 件
- axios-retry
- vue-axios-plugin
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |