JS

279 阅读22分钟

JS

js加密方法

  1. MD5:不可逆
  2. base64 Base64.encode/Base64.decode
  3. encodeURI/decodeURI
  4. escape/unescape
  5. sha1

闭包

  1. 为什么产生闭包
  • 浏览器想要执行代码,都要进栈执行的。在进栈过程中, 由于某些内容被上下文以外的一些事物所占用,则当前上下文不能被出栈释放,根据浏览器的垃圾回收机制 (GC),如果被占用不释放,就会保留下来,这种机制就是闭包。
  1. 闭包的作用

    1. 保护:保护私有上下文中的“私有变量”和外界互不影响。
    2. 保存:上下文不被释放,那么上下文中的“私有变量”和“值”都会被保存起来,可以供其下级上下文中使用。
  2. 闭包弊端

  • 如果大量使用闭包,会导致栈内存太大,页面渲染变慢,性能受到影响,所以真实项目中需要“合理应用闭包”;某些代码会导致栈溢出或者内存泄漏,这些操作都是需要我们注意的;
  1. 实战中的应用
  • 循环事件绑定方法:可以用事件索引,后来修改是let的方式,都是闭包的方式。闭包的方式不仅 浪费了堆内存,也浪费了栈内存。这种方式不太好,所以用的事件委托。
  1. JS高阶编程技巧 (闭包进阶应用)
    1. 循环事件绑定或者循环操作中对于闭包的应用;
    2. 基于“闭包”实现早期的“模块化”思想;
    3. 惰性函数(思想);
    4. 柯里化函数 & 重写reduce;
    5. compose组合函数;
    6. jQuery(JQ)中关于闭包的使用;
    7. 函数的防抖和节流;

this相关:函数执行的主体

  1. 事件绑定
    • DOM0:xxx.onxxx=function(){}
    • DOM2:
      1. xxx.addEventListener('xxx',function(){})
      2. xxx.attachEvent('onxxx',function(){})
    • 给当前元素的某个事件行为绑定方法,,当事件行为触发,浏览器会把绑定的函数执行,此时函数中的this-》当前元素本身
      • 基于attachEvent实现事件绑定,方法执行,方法中的this是window
  2. 函数执行
    • 正常的普通函数执行,看函数执行前是否有'.',有的话'.'前面是谁this就是谁,没有的话this就是window,严格模式下是undefined
    • 匿名函数
      1. 函数表达式(等同于普通函数或者事件绑定等机制)
      2. 自执行函数:this一般是window或者undefined
        (function(x){
            console.log(this)
        })(10)
        
      3. 回调函数:把一个函数a作为实参,传递给另外一个执行的函数b,在b函数执行中,可以把a执行---一般都是window或者UNdefined,但是如果另外函数执行时,对回调函数的执行做了特殊出库,以自己处理的为主
    • 括号表达式:小括号中包含多项,这样也只取最后一项,但是this受到影响(一般都是window或者undefinded)
  3. 新版本浏览器中
    1. 如果function出现在除函数,对象的大括号中,则在变量提升阶段,只声明不定义
    2. 如果出了函数和对象的大括号中,只要出现let、const、function关键词,都会产生块级私有上下文,对var无效
    3. 由于当前代码被全局和私有上下文都改变过,导致了一个特殊性:
      • 把这行代码以前对foo的操作,都映射给全局一份
      • 但是之后的代码只认为是操作ec【block】中的,和ec(g)没有关系
      • 在判断和循环中不要声明函数

数组去重

indexOf

let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function unique(ary){
   let arr = [];
   for (let i = 0; i < ary.length; i++) {
      let item = ary[i],
         args = ary.slice(i + 1)
      if (args.indexOf(item) < 0) {
         arr.push(item)
      }
   }
   return arr;
}
console.log(unique(ary));

Set

function unique(ary){
   return Array.from(new Set(ary))
}

forEach/includes

