前端知识点-基础(持续更新...)

164 阅读23分钟

JavaScript

数据类型

原始数据类型:Number、String、Boolean、Null、Undefined、Symbol

引用数据类型:Object、Function、Array

类型检测

  1. typeof:只能检测除 null 以外的其他原始数据类型;引用类型和 null 都会返回 "object" var a = 123; typeof a;

  2. instanceof:基于原型链实现,判断某个实例是否是由某个构造函数生成,返回 true 或 false.所有引用数据类型的值都是 Object 的实例。 a instanceof b

  • 实现 instanceof
function instanceof(left, right){
    const rightPrototype = right.prototype;
    const leftProto = left.__proto__;
    while(true){
        if (leftProto === null) {
            return false;
        }
        if (leftProto === rightPrototype) {
            return true;
        }
        leftProto = leftProto.__proto__;
    }
}
  1. Array.isArray():判断数组类型

  2. Object.prototype.toString.call():最准确,输出类似 [object Number] 的格式 Object.prototype.toString.call(a)

  3. 如何判断 NaN:

  • Number.isNaN(value)
  • Object.is(NaN, value)

类型转换

== 做比较时:

  1. Boolean == ?,Boolean 转为 Number
  2. String == Number,String 转为 Number
  3. String == Boolean,String 转为 Number,Boolean 转为 Number
  4. Object == ?,调用 valueOf() 将 Object 转为基本类型之后,再按照上面规则比较,转换失败返回 NaN
    • valueOf:返回对象的原始值,没有原始值就返回对象本身
      • 数组返回本身
      • Date 返回毫秒形式的时间戳
      • 函数和对象返回本身
      • Boolean、Number、String 返回各自的值
    • toString:返回对象的字符串。
      • 数组重写了 toString 方法,返回逗号分隔的字符串
      • Date 重写了 toString 方法,返回日期的文字表示
      • 函数返回 'function demo(){console.log(1);}'
      • Boolean、Number、String 返回各自用字符串表示的值
    • 数值运算优先 valueOf,字符串运算优先 toString
    • undefined 和 null 没有 toString 和 valueOf 方法
  5. NaN 和任何类型(包括自己)比较都返回 false
  6. undefined == null => true,除此之外,undefined 和 null 跟其他任何值比较都是 false

-、/、% 做运算时:

  1. 转化为 Number 运算

+做运算时:

  1. 加号一侧为字符串,则另一侧转换为字符串做拼接
  2. 加号一侧为数字,另一侧为原始类型,则原始类型转换为 Number 做运算
    • NaN、undefined 转换为数字是 NaN
    • false、null 转换为数字 0
  3. 加号一侧为数字,另一侧为引用类型,先调用 valueof 获取原始值,没有则使用 toString,再进行下一步计算

实现无限累加函数


function add(n){
    function _add(m){
        n = n + m;
        // 每次调用完都返回 _add 方法,以便下次调用
        return _add;
    }
    // 加号运算符还会导致隐式的调用到 valueOf 或 toString 方法
    // 这里重写 toString 来达到返回累加后的数据的目的
    _add.toString = function(){
        return n;
    }
    return _add;
}
// 使用+运算符触发 toString 方法的调用
console.log(+add(1)(2)(3)); // 6

升级版(不限参数个数)

function add(){
    let args = [...arguments];
    function _add(){
        const _args = [...arguments];
        return add.apply(null, [...args,..._args]);
    }
    _add.toString = () => {
        return args.reduce((pre, curr)=> pre + curr);
    }
    return _add;
}

原型

prototype:

  • 只要创建一个函数,就会为这个函数创建一个 prototype 属性
  • 只有函数才有 prototype 属性

_proto_:

  • 对象都有这个属性,指向它的构造函数的 prototype 原型链:
  • 当视图访问对象的某个属性,则会先查找对象本身的属性,若本身没有,则在该对象的原型里去查找,逐级向上,直到原型链的顶端 null
  • 原型链的顶端是 null
  • 每个对象都有一个 __proto__ 属性,指向它构造函数的原型对象 prototype,构造函数的原型对象也有它自己的__proto__属性,逐级向上直到一个对象的原型指向 null,这样一层一层的关系就叫做原型链。

prototype.webp

