Skip to main content

面试准备及总结-2021

知识点总结

内存管理

https://juejin.cn/post/6844903869525262349

模块化(type = "module")

https://zh.javascript.info/modules-intro

  • 始终使用严格模式
  • 每个模块具有作用域
  • 多次导入的话只会导入一次并共享
  • thisundefined
  • HTML文档准备就绪后才会运行(可以用async属性解决)
  • nomodule来解决兼容性问题
  • 每个文件最多只能有一个默认的导出
  • 动态导入使用import(),返回一个promise

Promise

https://zh.javascript.info/promise-basics

Promise链式调用

let p = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
})
p.then(res => {
console.log(res); //1
return new Promise((resolve, reject) => {
setTimeout(() => resolve(res*2), 1000)
})
}).then(res => {
console.log(res); //2
});

then中返回的结果会传递给下一个then中

  • Promise.all([...promises...])

    • 等所有promise完成后才会settled,结果的到所有promise resolve后的数组结果,且数组的顺序和所有promise完成的顺序无关。
    • 如果有一个promise被rejected,那整个promise将会立刻rejected,并带有的就是这个被rejected的promise的error,并且其他正在执行的promise将会被忽略(但是不会被取消)。
  • Promise.allSettled([...promises...])(ES2020 新增)

    • 解决了Promise.all([...promises...])中第二条特征中的问题。Promise.allSettled会等待所有promise都settled,无论结果如何。
    • 返回一个数组,数据类型为{status:"fulfilled"/"rejected", value:result/error}
  • Promise.race([...promises...])

    • 返回第一个settled的promise
  • Promise.resolve(value) 等同于let promise = new Promise(resolve => resolve(value));

  • Promise.reject(reject)

async/await

  • async包裹的函数总是返回一个 promise

vdom

  • React.creatElement(type, props, children)
  • 由于使用了js,所以可以实现跨平台
  • 内容经过了处理,可以防范XSS处理

diff dom

https://juejin.cn/post/6844904112027353096#heading-6

  1. 转化成虚拟dom
  2. 通过深度优先来对比差异(同级对比)
  3. 每遍历一个节点,就记录一个索引值
  4. 如果发现差异,就把索引值对应的的变化保存起来

应用差异

  1. 对真实dom进行深度优先遍历
  2. 在对应的索引节点上应用差异即可

跨域

cookie跨域

HTTP请求方法

方法描述
GET请求指定的页面信息,并返回实体主体。
HEAD类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
PUT从客户端向服务器传送的数据取代指定的文档的内容。
DELETE请求服务器删除指定的页面。
CONNECTHTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS允许客户端查看服务器的性能。
TRACE回显服务器收到的请求,主要用于测试或诊断。
PATCH是对 PUT 方法的补充,用来对已知资源进行局部更新 。

GET和POST的区别

GET

  • 可被缓存
  • 会保留在浏览器历史中
  • 请求数据长度有限制
  • 语意上用于取回数据
  • 安全性较差
  • 数据在URL中发送

POST

  • 数据在消息体中发出不可见
  • 不被缓存
  • 不保留在浏览器历史中
  • 请求数据长度不限制
  • 比GET安全一些
  • 语意上提交数据

面试题

不引用第三个变量的情况下2个变量交换值

例如达到一下效果

let a = 1;
let b = 2;

最终
a = 2
b = 1

方法一、异或 a = a ^ b b = a ^ b a = a ^ b

具体推断过程
a = 0010
b = 0001
||||
vvvv
a = 0011 = 3
b = 0001
||||
vvvv
b = 0010 = 2
a = 0011
||||
vvvv
a = 0001 = 1

方法二、ES6解构

[a, b] = [b, a]

问到的问题: 一面: js的eventloop和node. js的eventloop以及他们的区别 setTimeout的时间是否准确,如何解决不准确的问题 ts中never any unknown区别 webpack ts-loader原理 ast语法树 plugin调用的时刻 知道哪些hook,useEffect使用过程中遇到的问题,useRef的使用场景 平时遇到问题如何调试和解决 是否在CI中集成过代码质量检查工具 平时学习的途径,看掘金是看热点文章还是针对性的看 编程题

