JavaScript 总结

228 阅读39分钟

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])
    • 参数:
        1. valueToFind - 想要搜索的内容
      1. fromIndex - 从那个索引开始,默认为零
      2. 当 fromIndex 大于等于 array.length时返回 false
      3. 当 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
      
  • 总结:

    1. 返回数组 filter, map
    2. 返回布尔值 includes,every, some
    3. 数字 find, findIndex
    4. 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 的区别

    1. call 和 apply 的第一个参数会绑定导函数体的 this 上,如果不传参,如 fun.call(), 非严格模式下,this默认绑定到全局对象
    1. 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 调用函数时, 会执行怎样的流程:

    1. 创建一个空对象
    2. 将空对象的 proto 指向原对象的 prototype
    3. 执行构造函数中的代码,将属性和方法放入空对象中
    4. 返回这个新创建的对象 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 区别实例对象与函数对象
    1. 实例对象:new 函数产生的对象,称为实例对象,简称为对象
    2. 函数对象:将函数作为对象使用时,简称为函数对象 // 函数对象与实例对象 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 是函数对象 ('#test') // jQuery 函数.get('/test') // jQuery 函数对象
  • 1.2 两种类型的回调函数 回调函数的定义:自己定义的函数、一般自己不直接调用、可以执行
    1. 同步回调 理解:立即执行,完全执行完才结束,不会放入回调队列中 例子:数组遍历相关的回调函数、Promise的 excutor函数

    2. 异步回调 理解:不会立即执行,会放入回调队列中将来执行 例子:定时器回调、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)
    1. 错误的类型 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
    2. 错误处理 (为了程序继续向下执行) 捕获错误: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) }
    3. 错误对象 message属性:错误相关信息 stack属性:函数调用栈记录信息

第二章 Promise 的理解和使用

  • 2.1 Promise 是什么

    • 理解
      1. 抽象表达:Promise 是 Js中进行异步编程的新的解决方案 (旧的是纯回调的形式)
      2. 具体表达: 从语法上来说:Promise 是一个构造函数 从功能上来说:Promise 对象用来封装一个异步操作并可以获取其结果
    • promise 的状态改变
      1. pending 变为 resolved
      2. pending 变为 rejected 说明:只有以上这两种变化,且一个 promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为 value,失败的结果数据一般称为 reason。
    • promise 的基本流程
      1. new Promise() (pending状态) -> 成功了,执行resolve() -> promise对象(resolved状态) -> 回调onResolved() (then()) -> 新的 promise对象
      2. 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

    • 指定回调函数的方式更加灵活(时间)
      1. 旧的:必须在启动异步任务前指定
      2. promise:启动异步任务 -> 返回promise对象 -> 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
    • 支持链式调用,可以解决回调地狱问题
      1. 何为回调地狱?:回调函数嵌套调用,外部回调函数异步执行的结果,是嵌套的回调函数执行的条件。 回调地狱问题会涉及到多个异步操作,而且它是串联执行的,内部回调是以外部回调的结果为条件执行,涉及到回调的嵌套,并且还要分别做异常处理
      2. 回调地狱的缺点:不便于阅读/不便于异常处理
      3. 解决方案?:promise链式调用 代码从上向下编写,类似于同步编码方式,便于阅读/统一处理异常(异常传透)
      4. 终极解决方案:async / await
  • 2.3 如何使用 Promise

    • API
      1. Promise构造函数:Promise(excutor) excutor函数:同步执行 (resolve,reject) => {} resolve函数:内部定义成功时,我们调用的函数 resolve => {} reject函数:内部定义失败时,我们调用的函数 reject => {} 说明:excutor 会在 Promise内部立即同步回调,异步操作在执行器中执行

      2. Promise.prototype.then方法:(onResolved,onRejected) => {} onResolved函数:成功的回调函数 (value) => {} onRejected函数:失败的回调函数 (reason) => {} 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调,返回一个新的promise对象(链式调用的前提)

      3. Promise.prototype.catch方法:(onRejected) => {} onRejected函数:失败的回调函数 (reason) => {} 说明:then()的语法糖,相当于:then(undefined,onRejected)

      4. Promise.resolve方法:(value) => {} value: 成功的数据或promise对象 说明:返回一个成功/失败的promise对象

      5. Promise.reject方法:(reason) => {} reason:失败的原因 说明:返回一个失败的promise对象

      6. Promise.all方法:(promises) => {} promises:包含n个promise的数组 说明:返回一个新的promise,只有所有的promise都成功才成功,有一个失败就失败

      7. 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 的几个关键问题
      1. 如何改变 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 )
      2. 一个 promise 指定多个成功/失败回调函数,都会被调用吗?

        • 当 promise 改变为对应状态时都会调用
      3. 改变 promise 状态和指定回调函数谁先谁后?

        • 都有可能,正常情况下是先指定回调再改变状态,也可以先改变状态再指定回调
        • 如何先改变状态再指定回调?
          1. 在执行器中直接调用 resolve()/reject()
          2. 延迟更长时间后才调用 then()
        • 什么时候才能得到数据?
          1. 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
          2. 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据 // 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)
      4. promise.then() 返回的新 promise 的结果状态由什么决定?

        • 简单表达式:由 then() 指定的回调函数执行的结果决定
        • 详细表达:
          1. 如果抛出异常,新promise变为rejected,reason为抛出的异常
          2. 如果返回的是非promise的任意值,新promise变为resolved,value为返回值
          3. 如果返回的是另一个新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 => { } )
      5. 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) } )
      6. promise异常传/穿透?

        • 当使用promise的then链式调用时,可以在最后指定失败的回调
        • 前面任何操作出现异常,都会传到最后失败的回调中处理
      7. 中断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) } )