原型相关的方法

  • a instanceof b 判断 a 是否是 b 的一个实例
  • a.hasOwnPropertyOf(b) 判断 b 是否是 a 自己定义的方法(可以用来判断排除原型上的方法)
  • Object.getPrototypeOf(a) 获取 a 的原型上的方法

继承

原型链继承

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = () => {
    console.log("hi");
}
function Student(name, age) {
    this.name = name;
    this.age = age;
}
Student.prototype = new Person();

优点:

  • 可以继承父类原型链上的属性
  • 子类型创建不能向父类传递参数 缺点:
  • 如果原型链上的属性是引用类型,那么所有的实例都共享一个引用,一个实例改变引用类型的值,其他的实例也会受到影响

借用构造函数

function Person(name, age) {
    this.name = name;
    this.age = age;
}
function Student(name, age) {
    Person.call(this, name, age);
}
var student = new Student("xiaoming", 12);

优点:

  • 子类型创建能向父类传递参数
  • 各实例的属性保持独立,不会互相影响 缺点:
  • 不能继承父类原型链上的属性

组合继承

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log("hi")
}
function Student(name, age){
    Person.call(this, name, age);
}
Student.prototype = Object.create(Person);
Student.prototype.constructor = Student;

优点:

  • 使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承,结合了两者的优点

ES6 继承

class Parent {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends Parent {
    constructor(name, age) {
        super(name, age);
    }

}

ES5 继承和 ES6 继承的差别

详解

  • es5 会先创建子类型的构造函数实例,然后通过 call 继承父类属性,因此无法继承原生的构造函数
  • es6 先创建父类的 this,然后通过修饰子类型的 this 实现继承,因此可以继承原生的构造函数

几个关于创建对象的方法

Object.create(A)

  • 创建一个新对象,并继承 A 的原型 new Object()
  • 同 {}

new 操作符

  1. 创建一个空对象
  2. 将空对象的原型指向目标对象的 prototype
  3. 改变 this 指向为创建的新对象
  4. 执行构造函数,返回创建的新对象
function MyNew(o){
    var obj = {};
    obj.__proto__ = o.prototype;
    var result = o.call(obj);
    return typeof result === "object" ? result : obj;
}

this

this 永远指向一个对象;this 的指向取决于调用的位置

  • 对象调用:谁调用就指向谁
  • 直接调用函数:函数内部的 this 指向 window
  • new 创建新对象:this 指向新创建的对象
  • 定时器:this 指向 window
  • 箭头函数:定义函数时所在的上下文,而不是调用时的上下文

apply、call

都能改变 this 的指向,第一个参数是 this 新指向的对象,apply 接受一个数组作为第二个参数,call 需要将参数平铺依次传入

a.apply(b, [1, 2, 3]);
a.call(b, 1, 2, 3);

bind

改变 this 的指向,并返回一个新函数

var newFn = a.bind(b, 1, 2, 3);

实现 bind

// 基础版
function bind(){
    var newObj = arguments[0];
    var args = Array.prototype.slice.call(arguments, 1);
    var self = this;
    return function(_args){
        return self.apply(newObj, [...args, ..._args]);
        
    }
}
// 升级版,支持 new 操作符创建对象
function bindPro() {
    var newObj = arguments[0];
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    
    var fn = function(){};
    
    var newFn = function(_args){
        return self.apply(self instanceof fn ? this : newObj, [...args, _args]);
    }
    fn.prototype = this.prototype;
    newFn.prototype = new fn();
    
    return newFn;
}

执行上下文

定义:当前代码的执行环境 分三类:

  • 全局执行上下文:最外围的执行环境,在浏览器里指 window
  • 函数执行上下文:可以有无数个,当函数调用的时候被创建,每次调用会产生一个新的上下文
  • eval 执行上下文 执行上下文的三个属性:
  • 变量对象:执行环境里定义的所有变量和函数都保存在变量对象中。
    • 活动对象:活动对象就是变量对象,只不过是当进入一个执行环境时,变量对象会被激活,因此叫活动对象,只有活动对象中的属性是可访问的
  • 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链保证变量的有序访问
  • this

执行上下文生命周期过程:

