JavaScript
数据类型
原始数据类型:Number、String、Boolean、Null、Undefined、Symbol
引用数据类型:Object、Function、Array
类型检测
-
typeof:只能检测除 null 以外的其他原始数据类型;引用类型和 null 都会返回 "object"
var a = 123; typeof a;
-
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__;
}
}
-
Array.isArray():判断数组类型
-
Object.prototype.toString.call():最准确,输出类似 [object Number] 的格式
Object.prototype.toString.call(a)
-
如何判断 NaN:
- Number.isNaN(value)
- Object.is(NaN, value)
类型转换
== 做比较时:
- Boolean == ?,Boolean 转为 Number
- String == Number,String 转为 Number
- String == Boolean,String 转为 Number,Boolean 转为 Number
- 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 方法
- valueOf:返回对象的原始值,没有原始值就返回对象本身
- NaN 和任何类型(包括自己)比较都返回 false
undefined == null => true
,除此之外,undefined 和 null 跟其他任何值比较都是 false
-、/、% 做运算时:
- 转化为 Number 运算
+做运算时:
- 加号一侧为字符串,则另一侧转换为字符串做拼接
- 加号一侧为数字,另一侧为原始类型,则原始类型转换为 Number 做运算
- NaN、undefined 转换为数字是 NaN
- false、null 转换为数字 0
- 加号一侧为数字,另一侧为引用类型,先调用 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,这样一层一层的关系就叫做原型链。
原型相关的方法
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 操作符
- 创建一个空对象
- 将空对象的原型指向目标对象的 prototype
- 改变 this 指向为创建的新对象
- 执行构造函数,返回创建的新对象
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
执行上下文生命周期过程:
- 创建变量对象
- 初始化函数参数 arguments
- 函数声明
- 变量声明
- 创建作用域链
- 函数作用域链在定义的时候就确定了。查找变量时,会先从当前上下文的变量对象中查找,没找到则从父级执行上下文中的变量对象中查找
- 确定 this 的指向
- 变量赋值,代码执行
- 执行上下文出栈,回收
作用域
定义:是用于确定在哪里找,怎么找到某个变量的一套规则
词法作用域:在写代码时将变量写在哪里决定的。编译的词法分析阶段能够知道全局标识符在哪里以及如何声明
变量提升:指变量的声明在它的整个作用域范围内都存在
- 包含函数和变量在内的所有声明都会在任何代码执行之前被处理
- 函数声明会被提升,但是函数表达式不会,也就是说使用
function fun(){}
声明的函数,可以在声明它之前就使用,但是var fun = function(){}
的不可以,因为他的值是undefined
块级作用域:指在块级作用域以外,无法访问块级作用域内部的变量。
- 在 es6 之前,除了函数体,没有块级作用域
- es6 中,{} 可以创建块级作用域,
let
、const
可以创建块级作用域变量
作用域链:变量对象形成的链表。当查找某个变量时,会从第一个变量对象中查找,若没有这个属性,就查找链上的第二个变量对象,若最终没有找到则会抛出一个错误。
闭包
闭包:一个函数和其周围的词法环境的引用捆绑在一起,闭包能让内部函数访问外层函数的作用域。每当创建一个函数,闭包就会同时被创建出来 场景:
- 将函数作为值返回
- 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,在更新阶段不会执行
挂载阶段
componentWillMount16.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) ):
- DOM 结点跨层级移动情况很少,可以不考虑;
- 同类的组件生成相似的树结构,不同类的组件生成不同的树结构
- 同层级的组件,可以通过唯一标识区分 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 框架如何运作
内部分三层:
- virtualDom,描述页面
- reconciler(fiber reconciler),调用组件声明周期,进行 diff 运算
- rerender,根据不同的平台渲染不同的页面,比如
reactDOM
和reactNative
- 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 的内容
- 异步加载组件(react v16 只支持这个场景)
const LazyComponent = React.lazy(() => import("./component"));
const SuspenseComponent = () => {
return (
<React.Suspense fallback={<Spinner />}>
<LazyComponent />
</React.Suspense>
)
};
- 异步获取数据(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 优化手段
- 减少重新 render 的次数
- React.memo():仅检查 props 的变更
- React.PureComponent:浅对比 state 和 props 实现了 shouldComponentUpdate;但是在数据层次较复杂的情况下组件不能得到正确的更新
- Context 中只定义被公用的信息
- 避免将回调函数包裹在匿名函数中通过 props 传递,因为每次
- 降低重复的计算
- React.useMemo(fn, [dep])
- 渲染列表时记得加上 key
- 使用 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 兼容性更好