第三章:自定义(手写) Promise

第四章:async 与 await

  • 4.1 MDN文档
  • 4.2 async 函数
    1. 函数的返回值为 promise对象
    2. promise 对象的结果,由 async 函数执行的返回值决定
  • 4.3 await 表达式
    1. await 右侧的表达式一般为 promise 对象,但也可以是其它值
    2. 如果表达式是 promise 对象,await 返回的是 promise 成功的值
    3. 如果表达式是其它值,直接将此值作为 await 的返回值
  • 4.4 注意:
    1. await 必须写在 async 函树中,但 async 函数中可以没有 await
    2. 如果 await 的 promise 失败了,就会抛异常,需要 try ... catch ... 来捕获处理

第五章:Js异步之 宏队列与微队列

关于 JavaScript 语言

在js运行时,JavaScript Engine会创建和维护相应的堆(Heap)和栈(Stack),同时通过JavaScript Runtime提供一系列API(setTimeout、XMLHttpRequest等)来完成各种各样的任务。

JavaScript是一种单线程编程语言,只有一个调用栈,决定了它在同一时间只能做一件事。

在JavaScript的运行过程中,真正负责执行JavaScript代码的始终只用一个线程,通常被称为主线程,各种任务都会用排队的方式来同步执行。

然而,为了防止JavaScript运行时阻塞,使其异步、并发式,就有了事件循环(Event Loop)机制

基本过程

  • 5.1 堆、栈、队列

    1. 堆(Heap) 一种数据结构,在js中用于存放引用类型的数据
    2. 栈(Stack) 一种数据结构,仅在表尾进行插入或删除操作的线性表 按照后进先出的原则存储/操作数据,需要读取数据时,从栈顶开始弹出数据
    3. 队列(Queue) 是一种操作受限制的线性表,先进先出 它只允许在表的前端进行删除操作,在表的后端进行插入操作
  • 5.2 原理图(F:/HTCS/Promise)

    1. js引擎 主线程
      • heap堆: 存放对象
      • stack栈: 执行函数,并将其中的异步回调放入下面的分线程中
    2. 分线程 浏览器的Web APIs管理模块,在合适的时机放入队列中等待执行
      • DOM事件管理模块(DOM操作)
      • ajax回调管理模块(网络请求)
      • 定时器管理模块
      • Promise管理模块
      • Mutation回调管理模块
      • ...
    3. 队列
      • 宏队列:dom事件回调、ajax回调、定时器回调
      • 微队列:promise回调、mutation回调
  • 5.3 说明

    1. Js中用来存储执行回调函数的队列包含2个不同的队列
    2. 宏队列:用来保持待执行的宏任务(回调),比如 定时器回调、DOM回调、Ajax回调
    3. 微队列:用来保持待执行的微任务(回调),比如 Promise回调、MutationObserver回调
    4. Js执行时会区分这两个队列
      • Js引擎必须先执行所有的初始化同步任务代码
      • 每次准备取出一个新的宏任务执行前,都要将所有的微任务一个一个取出来执行