  1. 创建变量对象
    • 初始化函数参数 arguments
    • 函数声明
    • 变量声明
  2. 创建作用域链
    • 函数作用域链在定义的时候就确定了。查找变量时,会先从当前上下文的变量对象中查找,没找到则从父级执行上下文中的变量对象中查找
  3. 确定 this 的指向
  4. 变量赋值,代码执行
  5. 执行上下文出栈,回收

作用域

定义:是用于确定在哪里找,怎么找到某个变量的一套规则

词法作用域:在写代码时将变量写在哪里决定的。编译的词法分析阶段能够知道全局标识符在哪里以及如何声明

变量提升:指变量的声明在它的整个作用域范围内都存在

  • 包含函数和变量在内的所有声明都会在任何代码执行之前被处理
  • 函数声明会被提升,但是函数表达式不会,也就是说使用 function fun(){}声明的函数,可以在声明它之前就使用,但是 var fun = function(){} 的不可以,因为他的值是 undefined

块级作用域:指在块级作用域以外,无法访问块级作用域内部的变量。

  • 在 es6 之前,除了函数体,没有块级作用域
  • es6 中,{} 可以创建块级作用域,letconst可以创建块级作用域变量

作用域链:变量对象形成的链表。当查找某个变量时,会从第一个变量对象中查找,若没有这个属性,就查找链上的第二个变量对象,若最终没有找到则会抛出一个错误。

zuoyongyulian.png

闭包

闭包:一个函数和其周围的词法环境的引用捆绑在一起,闭包能让内部函数访问外层函数的作用域。每当创建一个函数,闭包就会同时被创建出来 场景:

  • 将函数作为值返回
  • IIFE 会保存全局作用域和当前函数作用域
  • 定时器、时间监听 作用:
  • 借助闭包封装私有变量
  • 把多参数函数变成单参数的函数(实现柯里化) 缺点:
  • 因为闭包对外部作用域存在引用,因此外部作用域不会被销毁,可能会造成内存泄露

Event Loop

javascript 是一门单线程的语言,即 js 在执行的任何的时候,只有一个主线程来处理所有的任务。那整个运行过程中的任务,就需要有一个调度机制来保证有序执行,这个机制就叫做事件循环。

执行栈

从执行上下文的知识里我们知道,当我们调用一个方法或函数的时候,会生成一个对应的执行上下文,当一系列方法被调用的时候,就会生成很多个执行上下文,因为 js 是单线程,所以这些执行上下文会被排在一个栈里,依次出栈调用,这个栈叫做执行栈。

事件队列

当 js 遇到一个异步事件后,不会一直等待它返回结果,而是会将事件挂起继续执行执行栈中的任务,等异步事件结果返回之后,会将事件加入另一个队列,这个队列叫事件队列。在一个事件循环中,会根据事件的类型,将事件分成宏任务和微任务,分别放到宏任务队列和微任务队列中

  • 宏任务

    • setInterval
    • setTimeout
  • 微任务

    • Promise
    • MutationObserver

当前执行栈执行完毕后,会优先处理完微任务队列中所有事件,然后从宏任务队列中取出最前面的任务执行,同一次事件循环中,微任务永远优先于宏任务执行

ES6 相关

块级作用域声明 let、const

  • var
    • var 关键字允许重复声明
    • 使用 var 声明的变量会被提升,也就是可以在声明一个变量之前使用该变量,初始化为 undefined,创建和初始化一起被提升;
    • 1、创建的同时进行初始化 undefined 2、赋值
  • let、const
    • 不允许重复声明
    • 使用 let 声明的变量也会被提升,但是不可以提前使用,会报错
    • let 声明的变量在环境实例化的时候被创建,但是在变量的词法绑定前不允许使用。创建被提升了,但是初始化没被提升。从 let 创建到被初始化这部分,变量时不可用的,也就是 「暂时性死区」
    • 暂时性死区的本质:只要一进入当前作用域,变量就已经存在了,但是不可获取,只有等到声明的那一行代码出现,才可以使用;
    • 存在于独立的块级作用域中,内部的变量声明会覆盖外部的变量声明,因此下面这段代码会报错
let a = 123;
if (true) {
    console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
    let a = 456;
}

数组方法

  • find():返回第一个匹配的元素的值
  • includes():若存在给定的值,返回 true
  • flat(depth):扁平化数组,默认只扁平一级,Infinity 表示完全打平所有嵌套层级
  • fill(value, start, end):用给定的值填充数组,
  • keys():返回数组键组成的迭代器(注意不是数组)
  • values():返回数组值组成的迭代器(注意不是数组)
  • Array.from(p1, mapFn):p1是任意可迭代的对象或类数组,使用它们的值来构建数组;mapFn 是一个映射函数,处理每个数组元素,返回新的值

for-in 和 for-of