二面: 栅格化,有什么优点以及原理 平时有没有玩过什么东西 tailwindcss和bootstrap有什么区别 当代javascript使用的继承方式 有没有开发过一些效率工具来提升活动页的开发效率

js基础点

  • + 运算符,只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。

可选链 ?.

前面的部分是 undefined 或者 null,它会停止运算并返回,且有短路效应

可以用来读取和删除变量,当时不能用来写入(赋值)

let user = {}; // user 没有 address 属性

alert( user?.address?.street ); // undefined(不报错)
let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
alert( user.address?.street ); // 报错

也可用于调用函数

let userAdmin = {
admin() {
alert("I am admin");
}
};

let userGuest = {};

userAdmin.admin?.(); // I am admin

userGuest.admin?.(); // 啥都没有(没有这样的方法)

也可使用 []

let user1 = {
firstName: "John"
};

let user2 = null; // 假设,我们不能授权此用户

let key = "firstName";

alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined

alert( user1?.[key]?.something?.not?.existing); // undefined

delete user1?.name; // 如果 user1 存在,则删除 user1.name

不同类型的比较

当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。null/undefined有特殊的规则

alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2
alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1

alert( true == 1 ); // true
alert( false == 0 ); // true

js中的特殊规则

// 死记
alert( null == undefined ); // true

当使用数学式或其他比较方法 < > <= >= 时: null/undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN

alert( null > 0 );  // (1) false 0>0
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true 0>=0

Symbol

Symbol([描述]); “Symbol” 值表示唯一的标识符,即使它们名字相同。

for inObject.keys() 会忽略对象属性键位Symbol的类型

let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};

for (let key in user) alert(key); // name, age

// 使用 Symbol 任务直接访问
alert( "Direct: " + user[id] ); // 123

相反,Object.assign 会同时复制字符串和 symbol 属性:

let id = Symbol("id");
let user = {
[id]: 123
};

let clone = Object.assign({}, user);

alert( clone[id] ); // 123

全局Symbol

// 从全局注册表中读取
let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它

// 再次读取(可能是在代码中的另一个位置)
let idAgain = Symbol.for("id");

// 相同的 Symbol
alert( id === idAgain ); // true
// 通过 name 获取 Symbol
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// 通过 Symbol 获取 name
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");

alert( Symbol.keyFor(globalSymbol) ); // name,全局 Symbol
alert( Symbol.keyFor(localSymbol) ); // undefined,非全局

alert( localSymbol.description ); // name

对象转原始类型