Event Loop中的任务队列

Event Loop是JavaScript实现异步的一种机制

Event Loop是让JavaScript做到既是单线程,又绝对不会阻塞的核心机制,也是JavaScript并发模型(Concurrency Model)的基础,是用来协调各种事件、用户交互、脚本执行、UI渲染、网络请求等的一种机制

Event Loop是属于JavaScript Runtime的,是由宿主环境提供的(比如浏览器)

在执行和协调各种任务时,Event Loop会维护自己的任务队列。

任务队列分为Task Queue和Microtask Queue两种

  1. Task Queue 一个Event Loop会有一个或多个 Task Queue,这是一个先进先出的有序列表 其存放着来自不同Task Source的Task 在HTML标准中定义了几中常见的Task Source:
    1. DOM manipulation(DOM操作)
    2. User interaction(用户交互)
    3. Networking(网络请求)
    4. History traversal(History API操作) 对于Task、Task Queue、和Task Source,有如下规定:
    5. 来自相同Task Source的Task,必须放在同一个Task Queue中
    6. 来自不同Task Source的Task,可以放在不同的Task Queue中
    7. 同一个Task Queue中的Task是按顺序执行的
    8. 对于不同的Task Queue(Task Source),浏览器会自行进行调度
  2. Microtask Queue Microtask Queue与Task Queue类似,也是一个有序列表 不同之处在于一个Event Loop只有一个Microtask Queue 在HTML标准中,并没有明确规定Micritask Source,通常认为有以下几种
    1. Promise
    2. MutationObserver

JavaScript Runtime 的运行机制

runtime: 执行环境

从相对宏观的角度了解JavaScript Runtime的运行机制

  1. 主进程不断循环
  2. 对于同步任务,创建执行上下文(Execution Context),按顺序进入执行栈
  3. 对于异步任务:
    • 与步骤2相同,同步执行这段代码
    • 将相应的Task/Microtask添加到Event Loop的任务队列
    • 由其它线程来执行具体的异步操作 其它线程指:尽管JavaScript是单线程的,但浏览器内核是多线程的,它会将GUI渲染、定时器触发、HTTP请求等工作交给专门的线程来处理
  4. 当主线程执行完当前执行栈中的所有任务,就会去读取Event Loop的任务队列,取出并执行任务
  5. 重复以上步骤...
  6. 图(F:/HTCS/Promise)

Event Loop处理模型

前面简单介绍了JavaScript Runtime的整个运行流程,而Event Loop作为其中重要一环,它的每一次循环过程也相当复杂,因此将它单独拿出来介绍。

根据HTML标准中对处理模型(Processing Model)的定义,尽量简化为如下三步:

  1. 执行Task: 从Task Queue中取出最前面的一个Task并执行;如果没有Task,直接跳过
  2. 执行Microtasks: 遍历Micritask Queue并执行所有Microtask
  3. 进入Update the rendering(更新渲染)阶段:
    • 设置Permance API中now()的返回值
    • 遍历本次Event Loop相关的Documents,执行更新渲染
    • 当浏览器确认继续本次更新后,处理更新渲染相关工作
  4. 图(F:/HTCS/Promise)

Microtask Queue 执行时机