  • for-in:循环可枚举对象
  • for-of:遍历迭代器对象(map、set、string、伪数组、generator)

Set & Map

  • Set:唯一值的集合。
    • 参数:一个可迭代的对象。
    • 创建:const s = new Set(["one","two"]);
    • 添加:s.add("tree").add("four");
    • 判断是否包含:s.has("one") === true
    • 删除:s.delete("one")
    • 长度:s.size()
    • 转换成数组:Array.from(s);
  • Map:一个事物到另一个事物的映射
    • 参数:可迭代的对象
    • 创建:const m = new Map([["a", "one"],["b", "two"]]);
    • 添加:m.set("c", "tree");
    • 获取:m.get("a");
    • 删除:n.delete("c");
    • 遍历:for(const [key,value] of m){}
    • 与对象的区别
      • 对象的键只能是字符串,Map 可以是任意值
      • 对象做映射取值的时候不能保证顺序
    • WeakMap
      • 只接受对象作为键
      • WeakMap 的键是弱引用,因此键所指向的对象是可以被回收的
      • 不能被遍历

垃圾回收

引用计数

给一个占用物理空间的对象加一个计数器,被引用一次就 +1,反之 -1,当计数为 0 的时候就会被回收

标记清除

遍历调用栈,被引用的对象标记为活动对象,没有被引用的对象标记为垃圾对象,垃圾对象会被清理掉

  • 在开发过程中,想要回收某个对象,只需要将它置为 null。但是当对象作为另一个对象的键或值,就不会被回收。

React

生命周期

初始化阶段

  • constructor,在这个阶段初始化 state 和 props,在更新阶段不会执行

挂载阶段

  • componentWillMount 16.3 废弃
    • 还没挂载 DOM
  • render
  • componentDidMount
    • DOM 已挂载
    • 发送网络请求、启用事件监听
    • 可以调用 setState 更新阶段
  • ~~componentWillReceiveProps(nextProps, nextState)~~16.3 废弃
    • 主要监听 props 的改变,在这个生命周期里可以设置 state,不会引起二次更新
  • static getDerivedStateFromProps(nextProps,nextState)
    • 在组件实例化、props 变动、组件状态更新时调用
    • 可以返回一个对象,会跟 state 合并,但是不会触发二次更新
    • 主要是提供了一个在 props、state发生变动后,根据 props 修改 state 的一个时机
    • 代替 componentWillMount、componentWillReceiveProps
  • shouldComponentUpdate(nextProps, nextState)
    • 需要返回 boolean 告知是否要更新,返回 false 就到此结束不会往下执行更新
    • 这里不能设置 state,会引起循环调用
  • componentWillUpdate16.3 废弃
    • 在这里可以在 DOM 发生更新之前获取一些信息,比如元素坐标。
    • 这里不能设置 state,还会引起循环调用

到此,state 和 props 都还未发生更新,这也是 setState 表现为异步的原因

  • render
  • getSnapshotBeforeUpdate
    • 在 render 函数调用之后,实际的 Dom 渲染之前,函数的返回值会作为 componentDidUpdate 的第三个参数化
  • componentDidUpdate(preProps, preState)
    • DOM 已完成更新,props、state 都已更新

卸载阶段

  • componentWillUnmount
    • 在组件卸载及销毁之前调用,这里多处理一些清理操作,比如清除 timer,取消订阅等

setState 是异步还是同步

setState 本身的执行过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,因此在合成事件和钩子函数中不能立即拿到更新后的值,使得看起来像是异步的,可以用函数式的写法拿到更新之后的值 setState(partialState, callback)

  • 异步:钩子函数、react 合成事件
  • 同步:setTimeout、原生事件
  • 说明:每次 setState 产生的新 state 会被放进一个队列里,有一个变量叫 isBathingUpdates,它默认是 false,表示不批量更新,也就是会直接更新 this.state,但是在调用事件处理函数之前,会调用一个 batchedUpdates 方法,这个方法会把 isBathingUpdates 设置为 true,就导致合成事件中的 setState 变成了异步。
  • setState 的更新优化也是建立在异步这个行为上的,当多次更新 state 的值,会被合并成一次执行

虚拟 DOM

虚拟 DOM 主要是为了解决浏览器性能问题而设计的,使用 js 对象模拟的 DOM 结点,主要包含 tag、props、children 三个属性。