let user = {
name: "John",
money: 1000,

[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};

// 转换演示:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500

如果没有 Symbol.toPrimitive 和 valueOf,toString 将处理所有原始转换

原始类型方法

  • “对象包装器”对于每种原始类型都是不同的,它们被称为 String、Number、Boolean 和 Symbol。因此,它们提供了不同的方法。
  • 在访问其属性时,会创建一个包含字符串字面值的特殊对象,使用完后特殊对象被销毁。
  • 所以原始类型可以提供方法,但它们依然是轻量级的。 JavaScript 引擎高度优化了这个过程。它甚至可能跳过创建额外的对象。但是它仍然必须遵守规范,并且表现得好像它创建了一样。
  • 除 null 和 undefined 以外的原始类型都提供了许多有用的方法。

数字类型

0.1 + 0.2 !== 0.3 由于二进制无法精确存储小数 2 以 2 的整数次幂为分母的小数在二进制数字系统中可以被精确地表示,例如 0.5(1/$2^1$)、0.25(1/$2^2$)

alert( 0.1.toFixed(20) ); // 0.10000000000000000555
  • parseInt(str,base) 将字符串 str 解析为在给定的 base 数字系统中的整数,2 ≤ base ≤ 36,它从字符串中读取数字,然后返回在发生 error 前可以读取到的值。

  • isFinite(value) 将其参数转换为数字,如果是常规数字,则返回 true,而不是 NaN/Infinity/-Infinity:

    alert( isFinite("15") ); // true
    alert( isFinite("str") ); // false,因为是一个特殊的值:NaN
    alert( isFinite(Infinity) ); // false,因为是一个特殊的值:Infinity

  • isNaN(value) 将其参数转换为数字,然后测试它是否为 NaN

字符串

函数规则
slice(start, end) *从 start 到 end(不含 end) 允许
substring(start, end)start 与 end 之间(包括 start,但不包括 end) 负值代表 0
substr(start, length)从 start 开始获取长为 length 的字符串 允许 start 为负数
  • 字符串比较(不同语言) str.localeCompare(str2) 如果 str 排在 str2 前面,则返回负数。 如果 str 排在 str2 后面,则返回正数。 如果它们在相同位置,则返回 0。

数组

  • arr.splice(start[, deleteCount, elem1, ..., elemN]) [原数组]

  • arr.slice([start], [end]) [新数组]

  • arr.concat(arg1, arg2...)

    let arr = [1, 2];

    let arrayLike = {
    0: "something",
    length: 1
    };

    alert( arr.concat(arrayLike) ); // 1,2,[object Object]
    let arr = [1, 2];

    let arrayLike = {
    0: "something",
    1: "else",
    [Symbol.isConcatSpreadable]: true,
    length: 2
    };

    alert( arr.concat(arrayLike) ); // 1,2,something,else
  • Array.isArray

  • reduce(acc, current, index, source)

Symbol.iterator

为了让一个对象变得可迭代,需要为对象添加一个 Symbol.iterator 方法,这个方法必须返回一个迭代器 —— 一个有 next 方法的对象。

next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当 done=true 时,表示迭代结束,否则 value 是下一个值。

let range = {
from: 1,
to: 5
};

// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {

// ……它返回迭代器对象(iterator object):
// 2. 接下来,for..of 仅与此迭代器一起工作,要求它提供下一个值
return {
current: this.from,
last: this.to,

// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};

// 现在它可以运行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}

显式调用迭代器

let str = "Hello";

// 和 for..of 做相同的事
// for (let char of str) alert(char);

let iterator = str[Symbol.iterator]();

while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // 一个接一个地输出字符
}
  • for of 应用于可迭代的对象

  • Iterable (可迭代),是实现了 Symbol.iterator 方法的对象。

  • Array-like (类数组) 是有索引和 length 属性的对象,所以它们看起来很像数组。

    // 下面的是类数组但却不可迭代
    let arrayLike = { // 有索引和 length 属性 => 类数组对象
    0: "Hello",
    1: "World",
    length: 2
    };

    // Error (no Symbol.iterator)
    for (let item of arrayLike) {}
  • Array.from 接受一个可迭代或类数组的值,并从中获取一个“真正的”数组

    let arrayLike = {
    0: "Hello",
    1: "World",
    length: 2
    };

    let arr = Array.from(arrayLike); // (*)
    alert(arr.pop()); // World(pop 方法有效)

WeakMap 和 Map 的不同

关键词:垃圾回收弱引用内存泄漏

  • WeakMap 和 Map 的第一个不同点就是,WeakMap 的键必须是对象,不能是原始值:
let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // 正常工作(以对象作为键)

// 不能使用字符串作为键
weakMap.set("test", "Whoops"); // Error,因为 "test" 不是一个对象
  • WeakMap 不支持迭代以及 keys(),values() 和 entries() 方法。所以没有办法获取 WeakMap 的所有键或值。

  • 对象作为键被引用是,对象清除后,weakMap中对应的引用也会清除

    let john = { name: "John" };

    let weakMap = new WeakMap();
    weakMap.set(john, "...");

    john = null; // 覆盖引用

    // john 被从内存中删除了!

Map 和 Set

  • Map存储键值对
  • Set存储值且唯一

Set 和 WeakSet

  • WeakSet仅存储对象

结构赋值

// 不需要第二个元素
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul
let user = {};
[user.name, user.surname] = "Ilya Kantor".split(' ');

alert(user.name); // Ilya
let guest = "Jane";
let admin = "Pete";

// 交换值:让 guest=Pete, admin=Jane
[guest, admin] = [admin, guest];
let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined
// 默认值
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name); // Julius(来自数组的值)
alert(surname); // Anonymous(默认值被使用了)
let options = {
title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title); // Menu
alert(width); // 100
alert(height); // 200
let options = {
title: "Menu"
};