执行Microtasks的前提是: 当前执行栈必须为空,且没有正在运行的执行上下文

实际上按照HTML标准,在以下几种情况中Microtask Queue都会被执行:

  • 某个Task执行完毕时
  • 进入脚本执行Calling scripts的清理阶段时
  • 创建和插入节点时
  • 解析XML文档时
  • 在当前Event Loop轮次中动态添加进来的Microtasks,也会在本次Event Loop循环中全部执行完

Microtask Queue 应用场景

一个典型应用就是Vue.js的异步更新队列的实现,Microtask Queue在这里起到的主要作用:

  1. 提供异步队列 使Vue.js能够缓冲在同一个Event Loop中发生的搜友数据变更,从而有机会在真正更新UI前,去除重复触发的数据变更,避免DOM进行不必要的更新

  2. 借助Promise.then、MutationObserver等实现Vue.nextTick()方法(本质就是利用Microtask Queue的执行时机) 使Vue.js有能力让UI尽早得到更新,即在当前Event Loop轮次(而非下一轮)中就更新完毕 这样带来的一个明显的好处就是UI更稳定、更流畅

Axios

第一章:HTTP 相关

  • MDN 相关

  • HTTP 请求交互的基本过程

    1. 请求行
    2. 请求头
    3. 请求体
    4. 状态行
    5. 响应头
    6. 实体内容(响应体)
    • 前后应用从浏览器端向服务器发送HTTP请求(请求报文 -> 1.2.3.)
    • 后台服务器接收到请求后,调度服务器应用处理请求,向浏览器端返回HTTP响应(响应报文 -> 4.5.6.)
    • 浏览器端接收到响应,解析显示响应体/调用监视回调
  • HTTP 请求报文

    1. 请求行 method :请求方式,常用如下 url:请求地址
      • GET /product_detail?id=2
      • POST /login
    2. 多个请求头 Host:www.baidu.com 主机 Cookie:BAIDUID=ARH65E4B5; BAIDUPSID=S6DF41B1AD3 Content-Type: application/x-www-form-urlencoded 或 application/json 请求体内容格式
    3. 请求体(post) username=tom&pwd=123 - urlencoded查询参数 {"username":"tom","pwd":123} - json对象格式
  • HTTP 响应报文

    1. 响应状态行: status:200 201 401 404 500 statusTest:ok、Created、Unauthorized、Not Found、Internal Server Error
    2. 多个响应头 Content-Type: text/html;charset=utf-8(编码字符集) Set-Cookie: BD_CK_SAM=1;path=/
    3. 响应体 html文本 / json文本 / js / css / 图片
  • post 请求体参数格式

    1. Content-Type: application/x-www-form-urlencoded;charset=utf-8(与get方式类似) 用于键值对参数,参数的键值用 = 连接,参数之间用 & 连接 如:name=%f5%f5%f5b%5tg&age=12
    2. Content-Type: application/json;charset=utf-8 用于 json 字符串参数 例如:{"name":"%f5%f5%f5b%5tg","age":12}
    3. 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 的分类(前后台交互的接口)

    1. REST API:restful
      • 发送请求进行 CRUD 哪个操作由请求方式决定
      • 同一个请求路径可以进行多个操作
      • 请求方式会用到 GET/POST/PUT/DELETE
    2. 非REST API:restless
      • 请求方式不决定请求的 CRUD 操作
      • 一个请求路径只对应一个操作
      • 一般只有 GET(查询)/POST(增删改)
  • 使用 json-server 搭建 REST API

    1. json-server 是什么? 用来快速搭建 REST API 的工具包
    2. 使用 json-server
      • github
      • npm install -g json-server 下载
      • json-server --watch db.json 运行