  • react 中虚拟 dom 主要结构
ReactDOM.render(<h1>Hello World</h1>,document.getElementById('root'));
// babel 转换之后
ReactDOM.render(React.createElement(
    'h1',
    null,
    'Hello World'
), document.getElementById('root'));
// 生成的 ReactElement(VDom)
{
    $$typeof: Symbol(react.element)
    key: null
    props: {children: "Hello World"}
    ref: null
    type: "h1"
    _owner: null
    _store: {validated: false}
    _self: null
    _source: null
    __proto__: Object
}

diff

详解 策略(算法复杂度从 O(n^3) 下降到 O(n) ):

  1. DOM 结点跨层级移动情况很少,可以不考虑;
  2. 同类的组件生成相似的树结构,不同类的组件生成不同的树结构
  3. 同层级的组件,可以通过唯一标识区分 tree diff
  • 只会对同层级的节点进行比较,当发现节点不存在,会直接删掉节点及其子节点 component diff
  • 同一类型的组件,按照原策略进行比较
  • 不是同类型的组件直接替换
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff element diff
  • 设置唯一标识。如果没有唯一标识,那么会依次对节点进行比较,如果只是同层级的节点被移动了,没有 key 的话就会平白做一些删除创建节点的工作;如果有唯一 key,那么通过唯一 key 判断有节点,那么只需要进行移动

Fiber

详解 15.x 的问题

  • 当 state 发生改变,react 会遍历所有结点计算出差异,再更新 UI,且整个过程是不能被打断的。我们知道,js 运算、页面布局、页面绘制都是运行在浏览器的主线程中,他们是互斥的,如果 js 运算持续占用主线程,那 UI 就不能及时得到更新,当运算量太大,超过 16ms,就会出现掉帧,页面表现就是卡顿。 解决方案
  • 问题出现在 js 运算量庞大长时间占用主线程,那就将运算切割,分批完成。完成一部分任务之后,就将控制权交给浏览器,让浏览器做优先级更高的工作,然后再来做之前没做完的运算。
  • react 将任务分成小片,在一个片段时间内运行这些分片,
  • 一个 fiber 是一个工作单元,组件的本质可以看做输入数据,输出 UI 的描述信息(即虚拟DOM) ui=f(data)
  • 旧版 react 通过递归的方式进行渲染,使用的是 js 引擎自身的函数调用栈,它会一直执行到栈空为止;fiber 实现了自己的组件调用栈,以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务(浏览器 requestIdleCallback API,react 用 RequestAnimationFrame+postMessage实现)
  • fiber tree 是按照虚拟 DOM 生成的,只是结点携带的信息不一样。因此每个组件实例和 DOM 结点的抽象表示都是一个 fiber。

react 框架如何运作

内部分三层:

  1. virtualDom,描述页面
  2. reconciler(fiber reconciler),调用组件声明周期,进行 diff 运算
  3. rerender,根据不同的平台渲染不同的页面,比如 reactDOMreactNative
  • fiber 是一种是数据结构
const fiber = { 
    stateNode, // 节点实例 
    child, // 子节点 
    sibling, // 兄弟节点 
    return, // 父节点 
}
  • 为了实现任务的有序执行,有一个调度器 Scheduler 来进行任务分配,任务按优先级分:
    • synchronous,同步任务,优先级最高(跟旧版表现一致)
    • task,当前调度正执行的任务
    • animation,动画,在下一帧之前执行
    • high,高优先级,在不久的将来立即执行
    • low,低优先级,延迟一会儿执行
    • offscreen,当前屏幕外的更新,优先级最低,下一次 render 执行
  • 优先级高的任务(如键盘输入)可以打断低优先级的任务(如 diff),优先执行
  • fiber 的两个阶段
    • reconciliation 阶段:生成fiber树,得出需要更新的结点信息存在 effect 中。可以被打断,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率
    • commit 阶段:将上一步 effect 中需要更新的结点一次性批量更新,不可以被打断

react 的异步渲染

时间分片(time slicing)