let {width: w = 100, height: h = 200, title} = options;

alert(title); // Menu
alert(w); // 100
alert(h); // 200

手写题

Promise

github

new

涉及知识点:原型链、proto、解构赋值、arguments

  • 创建一个对象
  • 从arguments中获取构造函数,并通过构造函数获取原型
  • 将新建的对象通过 __proto__ 链接到原型上
  • 绑定this并执行构造函数
function create() {
// 创建一个空的对象
let obj = {}
// 获得构造函数,shift删除第一个元素并返回该元素
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}

instance of

涉及知识点:原型链查找

function myInstanceOf(obj, cons) {
if (!obj || typeof obj !== 'object') {
return false;
}

let objPrototype = obj.__proto__;
let consPrototype = cons.prototype;

while (true) {
if (objPrototype === null) {
return false;
}

if (objPrototype === consPrototype) {
return true;
}

// 向上查找原型链
objPrototype = objPrototype.__proto__;
}
}

console.log(myInstanceOf({}, Object)); // true

call 和 apply

涉及知识点:

  • 第一个参数不传时,非严格模式下为window
  • 思路:给新对象添加该函数,并在新对象的上下文中执行该函数,执行完后删除即可
Function.prototype.myCall = function (...args) {
const context = args[0] || window;

// this为调用它的函数
console.log(this);
context.excuteFunc = this;

const result = context.excuteFunc(...args.slice(1));
delete context.excuteFunc;

return result;
};

Function.prototype.myApply = function (...args) {
const context = args[0] || window;

// this为调用它的函数
console.log(this);
context.excuteFunc = this;

let result;
if (args[1]) {
result = context.excuteFunc(...args[1]);
} else {
result = context.excuteFunc();
}
delete context.excuteFunc;

return result;
};

const foo = {
name: 'alan',
};

function sayName(...args) {
console.log(this.name, args);
}

sayName.myCall(foo, 'args1', 'args2');
sayName.myApply(foo, ['args1', 'args2']);

bind

涉及知识:

  • 函数柯里化

返回一个函数,可调用,可当作构造函数 new

Function.prototype.myBind = function (thisArg, ...restArgs) {
if (typeof this !== 'function') {
throw new TypeError('not a function');
}

const binder = this;

return function F() {
console.log(this);
// 处理new的情况
if (this instanceof F) {
return new binder(...restArgs, ...arguments);
}

// 函数柯里化处理
return binder.apply(thisArg, restArgs.concat(...arguments));
};
};

const a = {
outer: 'outer',
};

function say(name, age) {
this.name = name;
this.age = age;
console.log(this.outer, name, age);
}

const curry = say.myBind(a, 'alan');

const c = new curry(23);
console.log(c);

// outer alan 21
curry(21);

柯里化

function curry(fn) {
return function curr(...args) {
// fn.length是fn的形参个数
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...args2) => curr.apply(this, args.concat(args2));
};
}

function add(a, b, c) {
return a + b + c;
}

const curried = curry(add);

console.log(curried(1, 2, 3), curried(1)(3)(3), curried(1, 1)(1));
  • compose

类型判断

function isObject(x) {
return Object.prototype.toString.call(x) === '[object Object]';
}
function isArray(x) {
return Object.prototype.toString.call(x) === '[object Array]';
}
function isDate(x) {
return Object.prototype.toString.call(x) === '[object Date]';
}

垃圾回收

  • 种类
    • 标记清除(变量离开上下文、作用域,会被加上标记)
    • 引用计数(记录每个值得引用次数,存在循环引用问题,需手动解除->null)
  • 避免创建全局变量,导致变量无法被回收
  • 闭包造成的内存泄漏