第二章:XHR 的理解和使用

  • MDN 文档
  • 理解
    1. 使用 XMLHttpRequest(XHR)对象可以与服务器交互,也就是发送 ajax 请求
    2. 前端可以获取到数据,而无需让整个页面刷新
    3. 这使得Web页面可以只更新页面的局部,而不影响用户的操作
  • 区分一般 http 请求与 ajax请求
    1. ajax 请求是一种特殊的 http 请求
    2. 对服务器端来说,没有任何区别,区别在浏览器端
    3. 浏览器端发请求:只有 XHR 或 fetch 发出的才是 ajax请求,其它所有都是非 ajax请求
    4. 浏览器端接收到响应:
      • 一般请求:浏览器一般会直接显示响应体数据,也就是人们常说的刷新/跳转页面(自动显示得到的数据)
      • ajax请求:浏览器不会对界面进行任何更新操作,只是调用监视的回调函数并传入响应相关数据(需手动将获得的数据去更新页面局部)
  • API(MDN)
    1. XMLHttpRequest():创建XHR对象的构造函数
    2. status:响应状态码值(只读),200 201 401 404 500 ...
    3. statusText:响应状态文本
    4. readyState:标识请求状态的只读属性
      • 0:初始
      • 1:open()之后
      • 2:send()之后
      • 3:请求中
      • 4:请求完成
    5. onreadystatechange:绑定 readyState改变的监听
    6. responseType:指定响应数据类型,如果是 'json' ,得到响应后自动解析响应体数据
    7. response:响应体数据,类型取决于 responseType的指定
    8. timeout:指定请求超时时间,默认为0代表没有限制
    9. ontimeout:绑定超时的监听
    10. onerror:绑定请求网络错误的监听
    11. open():初始化一个请求,参数为:(method,url[,async])
    12. send(data):发送请求
    13. abort():中断请求
    14. getResponseHeader(name):获取指定名称的响应头值
    15. getAllReasonseHeaders():获取所有响应头组成的字符串
    16. setRequestHeader(name,value):设置请求头
  • XHR 的 ajax 封装(简单版axios)
    1. 特点
      • 函数的返回值为 promise,成功的结果为 response,异常的结果为error
      • 能处理多种类型的请求:GET/POST/PUT/DELETE
      • 函数的参数为一个配置对象(配置对象:属性名固定、属性名的意义/作用固定) { url: '', // 请求地址 method: '',// 请求方式 params: {},// GET/DELETE请求的 query参数 data: {}, // POST/DELETE请求的请求体参数 }
      • 相应 json 数据自动解析为 Js

第三章:axios的理解和使用

  • axios 是什么?
    1. 前端最流行的 ajax 的请求库
    2. react/vue 官方都推荐使用 axios 发 ajax 请求
    3. 文档 github.com/axios/axios
  • axios 的特点
    1. 基于 promise的异步ajax请求库 ----> 执行请求后返回的是 promise对象
    2. 浏览器端 / node 端都可以使用
    3. 支持 请求/响应 拦截器 ---> 对 请求和响应 做统一处理的函数
    4. 支持请求取消
    5. 支持 请求/响应 数据转换
    6. 批量发送多个请求 ---> 封装的是 Promise.all()
  • axios 常用语法
    1. axios(config):通用/最本质的发任意请求的方式
    2. axios(url[,config]):可以只指定 url发 get请求
    3. axios.request(config):等同于 axios(config)
    4. axios.get(url[,config]):发 get 请求
    5. axios.delete(url[,config]):发 delete 请求
    6. axios.post(url[,data,config]):发 post 请求
    7. axios.put(url[,data,config]):发 put 请求
    8. axios.defaults.xxx:请求的默认全局配置
    9. axios.interceptors.request.use():添加请求拦截器
    10. axios.interceptors.response.use():添加响应拦截器
    11. axios.create([config]):创建一个新的 axios实例(它没有下面的功能)
    12. axios.Cancel():用于创建取消请求的错误对象
    13. axios.CancleToken():用于创建取消请求的 token对象
    14. axios.isCancel():是否是一个取消请求的错误
    15. axios.all(promises):用于批量执行多个异步请求
    16. axios.spread():用来指定接收所有成功数据的回调函数的方法,与all()配合使用 注:如果data为对象类型,axios默认使用json发请求
  • axios 难点语法的理解和使用
    1. axios.create(config)
      • 根据指定配置创建一个新的 axios,也就是每个新axios都有自己的配置
      • 新axios只是没有取消请求和批量发请求的方法,其它所有语法都是一样的
      • 为什么要设计这个语法?
        1. 需求:项目中有部分接口需要的配置与另一部分接口需要的配置不大一样,如何处理
        2. 解决:创建两个新的axios,每个都有自己特有的配置,分别应用到不同要求的接口请求中
    2. 拦截器函数 / ajax请求 / 请求的回调函数的调用顺序 axios拦截器的执行顺序是:2112(axios_chain)
    3. 取消请求(请求以发出并且请求还没有完成)
      • 基本流程
        1. 配置 cancelToken 对象
        2. 缓存用于取消请求的 cancel函数
        3. 在后面特定时机调用 cancel函数取消请求
        4. 在错误回调中判断如果 error是cancel,做相应处理
      • 功能实现

第四章:axios 源码及分析

  • 源码目录结构 axios.js / Axios.js(request、dispatchRequest(adapter -> xhr)、interceptors、) / defaults.js
  • 源码分析
    • axios与Axios的关系

      1. 从语法上来说:axios(是函数)不是 Axios的实例 let instance = bind(Axios.prototype.request, context) // bind() 返回一个新函数,内部调用request()函数,类似如下 function () { Axios.prototype.request() }
      2. 从功能上来说:axios是Axios的实例
      3. axios是 Axios.prototype.response 函数 bind()返回的函数
      4. axios 作为对象有 Axios原型对象上的所有方法,有 Axios对象上所有属性
    • instance与axios的区别

      1. 相同
        • 都是一个能发任意请求的函数:request(config)
        • 都有发特定请求的各种方法:get()/post()/put()/delete()
        • 都有默认配置和拦截器的属性:defaults/interceptors
      2. 不同
        • 默认配置的值很可能不一样
        • instance 没有 axios后面添加的一些方法:create()/CancelToken()/all()
    • axios运行的整体流程

      1. 整体流程: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
      2. request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串联起来,返回 promise
      3. dispatchRequest(config): 转换请求数据 ==> 调用 xhrAdapter()发请求 ==> 请求返回后转换响应数据,返回 promise
      4. xhrAdapter(config): 创建 XHR 对象,根据 config 进行响应设置,发送特定请求,并接收响应数据,返回 promise
    • axios的请求/响应拦截器是什么?

    • axios的请求/响应数据转换

    • response的整体结构

    • error的整体结构

    • 如何取消未完成的请求?

      • 当配置了 cancelToken 对象时,保存 cancel函数
        1. 创建一个用于将来中断请求的 cancelPromise
        2. 并定义了一个用于取消请求的 cancel函数
        3. 将 cancel函数传递出来
      • 调用 cancel() 取消请求
        1. 执行 cancel函数,传入错误信息 message
        2. 内部会让 cancelPromise变为成功,且成功的值为一个 Cancel对象
        3. 在 cancelPromise的成功回调中中断请求,并让发请求的 promise失败,失败的reason为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

地址:juejin.cn/post/684490…

demo: E:\PracticeSublime\Utils\uploadFile

binary:二进制

  1. 原理概述

原理:根据http协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件

  • 请求头,设置如下 Content-Type: multipart/form-data
    • Content-Type:表示当前内容的MIME类型,是图片、文本、二进制数据
  • method必须:为post方式
  1. 无刷新上传
  • XMLHttpRequest:developer.mozilla.org/zh-CN/docs/…

  • 无刷新上传肯定要用到XMLHttpRequest2,可以读取和上传文本和二进制数据

  • 必须使用FormData构造函数创建实例管理表单数据

  • HTML input:type="file" 表示文件上传 multiple 表示可以同时上传多个文件

    submit

  • JS xhr open:初始化一个请求 send:发送请求 readyState:请求状态 onreadystatechange:绑定 readyState改变的监听 responseText:server返回值