  • 时间分片指的是把多个小粒度的任务放到一个时间切片(帧)中执行的一种方案
  • 时间分片依赖于requestIdleCallback API,在浏览器空闲的时候执行低优先级的任务

渲染挂起(Suspense)

  • Suspense 让子组件在渲染之前等待,并在等待的时候显示 fallback 的内容
  1. 异步加载组件(react v16 只支持这个场景)
const LazyComponent = React.lazy(() => import("./component"));

const SuspenseComponent = () => {
    return (
        <React.Suspense fallback={<Spinner />}>
            <LazyComponent />
        </React.Suspense>
    )
};
  1. 异步获取数据(react v18)
  • 请求需要额外做包装 详解
  • Suspense 的原理
    • 它其实是监听了子组件抛出来的 promise 异常
class Suspense extends React.Component {
    state = { loading: true }
    componentDidCatch(error){
        if (error && typeof error.then === "function"){
            error.then(() => {
                this.setState({loading: true});
            });
            this.setState({loading: false});
        }
    }
    render(){
        const {callback, children} = this.props;
        return this.state.loading ? callback : children;
    }
}

react 优化手段

  1. 减少重新 render 的次数
    • React.memo():仅检查 props 的变更
    • React.PureComponent:浅对比 state 和 props 实现了 shouldComponentUpdate;但是在数据层次较复杂的情况下组件不能得到正确的更新
    • Context 中只定义被公用的信息
    • 避免将回调函数包裹在匿名函数中通过 props 传递,因为每次
  2. 降低重复的计算
    • React.useMemo(fn, [dep])
  3. 渲染列表时记得加上 key
  4. 使用 immutable 数据(immutable.js)

Promise

使用场景:Promise 是为了解决 js 回调嵌套过多的问题而产生的。它支持链式调用,使用更方便。 规范:Promise 遵循 Promise/A+ 规范

  • pending:表示初始状态,可以转移到 rejected 或 fulfilled 状态
  • fulfilled:表示操作成功,不可转移状态
  • rejected:表示操作失败,不可转移状态
  • 必须有一个 then 异步执行方法,且接受两个参数并且返回 promise

实现思路

  • 几个变量
    • status:保存 promise 实例的状态
    • reason:保存 rejected 之后的原因
    • value:保存 fulfilled 之后的值
    • fulfilledCallbacks:fulfilled 回调队列
    • rejectedCallbacks:rejected 回调队列
  • resolve 和 reject 方法作为 executor 函数的两个参数
  • 在 then 方法中返回新的 promise 实例;判断 status 是否是 pending,如果是,则将成功和失败的回调分别加入队列
  • 在 resolve 和 reject,方法中,判断 status 是否为 pending,如果是,则分别改为 fulfilled 和 rejected,并批量执行回调队列中的方法
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function Promise1(executor){
    this.status = PENDING;
    this.value = "";
    this.reason = "";
    this.fulfiledCallbacks = [];
    this.rejectCallbacks = [];
    
    function resolve(value){
        if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value;
            this.fulfilledCallbacks.forEach(function(cb){
                cb(this.value);
            });
        }
    }
    function reject(reason){
        if (this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
            this.rejectCallbacks.forEach(function(cb){
                cb(this.reason);
            });
        }
    }
    try{
        excutor(resolve, reject);
    }catch(e){
        reject(e);
    }
}
Promise1.prototype.then = function(onFulfilled, onReject){
    return new Promise1(function(resolve, reject) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value);
        }
        if (this.status === REJECTED) {
            onReject(this.reason);
        }
        if (this.status === PENDING) {
            this.fulfiledCallbacks.push(function(){
                var result = onFulrilled(this.value);
                resolve(result);
            });
            this.rejectCallbacks.push(function(){
                var result = onReject(this.reason);
                reject(result);
            });
        }
    });
    
}
// 异常捕获
Promise1.prototype.catch = function(onReject){
    this.then(null, onReject);
};

Promise1.resolve = function(value){
    return new Promise1(function(resolve){
        resolve(value)
    });
}

