Js
arguments
特性:
- arguments对象和Function是分不开的
- arguments对象不能显示创建
- arguments对象只有函数开始时才可用
使用方法:
- arguments对象不是数组,但访问其中属性的方式与访问数组元素的方式相同
- 如:arguments[0]、arguments[1]
- 在js中不需要明确指出属性名就可以访问它们,如:
-
function test() { console.log(arguments[0], arguments[1]) } test('name', 'age') // name age - arguments.length -- 类似于 array.length
- arguments.callee -- 返回正被执行的 Function 对象代码如下
-
var sum = function (n) { if (1 = n) { return 1; } else { // arguments.callee(参数) 多用来处理递归 return n + arguments.callee(n - 1); } } alert(sum(6)) -
let i = Array.prototype.shift.apply(arguments) // 取出i = arguments[0]
apply&call
特性:
- 借用其它对象的方法
- 改变其它对象中的this指向当前传入的对象
- 如:小明家有果汁机和水果,小红加有水果
-
let xm = { // 小明 name: 'xm', fruit: '橙子', makeJuice: function() { console.log(this.name + '正在榨' + this.fruit + '汁') } } let xh = { // 小红 name: 'xh', fruit: '苹果' } xm.makeJuice(); // 小明 xm.makeJuice.apply(xh); // 小红 - 使用arguments传递参数
-
let xm = { name: 'xm', fruit: '香蕉', makeJuice: function(water, time) { console.log(this.name + '正在榨' + this.fruit + '汁;加水:' + water + 'mL,用时:' + time + '分钟。') } } let xh = { name: '小红', fruit: '葡萄' } let wang = { name: 'wang', helpFromXh: function(water, time) { xm.makeJuice.apply(xh, arguments) } } // 小红正在榨葡萄汁;加水:300mL,用时:1分钟。 wang.helpFromXh(300, 1) - 使用apply时,而第一个参数为null
-
var name = '社区' var fruit = '哈密瓜' let xm = { name: 'xm', fruit: '香蕉', makeJuice: function(water, time) { console.log(this.name + '正在榨' + this.fruit + '汁;加水:' + water + 'mL,用时:' + time + '分钟。') } } let xh = { name: '小红', fruit: '葡萄' } let wang = { name: 'wang', helpFromXh: function(water, time) { // 第一个参数为null,则函数体内的this会指向全局对象 xm.makeJuice.apply(null, arguments) } } // 社区正在榨哈密瓜汁;加水:300mL,用时:1分钟。 wang.helpFromXh(300, 1) -
let arr = [1, 8, 10, 9] let Max = Math.max.apply(null, arr) // Max = 10
总结:
- 如某个函数体明显使用了this,那就该传入一个明确的thisObj对象,并且这个对象应包含相关的属性。
- 如某个函数是‘工具’类型的,那使用apply时,可传入一个null作为thisObj(如上)。
Array
遍历数组
-
forEach()
-
- 语法:arr.forEach(callback)
- 参数:
-
- callback - 为数组中每个元素执行的函数,该函数接收三个参数
-
- current Value - 数组中当前正在处理的元素
- [index] - 数组中当前正在处理的元素的索引
- [array] - forEach() 方法正在操作的数组
- [thisArg] - 当执行回调函数时用作this的值
- forEach() 方法不会改变数组
-
let arr = [0, 4, 8, 10, 3] arr.forEach((item, index) => { console.log(index + '->' + item) // 0->0 console.table(item) // 0 }) - 注意:
-
- 没有办法终止或者跳出 forEach()循环,除了抛出一个异常。
- 若需要根据条件中止循环,可以使用
-
- 简单 for 循环
- for ... of 循环
- Array.prototype.every()
- Array.prototype.som()
- Array.prototype.find()
- Array.prototype.findIndex()
- 若条件允许可以使用 filter() 提前过滤出需要遍历的部分,再用 forEach() 处理。
-
find() --- return result / undefined
-
- 找到第一个满足条件的元素,停止遍历并返回,否则返回 undefined。
-
- 语法:arr.find(callback)
- 参数:callback 接收三个参数:
-
- element - 当前遍历到的元素
- [index] - 当前遍历到的索引
- [array] - 数组本身
- [thisArg] - 执行回调时用作this的对象
- find() 方法不会改变数组
-
let findArr = [ {id: 1, title: '拉拉'}, {id: 2, title: '哈哈'}, {id: 3, title: '默默'} ] let resultEl = findArr.find((item, index) => { return item.id = 2 }) console.log(resultEl) // Object:{id: 2, title: '哈哈'}
-
findIndex() --- return index / -1
-
- 找到第一个满足条件元素的索引,停止遍历并返回,否则返回 -1。
- 语法:arr.findIndex(callback)
- 参数:callback - 数组中的每个元素都会执行该回调函数,并接收三个参数
-
- element - 当前遍历到的元素
- [index] - 当前元素的索引
- [array] - 数组本身
- [thisArg] - 执行回调时作为this对象的值
- findIndex() - 不会修改所调用的数组
-
const indexArr = [1, 7, 3, 9] let indexResult = indexArr.findIndex((item, index) => { return item % 2 === 0 }) console.log(indexResult) // -1 // ----------------------------- function isPrime(element, index, array) { var start = 2; while (start <= Math.sqrt(element)) { if (element % start++ < 1) { return false; } } return element > 1; } console.log([4, 6, 8, 12].findIndex(isPrime)); // -1, not found console.log([4, 6, 7, 12].findIndex(isPrime)); // 2
-
some() --- return Boolean
-
- 作用:判断数组中是否有指定条件的元素,返回一个布尔值。
- 语法:arr.some(callabck)
- 参数:callback - 数组中的每个元素都会执行这个函数,此函数接收三个参数
-
- element - 当前遍历到的元素
- [index] - 当前元素的索引
- [array] - 数组本身
- some() - 不会修改所调用的数组
-
let arr = [ {id: 1, title: '花前月下'}, {id: 2, title: '女子好求'}, {id: 3, title: '打豆豆'} ] // console.log(arr.some(item => item.title === '花前月下')) let result = arr.some(item => item.title === '花前月下') console.log(result) // true
-
every() --- return Boolean
-
- 作用:与 some() 相反,判断数组中所有元素是否满足指定条件,返回一个布尔值。
- 语法:arr.every(callabck)
- 参数:callback - 此函数接收三个参数
-
- element - 当前遍历到的元素
- [index] - 当前元素的索引
- [array] - 数组本身
- every() - 不会修改所调用的数组,所用元素都符合条件返回 true
-
function biggerThanTen(item, index) { return item > 10 } let isOk = [2, 5, 10, 30, 50].every(biggerThanTen) console.log(isOk) // false
-
filter() --- return (new)Array
-
- 作用:在数组中筛选出满足条件的元素,得到一个新数组
- 语法:arr.filter(callback)
- 参数:callback - 此回调接收三个参数
-
- element - 当前遍历到的元素
- [index] - 当前元素的索引
- [array] - 当前数组本身
- filter() - 满足条件的元素组成一个新数组并返回
-
let arr = [ {id: 1, title: '花前月下', com: true}, {id: 2, title: '女子好求', com: false}, {id: 3, title: '打豆豆', com: true} ] let newArr = arr.filter((item, index) => { return item.com === true }) console.log(newArr)
-
map() --- return (new)Array
-
- 作用:根据原数据映射出一个新数组,元素个数不变,元素内容可以改变。
- 注:map 函数映射的个数和被遍历的原数组长度一样。
-
let arr = [ { id: 1, title: '吃' }, { id: 2, title: '喝' }, { id: 3, title: 'sa' } ] let newArr = arr.map(item => { if (item.id !== 2) { return { id: item.id + 10, title: item.title } } }) console.log(newArr) // [{...}, undefined, {...}] console.log(arr.map(item => item.id + 10)) // [11, 12, 13] ----------------------------- var kvArray = [{key: 1, value: 10}, {key: 2, value: 20}, {key: 3, value: 30}]; var reformattedArray = kvArray.map(obj =>{ var rObj = {}; rObj[obj.key] = obj.value; return rObj; }); // reformattedArray is now [{1: 10}, {2: 20}, {3: 30}], // kvArray is still: // [{key: 1, value: 10}, // {key: 2, value: 20}, // {key: 3, value: 30}] ----------------- function returnInt(element) { return parseInt(element, 10) } ['5', '8', '10'].map(returnInt) // (3) [5, 8, 10]
-
includes()
-
- 语法:arr.includes(valueToFind[, fromIndex])
- 参数:
-
-
- valueToFind - 想要搜索的内容
- fromIndex - 从那个索引开始,默认为零
- 当 fromIndex 大于等于 array.length时返回 false
- 当 fromIndex 的值小于等于 -1*array.length 时,搜索整个数组
-
-
[1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true [1, 2, NaN].includes(NaN); // true // array length is 3 // fromIndex is -100 // computed index is 3 + (-100) = -97 var arr = ['a', 'b', 'c']; arr.includes('a', -100); // true arr.includes('b', -100); // true arr.includes('c', -100); // true arr.includes('a', -2); // false
-
总结:
- 返回数组 filter, map
- 返回布尔值 includes,every, some
- 数字 find, findIndex
- forEach
-
reduce() - 对数组中每个元素执行一个由自己提供的函数(升序),将其结果汇总为单个返回值。
-
语法:Array.prototype.reduce(callback[, initialValue])
-
参数:callback(acu, cur[, index, array])
-
- acu - 接收 callback 的返回值,当存在 initialValue 时作为 acu的初始值
- cur - 当前 array 中的 element
- index - 当前 element 的索引
- array - 当前执行的 array
- initialValue - 作为 acu 的初始值
-
return Value - 唯一值
-
注:如果没有 initialValue 回调函数会从 index 1 执行,如果有 initialValue 回调函数从 index 0 开始执行,acu 默认为 array[0]
-
Examples
-
// 计算数组之和 let sum = [1, 3, 8].reduce((acu, cur) => { return acu + cur }, 0) console.log(sum) // 12 // 计算对象中数字之和 let objSum = [{x: 1}, {x: 2}, {x: 3}].reduce((acu, cur) => { return acu + cur.x }, 0) console.log(objSum) // 6 // 组合数组 let flattenArr = [[0, 2], [1, 3], [4, 6], [5, 7]].reduce((acu, cur) => { return acu.concat(cur) }, []) console.log(flattenArr) // (8) [0, 2, 1, 3, 4, 6, 5, 7] // 数组查重 var names = ['liu', 'long', 'wei', 'liu', 'long'] var countNames = names.reduce((acu, cur) => { if (cur in acu) { acu[cur]++ } else { acu[cur] = 1 } return acu // 返回自己想要的 }, {}) console.log(countNames)
返回数组索引
- indexOf() - 返回在数组中可以找到一个给定元素的第一个索引,不存在则返回 -1。
- 语法:arr.indexOf(searchElement) / arr.indexOf(searchElement[, fromIndex = 0])
- 参数:
-
- searchElement - 要查找的元素
- fromIndex - 元素开始查找的位置
-
- 索引值 >= arr.length, 不会在数组里查找,返回 -1。
- 索引值为负,-1表示从倒数第一个开始查找,-2表示从倒数第二个元素开始查找,依此类推,负索引值小于零,则会查找整个数组。
- return value - 首个被找到的元素在数组中的索引位置,若没有则返回 -1。
- focuse: indexOf 使用 strict equality( === ) 进行判断 searchElement与数组中包含的元素的关系。
-
// 确定某数据在数组中的位置 var arr = [2, 3, 8] arr.indexOf(2) // 0 arr.indexOf(8, 2) // 2 arr.indexOf(2, -3) // 0 // 找出指定元素在数组中的所有位置 var includes = [] var arr = [0, 3, 8, 9, 3, 6, 3, 5] var ele = 3 var index = arr.indexOf(ele) while (index != 1) { includes.push(index) index = arr.indexOf(ele, index + 1) } console.log(includes) // [1, 4, 6]
数组扁平化
- flat():方法会按照一个指定的深度递归遍历数组,将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
-
语法:
-
var newArr = arr.flat([depth]) - 参数:depth - 指定要提嵌套数组的结构深度
- 返回值:包含数组与子数组中所有元素的一维新数组
-
-
示例: // 1. 扁平化嵌套数组 let arr1 = [1, 2, [3, 4, 5]] let newArr = arr1.flat() // [1, 2, 3, 4, 5]
// 2. 使用 Infinity,可展开任意深度的嵌套数组 let arr2 = [1, 2, [3, 4, [5, 6, [7, 8]]]] arr2.flat(Infinity) // [1, 2, 3, 4, 5, 6, 7, 8] -
替代方案 // 1. 使用 reduce和 concat let arr = [1, 2, [3]] let newArr1 = arr.reduce((pre, cur) => pre.concat(cur), []) // [1, 2, 3]
// 2. 使用扩展运算符 ... let flatArr = arr => [].concat(...arr) // 3. reduce + concat + isArray let arr1 = [1, 2, 3, [4, 5, [6, 7, [8]]]] let deepFlatArr = (arr, d = 1) => { return d > 0 ? arr.reduce((pre, cur) => { pre.concat(Array.isArray(cur)? deepFlatArr(arr, d-1): cur), []}): arr.slice() } deepFlatArr(arr, Infinity) // 4. forEach 递归降维 let newArr = [] let deepFlatArr1 = (arr, newArr) => { arr.forEach(item => { if(Array.isArray(item)) { deepFlatArr1(item, newArr) }else { newArr.push(item) } }) } deepFlatArr1(oldArr, newArr) console.log(newArr) // 5. reduce 递归降维 let deepFlatArr2 = (arr) => arr.reduce((pre, cur, index) => { if(Array.isArray(cur)) { return pre.concat(...deepFlatArr2(cur)) } return pre.concat(cur) }, []) const newArr2 = deelFlatArr2(oldArr) console.log(newArr2)
-
js中的-this-
函数调用
-
- js(ES5)中有三种函数调用形式:
-
- fun(p1, p2)
- obj.obj1.method(p1, p2)
- fun.call(context, p1, p2)
- 其中第三种形式才是正真的调用形式 -- fun.call(context, p1 ,p2)
- 其余两种都是语法糖,可以等价的变为 call 形式
-
- fun(p1, p2) 等价于 fun.call(undefined, p1, p2)
- obj.obj1.method(p1, p2) 等价于 obj.obj1.method.call(obj.obj1, p1, p2)
- 所以总结函数调用就只有一种形式:fun.call(context, p1, p2), 这时 this 说白了就是 context
let a = 5;
let obj = {
a: 10,
foo: function() {
console.log(this.a)
}
}
let bar = obj.foo
obj.foo() // 10
bar() // undefined
- obj.foo() 转化为 call 的形式就是 obj.foo.call(obj), 所以 this 指向 obj
- 而 bar() 转化为 call 的形式就是 bar.call() 由于没有 context 故 this 就是 undefined [ ] 语法中的 this 关键字 function() {console.log(this)} var arr = [fn, fn1] arr0 // ?
- 我们可以把 arr0想象成为arr.0(),语法会报错,但可以和obj.obj1.method(p1, p2)对应上,于是就转换为: arr0 转换 arr.0() 再转换 arr.0.call(arr) 故:arr === this
this 绑定原则
##### 默认绑定
-
默认绑定是针对函数被独立调用时,不带任何修饰的函数引用进行调用,非严格模式下 this 指向全局对象(浏览器指向 Window, Node.js指向 Global),严格模式下,this 绑定到 undefined let a = 'hello' let obj = { a: 'liuV', foo: function() { console.log(this.a) } } let bar = obj.foo bar() // 当a为let声明时是 undefined,var 声明时是 hello 默认绑定的另一种情况
-
在函数中一函数被作为参数进行传递,如setTimeout和 setInterval等,这些函数中传递的函数参数中的this指向,在非严格模式下都是全局对象 var name = 'koala' var person = { name: '程序指北', sayHi: sayHi } function sayHi() { setTimeout(function() { console.log('Hello', this.name) }) } person.sayHi() // Hello koala setTimeout(function() { person.sayHi() // Hello koala }, 100)
var name = 'koala' var person = { name: '程序指北', sayHi: sayHi } function sayHi() { console.log(this) // setTimeout(function() { setTimeout(() => { // 换成箭头函数结果就不同 console.log('Hello', this.name) }) } person.sayHi() // Hello 程序指北 setTimeout(function() { person.sayHi() // Hello 程序指北 }, 100) ##### 隐式绑定 -
函数被调用时是否在上下文中调用,或是否存在某个对象调用函数 var a = 'loala' var obj = { a: 'this指北', foo: function() { console.log(this.a) } } obj.foo() // this指北
-
- foo 方法是作为对象的属性调用的,那此时 foo 方法执行时,this 指向 obj 对象 ##### 隐式绑定的另一种情况
-
当有多层对象嵌套调用某个函数时,如 对象.对象.函数,this 指向最后一层对象 function sayHi() { console.log('Hello', this.name) // Hello 指北 } var person2 = { name: '指北', sayHi: sayHi } var person1 = { name: 'koala', friend: person2 } person1.friend.sayHi() ##### 显式绑定
-
显示绑定,通过函数 call apply bind 可以修改函数 this 的指向。call 与 apply 都是挂载在 Function 原型下的方法,所有的函数都能使用 ###### call 和 apply 的区别
-
- call 和 apply 的第一个参数会绑定导函数体的 this 上,如果不传参,如 fun.call(), 非严格模式下,this默认绑定到全局对象
-
-
call 函数余下的参数接受一个参数列表,apply 函数接收一个参数数组 fun.call(this, p1, p2, p3) fun.apply(this, [p1, p2, p3 ...]) var person = { name: 'koala' } function changeJob(comp, work) { this.comp = comp this.work = work }
changeJob.call(person, 'baidu', 'IT') console.log(person.work) // IT console.log(person) // {name: "koala", comp: "baidu", work: "IT"} changeJob.apply(person, ['ali', 'ceshi']) console.log(person.work) // ceshi console.log(person) // {name: "koala", comp: "ali", work: "ceshi"} ###### call和 apply的注意点
-
-
这两个方法在调用时,如果传入数字或字符串,这两个方法会把传入的参数转为对象类型 var number = 1, string = '程序员成长指北'; function getThisType () { var number = 3; console.log('this指向内容',this); console.log(typeof this); } getThisType.call(number); getThisType.apply(string); // 输出结果 // this指向内容 [Number: 1] // object // this指向内容 [String: '程序员成长指北'] // object ###### bind函数
-
bind函数会创建一个新函数,当这个新函数被调用时,bind()的第一个参数将作为它运行时的 this,之后的一序列参数会在传递的实参前作为它的参数。 fun.bind(this, p1, p2, p3...) var public = { name: '指北', author: 'koala', sub(temp) { console.log(temp+this.name) } } public.sub('xiao') // xiao 指北
var sub1 = public.sub.bind({name: 'node指北', author: 'lala'}, 'da') sub1() // da node指北 ###### new 绑定 -
使用 new 调用函数时, 会执行怎样的流程:
- 创建一个空对象
- 将空对象的 proto 指向原对象的 prototype
- 执行构造函数中的代码,将属性和方法放入空对象中
- 返回这个新创建的对象 function Study(name) { this.name = name } var stu = new Study('lala') console.log(stu) // Study {name: "lala"} console.log('Hello', stu.name) // Hello lala
-
注:在 new Study('lala') 时,会改变 this 的指向,将 this 指向指定到 stu 对象。如果创造新的对象,且构造函数不传值的话,新对象的属性不会有值,但新的对象会有这个属性。 ##### this绑定优先级 ###### new绑定 > 显示绑定 > 隐式绑定 > 默认绑定 ##### 箭头函数中的this
-
注:箭头函数不能绑定自己的 this, arguments, super等
-
箭头函数没有自己的 arguments
- 常规函数可以拿到 arguments 属性,但在箭头函数中使用 arguments,拿到的是箭头函数外层函数的 arguments 属性。 function con() { return () => arguments[0] } let result = con(1) console.log(result()) // 1
-
箭头函数没有构造函数、原型、super等
-
箭头函数没有自己的 this
- 箭头函数中的 this不能用 call、apply、bind 这些方法来改变 this 的指向,箭头函数的 this 指向调用函数的上一层运行时 let a = 'kola' var b = 'ceshi' let obj = { a: '指南', b: '测试', foo: () => console.log(this.a, this.b) } obj.foo() // undefined "ceshi"
- 箭头函数中的 this 指向调用函数的上一层运行时 自执行函数
-
自执行函数无需调用,立即执行 (function() { console.log('指南') // 指南 }()); (function() { console.log('指北') // 指北 })();
(() => { // 箭头函数只能这样写 console.log('指点') // 指点 })() var length = 10; function fn() { console.log(this.length); } var obj = { length: 5, method: function(fn) { fn(); arguments[0](); } }; obj.method(fn, 1) // 10 2
Promise
第一章 准备
- 1.1 区别实例对象与函数对象
- 实例对象:new 函数产生的对象,称为实例对象,简称为对象
- 函数对象:将函数作为对象使用时,简称为函数对象
// 函数对象与实例对象
function Fn() { // Fn函数
}
const fn = new Fn() // 此时 Fn是构造函数,fn是实例对象(简称对象)
// 比如创建一个person对象的前提是有一个Person构造函数
function Person() { // Person 构造函数
}
const person = new Person() // person 对象
console.log(Fn.prototype)
// 我们从语法方向分析,如:f1(),由括号可知括号的左边f1是一个函数
// a.b.c() 分析可知 a.b.c一定是一个函数
// 得出的结论为: 括号'()'的左边必然是函数
// 那 点'.'的左边必然是对象
// 所以 Fn.protptype 中的 Fn就是一个对象,但其本质是一个函数,所以,此时Fn称为 '函数对象'
// 注:函数本身就是一个对象,只有当其右边为点'.'时才能体现其对象的特点
// 比如,此时调用函数对象的 bind({})方法
Fn.bind({}) // 此时 Fn 是函数对象
.get('/test') // jQuery 函数对象
- 1.2 两种类型的回调函数
回调函数的定义:自己定义的函数、一般自己不直接调用、可以执行
-
同步回调 理解:立即执行,完全执行完才结束,不会放入回调队列中 例子:数组遍历相关的回调函数、Promise的 excutor函数
-
异步回调 理解:不会立即执行,会放入回调队列中将来执行 例子:定时器回调、ajax回调、Promise的成功|失败回调 // 1. 同步回调 const arr = [1, 3, 5] arr.forEach(item => { // 同步回调函数,立即执行 console.log(item) }) console.log('forEach()---之后') // 先执行 console.log(item) 再执行 console.log('forEach()---之后')
// 2. 异步的回调 setTimeout(() => { // 异步回调函数,会放入队列中将来执行 console.log('timeout callback()') },0) console.log('setTimeout()之后') // 运行可知 console.log('setTimeout()之后')先执行 console.log('timeout callback()')后执行
-
- 1.3 Js中 error的处理 (MDN)
- 错误的类型 Error: 所有错误的父类型 ReferenceError: 引入的变量不存在 TypeError: 数据类型不正确 RangeError: 数据值不在其所允许的范围内 SyntaxError: 语法错误 // ReferenceError console.log(q) // ReferenceError: q is not defined; 后面的代码不会执行 // TypeError let b console.log(b.xxx) // TypeError: Cannot read property xxx of undefined // RangeError function fn() { fn() } fn() // RangeError: Maximum call stack exceeded // SyntaxError const a = 0 a = 1 // TypeError: Assignment to constant variable
- 错误处理 (为了程序继续向下执行) 捕获错误:try ... catch 抛出错误:throw error // try...catch -- 捕获处理 try { let d console.log(d.xxx) } catch (error) { // console.log(error) // 断点调试 {message:'...', stack: '...'} console.log(error.message) // Cannot read property 'xxx' of undefined console.log(error.stack) // TypeError: Cannot read property 'xxx' of undefined at :3:19 } // -- 之后的代码可以继续执行 // throw error -- 主动抛出异常 function todo() { if(Date.now()%2 === 1) { console.log('奇数') }else { throw new Error('偶数') } } // 捕获处理异常 try{ todo() }catch (error) { alert(error.message) }
- 错误对象 message属性:错误相关信息 stack属性:函数调用栈记录信息
第二章 Promise 的理解和使用
-
2.1 Promise 是什么
- 理解
- 抽象表达:Promise 是 Js中进行异步编程的新的解决方案 (旧的是纯回调的形式)
- 具体表达: 从语法上来说:Promise 是一个构造函数 从功能上来说:Promise 对象用来封装一个异步操作并可以获取其结果
- promise 的状态改变
- pending 变为 resolved
- pending 变为 rejected 说明:只有以上这两种变化,且一个 promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为 value,失败的结果数据一般称为 reason。
- promise 的基本流程
- new Promise() (pending状态) -> 成功了,执行resolve() -> promise对象(resolved状态) -> 回调onResolved() (then()) -> 新的 promise对象
- new Promise() (pending状态) -> 失败了,执行reject() -> promise对象(rejected状态) -> 回调onRejected() (then()/catch()) -> 新的 promise对象
- promise 的基本使用 // 1. 创建一个新的 promise对象 const p = new Promise((resolve, reject) => { // 执行器函数(同步回调) // 2. 执行异步操作任务 setTimeout(() => { const time = Date().now() if(time%2 === 0) { // 3.1 成功,调用resolve(value) resolve('成功的数据,time =' + time) } else { // 3.2 失败,调用reject(reason) reject('失败的数据,time = ' + time) } }, 1000) }) p.then( value => { // 接收得到成功的value数据 -- onResolved console.log('成功的回调', value) }, reason => { // 接收得到失败的reason数据 -- onRejected console.log('失败的回调', reason) })
- 理解
-
2.2 为什么要用 Promise
- 指定回调函数的方式更加灵活(时间)
- 旧的:必须在启动异步任务前指定
- promise:启动异步任务 -> 返回promise对象 -> 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
- 支持链式调用,可以解决回调地狱问题
- 何为回调地狱?:回调函数嵌套调用,外部回调函数异步执行的结果,是嵌套的回调函数执行的条件。 回调地狱问题会涉及到多个异步操作,而且它是串联执行的,内部回调是以外部回调的结果为条件执行,涉及到回调的嵌套,并且还要分别做异常处理
- 回调地狱的缺点:不便于阅读/不便于异常处理
- 解决方案?:promise链式调用 代码从上向下编写,类似于同步编码方式,便于阅读/统一处理异常(异常传透)
- 终极解决方案:async / await
- 指定回调函数的方式更加灵活(时间)
-
2.3 如何使用 Promise
- API
-
Promise构造函数:Promise(excutor) excutor函数:同步执行 (resolve,reject) => {} resolve函数:内部定义成功时,我们调用的函数 resolve => {} reject函数:内部定义失败时,我们调用的函数 reject => {} 说明:excutor 会在 Promise内部立即同步回调,异步操作在执行器中执行
-
Promise.prototype.then方法:(onResolved,onRejected) => {} onResolved函数:成功的回调函数 (value) => {} onRejected函数:失败的回调函数 (reason) => {} 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调,返回一个新的promise对象(链式调用的前提)
-
Promise.prototype.catch方法:(onRejected) => {} onRejected函数:失败的回调函数 (reason) => {} 说明:then()的语法糖,相当于:then(undefined,onRejected)
-
Promise.resolve方法:(value) => {} value: 成功的数据或promise对象 说明:返回一个成功/失败的promise对象
-
Promise.reject方法:(reason) => {} reason:失败的原因 说明:返回一个失败的promise对象
-
Promise.all方法:(promises) => {} promises:包含n个promise的数组 说明:返回一个新的promise,只有所有的promise都成功才成功,有一个失败就失败
-
Promise.race方法:(promises) => {} promises:包含n个promise的数组 说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态 new Promise((resolve,reject) => { setTimeout(() => { resolve('成功') }) }).then( value => { console.log('onResolved1()', value) } ).catch( reason => { console.log('onRejected1()', reason) } )
const p1 = Promise.resolve(2) const p2 = Promise.reject(3) p1.then(value => {console.log(value)}) // 2 p2.catch(reason => {console.log(reason)}) // 3
const pAll = Promise.all([p1,p2]) pAll.then( values => { console.log('all onResolved', values) // [2] }, reason => { console.log('all onRejected', reason) // 3 } )
const pRace = Promise.race([p1,p2]) pRace.then( value => { console.log('race onResolved', value) // 2 }, reason => { console.log('race onRejected', reason) // 3 } )
-
- promise 的几个关键问题
-
如何改变 promise 的状态?
- resolve(value):如果当前是 pending 就会变为 resolved
- reject(reason):如果当前是 pending 就会变为 rejected
- 抛出异常:如果当前是 pending 就会变为 rejected const p = new Promise((resolve,reject) => { // resolve(1) // promise 变为 resolved 成功状态 // reject(2) // promise 变为 rejected 失败状态 // throw new Error('出错了') // 抛出异常,promise 变为 rejected 失败状态 reason 为抛出的 error throw 3 // 可以抛任何东西,reason 为 3 }) console.log(p) p.then( value => {}, reason => {console.log('reason', reason)} // reason 3 ) p.then( value => {}, reason => {console.log('reason1', reason)} // reason1 3 )
-
一个 promise 指定多个成功/失败回调函数,都会被调用吗?
- 当 promise 改变为对应状态时都会调用
-
改变 promise 状态和指定回调函数谁先谁后?
- 都有可能,正常情况下是先指定回调再改变状态,也可以先改变状态再指定回调
- 如何先改变状态再指定回调?
- 在执行器中直接调用 resolve()/reject()
- 延迟更长时间后才调用 then()
- 什么时候才能得到数据?
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据 // 1. 先指定回调,后改变状态 new Promise ((resolve,reject) => { setTimeout(() => { resolve(1) // 后改变状态(同时指定数据),异步执行回调函数 }) }).then( // 先指定回调函数,保存当前指定的回调函数 value => {}, reason => {console.log('reason', reason)} ) // 2. 先改变状态,后指定回调 const p = new Promise((resolve,reject) => { setTimeout(() => { resolve(1) // 先改变状态,保存数据 }, 0) }) setTimeout(() => { p.then( // 后指定回调函数,异步执行回调函数 value => {console.log(value)}, reason => {} ) }, 1000)
-
promise.then() 返回的新 promise 的结果状态由什么决定?
- 简单表达式:由 then() 指定的回调函数执行的结果决定
- 详细表达:
- 如果抛出异常,新promise变为rejected,reason为抛出的异常
- 如果返回的是非promise的任意值,新promise变为resolved,value为返回值
- 如果返回的是另一个新promise,此promise的就会成为新promise的结果 new Promise((resolve,reject) => { // resolve(1) reject(1) }).then( value => { console.log('onResolved1()', value) // onResolved1() 1 // return 2 // return Promise.resolve(3) // return Promise.reject(4) throw 5 }, reason => { console.log('onRejected1()' reason) // onRejected1() 1 } ).then( value => { console.log('onResolved2()', value) // onResolved2() undefined;onResolved2 undefined }, reason => { } )
-
promise 如何串联多个操作任务?
- promise的then()返回一个新的promise,可以形成then()的链式调用
- 通过then()的链式调用串连多个同步/异步任务 new Promise((resolve,reject) => { setTimeout(() => { console.log('执行任务1(异步)') resolve(1) },1000) }).then( value => { console.log('任务1的结果:',value) console.log('执行任务2(同步)') return 2 } ).then( value => { console.log('任务2的结果:',value) return new Promise((resolve,reject) => { setTimeout(() => { // 异步任务3 console.log('执行任务3(异步)') resolve(3) }, 1000) }) } ).then( value => { console.log('任务3的结果:',value) } )
-
promise异常传/穿透?
- 当使用promise的then链式调用时,可以在最后指定失败的回调
- 前面任何操作出现异常,都会传到最后失败的回调中处理
-
中断promise链?
- 当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
- 办法:在回调函数中返回一个pending状态的promise对象 new Promise((resolve,reject) => { reject(1) }).then( value => { console.log('onResolved1()', value) }, reason => {throw reason} // 如果不写默认如此或下面一条语句 // reason => Promise.reject(reason) ).then( value => { console.log('onResolved()2',value) }, reason => {throw reason} // 如果不写默认如此 ).then( value => { console.log('onResolved3()',value) }, reason => {throw reason} // 如果不写默认如此 ).catch( reason => { console.log('onRejected1()',reason) // throw reason // return Promise.reject(reason) return new Promise(() ={}) // 返回一个pending状态的promise,中断promise链 } ).then( value => { console.log('onResolved4()',value) }, reason => { console.log('onRejected2()',reason) } )
-
- API
第三章:自定义(手写) Promise
第四章:async 与 await
- 4.1 MDN文档
- 4.2 async 函数
- 函数的返回值为 promise对象
- promise 对象的结果,由 async 函数执行的返回值决定
- 4.3 await 表达式
- await 右侧的表达式一般为 promise 对象,但也可以是其它值
- 如果表达式是 promise 对象,await 返回的是 promise 成功的值
- 如果表达式是其它值,直接将此值作为 await 的返回值
- 4.4 注意:
- await 必须写在 async 函树中,但 async 函数中可以没有 await
- 如果 await 的 promise 失败了,就会抛异常,需要 try ... catch ... 来捕获处理
第五章:Js异步之 宏队列与微队列
关于 JavaScript 语言
在js运行时,JavaScript Engine会创建和维护相应的堆(Heap)和栈(Stack),同时通过JavaScript Runtime提供一系列API(setTimeout、XMLHttpRequest等)来完成各种各样的任务。
JavaScript是一种单线程编程语言,只有一个调用栈,决定了它在同一时间只能做一件事。
在JavaScript的运行过程中,真正负责执行JavaScript代码的始终只用一个线程,通常被称为主线程,各种任务都会用排队的方式来同步执行。
然而,为了防止JavaScript运行时阻塞,使其异步、并发式,就有了事件循环(Event Loop)机制
基本过程
-
5.1 堆、栈、队列
- 堆(Heap) 一种数据结构,在js中用于存放引用类型的数据
- 栈(Stack) 一种数据结构,仅在表尾进行插入或删除操作的线性表 按照后进先出的原则存储/操作数据,需要读取数据时,从栈顶开始弹出数据
- 队列(Queue) 是一种操作受限制的线性表,先进先出 它只允许在表的前端进行删除操作,在表的后端进行插入操作
-
5.2 原理图(F:/HTCS/Promise)
- js引擎
主线程
- heap堆: 存放对象
- stack栈: 执行函数,并将其中的异步回调放入下面的分线程中
- 分线程
浏览器的Web APIs管理模块,在合适的时机放入队列中等待执行
- DOM事件管理模块(DOM操作)
- ajax回调管理模块(网络请求)
- 定时器管理模块
- Promise管理模块
- Mutation回调管理模块
- ...
- 队列
- 宏队列:dom事件回调、ajax回调、定时器回调
- 微队列:promise回调、mutation回调
- js引擎
主线程
-
5.3 说明
- Js中用来存储执行回调函数的队列包含2个不同的队列
- 宏队列:用来保持待执行的宏任务(回调),比如 定时器回调、DOM回调、Ajax回调
- 微队列:用来保持待执行的微任务(回调),比如 Promise回调、MutationObserver回调
- Js执行时会区分这两个队列
- Js引擎必须先执行所有的初始化同步任务代码
- 每次准备取出一个新的宏任务执行前,都要将所有的微任务一个一个取出来执行
Event Loop中的任务队列
Event Loop是JavaScript实现异步的一种机制
Event Loop是让JavaScript做到既是单线程,又绝对不会阻塞的核心机制,也是JavaScript并发模型(Concurrency Model)的基础,是用来协调各种事件、用户交互、脚本执行、UI渲染、网络请求等的一种机制
Event Loop是属于JavaScript Runtime的,是由宿主环境提供的(比如浏览器)
在执行和协调各种任务时,Event Loop会维护自己的任务队列。
任务队列分为Task Queue和Microtask Queue两种
- Task Queue
一个Event Loop会有一个或多个 Task Queue,这是一个先进先出的有序列表
其存放着来自不同Task Source的Task
在HTML标准中定义了几中常见的Task Source:
- DOM manipulation(DOM操作)
- User interaction(用户交互)
- Networking(网络请求)
- History traversal(History API操作) 对于Task、Task Queue、和Task Source,有如下规定:
- 来自相同Task Source的Task,必须放在同一个Task Queue中
- 来自不同Task Source的Task,可以放在不同的Task Queue中
- 同一个Task Queue中的Task是按顺序执行的
- 对于不同的Task Queue(Task Source),浏览器会自行进行调度
- Microtask Queue
Microtask Queue与Task Queue类似,也是一个有序列表
不同之处在于一个Event Loop只有一个Microtask Queue
在HTML标准中,并没有明确规定Micritask Source,通常认为有以下几种
- Promise
- MutationObserver
JavaScript Runtime 的运行机制
runtime: 执行环境
从相对宏观的角度了解JavaScript Runtime的运行机制
- 主进程不断循环
- 对于同步任务,创建执行上下文(Execution Context),按顺序进入执行栈
- 对于异步任务:
- 与步骤2相同,同步执行这段代码
- 将相应的Task/Microtask添加到Event Loop的任务队列
- 由其它线程来执行具体的异步操作 其它线程指:尽管JavaScript是单线程的,但浏览器内核是多线程的,它会将GUI渲染、定时器触发、HTTP请求等工作交给专门的线程来处理
- 当主线程执行完当前执行栈中的所有任务,就会去读取Event Loop的任务队列,取出并执行任务
- 重复以上步骤...
- 图(F:/HTCS/Promise)
Event Loop处理模型
前面简单介绍了JavaScript Runtime的整个运行流程,而Event Loop作为其中重要一环,它的每一次循环过程也相当复杂,因此将它单独拿出来介绍。
根据HTML标准中对处理模型(Processing Model)的定义,尽量简化为如下三步:
- 执行Task: 从Task Queue中取出最前面的一个Task并执行;如果没有Task,直接跳过
- 执行Microtasks: 遍历Micritask Queue并执行所有Microtask
- 进入Update the rendering(更新渲染)阶段:
- 设置Permance API中now()的返回值
- 遍历本次Event Loop相关的Documents,执行更新渲染
- 当浏览器确认继续本次更新后,处理更新渲染相关工作
- 图(F:/HTCS/Promise)
Microtask Queue 执行时机
执行Microtasks的前提是: 当前执行栈必须为空,且没有正在运行的执行上下文
实际上按照HTML标准,在以下几种情况中Microtask Queue都会被执行:
- 某个Task执行完毕时
- 进入脚本执行Calling scripts的清理阶段时
- 创建和插入节点时
- 解析XML文档时
- 在当前Event Loop轮次中动态添加进来的Microtasks,也会在本次Event Loop循环中全部执行完
Microtask Queue 应用场景
一个典型应用就是Vue.js的异步更新队列的实现,Microtask Queue在这里起到的主要作用:
-
提供异步队列 使Vue.js能够缓冲在同一个Event Loop中发生的搜友数据变更,从而有机会在真正更新UI前,去除重复触发的数据变更,避免DOM进行不必要的更新
-
借助Promise.then、MutationObserver等实现Vue.nextTick()方法(本质就是利用Microtask Queue的执行时机) 使Vue.js有能力让UI尽早得到更新,即在当前Event Loop轮次(而非下一轮)中就更新完毕 这样带来的一个明显的好处就是UI更稳定、更流畅
Axios
第一章:HTTP 相关
-
MDN 相关
-
HTTP 请求交互的基本过程
- 请求行
- 请求头
- 请求体
- 状态行
- 响应头
- 实体内容(响应体)
- 前后应用从浏览器端向服务器发送HTTP请求(请求报文 -> 1.2.3.)
- 后台服务器接收到请求后,调度服务器应用处理请求,向浏览器端返回HTTP响应(响应报文 -> 4.5.6.)
- 浏览器端接收到响应,解析显示响应体/调用监视回调
-
HTTP 请求报文
- 请求行
method :请求方式,常用如下
url:请求地址
- GET /product_detail?id=2
- POST /login
- 多个请求头 Host:www.baidu.com 主机 Cookie:BAIDUID=ARH65E4B5; BAIDUPSID=S6DF41B1AD3 Content-Type: application/x-www-form-urlencoded 或 application/json 请求体内容格式
- 请求体(post) username=tom&pwd=123 - urlencoded查询参数 {"username":"tom","pwd":123} - json对象格式
- 请求行
method :请求方式,常用如下
url:请求地址
-
HTTP 响应报文
- 响应状态行: status:200 201 401 404 500 statusTest:ok、Created、Unauthorized、Not Found、Internal Server Error
- 多个响应头 Content-Type: text/html;charset=utf-8(编码字符集) Set-Cookie: BD_CK_SAM=1;path=/
- 响应体 html文本 / json文本 / js / css / 图片
-
post 请求体参数格式
- Content-Type: application/x-www-form-urlencoded;charset=utf-8(与get方式类似) 用于键值对参数,参数的键值用 = 连接,参数之间用 & 连接 如:name=%f5%f5%f5b%5tg&age=12
- Content-Type: application/json;charset=utf-8 用于 json 字符串参数 例如:{"name":"%f5%f5%f5b%5tg","age":12}
- Content-Type: multipart/form-data 用于文件上传请求
-
常见的响应状态码 200 OK 请求成功。一般用于get和post请求 201 Created 已创建。成功请求并创建了新的资源 401 Unauthorized 未授权/请求要求用户的身份认证 404 Not Found 服务器无法根据客户端的请求找到资源 500 Internal Server Error 服务器内部错误,无法完成请求
-
不同类型的请求及作用 get:从服务器端读取数据(query/params)
- query 对所有的数据进行过滤处理 (/posts?id=1)
- params 直接定位到目标数据 (/posts/1) post:向服务器端添加新数据 put:更新服务器端已有数据 delete:删除服务器端数据
-
API 的分类(前后台交互的接口)
- REST API:restful
- 发送请求进行 CRUD 哪个操作由请求方式决定
- 同一个请求路径可以进行多个操作
- 请求方式会用到 GET/POST/PUT/DELETE
- 非REST API:restless
- 请求方式不决定请求的 CRUD 操作
- 一个请求路径只对应一个操作
- 一般只有 GET(查询)/POST(增删改)
- REST API:restful
-
使用 json-server 搭建 REST API
- json-server 是什么? 用来快速搭建 REST API 的工具包
- 使用 json-server
- github
- npm install -g json-server 下载
- json-server --watch db.json 运行
第二章:XHR 的理解和使用
- MDN 文档
- 理解
- 使用 XMLHttpRequest(XHR)对象可以与服务器交互,也就是发送 ajax 请求
- 前端可以获取到数据,而无需让整个页面刷新
- 这使得Web页面可以只更新页面的局部,而不影响用户的操作
- 区分一般 http 请求与 ajax请求
- ajax 请求是一种特殊的 http 请求
- 对服务器端来说,没有任何区别,区别在浏览器端
- 浏览器端发请求:只有 XHR 或 fetch 发出的才是 ajax请求,其它所有都是非 ajax请求
- 浏览器端接收到响应:
- 一般请求:浏览器一般会直接显示响应体数据,也就是人们常说的刷新/跳转页面(自动显示得到的数据)
- ajax请求:浏览器不会对界面进行任何更新操作,只是调用监视的回调函数并传入响应相关数据(需手动将获得的数据去更新页面局部)
- API(MDN)
- XMLHttpRequest():创建XHR对象的构造函数
- status:响应状态码值(只读),200 201 401 404 500 ...
- statusText:响应状态文本
- readyState:标识请求状态的只读属性
- 0:初始
- 1:open()之后
- 2:send()之后
- 3:请求中
- 4:请求完成
- onreadystatechange:绑定 readyState改变的监听
- responseType:指定响应数据类型,如果是 'json' ,得到响应后自动解析响应体数据
- response:响应体数据,类型取决于 responseType的指定
- timeout:指定请求超时时间,默认为0代表没有限制
- ontimeout:绑定超时的监听
- onerror:绑定请求网络错误的监听
- open():初始化一个请求,参数为:(method,url[,async])
- send(data):发送请求
- abort():中断请求
- getResponseHeader(name):获取指定名称的响应头值
- getAllReasonseHeaders():获取所有响应头组成的字符串
- setRequestHeader(name,value):设置请求头
- XHR 的 ajax 封装(简单版axios)
- 特点
- 函数的返回值为 promise,成功的结果为 response,异常的结果为error
- 能处理多种类型的请求:GET/POST/PUT/DELETE
- 函数的参数为一个配置对象(配置对象:属性名固定、属性名的意义/作用固定) { url: '', // 请求地址 method: '',// 请求方式 params: {},// GET/DELETE请求的 query参数 data: {}, // POST/DELETE请求的请求体参数 }
- 相应 json 数据自动解析为 Js
- 特点
第三章:axios的理解和使用
- axios 是什么?
- 前端最流行的 ajax 的请求库
- react/vue 官方都推荐使用 axios 发 ajax 请求
- 文档 github.com/axios/axios
- axios 的特点
- 基于 promise的异步ajax请求库 ----> 执行请求后返回的是 promise对象
- 浏览器端 / node 端都可以使用
- 支持 请求/响应 拦截器 ---> 对 请求和响应 做统一处理的函数
- 支持请求取消
- 支持 请求/响应 数据转换
- 批量发送多个请求 ---> 封装的是 Promise.all()
- axios 常用语法
- axios(config):通用/最本质的发任意请求的方式
- axios(url[,config]):可以只指定 url发 get请求
- axios.request(config):等同于 axios(config)
- axios.get(url[,config]):发 get 请求
- axios.delete(url[,config]):发 delete 请求
- axios.post(url[,data,config]):发 post 请求
- axios.put(url[,data,config]):发 put 请求
- axios.defaults.xxx:请求的默认全局配置
- axios.interceptors.request.use():添加请求拦截器
- axios.interceptors.response.use():添加响应拦截器
- axios.create([config]):创建一个新的 axios实例(它没有下面的功能)
- axios.Cancel():用于创建取消请求的错误对象
- axios.CancleToken():用于创建取消请求的 token对象
- axios.isCancel():是否是一个取消请求的错误
- axios.all(promises):用于批量执行多个异步请求
- axios.spread():用来指定接收所有成功数据的回调函数的方法,与all()配合使用 注:如果data为对象类型,axios默认使用json发请求
- axios 难点语法的理解和使用
- axios.create(config)
- 根据指定配置创建一个新的 axios,也就是每个新axios都有自己的配置
- 新axios只是没有取消请求和批量发请求的方法,其它所有语法都是一样的
- 为什么要设计这个语法?
- 需求:项目中有部分接口需要的配置与另一部分接口需要的配置不大一样,如何处理
- 解决:创建两个新的axios,每个都有自己特有的配置,分别应用到不同要求的接口请求中
- 拦截器函数 / ajax请求 / 请求的回调函数的调用顺序 axios拦截器的执行顺序是:2112(axios_chain)
- 取消请求(请求以发出并且请求还没有完成)
- 基本流程
- 配置 cancelToken 对象
- 缓存用于取消请求的 cancel函数
- 在后面特定时机调用 cancel函数取消请求
- 在错误回调中判断如果 error是cancel,做相应处理
- 功能实现
- 基本流程
- axios.create(config)
第四章:axios 源码及分析
- 源码目录结构 axios.js / Axios.js(request、dispatchRequest(adapter -> xhr)、interceptors、) / defaults.js
- 源码分析
-
axios与Axios的关系
- 从语法上来说:axios(是函数)不是 Axios的实例 let instance = bind(Axios.prototype.request, context) // bind() 返回一个新函数,内部调用request()函数,类似如下 function () { Axios.prototype.request() }
- 从功能上来说:axios是Axios的实例
- axios是 Axios.prototype.response 函数 bind()返回的函数
- axios 作为对象有 Axios原型对象上的所有方法,有 Axios对象上所有属性
-
instance与axios的区别
- 相同
- 都是一个能发任意请求的函数:request(config)
- 都有发特定请求的各种方法:get()/post()/put()/delete()
- 都有默认配置和拦截器的属性:defaults/interceptors
- 不同
- 默认配置的值很可能不一样
- instance 没有 axios后面添加的一些方法:create()/CancelToken()/all()
- 相同
-
axios运行的整体流程
- 整体流程:request(config) ===> dispatchRequest(config) ===> xhrAdapter(config) promise链回调: chain:[ fulfilled2,rejected2,fulfilled1,rejected1, dispatchRequest,undefined, fulfilled11,regected11,fufilled22,rejected22 ] config => (fulfilled2,rejected2) => (fulfilled1,rejected1) // 请求拦截器处理 => (dispatchRequest,undefined) // 发请求 => (fulfilled11,regected11) => (fufilled22,rejected22) // 响应拦截器 => (onResolved, onRejected) // axios发请求回调处理 while(chain.length) { // 通过promise的then()串联起所有的 请求拦截器/请求方法/响应拦截器 promise = promise.then(chain.shift(),chain.shift()) } return promise // 返回用来指定我们的 onResolved/onRejected的promise
- request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串联起来,返回 promise
- dispatchRequest(config): 转换请求数据 ==> 调用 xhrAdapter()发请求 ==> 请求返回后转换响应数据,返回 promise
- xhrAdapter(config): 创建 XHR 对象,根据 config 进行响应设置,发送特定请求,并接收响应数据,返回 promise
-
axios的请求/响应拦截器是什么?
-
axios的请求/响应数据转换
-
response的整体结构
-
error的整体结构
-
如何取消未完成的请求?
- 当配置了 cancelToken 对象时,保存 cancel函数
- 创建一个用于将来中断请求的 cancelPromise
- 并定义了一个用于取消请求的 cancel函数
- 将 cancel函数传递出来
- 调用 cancel() 取消请求
- 执行 cancel函数,传入错误信息 message
- 内部会让 cancelPromise变为成功,且成功的值为一个 Cancel对象
- 在 cancelPromise的成功回调中中断请求,并让发请求的 promise失败,失败的reason为Cancel对象
- 当配置了 cancelToken 对象时,保存 cancel函数
-
第五章 axios 的应用
-
GET 请求 获取数据 请求参数:params: {name, title...} axios({ method: 'GET', // 请求方法,大小写无所谓,建议大写 url: 'http://localhost:3000/heros', // 请求地址 // url: 'http://localhost:3000/heros?name=' + name, // 请求地址 // url: 'http://localhost:3000/heros?name=李白', // 请求地址 params: { // GET 查询参数 // 等价于 name: name name: name }, }).then(function (res) { // axios 默认给你的是一个响应对象结果,响应对象中的 data 是具体的接口返回数据 console.log(res.data) })
-
POST 请求 添加数据 请求体:data: {name, bio} / data: hero const hero = { name: '小书包', bio: '智商250,嘟嘟嘟。。。' } axios({ method: 'POST', // 请求方法,大小写无所谓,建议大写 url: 'http://localhost:3000/heros', // 请求地址 data: hero // data 选项用来传递 POST 请求体 }).then(function (res) { // axios 默认给你的是一个响应对象结果,响应对象中的 data 是具体的接口返回数据 console.log(res.data) })
-
PATCH 请求 修改属性值,需要传递 id和data 请求参数: id,放到url中 请求体: data const hero = { name: '豆豆' } const id = 5 axios({ // patch 修改属性值, 需要传递id method: 'PATCH', // 请求方法,大小写无所谓,建议大写 url:
http://localhost:3000/heros/${id}, // 请求地址 // url: 'http://localhost:3000/todos/' + id, // 请求地址 data: hero // data 中放置要修改的数据内容悐 }).then(function (res) { // axios 默认给你的是一个响应对象结果,响应对象中的 data 是具体的接口返回数据 console.log(res.data) }) -
DELETE 请求 删除数据 请求参数: id,放到url中 const id = 3 axios({ method: 'DELETE', // 请求方法,大小写无所谓,建议大写 url:
http://localhost:3000/heros/${id}, // 请求地址 // url: 'http://localhost:3000/todos/' + id, // 请求地址 }).then(function (res) { // axios 默认给你的是一个响应对象结果,响应对象中的 data 是具体的接口返回数据 console.log(res.data) })
类和对象
第一章:什么是类
- 生活中:一类、种类
- 编程中:
- 类指的是抽象的名称:人、计算机 ...
- class关键字,ES6之前没有类的概念
- 在ES5及以前通过构造函数和new关键字来创建对象 // ES5 function Person (name,age) { this.name = name this.age = age // ... } // ES6 class Person { constructor(name,age) { this.name = name this.age = age // ... } }
第二章:什么是对象
- 生活中:任何具体的事和物都可以看作对象;万物皆对象。
- 编程中:对象由属性和方法组成(键值对)
- 属性:对象的静态特征,人的姓名、性别、身高等
- 方法:对象的功能特征,说话、跑步等
第三章:类(构造函数)与对象的关系
-
类是对象的模板
-
对象是类的具体实例
-
对象是通过类创建的 let p1 = new Person('小明',10) // ES5 function Person (name, age) { this.name = name this.age = age this.hello = function() { console.log('大家好,' + this.name) // 大家好,小明 } } let p1 = new Person('小明',10) p1.hello()
// ES6 class Person { constructor(name, age) { this.name = name this.age = age } hello () { console.log('大家好,' + this.name) // 大家好,小明 } } let p1 = new Person('小明',10) p1.hello()
第四章:继承 extends
- 子类继承父类中的成员 class Person { constructor(name, age) { this.name = name this.age = age } hello () { console.log('继承,' + this.name) // 继承,s1 } } class Student extends Person { constructor (type, name = 'student', age = 10) { // 传递参数 super super(name,age) this.type = type } } let s1 = new Student('student', 's1', 20) s1.hello() console.log(s1.type) // student
文件上传 file
demo: E:\PracticeSublime\Utils\uploadFile
binary:二进制
- 原理概述
原理:根据http协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件
- 请求头,设置如下
Content-Type: multipart/form-data
- Content-Type:表示当前内容的MIME类型,是图片、文本、二进制数据
- method必须:为post方式
- 无刷新上传
-
XMLHttpRequest:developer.mozilla.org/zh-CN/docs/…
-
无刷新上传肯定要用到XMLHttpRequest2,可以读取和上传文本和二进制数据
-
必须使用FormData构造函数创建实例管理表单数据
-
HTML input:type="file" 表示文件上传 multiple 表示可以同时上传多个文件
submit -
JS xhr open:初始化一个请求 send:发送请求 readyState:请求状态 onreadystatechange:绑定 readyState改变的监听 responseText:server返回值