var 和 const/let

  • var
    • 函数作用域
    • 非严格模式下,省略 var 声明变量时,变量会变成全局变量
    • 变量会自动提升到函数作用域顶端
    • 可以重复声明
    • 全局作用域中声明会成为全局对象(window)的属性
  • const/let
    • 块级作用域
    • 不能重复声明
    • 暂存性死区:在声明之前的执行瞬间
    • 全局作用域中声明不会成为全局对象(window)的属性

TODO

  • 文件上传/大文件上传/断点续传(根据文件名字生成hash(缺陷文件名修改了就失效了),可以根据文件内容(spark-md5)来生成hash值)

看题写答案

题目一

https://segmentfault.com/a/1190000008475665

var a = {n: 1};

var b = a;

a.x = a = {n: 2};

console.log(a.x);
console.log(b.x);
console.log(b.x === a);

undefined {n:2} true

题目二

const arr1 = ['A1', 'A2', 'B1', 'B2'];
const arr2 = ['A', 'B'];

const c = [...arr1, ...arr2].sort((a,b) => a.charCodeAt(0) - b.charCodeAt(0) || a.length - b.length || a.charCodeAt(1) - b.charCodeAt(1));


// ['A', 'A1', 'A2', 'B', 'B1', 'B2']

包管理工具

  • npm1/npm2
  • npm3/yarn
  • pnpm

npm1/2 采用了嵌套树的结构,大量的包会被重复安装。

npm3/yarn 将依赖拍平后(hoist)放在了根目录下。但是存在安全性的问题,由于依赖被拍平造成的扁平化结构,会导致能够使用到未声明的其他包。 例如:A 依赖 B、B 依赖 C,拍平后 A 也能使用到 C (幽灵包)。不确定性,例如 A 依赖于 C1.0、B 依赖于 C2.0,导致 A 中 C 和 B 中的 C 都有可能被提升到根节点上,而这取决于 A 和 B 在 package.json 中的顺序。

pnpm

  • 速度快
  • 通过 hard link 不同的项目也可以复用相同的包
  • 支持 monorepo
  • .pnpm store hardlink

sass

  • 嵌套写法

  • 变量

    $name: alan;

    .#{$name} -> .alan
  • @import 模块

  • 函数 @function

  • @if @for @each @while

  • mixin

    @mixin button-base($width) {
    width: $width;
    }

    .xxx {
    @include button-base(12)
    }

性能

  • 接口缓存(redis/localStorage) 简单的例子
  • serverDNS 缓存
  • 借用工具 pageSpeed lighthouse
  • SSR
  • 懒加载(代码分割)
  • 图片
    • 图片懒加载 原生/getBoundingClientRect/IntersectionObserver
    • 渐进式图片
    • 响应式图片(picture)
    • webp
  • DNS预解析,使用 <link rel="dns-prefetch" href="https://fonts.googleapis.com/"> 预解析第三方网站
  • 性能监控
    • 工具:web-vitals
    • 参数:FCP、LCP、FID、CLS
  • 减少 HTTP 请求
  • 使用 HTTP2
  • 静态资源使用 CDN、DNS 预解析
  • 使用字体图标
  • request header 使用 Accept-Encoding: gzip
  • 事件委托
  • 减少css重绘回流
  • HTTP 缓存 304(协商缓存,内容没有改变)
  • 可以在 documentFragment 上进行操作
  • web-worker 独立于主线程工作
  • css 放头部,js 放底部
  • 降低 css 选择器复杂度
  • requestAnimationFrame 实现动画,减少丢帧

SEO相关

  • html 标签语义化
  • 减少不必要的元素、减少嵌套、降低css选择器复杂性
  • img alt
  • TDK(react-helmet)
    • title
    • description
    • keywords
  • 结构化数据-application/ld+json 资料
  • rel='nofollow' 爬虫不爬取该链接 <a href="xxx" rel="nofollow">
  • <link rel="canonical" href="https://alanwang.site" > 防止爬虫爬取 https://alanwang.site?name='alan'
  • h1 和 h2