Promise1.all = function(promises){
    return new Promise1((resolve, reject) => {
        let pLength = promises.length;
        let resolveResult = [];
        let resolveNum = 0;
        
        for (let i = 0; i < pLength; i ++) {
            promises[i].then((res) => {
                resolveResult.push(res);
                resolveNum ++;
                if (resolveNum === pLength) {
                    resolve(resolveResult);
                };
            }).catch((e) => {
                reject(e);
            });
        }
        
    });
    
}
Promise1.race = function(promises){
    return new Promise1((resolve, reject) => {
        promises.forEach((p) => {
            p.then(resolve, reject);
        });
    });
}

var p = new Promise1(function(resolve, reject){
    resolve(1);
})
.then(function(value){
    console.log(value);
});

async、await 的理解

async/await 是一种更方便完成异步调用的语法。

  • async/await 是 generator 的语法糖,async 使得函数始终返回一个 promise,await 必须在 async 内部使用
  • 与 generator 相比,它内置了执行器,不用手动调 next() 方法;await 后面可以是普通方法也可以时候 promise,而 yield 后面必须是 promise 或 thunk 函数

原理

async 函数其实是将 generator 函数和自动执行器包装在一个函数里。

generator

generator 封装了多个内部状态。执行 generator 会返回一个遍历器对象,可以调用 next() 依次访问内部的每个状态,因此其实是提供了一个可以暂停执行的函数,yield 就是暂停执行的标识。只不过需要手动调用 next() 来执行下一步,因此需要封装一个自动执行器。

// 基于 promise 的简易自动执行器
function run(gen){
    const g = gen();
    function next(value){
        const result = g.next(value);
        if (result.done) {
            return result.value;
        }
        // 只要没执行完,就继续调用自身
        result.value.then((res) => {
            next(res);
        });
    }
    next();
}

function* foo() { 
    let response1 = yield fetch('https://xxx') //返回promise对象 
    console.log(response1) 
    let response2 = yield fetch('https://xxx') //返回promise对象 
    console.log(response2) 
} 
run(foo);

CSS

BFC

块级格式化上下文。是一块独立的渲染区域,用于决定块元素布局及浮动相互影响范围的一块区域 触发规则

  • 根元素:HTML 标签
  • 浮动元素:float 为 left、right
  • overflow 的值不为 visible
  • display 的值为 table-cell、inline-block、flex、inline-flex
  • position 值为 absolute、fixed BFC 区域的布局规则:
  • BFC 区域内部的元素会一个接一个垂直排列
  • 属于同一个 BFC 的两个垂直相邻的盒子,上下 margin 会发生重叠,取较大的一个 margin
  • BFC 的区域不会与 float 区域发生重叠(可用作浮动的清除)
  • 计算 BFC 区域高度时,float 元素的高度也参与计算

浮动

浮动会是元素脱离文档流,按照设定的方向(left、right)移动,直到接触到包含框的边缘或另一个浮动元素的边缘

  • 浮动可以设置三个属性,float:left、right、none; float 元素的特性
  • 行内元素围绕浮动元素摆放:浮动最初设计的目的就是为了文字环绕图像
  • 父元素高度塌陷:浮动元素的高度不会被计算
  • 块元素认为浮动元素不存在:因此跟在浮动元素后面的块元素会被前面的浮动元素遮挡;但是浮动元素前面的块元素不会被后面的浮动元素遮挡;
  • 浮动元素一个挨着一个摆放 清除浮动
  • BFC
  • 给浮动元素后面添加伪元素
.clear::after {
    content: "";
    clear: both;
    display: block;
}

定位

position:relative

  • 相对定位,相对于自己最初的位置定位。
  • 相对定位的元素偏移之后依然占据原来的位置,也不会挤开别的元素 position:absolute
  • 绝对定位,相对于最近的一个设置了 position 不为 static 的父元素定位,如果没有,则相对于 body 定位
  • 绝对定位的元素不占据原来的位置 position:fixed
  • 固定定位,以浏览器窗口为参考进行定位
  • 脱离文档流,不跟随页面滚动二发生变化 position:sticky
  • 粘性定位,可以理解为 relative + fixed 的结合效果,必须结合 tlrt 属性使用
  • 当页面开始滚动,父元素开始脱离视口时,只要与 sticky 元素的距离达到生效距离,sticky 元素就会表现为 fixed 定位

transform 和直接使用 top、left 改变位置有什么优缺点

  • top、left 是布局类样式,改变会导致重排
  • transform: translate(x,y) 只是导致重绘
  • 总结就是 transform 性能更好,top、left 兼容性更好