function unique(ary) {
    let arr = [];
    ary.forEach((i) => {
        return arr.includes(i) ? "" : arr.push(i);
    });
    return arr;
}

includes

function unique(ary) {
    let arr = [];
    for (let i = 0; i < ary.length; i++) {
        if (!arr.includes(ary[i])) {
            arr.push(ary[i]);
        }
    }
    return arr;
}

先排序再相邻元素比较

function unique(ary) {
    const formatArr = ary.sort();
    let arr = [];
    // console.log(formatArr);
    for (let i = 0; i < formatArr.length; i++) {
        if (formatArr[i] !== formatArr[i + 1]) {
            arr.push(formatArr[i]);
        }
    }
    return arr;
}

数组排序

冒泡排序

  1. 每一轮都拿当前项和后一项作对比,每一轮结束后最大值会到末尾
  2. 一共需要比多少轮:数组长度-1
  3. 一共需要比较多少次:最多(数组长度-1)次,而且要把之前放到末尾的大值排除掉
  4. 两者交换位置
  5. 时间复杂度O(n^2)
function bubble(arr) {
    let len = arr.length - 1;
    for (let i = 0; i < len; i++) {
        for (let j = 0; j < len - i; j++) {
            if (arr[j] > arr[j + 1]) {
                let temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}
console.log(bubble(ary));

插入排序

1.手里先抓一张牌
2.一次从桌面上取牌
    - 每一次取出一张牌,都和手里的牌进行比较(倒着比)
        - 如果新取出的牌比当前比较的这张大,则放到当前这张的后面
        - 如果新取出的牌比当前比较的这张小,继续向前比,直到遇到比他大的
        -如果比到头都没有发现比新抓牌小的,则放到开始位置即可
function insert(ary) {
    //准备一个新数组,用来存储拿到手里的牌
    let handle = [];
    handle.push(ary[0]);
    // 从第二项开始一次抓牌,一直把台面上的牌抓光
    for (let i = 1; i < ary.length; i++) {
        //A是新抓的牌
        let A = ary[i];
        // 和handle手里的牌依次比较(从后往前比)
        for (let j = handle.length - 1; j >= 0; j--) {
            // 每一次要比较的手里的牌
            let B = handle[j];
            // 如果当前新牌A比要比较的牌B大了,把A放到B的后面
            if (A > B) {
                handle.splice(j + 1, 0, A);
                break;
            }
            if (j === 0) {
                handle.unshift(A);
            }
        }
    }
    return handle;
}
let ary = [12, 8, 24, 16, 1];
console.log(insert(ary));

快速排序

1.取出数组中间项
2.拿每一项和中间项比较
    - 比中间项小的放在左边
    - 比中间项大的放在右边
3.再依次把左边和右边用同样的方式处理
4.直到某一边的数组是一项或者没有,则不再拆分
function quick(ary) {
    // 结束递归(当数组中小于等于一项,则不用处理)
    if (ary.length < 2) {
        return ary;
    }
    // 找到数组的中间项,在原有的数组中把它移除
    let midIdx = Math.floor(ary.length / 2);
    let midVal = ary.splice(midIdx, 1)[0];
    // 准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,比当前项大的放到右边数组中
    let leftAry = [],
        rightAry = [];
    for (let i = 0; i < ary.length; i++) {
        let item = ary[i];
        item < midVal ? leftAry.push(item) : rightAry.push(item);
    }
    return quick(leftAry).concat(midVal, quick(rightAry));
}
let ary = [12, 8, 24, 16, 1];
console.log(quick(ary));

数组扁平化

flat

arr = arr.flat(Infinity);

toString转换为字符串

arr = arr.toString().split(',').map(i=>ParseFloat(i));

循环验证是否为数组

while(arr.some(i=>Array.isArray(i))){
    arr=[].concat(...arr)
}
function flatten(arr) {
    return arr.reduce((prev, item) => {
        return prev.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}

复杂度

  1. 时间复杂度
    1. Ο(1)<Ο(log2(n))<Ο(n)<Ο(n^2)<Ο(n^3)<…<Ο(2^n)

map 和 forEach 区别

  1. 相同点
    1. 都是循环遍历数组中每一项
    2. 都支持三个参数(item,index,原数组)
    3. this 都指向 window
    4. 只能遍历数组
  2. 不同点
    1. forEach 没有返回值,map 有

let/const/var

  • let 声明的是变量
  • const 声明的也是变量(只不过不允许重定向变量的指针)

var VS let

  1. let不存在变量提升
  2. let不允许重复声明,不论基于什么方式声明了这个变量,再次基于let、const声明都会报重复声明
  3. 全局上下文中,基于var声明的变量,不是存放到VO(G)中,而是直接放在了GO(window),基于let声明的变量是存放到VO(G)中的。
  4. 暂时性死区
    1. typeof检测一个未被声明的变量是不会报错的,而是会返回undefined
  5. 块级作用域

js代码执行

  1. 词法解析(AST):我们基于Http从服务器拉取回来的js代码其实是一些字符串,浏览器首先会按照ECMAScript规范,把字符串变为c++可以识别和解析的一套对象
  • 词法解析阶段,发现有在相同上下文中,基于let重复声明变量的操作,则直接报错,所以代码不会执行
  1. 全局上下文
    1. 变量提升
    2. 代码执行。。。

js 中有几种作用域

  1. 没有ES6之前,作用域只有两种
    1. 全局上下文
    2. 函数执行的私有上下文
  2. 有了let之后,产生了第三种:块级作用域「如果在{}(排除函数和对象的大括号)中出现let/const/function,则当前的{}会成为一个块级私有上下文」

Map,Set,weakMap,weakSet

  1. Set
    1. 类数组,成员值唯一,Set 是一个构造函数,需要 new
    2. Set.add/Set.delete/Set.has/Set.clear
    3. 数组去重
      1. […new Set(arr)]
    4. 并集
      1. Array.from(new Set([arr1,arr2])
    5. 交集
      1. […arr1].filter(item=>arr2.has(item))
    6. 差集
      1. […arr1].filter(item=>!arr2.has(item))
  2. WeakSet
    1. 与 Set 类似,也是不重复的值得结合,区别在于成员只能是引用类型
    2. WeakSet 中的对象都是弱引用,垃圾回收机制不会考虑 WeakMap 对该对象的应用,如果其他对象都不再引用该对象,name 垃圾回收机制就会自动回收该对象所占用的内存,不考虑对象是否还存在于 WeakSet 中,所以 WeakSet 没有 size,不能遍历
    3. let ws = new WeakSet();=>ws.add()/ws.has()/ws.delete()
    4. 深拷贝
  3. Map
    1. 键值对的合集,键的范围不限于字符串
    2. map.size()/map.set()/map.get()/map.delete()/map.has()/map.clear()
    3. 转换为数组=>[…map]
  4. WeakMap
    1. WeakMap 只接受对象为键名 null 除外,为弱引用,不计入垃圾回收机制
    2. WeakMap 只是键名弱引用
    3. vm.set()/vm.get()/vm.has()/vm.delete()

浏览器垃圾回收机制

  1. 内存泄漏
    1. 如果那些不想再使用的变量所占用的内存没有被释放,就会造成内存泄漏
    2. 内存泄漏值我们程序中已经动态分配的堆内存,由于某些原因没有被释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果
  2. 垃圾回收机制
    1. 标记清除
      1. 当代码执行在一个环境中时,每声明一个变量,就会对该变量做一个标记,当代码执行进入另一个环境中时,这时对上一个环境中的变量做一个标记,等到垃圾回收执行时,会根据标记来觉得要清除那些变量进行释放内存
    2. 引用计数
      1. 创建了堆内存,被占用一次,则浏览器计数+1,取消占用计数—1,当记录的数字为零的时候,则内存释放掉
    3. 垃圾回收算法
      1. 从根节点出发,遍历所有的对象,可以遍历到的是可达的,不能遍历到的是不可达的
        1. 根节点:不会被回收
          1. window
          2. DOM
          3. 存放在栈上的变量
      2. 标记完成后,统一清理内存中不可达的对象
      3. 做内存整理

Array.slice(start,end)

  1. start:必须,从何处开始选取,如果是负数,则是从数组尾部开始算起的位置(-1 指倒数第一个元素)
  2. end:可选,规定从何处结束选取,是数组片段结束出的数组下标,如果没有此参数,则切分的数组包含从 start 到数组结束的所有元素,如果是负数,则规定从数组尾部开始算起的元素
  3. 返回一个新数组

Array.splice():方法向/从数组中添加/删除项目,然后返回被删除的项目

String.split()/Array.join()

  1. String.split(''):'1111'=>["1", "1", "1", "1"]
  2. Array.join(''):["1", "1", "1", "1”]=>'1111'

Array.flat():返回一个新数组

  1. flatMap()只能展开一层数组:[2, 3, 4].flatMap((x) => [ x, x * 2])//[2,4,3,6,4,8]

concat:连接两个或多个数组,不会改变现有的数组,会返回被连接数组的副本

Super()作用

  1. 子类必须在 constructor 中调用 super,因为子类没有自己的 this 对象,而是继承父类的 this 对象,如果不调用 super,子类就拿不到 this 对象

reduce 方法

  1. arr=[1,2,3,4,5,6]
  2. arr.reduce((a,b)=>{return a+b},a)
    1. a=1,b=2
    2. a=3,b=3
    3. a=6,b=4
    4. a=10,b=5
    5. a=15,b=6
    6. 21

  1. 展开运算符:在等号右边或者实参上
  2. 剩余运算符:在等好左边或者形参上

Object.create()

  • 创建一个新对象,使用现有的对象来提供新创建对象的__proto__
  • [Child.prototype = Object.create(Parent.prototype)]====[Child.prototype.proto=Parent.prototype]

proto 和 prototype

  1. 大部分(函数数据类型)都具备 prototype,并且属性值是一个对象,浏览器会为其默认开辟一个堆内存,用来存储当前类所属实例,可以调用的公共的属性和方法,在浏览器默认开辟的这个堆内存中
    1. 函数数据类型
      • 普通函数(实名或者匿名函数)
      • 箭头函数
      • 构造函数、类[内置类,自定义类]
      • 生成器函数
    2. 不具备prototype的函数
      • 箭头函数 const fn = ()=>{}
      • 基于es6给某个成员复制函数值得快捷函数
  2. 原型对象上天生具备一个属性 constructor,指向类本身
  3. 每一个【对象数据类型】都具备__proto__,属性值是当前实例所属类的prototype
    • 普通对象
    • 特殊对象:数组,正则,日期,math,error
    • 函数对象
    • 实例对象
    • 构造函数.prototype
  4. 由于Object类是所有对象的‘基类’,所以Object.prototype.proto=null
  5. 实例.prototype -》一般是获取直属的类的信息,值可以被修改,所以不一定准确

new 方法做了什么

  1. 创建一个新的实例对象
  2. 新的实例对象的proto关联到构造函数的 prototype
  3. 改变构造函数的 this 指向,让其指向新创建的实例对象
  4. 如果返回的是空对象或是基本对象,则 return 实例本身,否则 renturn 原本的结果
  5. new Fn()/new Fn,优先级不同

实现异步的 forEach 方法

var arry = [...];
Promise.all(arry.map(function(elem){
  return new Promise(function(resolve, reject){
    ...
    resolve(result);
  })
})).then(function(data){
  //在这就可以等所有的返回结果可以得到
})

for in/for of数组遍历

  1. for in获取的是key
  2. for of获取的是value
  3. for in会遍历对象的整个原型链,for of 只遍历当前对象
  4. for in会返回数组中所有可枚举的属性,for of只会返回数组的下标对应的属性值

基础类型和引用类型有什么区别

  1. 基本类型是存放在栈内存中,引用类型的值是同事保存在栈内存和堆内存中的对象
  2. 基本类型的值是不可变的,引用类型的值是可变的
  3. 基本类型的比较是值得比较,引用类型的比较是引用的值
  4. 在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,肉厚再把该值复制到为新变量分配的位置上
  5. 当从一个变量向另一个变量赋值引用类型时,同样也会将存储在变量中的对象的值赋值一份方法哦为新变量分配的空间中,保存在变量中的是对象在堆内存中的地址,这个值得副本实际上是一个指针,二这个指针指向存储在堆内存的一个对象,赋值后,两个边路都保存了同一个对象地址,则这两个变量指向了同一个对象,因此改变其中任何一个变量,都会相互影响。
  6. 引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何操作都会互相影响

类数组转换为数组

1. Array.from
2. Array.prototype.slice.call()/[].slice.call()
3. […ele]
4. for(let I =0;i<arguments.length;i++){
    let item = arguments[I];arr.push()
  }

数据类型检测

  1. Typeof
    1. 返回一个字符串,‘number/string/boolean/undefined/symbol/bigint/object/function’
    2. null 会返回 object
    3. 所有对象都是 000 开头,所以 typeof 无法细分是普通对象还是数组对象
    4. 原理:按照计算机底层存储的二进制结果进行检测
  2. instanceof
    1. 用来检测当前实例是否属于这个类,并不是检测数据类型的
    2. 应用于普通对象,数组对象,正则对象,日期对象等的具体细分
    3. Array Instanceof Object 是 true
    4. 无法应用到原始数据类型的检测上
    5. 基于‘实例instanceof类’检测的时候,浏览器底层是这样处理的'类[Symbom.hasInstance]'
    6. Function.prototype[Symbom.hasInstance]=function Symbom.hasInstance{[native code]}
    7. Symbom.hasInstance方法执行的原理
      1. 根据当前实例的原型链上(proto)是否存在这个类的原型(prototype)
      2. arr.proto === Array.prototype => arr instanceof Array => true
      3. arr.proto.proto === Object.prototype => arr instanceof Object => true

截屏2021-03-16 下午4.22.08.png 截屏2021-03-16 下午4.15.03.png 3. constructor

  1. 获取实例的构造函数,基于这些特点可以充当数据类型检测,比 instanceof 好用
  2. 但是不准确,constructor 是可以随意被修改

截屏2021-03-16 下午4.44.06.png 4. Object.prototype.toString.call([value])或者({}).toString.call([value])

  1. 专门用来检测数据类型
  2. number/string/boolean/symbol/bigint/array/regexp/date/object...的原型上都有toString,除了Object.prototype.toString不是转换字符串的,其余都是,Object.prototype.toString是用来检测数据类型的
  3. 返回结果'[object 对象[Symbol.toStringTag||对象.构造函数(不受自己更改的影响)||Object]'

事件循环

进程和线程

  • 一个进程中可能包含多个线程
  • 进程:程序(浏览器打开一个页面,就会开辟一个进程)
  • 线程:处理任务,一个线程同时只能处理一个任务

浏览器是多线程的

  • http网络线程:用于资源文件的加载
  • GUI渲染线程:用于页面自上而下渲染,最后绘制出页面
  • JS渲染线程:专门用于渲染我们的js代码
  • ...

JS是单线程的

  • 浏览器只会开辟一个线程来渲染js代码,所以js的本质都是同步的,当前事件处理完成后,才能处理下一个事情,不能同时干两件事
  • js中是有异步编程代码的,但是此处的异步编程也不是让其同时处理多件事情,只不过是基于浏览器的多线程性,再结合eventloop时间循环机制,构建出来的异步效果

异步编程代码

宏任务队列

  • 定时器:设置定时器是同步的过程,而’间隔Interval这么长时间后,触发绑定的方法执行‘这个事情是异步的
  • 事件绑定:监听事件,事件触发执行绑定的方法
  • ajax、fetch等创建的网络请求,从服务器请求数据

微任务队列

  • promise:then/resolve(reject)通知注册的onfulfilled/onrejected方法执行
  • async/await

JS渲染线程

  1. 代码执行过程中,只要创建一个异步的任务,则会把这个异步任务放置到eventqueue中
    • 任务放置完成,不需要等待,主线程会继续向下渲染同步的代码
    • 对于定时器或者事件绑定等创建的异步任务来讲,放置到事件队列中,浏览器此时会开辟一个新的线程-》监听线程,监听定时器是否到达指定的时间
  2. 当同步任务都执行完,主线程空闲下来后,才会看eventqueue是否存在等待的任务,如果存在,按照顺序依次把等待任务拿出来,放置到占内存中,让主线程去执行,当此任务执行完,主线程再次空闲下来后,再去eventqueue中查找,我们这个过程称之为eventloop,事件循环机制
    • 在同步任务没有执行完,或者主线程还没有空闲下来,此时哪怕有异步任务已经符合了执行的条件(到时间了),也不会执行异步的任务,继续等待主线程空闲下来后,才回去事件队列中按照顺序查找符合条件的异步任务
    • 定时器设定的时间到达后,一定会触发方法执行吗?不一定

查找过程

  1. 先找微任务队列,如果微任务队列中有,先从微任务队列中,按照存放顺序获取并且执行
  2. 如果微任务中没有,则再去宏任务队列中查找,在宏任务队列中,一般按照谁先到达执行的条件,就先把谁拿出来执行
  3. 不是有微任务一定要先执行微任务,我们还需要观察微任务是否已经到达了指定的执行条件,到了条件才执行,没到即使有也需要等待,此时可以先去吧到了条件的宏任务执行,但是没一次再次进来查找的时候,一定还是先查找符合条件的宏任务执行

2.png

3.png 1.png

1-2.png

1-3.png

1-4.png

Promise

Promise 用法

  1. 会立即执行传递的 executor 函数(let p = new Peomise(executor)
      • new Promise的时候,在promise内部会立刻把executor函数执行
      • 函数中一般用来管理一个异步编程代码(不管控异步编程也是可以的)
      • 同时给executor函数传递两个值(函数类型)
        • resolve
        • reject
    1. p是Promise类的一个实例
      let p1 = new Promise((resolve,reject)=>{
          // 在[executor]执行resolve/reject都是为了改变promise实例的状态和值(结果)
              // 一旦状态被改变成fulfilled/rejected/则不能再改变为其他的状态
              //resolve('ok');[[PromiseState]]:fulfilled;[[PromiseResult]]:'ok'
              //reject('no');[[PromiseState]]:rejected;[[PromiseResult]]:'no'
          // 如果[executor]函数执行报错,则
              // [[PromiseState]]:rejected
              // [[PromiseResult]:报错原因
              // 内部做了异常信息捕获:try/catch
          console.log(a);
      });
      //实例的状态改变,可以控制执行then时存放的两个方法中的某一个方法执行 
          //p.then(onfulfilledcallback,onrejectedcallback)
              //状态成功执行的是:onfulfilledcallback
              //状态失败执行的是:onrejectedcallback
              //并且把[[PromiseResult]]传递给方法
      p1.then(result=>{
          console.log('成功',result)
          },reason=>{
          console.log('失败',reason)
      });
      p1.then(result=>{
          console.log('成功',result)
          },reason=>{
          console.log('失败',reason)
      });
      
      • 内置私有属性
        1. [[PromiseState]]Promise状态:'pending'准备状态,'fullfilled/resolved'已兑现,'rejected'已拒绝
        2. [[PromiseResult]]Promise值:默认值是undefined,一般存储成功的结果或者失败的原因
      • 公共属性方法
        • p1.proto=>Promise.prototype
          1. then
          2. catch
          3. finally
          4. Symbol(Symbol.toStringTag):'promise'
let p = new Promise((resolve) => {
    console.log(1);
    resolve('ok');//同步修改其状态和结果
    console.log(2);
})
console.log(p);//此时状态已经修改为成功
//执行p.then(onfulfilledcallback,onrejectedcallback)
    //1. 首先把传递进来的onfulfilledcallback,onrejectedcallback存储起来[存储在一个容器中](基于then给其存放好多个回调函数)
    //2. 其次再去验证当前实例状态
        //- 如果实例状态是pending则不作任何处理
        //- 如果已经变为fulfilled、rejected,则会通知对应的回调函数执行,但是不是立即执行,而是把其方式在eventqueue中的微任务中,'promise本身不是异步的,是用来管理异步的,但是then方法是异步的‘微任务’
p.then(result => {
    console.log('成功',result);
});
console.log(3)
let p = new Promise((resolve) => {
    console.log(1);
    setTimeout(()=>{
        resolve('ok');
        //改变实例的状态和值「同步」
        //通知之前基于then存放的onfulfilledcallback执行『异步微任务:也是把执行方法的事情放置在eventqueue中的微任务队列中』
        console.log(4)
    },1000)//存储了一个异步宏任务
    console.log(2);
})
console.log(p);
//此时接收onfilledcallback的时候,状态还是pending,此时只把方法存储起来
p.then(result => {
    console.log('成功',result);
});
console.log(3)
//等1000ms之后执行定时器中的函数[把异步宏任务拿出来执行]

执行.then方法返回一个全新的promise实例

  1. 【promise实例状态和值得分析】
    1. new Promise出来的实例
      1. resolve/reject执行控制其状态[[PromiseState]]以及[[PromiseResult]]
      2. executor函数执行失败控制其[[PromiseState]]=rejected &[[PromiseResult]]=报错信息
    2. .then返回的新实例
      1. then注入的两个方法,不论哪个方法执行,只要执行不报错,新实例的状态就是fulfilled;只要执行报错,新实例的状态就是rejected;并且新实例的[[PromiseResult]]是方法返回的值;
      2. 但是如果方法执行返回的是一个新的promise实例,则此实例最后的成功或者失败,直接决定.then返回实例的成功和失败,得到的结果都是一样的
        let p1 = new Promise((resolve,reject) => {
            resolve("ok"); 
            reject("no"); 
      
        });
        let p2 = p.then(
            (result) => {
                console.log("p1成功", result);
                return Promise.reject(10)
            },
            (reason) => {
                console.log("p1失败", reason);
            }
        );
        let p3 = p2.then(
            (result) => {
                console.log("p2成功", result);
            },
            (reason) => {
                console.log("p2失败", reason);
                return Promise.resolve(10)
            }
        );
        p3.then(
            (result) => {
                console.log("p3成功", result);
            },
            (reason) => {
                console.log("p3失败", reason);
                return Promise.resolve(10)
            }
        );
        //执行then方法会返回一个全新的promise实例p2
          //p2的状态和值是怎么改变的
            // 不论执行的是基于p1.then存放的 onfulfilledcallback或者onrejectedcallback两个方法中的哪一个,
            //   只要方法执行不报错
            //     如果方法中返回一个全新的promise实例,则全新的promise实例的成功和失败决定p2的成功和失败
            //     如果不是返回promise,则[[PromiseState]]:fulfilled ;[[PromiseResult]]:返回值
            //   如果方法执行报错,p2的[[PromiseState]]:rejected ;[[PromiseResult]]:报错原因
        console.log(p2)
      
      
      //如果onfulfilledcallback或者onrejectedcallback不传,则状态和结果都会顺延到下一个同等状态应该执行的回调函数上,内部其实自己补充了一些函数
        new Promise((resolve, reject) => {
            resolve("ok");
        })
            .then(null, (reason) => {
                console.log("失败", reason);
            })
            .then(
                (result) => {
                    console.log("成功", result);
                },
                (reason) => {
                    console.log("失败", reason);
                }
            )
            .then(
                (result) => {
                    console.log("成功", result);
                },
                (reason) => {
                    console.log("失败", reason);
                }
            );
        //catch只处理状态为失败下做的事情
        Prommise.prototype.catch= function (onrejectedcallback){
            return this.then(null,onrejectedcallback);
        }
      

Promise.all和Promise.race和Promise.allSettled

  1. Promise.all([promise数组:要求数组中的每一项尽可能都是promise实例]):返回一个新的promise实例AA,AA成功还是失败,取决于数组中每一个promise实例是成功还是失败,只要有一个是失败,AA就是失败的,只有都成功AA才是成功的,返回结果按照参数顺序,只要有一个失败,整体状态就是失败,
  2. Promise.race([promise数组:要求数组中的每一项尽可能都是promise实例]):最先知道状态的promise实例,是成功还是失败,决定了AA是成功还是失败
  3. Promise.allSettled:在所有给定的promise已被解析或被拒绝后解析,并且返回的结果里包含每个

async/await

  • generator的语法糖
    • async修饰符的作用:控制函数返回promise实例
      • 函数内部执行报错,则返回失败的promise实例,值是失败的原因
      • 自己返回一个promise,以自己返回的为主
      • 如果函数内部做了异常捕获,则还是成功态
    • 使用async的主要目的,是为了在函数内部使用await
    • await:函数中如果需要使用await,所在的函数必须基于async修饰
    • await可以使异步编程模拟出同步的效果
      • await后面一般会放置一个promise实例(其他正常值也可以,浏览器会把其变为promise实例),await终端函数体中,其下面的代码执行,(await表达式会暂停整个async函数的执行进程并出让其控制权
      • 等待promise状态为成功后,获取成功的结果,并且执行函数体重await下面的代码
        • await是异步代码
        • 函数体中遇到await,后面代码该怎么执行怎么执行,但是下面的代码会暂停执行,把他们当做一个任务,放在eventqueue的微任务队列中
      • 如果await后面的promise实例是失败的,则不再处理之前存放的微任务(也就是下面的代码):因为没有处理失败,浏览器会抛出异常
      • 处理异常,用try/catch
  • async修饰一个函数:保证函数返回的是一个promise实例,让函数中可以使用await
    1. 与then相似,函数执行不报错,返回成功的promise实例,报错返回的是失败的
    2. return的值或者报错的原因就是promise实例的结果
    3. 如果return的是一个新的promise实例,则新实例的结果影响返回promise的结果

es6的import/export和nodejs require/exports有什么区别

  1. require是commonjs规范/import是ES6语法,需要babel编译
  2. import/export最终都会编译为require/exports执行
  3. require的调用时间为运行时调用,require可以出现在文件的任何地方,动态加载
  4. import是编译时调用,必须放在文件头部,静态加载
  5. import采用的是实时绑定,导入和导出的值都指向同一个内存地址,导入值会随着导出值变化
  6. require导出的是拷贝,导出值不会影响导入值
  7. export default导出的东西只能用import导入

amd、cmd

柯里化

token和cookie区别

segmentfault.com/a/119000001…

ajax/axios/fetch的核心区别

www.jianshu.com/p/8bc48f8fd…

ajax:前后端数据通信,同源跨域

axios:也是对ajax的封装,基于promise管理请求,解决回调地狱

fetch

  1. 语法简洁,更加语义化
  2. 基于标准 Promise 实现,支持 async/await
  3. 同构方便,使用 isomorphic-fetch
  4. 更加底层,提供的API丰富(request, response)
  5. 脱离了XHR,是ES规范里新的实现方式