ESM 和 CJS

  • ESM
    • 编译时加载、静态加载
    • 值是只读的,但是对象还是可以改写
  • CJS
    • 运行时加载,同步加载
    • 值的拷贝

滚动Tab

  • mount 时获取所有 item 高度保存到 this 的某个变量上 locationInfo
  • 监听 scroll 事件,根据对比高度高亮相应的 item
  • 由于屏幕大小改变或其他原因导致的位置改变时,重新计算所有 item 的高度

错误捕获

  • window.onerror 捕获运行时错误、语法错误
  • 资源加载失败时,如 img scripterror 不会向上冒泡,所以 window.onerror 无法捕获,可以使用 window.addEventListener('error', function(event) {}) 来捕获
  • window.addEventListener("unhandledrejection", event => {}); 来捕获 Promisereject 错误

资料文章

  • 通过 Set-Cookie 设置响应头告诉浏览器下次请求携带 cookie
  • Secure 属性和 HttpOnly 属性可以限制访问 cookie

interface 和 type 的不同

文档

  • type 不能声明合并,interface 可以

    interface Window {
    title: string
    }

    interface Window {
    ts: TypeScriptAPI
    }
  • interface 只能用来声明对象的结构,不能重命名原始类型

React

合成事件

https://juejin.cn/post/6844903988794671117

代码可维护性

  • 可分析性/可读性
    • 快速定位线上问题
    • code review
    • lint
    • source-map 定位
  • 可改变性/可拓展性
  • 稳定性
    • 避免修改代码造成bug
    • 核心业务代码覆盖率
  • 减少人为因素,加强工具干预

class 和 hooks

hooks
  • 设计初衷
    • class 逻辑难以复用,class 需要使用 HOC(而HOC 可能会造成嵌套过多的问题),例子:渲染劫持(根据权限渲染 HOC)
    • 复用组件内部的逻辑还可以通过 render props 实现
    • class 逻辑分散,比如 subscribe 和 unsubscribe 分散在 componentDidMountcomponentWillUnmount
    • 生命周期函数与业务逻辑耦合,通过 hook 可以将业务逻辑封装到自定义 hook 中,例子:将用户操作的相关逻辑封装到 hook 中
    • class 中 this 问题
  • 通过 eslint-plugin-react-hooks 防范错误使用 hooks
  • 在顶层使用 hook,不要在条件语句中使用
  • 解决的问题
    • 函数组件没有状态(state)的问题
  • 函数更易于单元测试
  • 缺点:只能在顶层使用,不能在条件语句中使用

useEffect 和 useLayoutEffect

  • 共同点
    • 使用方式一致
    • 都用于处理副作用
  • 不同点
    • 使用场景
      • 大多数场景使用 useEffect
      • 当代码引起页面闪烁是使用 useLayoutEffect,即有引起 DOM 样式更新的场景使用
    • useEffect 异步调用
    • useLayoutEffect DOM 更新后同步调用

React Router

  • 路由
    • 前端控制(监听url变化,渲染不同组件)
      • hash
      • pushState
    • 后端控制
  • history 原理
    • pushState
    • replaceState
    • popstate (当历史条目更改时触发,如history.back()/history.forward或者用户点击浏览器前进、回退按钮。pushState/replaceState不会触发)
  • hash 原理
    • window.location.hash 设置和获取
    • hashchange 监听
  • 404 问题 nginx try_fileswebpack historyApiFallback
  • 总的流程:改变路由的两种方式,通过 history.push 和直接改变url
    • 直接改变 url,会触发 popState 事件,会触发 history 下的 setState 方法,产生新的 location 对象,并通过 context 进行传递,匹配目标组件进行渲染。
    • 通过 history.push 底层会使用 pushState 来改变 url,并调用 history 下的 setState 方法

React 生态/库

从前端构建流程进行分类

  • 初始化
    • create-react-app
    • umi
    • storybook
  • 开发过程
    • 路由
      • react-router
    • 测试
      • enzyme
      • react-testing-library
      • jest-dom
    • 基础组件
      • antd
      • material-ui
    • 状态管理
      • react-redux
      • mobx
    • hooks
      • ahook
      • react-use