LTN做题
LTN ①③ - ①⑥记录
LTN①⑥ 错题重做
错题 ①⑥ 9继续错题/28错题/89题
✅作业58:React和Vue是怎么描述UI的 ✅作业62:fiber架构是什么?优点和实现方式? ✅作业64:Vue渲染怎么实现的? ✅作业67:React 怎么做的性能优化? ✅作业68:React hooks 的原理是什么?useEffect useState模拟实现 ✅作业87:Nextjs 中预渲染是什么?有哪些模式?写一个动态路由的page ✅作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接 ❌作业94:为什么接口查询之后,一般都要使用 .json() 来转化一次数据后再返回? ❌作业95:唯一索引什么作用?Sequelize 中 define modal 的时候,怎么创建组合字段唯一索引?SQL怎么写?updateOnDuplicate 是什么作用?怎么配合唯一索引。 define 中的第三个参数中常见配置 ❌作业 96:Sequelize 中 Date 的怪问题 - POST 存储 DATE 类型,GET 传参数2025-01-27 STRING,无法直接查询,有那些性能高的查询方法?索引是什么,为什么性能高? ❌作业97:Sequelize 中查找某条数据,如果存在就更新;不存在就创建一条 ❌作业98:sequelize 怎么写关联表的查询,指定关联的 column 是date ❌2020.07 对象分类、Promise按序执行、实现map ✅2024.11 第十七章 事件 ✅2021-07 常见Webpack问题 ✅作业45:原生+canvas 显示一张png格式的图片 ✅2024.10 第十四章-第十五章 DOM、DOM扩展 ✅2024.12 模拟实现instanceof ✅2019.07 h5新标签和语义化,块/行内元素 ✅作业2:支持迭代器的原生语言特性有哪些? ✅2019.07 为什么要清除浮动?怎么清除浮动? ❌作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用 ✅作业4:什么是生成器?有什么作用? ✅2021.07 防抖节流 ✅2024.12 模拟实现jsonp ❌作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询 ❌2023.07 clickhouse、mysql、mongodb异同 ✅作业70:Vue 和 React 的 Diff 算法比较
// 作业70:Vue 和 React 的 Diff 算法比较
- 第八遍 2025.2.18 -
Vue 双端指针算法,没找到 ⭐️ // 组件级别的更新
- 同标签+同key ,先查询索引是否一致
- 索引不一致,列表:移动修改使用
- 跳过静态节点
react 分层级比较 + 同类型有限比较 ⭐️ // 同层级优先比较 + 节点属性级别的更新
- 不同标签,直接重新渲染
- 同标签,修改属性使用
- 列表:同标签+同key
- 第七遍 2025.2.16 -
❌ // 1、Vue 少写了:静态节点跳过;双端指针比较算法找不到时,要判断 同类型+同key 节点是否同索引
- 第六遍 2025.1.31 -
⭐️ // React 是精确到节点属性级别的 Diff 计算,适合大型动态复杂场景;Vue 基于组件级别的 Diff 计算
- 第五遍 2025.1.25 -
❌ // 1、Vue 少写了:静态节点跳过 ;是基于组件级别的更新算法
❌ // 2、React 少写了:是基于单个节点属性级别的,适合大型动态复杂场景
- 第四遍 2025.1.24 -
❌ // 1、Vue:找不到匹配的点后,再找到同类型+同 key的节点,进行索引比较,一致则复用,不一致则移动到新位置修改使用
- 第三遍 2025.1.18 -
❌ // 1、Vue:静态节点跳过比较 - 不要遗漏了,是个很重要的性能优化
❌ // 2、Vue:双端指针比较后,还有找不到的 -> 找同类型+同key来比较
- 第二遍 2025.1.13 -
⭐️ // React:「列表组件,根据 key 确定新老节点关系」
❌ // 1、Vue:双端比较算法是直接找匹配节点,不进行 标签修改
❌ // 2、Vue:key 是第三层比较逻辑,第二层比较逻辑是「索引」 - 若索引没有变化,则不更新当前节点;如果索引有变化,判断是否「同类型 + 同key」,移动到新位置
❌ // 3、Vue:性能优化,编译时标记的静态节点跳过 - 虽然老生常谈,但是这其实是一大优化
- 第一遍 -
❌ // 1、简单描述下 React 的 Diff 算法是「精确到单个节点的属性级别」,适合「大型复杂场景」,例如大量动态组件和频繁更新的场景
❌ // 2、React:这里可以标题记忆 「① 分层比较 + 同类型比较优先策略」
❌ // 3、React:这里主要是指:「列表组件」中,根据 key 确定新老树子节点的关系
❌ // 4、React:少写了「② DFS 深度优先遍历子节点」
❌ // 5、描述:「更新基于组件级别」
❌ // 6、双指针其实很不明确,「双端比较算法-双向指针」
❌ // 7、是会复用,但是场景是「双端比较法没有找到足够匹配的节点,就根据节点的索引进行比较」
// 8、检索「同类型+同key 的节点」,「索引位置」没有变化,就认为这个节点不需要更新;若有变化,就「移动到新位置」,「复用旧列表」
// 2023.07 clickhouse、mysql、mongodb异同
- 第五遍 2025.2.18 -
mysql 关联数据库;读写性能都优;但高并发时,会有数据不一致;适合小型应用 ❌ // 1、少写了:支持事务操作
mongodb 文档型数据库;读性能优,写性能差;在分布式操作时,会有数据不一致;适合非结构式/半结构式数据 HTML、XML等
clickhouse 列式数据库;读写性能都好;支持异步/分布式操作;适合高吞吐、低延迟场景 ❌ // 2、少写了:不支持事务
- 第四遍 2025.2.16 -
❌ // 1、mysql 少写了:支持事务操作;出现数据一致性问题:高并发时
❌ // 2、mongodb 出现数据一致性问题:在分布式场景时
❌ // 3、少写了:列式数据库;少写了:不支持事务,支持分布式查询;少写了适合高吞吐、低延迟的场景
- 第三遍 2025.1.30 -
⭐️ // clickhouse:高吞吐、低延迟,支持复制、分布查询 + mongodb:分布式场景下一致性问题
- 第二遍 2025.1.25 -
❌ // 1、mongodb:在分布式场景下可能出现一致性问题 ;适合半结构化/非结构化数据
- 第一遍 2025.1.22 -
❌ // 1、mysql 是关系型数据库-适合事务处理场景;MongoDB 是文档型数据库-半结构化/非结构化数据;ClickHouse 是列式数据库-适合高吞吐、低延迟的分析场景
❌ // 2、mysql:一致性描述错误:高并发时可能出现一致性问题
❌ // 3、mongdb:性能描述错误 - 写的性能弱,读的性能好;适合数据少写了:半结构化数据,类似 XML HTML JSON 等,以及非结构化数据;一致性描述:在分布式场景下可能出现数据一致性问题
❌ // 4、clickhouse:少写了:读写性能都高,场景适合少了归类:高吞吐、低延迟场景,例如日志;操作少写了:支持复制和分布查询
// 作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询
- 第五遍 2025.2.18 -
INNER JOIN AB表的交集,内联查询
LEFT OUTER JOIN A 表的所有满足条件行 + 与A表条件行匹配的B表数据
RIGHT OUTER JOIN B 表的所有满足条件行 + 与B表条件行匹配的A表数据
UNION 拼接两次查询
封装 CREATE VIEW my ON SELECT * FROM table ❌ // 1、封装:关键词不是 ON 是 AS: CREATE VIEW my AS SELECT * FROM table
- 第四遍 2025.2.16 -
❌ // 1、INNER JOIN 不是合集,是交集,表达错误 - AB都满足的行,内连接
⭐️ // UNION 拼接
CREATE VIEW my ON SELECT * FROM table ❌ // 2、封装:关键词不是 ON 是 AS
- 第三遍 2025.1.29 -
- 第二遍 2025.1.26 -
❌ // 1、少了一个 AS:封装 CREATE VIEW ... AS
- 第一遍 2025.1.22 -
❌ // 1、create View MY AS 关键词:Create View ... AS
// 2024.12 模拟实现jsonp
- 第四遍 2025.2.18 -
function jsonp(url, params, callback){
let suffix = Object.keys(params).map(it => `${it}=${params[it]}`).join('&');
let totalUrl = url + url.includes('?') ? '&' : '?' + suffix + `&callback=${callback}`;
let dom = document.createElement('script');
window[callback] = function(data){
callback(data);
document.body.removeChild(dom);
}
dom.src = totalUrl;
document.body.appendChild(dom);
}
- 第三遍 2025.2.16 -
const suffix = Object.keys(params).reduce((pre, cur) => pre + `${cur}=${params[cur]}`, '');
❌ // 1、计算 url 时少了 '&' -> 使用 map + join 更简洁:Object.keys(params).map(it => `${it}=${params[it]}`).join('&') -> 使用 entries 也可以 Object.entries(params).map(([key, vlaue]) => `${key}=${value}`).join('&')
- 第二遍 2025.1.21 -
- 第一遍 -
// 2021.07 防抖节流
- 第四遍 2025.2.18 -
function debounce(fn, delay){
let timeId;
return (...args) => {
if(timeId) {
clearTimeout(timeId);
}
timeId = setTimeout(() => {
fn(...args)
}, delay)
}
}
function throttle(fn, delay) {
let flag = false;
return (...args) => {
if(flag) {
return
}
flag = true;
fn(...args);
setTimeout(() => {
flag = false;
}, delay)
}
}
- 第三遍 2025.2.16 -
❌ // 1、debounce 遗漏赋值:timeId = setTimeout()
⭐️ // throttle:间隔多少秒执行一次
❌ // 2、throttle:判断条件应该是 if(flag){return}:flag 为 true 时,证明这段时间内已经执行了
- 第二遍 2025.1.21 -
- 第一遍 -
// 作业4:什么是生成器?有什么作用?
- 第三遍 2025.2.18 -
* 声明,恢复、暂停代码,yield + next 实现,next 返回{done, value},实现遍历可迭代对象
作用:实现协程,自定义可迭代对象
- 第二遍 2025.2.15 -
❌ // 1、少写了:暂停、恢复代码执行,* 声明
- 第一遍 2025.1.30 -
// 作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用
- 第五遍 2025.2.18 -
redux 将 react 的全局状态存储在 store 对象树中
reducer 使用 oldState + action 生成 新State,只能通过 store 的 dispatch 来更新状态 state
RTK redux ToolKit 官方推荐工具包
import { createStore } from 'redux'
const reducer = (oldState = {v:1}, action) => {
if(action.s === 'new') { return {v: oldState.v + 1} }
}
const store = createStore(reducer); (dispatch+subscribe+getValue) ❌ // getState
store.dispatch({s: 'new'});
store.subscribe(() => {console.log(store.getValue())}) ❌ // 1、 函数名称错了:getState
- 第四遍 2025.2.15 -
❌ // 1、注意 store 是对象树:Redux 将全局状态保存在 store 对象树中
⭐️ // reducer:返回新的 state 而不是改变
❌ // 2、reducer 参数顺序是 oldState,action
❌ // 3、 函数名称错了:getState,通过 subscribe 更新 UI
- 第三遍 2025.1.29 -
- 第二遍 2025.1.26 -
- 第一遍 2025.1.22 -
❌ // dispatch getState 1、少一个 subscribe
❌ // 2、通过subscribe更新UI来响应 state - store.subcribe(()=>{console.log(store.getState())})
// 2019.07 为什么要清除浮动?怎么清除浮动?
- 第五遍 2025.2.18 -
原因:块级元素把浮动元素当做不存在,行内元素环绕浮动元素布局;父盒子中子盒子浮动,会导致高度塌陷,无法撑开背景
解决:①父内最后一个元素后添加 box .box{clear:both} ②父.father::after{ content:''; display: block; clear: both; } ③BFC .father { overflow: hidden } ④父 设置高度
- 第四遍 2025.2.15 -
❌ // 1、父元素+伪元素 应该是 display: block,否则撑不开父级全部宽度
⭐️ // BFC 的盒子计算高度时包含浮动元素
- 第三遍 1.28 -
- 第二遍 1.25 -
❌ // 1、高度塌陷的描述:背景无法撑开 + padding、border 无法正常展示
❌ // 2、给父元素添加高度:宽度不对,高度+padding+border
- 第一遍 1.20 -
❌ // 1、写错了:是给父元素添加BFC + 父盒子设置高度(宽度不行
// 作业2:支持迭代器的原生语言特性有哪些?
- 第五遍 2025.2.18 -
Array.from、for...of、...扩展运算符、数组结构、new Map、new Set、yield *、Promise.all/race
- 第四遍 2025.2.15 -
❌ // 1、少写了 yield * 只能在生成器中使用
- 第三遍 1.28 -
- 第二遍 1.25 -
- 第一遍 1.20 -
❌ // 1、少写了:yield * 操作符 - 只能在生成器中使用;不是对象解构,是「数组解构」
// 2019.07 h5新标签和语义化,块/行内元素
- 第三遍 2025.2.18 -
1、SEO 优化 2、标签样式优化
h5新标签:header footer article address nav aside time filedSet/legend section hgroup datalist ⭐️ // detail
input 新属性 required multiple autocomplete autofocus placeholder accesskey
input type 新属性 tel url email range search number date month time datetime week
行内块 img td input
行内 span br select textarea button ⭐️ // i u a strong em b label
块 p div table caption li dd dt dl ul h1-h6 section ⭐️ // td ol tr address hr
- 第二遍 2025.2.15 -
1、SEO 优化 2、标签样式不一样
❌ // 1、新标签少了:datalist detail time hgroup
❌ // 2、input 属性少了:accesskey
❌ // 3、input type 属性少了:url range search date time datetime week month
❌ // 4、br - 错了,不是块元素,而是行内元素 + 块元素少了 h1-h6 td ol table-caption tr address hr
❌ // 5、行内元素少了:br i u a strong em button b label
- 第一遍 2025.1.9 -
// 2024.12 模拟实现instanceof
- 第三遍 2025.2.18 -
function fakeInstanceOf(target, Ctor){
let proto = target.__proto__; ⭐️ // Object.getPrototype(target)
while(proto) {
if(proto === Ctor.protorype){
return true
}
proto = proto.__proto__;
}
return false
}
- 第二遍 2025.2.15 -
❌ // 1、获取 target 的原型链->应该是 target.__proto__ 或者 Object.getPrototype(target)
❌ // 2、判断原型链上是否和构造函数的原型对象相等,而不是和构造函数相等proto===Ctor.prototype
❌ // 3、不停往上找原型链 -> 通过 __proto__或者getPrototype找,而不是 prototype
- 第一遍 2025.1.9 -
// 作业45:原生+canvas 显示一张png格式的图片
- 第三遍 2025.2.18 -
<canvas id='drawing' width=200 height=200 ></canvas>
const drawing = document.getElementbyId('drawing');
if(drawing.getContext){
const imgData = drawing.toDataUrl('image/png');
const dom = document.createElement('img');
dom.src = imgData;
document.body.appenChild(dom);
}
- 第二遍 2025.2.14 -
❌ // 1、需要判断是否存在 context 作为大前提:if(drawing.getContext){}
❌ // 2、image 标签添加 url 的属性是 src:image.src = imageData;
- 第一遍 2025.1.9 -
// 2021-07 常见Webpack问题
/** 1、webpack的几个核心概念理解:Chunk、Module、plugin
2、常见配置项:entry、output、module、resolve等
自定义 loader 怎么配置?
3、Code Splitting 和 Tree Shaking 的区别?懒加载怎么实现?
4、html-webpack-plugin 作用?
5、sourceMap不同环境的区别?怎么开启配置?
6、热更新怎么实现?
7、webpack原理/执行过程?
开发插件的桥梁?*/
- 第三遍 2025.2.18 -
1、Chunk 由多个模块组成、Module 万物皆模块、plugin 插件,在构建时,指定时机注入代码
2、entry:'main.js'/{main:'main.js', sub:'sub.js'}
output:{fileName: '[name]-[hash].js', path: path.resolve(_dirname, 'dist')}
module 模块解析规则 {rules: [{ test: '/\.js/', use: ['my-loader']}, {test: '/\.css/', use: ['style-loader', 'css-loader', 'less-loader']}]}
resolve 模块查找规则 {
alias: {'@c': '/src/component'},
extensions: ['jsx', 'js', 'json'],
modules: ['node_modules', path.resolve(_dirname, 'src/myL')]
}
自定义 loader:①module.rules use 时直接使用具体地址下的loader use:[path.resolve(_dirname, 'src/myL/my-loader.js')]
②resolve.modules 配置时添加指定路径,如上 + module.rules use 时直接使用 loader 名,如上
3、Code Splitting 按需加载,根据 entry 配置分组,在访问页面时,加载需要的模块,提高渲染效率,通过 splitChunk 插件开启配置
Tree Shaking 按需打包,减少包体积,根据 entry 入口打包关联代码,不使用的代码不打包,webpack 默认开启
懒加载 使用 code splitting 开启,在首页初始渲染时,只加载需要渲染的模块
4、html-webpack-plugin 将 webpack 打包出来的 js 和 css 插入 html
5、sourceMap devtool: cheap-eval-source-map; 本地
devtool: hidden-source-map; 生产
6、热更新:①启动 webpack 时,dev-server 在内存中启动服务,和浏览器 websocket 实时通讯 ⭐️ // 同时在服务中注入了 HMR 热更新代码
②有代码修改时,通过监听 Compiler 的 done 事件,得到 mainfest.json 配置文件 和 chunk.js 模块文件,通过 websocket 推送给浏览器
③浏览器从中获取到 ChunkId 等信息,通过 ajax 获取 chunk.js
④替换模块代码 window.webpackUpdate -> hotApply 热更新替换指定模块,而不需要刷新整个页面 ⭐️ // window.webpackHotUpdate
⑤执行模块代码,进行渲染 webpack.__require__
7、webpack原理 ①整合参数:将 shell 指令传入的配置和配置文件整合成配置参数 ⭐️ // 初始化配置参数
②通过配置实例化 webpack Compiler,引入所有插件,执行.run开始构建
③根据 entry 配置,依次遍历所有文件,按序执行相关的所有依赖
④得到转换后的文件和文件关系,将文件输出到下载列表 ⭐️ // 以 entry 为分组依据打包为多个 chunk
⑤根据 output 配置,输出到指定路径
开发插件的桥梁: Compiler webpack 唯一实例,代表 webpack 的生命周期;Compilication 更新一次,创建一次构建的实例
- 第二遍 2025.2.14 -
❌ // 1、entry: 不识别 path 这个属性,entry: 'main.js'/entry: { main: 'src/main.js', sub: 'src/sub.js' }
❌ // 2、output: { filename: '[name]-[hash].js', path: path.resolve(_dirname, 'dist') }
❌ // 3、resolve 的作用不是解析,是「依赖查找配置规则」+ module 的作用是:依赖解析、转换配置
❌ // 4、resolve.modules :查找 module 的时候,需要传递具体地址 modules: [path.resolve(_dirname, 'src/myC'), 'node_modules']
❌ // 5、resolve.extensions: 少写了常见的对扩展的处理 extensions:['jsx', 'js', 'json']
❌ // 6、开启 Code Splitting 配置的方式:splitChunk 插件配置
⭐️ // 懒加载:首次渲染
❌ // 7、devtool:cheap-eval-source-map; (不提供列信息)方便代码调试
⭐️ // 启动 webpack 启动的内存中的服务:dev-server
❌ // 8、 监听到 compiler 的 done 事件 得到的文件是 chunk.js 文件,浏览器从 mainfest.json 文件中得到了 ChunkId,获取 chunk.js
❌ // 9、window.webpackHotUpdate -> hotApply 热更新函数,替换原有模块代码
⭐️ // webpack 原理:step1 初始化配置参数
⭐️ // webpack 原理:step2 执行 Compiler.run 开始构建
- 第一遍 2024.12.26 -
⭐️ // 1、coding splitting:开启方式是 splitChunk 插件配置 - 加快响应速度
// 2020.07 对象分类、Promise按序执行、实现map
- 第五遍 2025.2.18 -
const a = [{name:'1', age:1}, {name:'1', age:2}, {name: '2', age:1}]
function classify(arr, property) {
return arr.reduce((pre, cur) => {
const key = cur[property];
if(!pre[key]){
pre[key] = [];
}
pre[key].push(cur);
return pre
}, []) ❌ // 1、reduce 的初始值应该是 {} 而不是 []
}
function listP(arr, init) {
return arr.reduce((pre, cur) => pre.then(cur), Promise.resolve(init))
}
❌ // 2、map 是 Array 上的高阶函数,之前写错成 Function(想成bind了),重写了
Array.prototype.fakeMap = function(callback, thisArg) {
return this.reduce((pre, cur, index, array) => {
pre[index] = callback.call(thisArg, cur, index, array);
return pre
}, [])
}
- 第四遍 2025.2.14 -
❌ // 1、again,map 接收两个参数,一个 callback 函数,另一个是 this 指向
/** Array.prototype.fakeMap = (callback, thisArg) => {
return this.reduce((pre, cur, index, array) => {
pre[index] = callback.call(thisArg, cur, index, array);
return pre
}, [])
}*/
- 第三遍 2025.1.24 -
⭐️ // classify:少写了 reduce 的初始值 - 一个空对象 {}
- 第二遍 2025.1.19 -
❌ // 1、map 函数接收两个参数,第一个是 callback,第二个是 thisArg - 用来指定 this -> 配合 cb.call(thisArg, cur, index, array);
⭐️ // reduce filter map forEach 会跳过 empty 的数组项( map 和 reduce 都跳过)
- 第一遍 -
❌ // 1、注意 map 除了 callback,第二个参数是可选的的 thisArg,用于指定前面函数的 this
// 作业98:sequelize 怎么写关联表的查询,指定关联的 column 是date
- 第二遍 2025.2.17 -
A.hasMany(B, {
foreignKey: 'B_id', // 多的id
sourceKey: 'A_id', // 一的id
})
B.belongsTo(A, {
foreignKey: 'B_id', // 多的id
targetKey: 'A_id' // 一的id
})
❌ // 1、await A.findAll({ include: [{ model:B }] })
- 第一遍 2025.2.13 -
A.hasMany(B, {
foreignKey: B_id,
sourceKey: A_id
})
B.belong(A, { ❌ // 1、belongsTo
foreignKey: B_id,
targetKey: A_id
})
❌ // 2、await A.findAll({ include: [{ model: B }] })
// 作业97:Sequelize 中查找某条数据,如果存在就更新;不存在就创建一条
- 第二遍 2025.2.17 -
const [issue, created] = await table.findOrCreate({
where,
default: data ❌ // 1、数据源属性 defaults
})
if(!created) {
issue.save(); ❌ // 2、先set数据,再保存 issue.set(data); await issue.save();
}
- 第一遍 2025.2.13 -
await Modal.findOrCreate({ ❌ // 1、需要获取函数的返回值,获取是否为新创建 const [issue, created]
where
❌ // 2、数据源没有传入 defaults: data
})
❌ /** 3、如果已经存在,更新描述
if(!created) {
issue.set(data);
await issue.save();
} */
// 作业 96:Sequelize 中 Date 的怪问题 - POST 存储 DATE 类型,GET 传参数2025-01-27 STRING,无法直接查询,有那些性能高的查询方法?索引是什么,为什么性能高?
- 第二遍 2025.2.17 -
索引是数据库表为了快速搜索,单独为表创建的树结构
搜索时,直接通过索引查询数据,而不是遍历整个表查询
方法一
const start = dayjs(new Date(time)).startOf('day').toDate();
const end = dayjs(new Date(time)).endOf('day').toDate();
table.findAll({
where: { date:{
[op.between]: [start, end]
}}})
方法二
table.findAll({
where: { date: { sequelize.fn(DATE, sequelize.cols('date'), '=', time)}}})
❌ /** 1、const options = {
where: Sequelize.where(
Sequelize.fn('DATE', Sequelize.col('date')),
'=',
time
)}
或者函数是:Sequelize.fn('DATE_FORMAT', Sequelize.col('date'), '%Y-%M-%D')
*/
- 第一遍 2025.2.13 -
❌ // 1、方法一:需要先将传入的 string 转换成 date:const searchTime = new Date(string)
❌ // 2、还需要处理 .toDate(),因为 date 字段存储的也是 Date 类型
❌ /** 3、方法二:转换成 Date 再匹配
const options = {
where: Sequelize.where(
Sequelize.fn('DATE', Sequelize.col('date')),
// 或 Sequelize.fn('DATE_FORMAT', Sequelize.col('date'), '%Y-%M-%D')
'=',
string
)
}*/
❌ // 4、方法一性能高,可以有效利用索引优化 - 索引本质是为了表单独存储的数据结构,可以快速定位到查询条件的数据位置,从而减少IO操作,提高性能
// 作业95:唯一索引什么作用?Sequelize 中 define modal 的时候,怎么创建组合字段唯一索引?SQL怎么写?updateOnDuplicate 是什么作用?怎么配合唯一索引。 define 中的第三个参数中常见配置
- 第二遍 2025.2.17 -
唯一索引:保证唯一索引的组合唯一,不重复
se.define(database, {
name: DataTypes.STRING
},{
underScore: true,
timeStamp: false,
indexes: [{
unique: true,
fields: ['date', 'sort']
}]
})
SQL: CREATE UNIQUE KEY 'date','sort'
❌ // 1、CREATE UNIQUE INDEX idx_date_sort ON `table` (`date`, `sort`)
ALTER TABLE table
ADD UNIQUE KEY 'date','sort'
❌ // 2、ADD UNIQUE KEY `table` (`date`, `sort`)
updateOnDuplicate: buldCreate(data, { ⭐️ // 索引冲突时,执行更新,而不是抛出错误 - 被指定的字段才会被更新,未指定的字段保持不变
updateOnDuplicate: ['name', 'age']
})
se.define(database, {
name: DataTypes.STRING
},{
underScore: true,
timeStamp: false, ❌ // 3、timestamps: false, tableName: 'users'
indexes:[{}]
})
- 第一遍 2025.2.13 -
❌ // 1、唯一索引的作用:确保索引字段的组合在表中是唯一的,不允许重复
❌ // 2、hasMany/belongsTo 这是关联表的写法,唯一索引不是这种写法 - indexes + unique + fields
/** sequelize.define(database, {...定义...}, {
indexes: [
{
unique: true,
fileds: ['date', 'day_sort']
}
]
}) */
❌ // 3、CREATE UNIQUE INDEX idx_date_day_sort ON 'daily' ('date', 'day_sort')
❌ // 4、updateOnDuplicate 用于保证在批量插入数据时,若冲突,执行更新而不是抛出错误 - 在 updateOnDuplicate 中配置的字段会被更新,未指定的保持不变
/** await Daily.bulkCreate(data, {
updateOnDuplicate: ['value', 'weekday']
})*/
❌ // 5、属性名称错了 underscored: true - 将驼峰映射为下划线 + tableName:'daily' 指定数据库中表名 + timestamps:false 禁用时间戳字段
// 作业94:为什么接口查询之后,一般都要使用 .json() 来转化一次数据后再返回?
- 第二遍 2025.2.17 -
NextResponse 包含了响应头、响应体、数据 ❌ // 1、状态码、响应头、响应体
.json() 可以将 NextResponse 转换成 json 数据,再转成 js 对象,方便数据操作 ❌ // 2、await res.json()
- 第一遍 2025.2.12 -
❌ // 1、(Nextjs) 接口返回的是 Response 对象,包含了服务器返回的所有信息 - 状态码、响应头、响应体,其中响应体 默认是只读的 ReadableSream,不能直接使用
❌ // 2、对的,详细是:先解析成 json 格式,然后转成 js 对象 - Response.json() 将响应体解析为 json 格式的数据,并将其转换为 js 对象:await res.json()
// 作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接
- 第六遍 2025.2.17 -
mysql2:为了 node 创建的数据库驱动,可以直连数据库,但是需要写比较多的 SQL 查询
提供了 mysql2/promise,支持 async/await 异步查询,管理多个数据库连接
Sequelize:ORM 将数据库表和行映射为对象模型,比较好的函数编程操作
mysql2
import { createPool } from 'mysql2/promise'
const pool = createPool({ host, user, password, database });
const con = await pool.getConnect();
const [rows] = await con.execute('SELECT * FROM table');
sequelize
import { Sequelize, Modal, DataTypes } from 'sequelize';
const se = new Sequelize(database, user, password, {
host: 'localhost',
dialect: 'mysql2'
});
// 方法1
class Usr extends Modal {}
Usr.init({
name: DataTypes.STRING
}, {
sequelize: se,
modalName: 'Usr'
});
// 方法2
se.define(database, {
name: DataTypes.STRING
}, {
underScore: true,
timeStamp: false,
indexes: [{}]
})
- 第五遍 2025.2.12 -
⭐️ // mysql2:execute
❌ // 1、sequelize:实例化参数错误:new Sequelize(database, user, password, { host: 'localhost', dialect: 'mysql2' })
❌ // 2、sequelize:init 参数错误 init({ name: DataTypes.STRING }, { sequelize: se, modalName: 'User' })
❌ // 3、sequelize:define 第三个参数应该是一些配置,例如 underscore: false ,这里把 init 和 define 的参数 弄混了
- 第四遍 2025.2.9 -
❌ // 2、sequelize:实例化的参数错误 new Sequelize(database, user, password, { host: 'localhost', dialect: 'mysql2'})
❌ // 3、sequelize:init 还需要第二个参数传递 连接实例、模型名称
- 第三遍 2025.1.31 -
⭐️ // mysql2:提供了 mysql2/promise 模块,允许 async/await 管理多个数据库连接;没有内置的对象关系映射ORM
❌ // 1、mysql2:导入依赖包错了 mysql2/promise
❌ // 2、mysql2:createPool的参数 少写了 database
❌ // 3、mysql2:获取连接的函数错了 await pool.getConnect()
❌ // 4、mysql2:需要使用 await const [rows] = await connection.execute('SELECT * FROM table')
⭐️ // ORM 把数据库的表和行映射为对象模型
❌ // 5、sequelize:有两种模型定义方法:一种是 define,另一种 extends Modal + init
/** 方法一 :const User = se.define('table', { id: Datatypes.number}, {
underScore: false;
})
方法二:class User extends Modal {}
User.init({ id: Datatypes.number }, {
sequelize: se, // 传递连接实例
modalName: 'User' // 模型名称
})
最后调用: User.findAll({ attribute: ['id', 'name'] })
*/
- 第二遍 2025.1.26 -
❌ // 1、mysql2:依赖包是 mysql2/promise
❌ // 2、mysql2:创建连接池不需要 await - const pool = createPool({host, user, password, database})
❌ // 3、mysql2:获取连接时需要 await - const connection = await pool.getConnect()
❌ // 4、mysql2:查询数据时需要 await - const [rows] = await connectiom.execute('SQL')
❌ // 5、sequelize:实例化创建连接 const s = new sequelize(database, user, password, { host, dialect })
❌ // 6、sequelize 模型定义的底层 - 先扩展,再init定义
- 第一遍 2025.1.22 -
❌ // 1、mysql2:提供的连接的两个特点 - mysql2/promise 支持 async/await + 管理多个数据库连接池
❌ // 2、sequelize:将数据库的表和行映射为对象模型,直接通过 js 操作
// 作业87:Nextjs 中预渲染是什么?有哪些模式?写一个动态路由的page
- 第六遍 2025.2.17 -
预渲染:服务端渲染SSR-在请求时,服务端直接将数据引入生成HTML,客户端直接渲染,不需要再使用客户端js 静态生成SSG 增量静态生成ISR-构建、运行时,间隔更新时间,后端自动增量式创建新的HTML
SSG
export function Blog({slug})){ return <p>{slug}</p>}
export async function generateParams() {
return [{slug:'first'}, {slug: 'second'}]
}
export const revalidate = 60;
SSR
import { getServerSideProps } from 'next' ⭐️ // from 'next/server'
export function Blog({data})){ return <p>{data}</p>}
export async function getServerSideProps() {
const res = await fetch('xxx');
const data = await res.json();
return {
props: {data}
}
}
- 第五遍 2025.2.12 -
⭐️ // SSR 在请求时,在服务端直接将数据引入,生成的 HTML 直接返回给客户端渲染,不需要客户端js执行
⭐️ // SSG 在客户端构建时,将页面提前渲染完成,使用 generateStateParams 返回动态路由参数列表,使用 revalidate 配置重新验证时间,增量更新
export function generateStateParams() { ❌ // 1、SSG:少写了 async
export function getServerSideProps() { ❌ // 2、SSR:少写了 async
- 第四遍 2025.2.9 -
❌ // 1、名称错误:服务端渲染SSR、静态生成SSG、增量静态生成ISR
❌ // 2、缺少说明一下结果:客户端、服务端生成的结果都是 HTML,得说明一下
❌ // 3、SSR:函数名称、依赖包错误:import { getServerSideProps } from 'next/server'
❌ // 4、SSR:核心是在服务端提前获取数据 -> 传入组件
❌ // 5、SSG:函数名称错误 generateStaticParams
- 第三遍 2025.1.31 -
❌ // 1、记错函数名称,13.x 之前是 getStaticProps,现在已经改为 generateStaticParams,用于生成动态路由的参数列表
❌ // 2、SSR:data.json() 是异步的,异步解析数据,处理 json 数据转换为 js 对象 const res = await data.json();
❌ // 3、SSG:return 的应该是个路由列表 例如 return [{slug: 'first'}, {slug: 'second'}]
❌ // 4、SSG:export const revalidate = 60; // 页面重新验证时间
- 第二遍 2025.1.26 -
❌ // 1、SSR getServerSideProps: fetch 数据需要 await: const data = await fetch('xxx') + 异步解析数据需要 await: const res = await data.json()
- 第一遍 2025.1.22 -
❌ // 1、SSG generateStaticParams 用于生成动态路由的参数列表
❌ // 2、SSG revalidate 用于定义页面重新验证时间 - 页面将重新生成,运行时生成/更新静态页面以增量更新
// 作业68:React hooks 的原理是什么?useEffect useState模拟实现
- 第七遍 2025.2.17 -
闭包+链表,目的:在不使用类组件的情况下,也可以使用 state、状态管理、模拟生命周期
useEffect
const memorized = {};
const cursor = 0;
const render = () => { ReactDOM.render(<App />, document.getElementById('root')) };
function fakeUseEffect(cb, arr){
const lastArr = memorized[cursor];
const hasChanged = lastArr && lastArr.some((it, index) => !Object.is(it, arr[index]));
if(!lastArr || hasChanged) {
memorized[cursor] = arr;
cb();
render();
}
cursor++;
}
useState
function fakeUseState(init){
const current = cursor;
memorized[current] = memorized[current] ?? init;
const useValue = (newV) => {
memorized[current] = newV;
render();
}
cursor++;
return [memorized[current], useValue]
}
- 第六遍 2025.2.12 -
❌ // 1、原理:没有事件委托,主要是 闭包和链表 + 少写了 hooks 的创建目的:在不使用类组件也可以使用 state、状态管理、模拟生命周期
/**
原理:闭包-每次渲染都拿到最新值;
hooks按序存储在 fierNode.memorizedState,更新时按序执行全部 hooks,使用副作用链表收集副作用,在 commit 阶段挂载后执行副作用链表
*/
❌ // 2、ReactDOM.render(<App />, document.getElementById('root'));
❌ // 3、使用 find 得到的是值,应该使用 some + 判断条件不是 Object.is 应该是 !Object.is
❌ // 4、少写了 值 的初始化: memorized[current] = memorized[current] ?? init;
- 第五遍 2025.1.23 -
- 第四遍 2025.1.18 -
❌ // 1、fakeUseEffect:ReactDOM.render(<App />, document.getElementById('root'));
❌ // 2、fakeUseState:cursor++; 忘记写游标自增了
- 第三遍 2025.1.13 -
❌ // 1、这里描述的是 hooks 的执行过程 - 大虐,第一遍/第二遍的时候都记得是用于给函数组件添加功能用的,这一次反而完全没想起来
❌/** 记忆 React hooks原理
先描述【目的】:16.8的新增功能,可以在不编写类组件的情况下「使用 state」及其他特性,「状态管理能力」、「生命周期替代能力」、逻辑复用能力
再描述【实现上述目的的方法】,也就是【原理】:怎么用来「使用 state + 状态管理能力」 - ①主要是利用「闭包」,组件每次渲染时,使用闭包保持其状态;怎么用来「生命周期替代」 - ②使用链表保存一系列hooks,存储在 fiberNode 上,「在 Fiber 树更新时,能从 hook 中计算出最终输出的状态和需要执行的相关副作用」
*/
/** 注意其实要写下 render 函数,因为 useEffect 触发 render 后,对应的游标要置0
*/
❌ // 2、fakeUseEffect:这里应该是判断「不等于」,有不相等的,代表依赖数组变化,需要更新:!Object.is(it, lastArray[index])
❌ // 3、fakeUseEffect:少写了「更新依赖数组」的逻辑:memorized[count] = array;
function fakeUseState(init) {
const currentCount = count;
let value = memorized[currentCount] ?? init;
❌ // 4、使用 value 会导致返回后,value 不会自动更新,和 memorized[currentCount] 失去了联系,本行改为:memorized[currentCount] = memorized[currentCount] ?? init;
const setValue = (newValue) => {
memorized[currentCount] = newValue;
❌ // 5、调用 useValue 必须触发 render:render();
}
count++;
return [value, setValue]
❌ // 4、延续问题4,本行修改为:return [memorized[currentCount], setValue]
}
- 第二遍 -
❌❌ // 1、和第一遍的逻辑是一样的,都是想到了「函数组件」缺失「类组件的生命周期」,但是 hooks 不只是「生命周期替代能力」,还有「状态管理能力」、「逻辑复用能力」,函数组件也不止缺失「生命周期」,还包括 state(没有 hooks 的函数组件主要接收 props 来展示 UI)
❌❌ // 2、质疑是对的: hooks 在组件更新时是「全部按序重新执行一遍」,副作用链表是「在 hooks 执行过程中,如果 useEffect 依赖有更新」,就更新到副作用链表
// 和第一遍一样想到链表:第一遍想到存储位置,第二遍描述收集、执行过程 - 而不是原理
// 原理是从「链表+闭包」描述的:利用闭包保存状态,使用链表保存一系列的 hooks 与 Fiber关联。在 Fiber 更新时,就能从 hooks 中计算出最终输出的状态 和执行相关的副作用。
❌ // 5、fakeUseEffect:cursor应该使用其他变量保存,和下文中 setValue 形成了闭包 - 在异步情况下,cursor 可能会变化,导致索引错误 const currentCursor = cursor; 这里使用 currentCursor 进行索引
❌ // 6、更新使用闭包保存的 memorizedState[currentCursor] = newValue;
❌ // 7、fakeUseState:这里还要触发一次 render() 函数执行
- 第一遍 -
❌ // 1、描述得不准确:hooks 可以在不编写类组件的情况下使用 state 及React其他特性
❌ // 2、hooks 链表说得不错,链表是为了在更新时,以稳定的顺序执行,输出最终的状态和相关副作用
❌ // 3、还有一个 hooks 利用闭包保存状态,在组件每次渲染时通过闭包保持其状态
⭐️ // 注意 hooks:1、不要在循环、条件、嵌套、try/catch/finally 中使用 - 无法保证顺序的稳定
⭐️ // 2、只有在 函数组件和hooks 中能够调用 hooks
❌ // 5、fakeUseEffect:当前定义时的 cursor 需要被闭包保存 let currentCursor = cursor;
❌ // 4、fakeUseEffect:少处理了游标自增: cursor++;
// 作业67:React 怎么做的性能优化?
- 第六遍 2025.2.17 -
fiber 架构:优先级、渐进式渲染、时间分片、增量式 Diff
避免重新渲染的策略 - eagerState策略、bailout策略
eagerState策略:如果新的 state 值和旧的无关,可以直接渲染,而不是再获取旧值再重新渲染
bailout策略:比较 state props 一致时,跳过重新渲染
bailout策略的相关函数+hooks:
类组件的shouldComponentUpdate(nextProps, nextState)、函数组件的 React.memo((props) => { return <div>{props.text}</div> })、useMemo+useCallback
⭐️ // React.memo 是浅比较 props,如果不变则直接使用
- 第五遍 2025.2.12 -
⭐️ // 避免组件重新渲染
❌ // 1、少写了 函数组件 const MyMemo = React.memo(props => (<div>{props.text}</div>)) - 浅比较props,不变则直接复用
// 记忆法:shouldComponentUpdate(类组件)/React.memo(函数组件) + 2个hook:useMemo/useCallback
- 第四遍 2025.1.23 -
- 第三遍 2025.1.18 -
useCallback ⭐️ // 每次渲染,函数都是重新生成的,用了这个就缓存住了
- 第二遍 2025.1.12 -
⭐️ // 主要是避免组件重新渲染
⭐️ // shouldComponentUpdate 是为了「类组件」的性能优化设计的生命周期
/**
❌❌ 1、对于「函数组件」,React 提供了 React.memo 这个高阶函数来处理 -> 对组件的 props 进行浅比较,如果不变则跳过重新渲染
const MyMemo = React.memo((props) => { return <div>{props.time}</div> })
*/
⭐️ // const expensiveMemoValue = useMemo(() => { return count * 2 }, [count])
// const expensiveCallback = useCallback(() => { setCount(count + 1) }, [count])
// 这里的记忆方法:shouldComponentUpdate(类组件)/React.memo(函数组件) 都是用于通过比较前后 props;2个函数组件的性能 hook:useMemo/useCallback
- 第一遍 -
❌ // 1、对,在运行时优化,但是这个「架构级别」的属于渲染层面的优化,性能方便还是两个优化策略
❌ // 2、一般不叫做「是否复用组件」,而是「避免组件重新渲染」
❌ // 3、eagerState:在useState 更新过程中,如果新值不依赖于旧状态,则直接使用新状态来更新组件
❌ // 4、只需要组件的 props 不变,即可避免重新渲染[state可能会影响子组件的 props,还是要考虑的]
❌ // 5、两个策略中,bailout 策略可以通过调用 API 来实现
❌ // 6、React.memo - 针对函数组件,对组件的 props 浅比较,不变则跳过重新渲染
❌ // 7、shouldComponentUpdate - 针对类组件,在组件更新前调用,自定义是否需要重新渲染
❌ // 8、useMemo/useCallback - 依赖项不变,返回之前的缓存
❌ // 9、除了两个策略,还有 VDOM + Diff - 减少对真实 DOM 的操作,避免不必要的回流重绘
// 作业64:Vue渲染怎么实现的?
- 第七遍 2025.2.17 -
AOT 预编译,参与全程
模版编译:
parse 词法分析、语法分析,得到 AST
optimize 静态节点标记,优化 Diff 时方便跳过
generate 生成 render 函数
双向数据绑定:
数据劫持 + 发布订阅模式,实现 Object.defineProperty(obj,属性,{get, set})/Proxy
初始化组件,实例 Watcher,set 属性变量时,将正在运行的 Watcher 收集到组件的 Dep ⭐️ // getter
更新/初次渲染组件时,访问 get,触发所有订阅的 Watcher 执行 - dep.notify() ⭐️ // setter
执行 render 函数,组合成 VDOM
执行 patch/diff,得到最小 UI 更新量,应用到真实 DOM
- 第六遍 2025.2.11 -
❌ // 1、模版编译的阶段名称错误:parse - optimize - generate
❌ // 2、函数使用方法错误:Object.defineProperty(obj, 属性, { get, set })
- 第五遍 2025.1.23 -
- 第四遍 2025.1.18 -
- 第三遍 -
1、模板编译: ⭐️ // AOT 参与全程
❌ // 1、getter 被劫持是结果,「劫持方式是 Object.defineProperty(target, property, {get:()=>{}, set:()=>{}})」/Proxy
❌ // 2、「每一个响应式对象属性都会创建一个对应的 Dep 类的实例」 -> 我写的这里理解有误,同时数据劫持之后,触发订阅的时机是「渲染组件,实例化 Watcher 时,执行到 getter 触发对应的 Dep 收集正在执行的 Watcher」
- 第二遍 -
❌ // 1、执行步骤的最后一步是 generate
// 三个步骤分别作用:① parse 词法解析+语法解析 template 得到 AST ② optimize 对AST静态内容优化 ③ generate 递归转换 AST 得到 render 函数
❌ // 2、AOT 预编译功能是针对 template 的,不是针对 js 的(第一次做的时候知道,这次做的时候误以为预编译是针对 js 的 -> 其实通过 AOT 和 JIT 的区别就可以推断,AOT 既然依赖的是静态编译,就是需要像模版语法这种,方便标记静态节点 - js 那么动态肯定是不行🚫的)
⭐️ // Dep 用于依赖收集,作为容器管理多个订阅者 Watcher
- 第一遍 -
❌ // 1、整个渲染「不止是 render + patch」,应该分成4步,①html:先「模版编译,得到render函数」 ②js:「实例化组件,同时收集依赖」(也就是数据双向绑定-实现响应式数据) ③html+js:「执行渲染函数/更新,得到 VDOM」 ④Diff/patch:「计算最小更新,应用到真实 DOM」
// 作业62:fiber架构是什么?优点和实现方式?
- 第七遍 2025.2.17 -
Schedueler:调度器,优先级调度,不直接参与渲染,宏观调控
Reconciler:架构器,时间分片,在重绘前根据 shouldYield 判断是否够时间分片 ⭐️ // 协调器
Render:渲染器,对应 commit 阶段
Schedueler Reconciler:异步,因为这两个在内存中计算,随时可能被以下情况打断 报错、时间分片时间不够、有更高优先级
Render:同步,将最小更新 UI 量应用到真实 UI,无法被打断
优点:
1、时间分片:之前 react 是同步渲染的,因为要更新整棵 VDOM 树,对比+计算较长时间占用主线程,阻塞重绘;使用 fiber架构后,利用中断恢复机制,将渲染分成多个小的渲染任务单元 ⭐️ // 在重绘和空闲时间执行
2、渐进式渲染 + 优先级调度:优先渲染视口部分的视图、或者用户交互
3、增量式 Diff:不再整棵树比较实现 Diff,而是根据渲染任务的执行,逐步计算 diff 得到最小更新 UI,提高 diff 效率
- 第六遍 2025.2.10 -
❌ // 1、时间切片的优点 - 少写了:之前 react 会对整棵树 对比+更新,同步执行,会长时间占用主线程,阻塞重绘 ; 描述优化:小的单元任务,在空闲 + 重绘判断执行
- 第五遍 2025.1.22 -
// ⭐️ 增量式 Diff- 根据任务的执行进度,逐步比较和更新部分节点
- 第四遍 2025.1.18 -
❌ // 1、忘记中文:Scheduelor 是调度器
❌ // 2、少:应该先说明 react 每次更新都要递归构建整棵 fiber 树+比较+更新,长时间占用主线程,会阻塞重绘 + 改:[Scheduelor+中断恢复机制]
❌ // 3、改:[Scheduelor+优先级调度机制]
❌ // 4、修改描述:不是一次性比较整棵树,而是根据任务执行进度,逐步比较、更新部分节点 - 减少了每次比较的范围,提高了效率
- 第三遍 -
// ⭐️ 不是「三大部分」,是「三个主流程」
❌ // 1、不是「Commit」,是「Reconciler」,叫做「协调器」,作用是「调用 shouldYield 判断当前 Time Slice 是否有剩余时间」
❌ // 2、不是因为「异步」才可以被打断,是以为「他们都在内存中执行,不会更新宿主环境 UI」
❌ // 3、不是「Reconciliator」,是「Render」「渲染器」
❌ /** 上述三个主流程记忆法:
Scheduler 调度器 - 优先级宏观调控
Reconciler 协调器 - render 阶段,用于判断时间切片剩余时间
Render 渲染器 - commit 阶段(更新UI)*/
❌ /** 1、第一个优点的关键词是「渐进式渲染」。
描述内容和上述很像,但是「占用主线程」不是重点,是「长时间」占用主线程。
不是「react 的解析、渲染」在占用,是「react 每次更新都要递归构建整棵 fiber 树、比较、更新」,重点要突出导致访问卡顿感的原因是渲染卡住了。
实现方式:Scheduler调度器 + 中断恢复机制*/
❌ // 2、第二个优点的关键词是「优先级调度」。实现方式:Scheduler 调度器 + 优先级调度机制
❌ /** 3、第三个优点,现在描述的是怎么实现 diff,但是 fiber 对 diff 的优化主要是「增量式Diff」。
「不是一次性比较整棵树,而是根据任务的执行进度,逐步比较和更新部分节点,根据时间和优先级安排,逐步更新,减少了每次比较的范围,提高了效率」。
实现方式:Scheduler调度器 + 中断恢复机制 + Reconciler协调器 + 双缓存机制*/
❌ /** 4、少一个优点「时间分片」。
「有效利用浏览器空闲时间,在每帧开始之前检查剩余时间,是否执行一个小的渲染任务单元」。
实现方式:Reconciler协调器 + 中断恢复机制*/
- 第二遍 -
⭐️ // 总结:「渐进式渲染」、「暂停恢复渲染能力」、「优先级调度」、「时间分片管理」
⭐️ // 「优先级调度器」其实是两样,「任务调度」 + 「优先级管理」
❌ // 1、这里缺少一个 Diff 算法的执行优化:增量式
- 第一遍 -
❌ // 1、不是「连续的 js 执行」会长时间占用主线程,是「React递归构建整颗 DOM 树、比较、更新」
❌ // 2、明确 React 由于每次构建的都是整颗树 + 之前是同步渲染 - fiber 做到了异步渲染
❌ // 3、分成小模块的自然也不只是「代码执行」,是「渲染工作」被分解成了「小任务单元」 + 同时可以「暂停 + 恢复」 -> 使得 在每帧开始前检查剩余时间,能否执行的也就是「小渲染任务单元」
❌ // 4、少写了:渐进式渲染
❌ // 上述写的都是原理,并不是具体实现 1、双缓存机制 current tree 和 work-in-progress tree 2、任务调度和优先级管理 3、Diff 算法优化为增量式 Diff
// 作业58:React和Vue是怎么描述UI的
- 第六遍 2025.2.17 -
React:JSX=HTML+JS、类组件+函数组件、state组件状态+props传入的属性、花括号{}
Vue:模版语法 + 指令 v-model、v-if/v-else-if/v-else、v-show、v-for + 双花括号
- 第五遍 2025.2.10 -
❌ // 1、React 少写了 :类组件+函数组件
❌ // 2、Vue 少写了:模版语法 + v-show
- 第四遍 2025.1.22 -
⭐️ // Vue 少写了:v-show
- 第三遍 2025.1.18 -
- 第二遍 -
❌ // 1、React 少写了 ①自定义组件-函数组件+类组件 ②state-状态,组件内部自变量;props-属性,外部传入自变量
⭐️ // Vue 指令还有 v-for v-bind v-else/v-else-if v-on v-model等
- 第一遍 -
// ❌ 1、React 少写了: 可以自定义组件 + 使用花括号{}将js嵌入JSX {note} + state-状态,组件内部自变量、props-属性,其他组件传入的自变量
// ❌ 2、Vue 少写了:模拟语法 - 结合数据+预先定义的模版 + 使用双大括号展示数据 + 指令v-if="show"/v-for="item in items"
LTN①⑥
LTN①⑥ 工具推荐做题周期 2.4 - 2.14 89题(LTN1-23, LTN2-45, LTN3-22)
LTN1 【推荐做题时间 02-04 - 1题 实际做题时间: 2.10】 ✅作业6:new操作符实例化一个对象的过程
LTN1 【推荐做题时间 02-05 - 1题 实际做题时间: 2.10】 ✅作业66:React如何处理错误
LTN2 【推荐做题时间 02-05 - 11题 实际做题时间: 2.10 + 2.11】 ❌作业58:React和Vue是怎么描述UI的 ❌作业62:fiber架构是什么?优点和实现方式? ✅作业71:React为什么要合成事件?和原生事件有什么不同? ✅作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口) ✅作业53:胜利者一无所获 说的是什么 ✅作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的 ✅作业85:Lambdas是什么 ✅2024.12 模拟实现promise ✅2024.05 rem是基于什么原理进行适配的? ✅2021.04 05-3 白屏优化? ✅作业75:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序?
LTN1 【推荐做题时间 02-06 - 3题 实际做题时间: 2.11】 ✅作业3:迭代器原理 ✅作业24:写出事件循环系统的流程图,常见宏任务、微任务有哪些。并写出几个常见题目的输出结果 ✅2021.06 子盒子在父盒子中水平垂直居中有几种方法?
LTN2 【推荐做题时间 02-06 - 6题 实际做题时间: 2.11 + 2.12】 ✅作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count, setCount]=useState(0)具体说明 ❌作业64:Vue渲染怎么实现的? ✅作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期 ❌作业67:React 怎么做的性能优化? ❌作业68:React hooks 的原理是什么?useEffect useState模拟实现 ✅作业78:使一个标签看不见的几种样式及其区别
LTN1 【推荐做题时间 02-07 - 9题 实际做题时间: 2.12 + 2.13】 ✅作业72:react 的 声明周期有哪些,在不同生命周期中做什么事情? ❌作业87:Nextjs 中预渲染是什么?有哪些模式?写一个动态路由的page ❌作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接 ✅2021.07 数组扁平化(一层、全部展开、指定深度) ❌作业94:为什么接口查询之后,一般都要使用 .json() 来转化一次数据后再返回? ❌作业95:唯一索引什么作用?Sequelize 中 define modal 的时候,怎么创建组合字段唯一索引?SQL怎么写?updateOnDuplicate 是什么作用?怎么配合唯一索引。 define 中的第三个参数中常见配置 ❌作业 96:Sequelize 中 Date 的怪问题 - POST 存储 DATE 类型,GET 传参数2025-01-27 STRING,无法直接查询,有那些性能高的查询方法?索引是什么,为什么性能高? ❌作业97:Sequelize 中查找某条数据,如果存在就更新;不存在就创建一条 ❌作业98:sequelize 怎么写关联表的查询,指定关联的 column 是date
LTN2 【推荐做题时间 02-07 - 8题 实际做题时间:2.14】 ✅2024.11 简单的发布 - 订阅模式实现 EventEmitter ✅2024.10 怎么实现跨域 ✅2022.08 包管理工具 ❌2020.07 对象分类、Promise按序执行、实现map ✅2019.10 两栏,左边固定,右边自适应的布局 ✅2019.10 三栏,两边固定,中间自适应 ❌2024.11 第十七章 事件 ✅2024.09 第八章 对象、类和面向对象编程 小结
LTN2 【推荐做题时间 02-08 - 3题 实际做题时间:2.14】 ✅2021-06 Position属性 - 占位?相对什么定位? ❌2021-07 常见Webpack问题 ✅2019-06 第四章 变量、作用域和内存问题 小结
LTN3 【推荐做题时间 02-08 - 14题 实际做题时间: 2.14 + 2.15】 ✅作业10:分别用两种方式新建一个映射实例、一个集合实例 ✅作业29:Promise的进度通知 ✅作业44:怎么能够不把所有事情、语言往心里去 ❌作业45:原生+canvas 显示一张png格式的图片 ✅作业48: input 和 textarea 对比(宽度设置,初始值设置);option 标签的初始值设置 ✅作业50:学习的感觉是? ✅作业51: 如何停止过度思考?如何停止焦虑 ✅作业52:道可道,非常道 翻译一下,有什么含义 ✅作业56:对那些没有 deadline 的事情,怎么解决拖延症 ❌2024.10 第十四章-第十五章 DOM、DOM扩展 ✅2024.11 第十六章 DOM2和DOM3 ❌2024.12 模拟实现instanceof ✅2021.04 02 网络协议 - IP/UDP/TCP协议 ❌2019.07 h5新标签和语义化,块/行内元素
LTN1 【推荐做题时间 02-09 - 7题 实际做题时间: 2.15】 ✅作业25:简单写下 请求创建过程 ✅作业74:起舞弄清影 是那首诗?谁写的?下一句是什么? ✅作业55:flex 常见缩写 ✅作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例 ✅作业61:常见的数据结构有哪些 ✅作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次? ✅作业76:显卡的作用?
LTN2 【推荐做题时间 02-11 - 8题 实际做题时间: 2.15 + 2.16】 ❌作业2:支持迭代器的原生语言特性有哪些? ✅作业43:TED 怎么提升自信 ✅作业42:TED 如何和大脑正确交流沟通 ✅作业39:shim 和 polyfill 区别 ❌2019.07 为什么要清除浮动?怎么清除浮动? ✅2021.06 CSS 选择器 - 权重/匹配方式 ❌作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用 ✅2021.07 bind、apply/call三者异同+apply/call实现bind ❌作业4:什么是生成器?有什么作用? ✅作业20:浏览器打开一个 URL 发生了什么 ✅2021.09 Object属性排序、与Map的区别
LTN3 【推荐做题时间 02-11 - 8题 实际做题时间: 2.16】 ✅作业17:对斐波那切数列进行尾调用优化 ✅作业26:给出一个场景,要求A、B请求执行结束后,再执行C请求,其中A、B请求同时开始,怎么实现 ✅作业34:写出强缓存、协商缓存的流程及相关属性 ✅作业57:“假装成功”的意义是什么 ✅2021.06 BFC特点、触发、存在/解决的问题 ✅2020.03 改变原数组+结束循环+性能排序 ❌2021.07 防抖节流 ❌2024.12 模拟实现jsonp
LTN2 【推荐做题时间 02-12 - 3题 实际做题时间: 2.16】 ❌作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询 ✅作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别?
LTN2 【推荐做题时间 02-13 - 2题 实际做题时间: 2.16】 ✅作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序) ❌2023.07 clickhouse、mysql、mongodb异同
LTN2 【推荐做题时间 02-14 - 4题 实际做题时间: 2.16】 ❌作业70:Vue 和 React 的 Diff 算法比较 ✅作业80:对DOM 树的理解 - 定义和作用 ✅作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更?
做题记录
// 作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更?
- 第四遍 2025.2.16 -
13.x Link 之后都直接渲染为 a 标签了,所以 Link 添加属性 href 即可,不需要在里面写 a 标签
路由:路由管理系统改为 src/app/pages.tsx + layout.tsx + global.css
api 也改为 src/app/api 管理后端接口路由
- 第三遍 2025.1.31 -
- 第二遍 2025.1.26 -
- 第一遍 2025.1.22 -
❌ // 1、只写了「文件路由系统」的变更,还少写了「后端路由系统」的变更 从 pages/api 变更为 src/app/api
// 作业80:对DOM 树的理解 - 定义和作用
- 第四遍 2025.2.16 -
DOM 是对 HTML 的对象模型,表示 HTML 的结构 ⭐️ // 对象化表示 - 树形结构的对象模型
浏览器:是浏览器渲染页面的基础,按照 DOM 结构渲染 ⭐️ // 浏览器根据 DOM 结构构建页面布局
框架:可以通过 VDOM 模拟真实 DOM,计算出最小更新量 UI 后,再应用到真实 DOM,提高性能 ⭐️ // 提高页面更新性能
js:提供了可以和 HTML 交互的接口,可以访问
- 第三遍 2025.1.31 -
❌ // 1、少写了浏览器视角-渲染页面的依据 和 框架视角-VDOM Diff 计算提高更新性能
- 第二遍 2025.1.25 -
- 第一遍 2025.1.24 -
⭐️ // DOM 是 HTML 文档的「对象化表示」,是按照 HTML 结构转换的树形结构的对象模型
⭐️ // 提供了一个编程接口 - DOM 是 js 可以访问、修改 HTML 的基础
❌ // 1、除了 js,还少写了 浏览器 + 框架 视角
/**
对浏览器而言,DOM 是「渲染页面的依据」,浏览器根据 DOM 结构构建页面布局
对框架而言,框架通过比较 虚拟DOM 和实际 DOM 树的差异,以最小 UI 更新量,提高页面更新性能
*/
// 作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序)
- 第四遍 2025.2.16 -
SELECT DISTINCT t.name,
CONCAT('(', t.age, ')') AS t_age,
UPPER(t.sex) AS t_sex,
COUNT(*) AS num_with_null,
COUNT(t.nums) AS num_without_null,
SUM(t.age) AS sum_age, (MAX MIN AVG)
FROM table AS t, serials AS s
WHERE t.id = 1 AND
(t.price > 10 OR
t.price IS NULL OR
t.price BETWEEN 5 AND 10 OR
t.price IN (1, 2) OR
YEAR(t.time) = 2025
)
GROUP BY t.age ⭐️ // 优先于 SORT 执行
SORT BY t.age DESC ⭐️ // LIMIT 在 SORT 之后
- 第三遍 2025.1.30 -
- 第二遍 2025.1.26 -
- 第一遍 2025.1.23 -
❌ // 1、由于 from 中有两张表,如果查询的 column 名在两张表都存在,有重名就会报错 - 所以查询的时候最好声明表名 t.name1 t.age
SELECT constinct name1, age, ❌ // 2、关键词错了 distinct
concat(sex, height) as answer2, // ⭐️ concat(t.sex, '(', t.height, ')') as answer2
toUpperCase(weight3) as weight3, ❌ // 3、函数名错了 upper
count(name) as name42, count() as name41, ❌ // 4、关键词参数错了 包含 null - 查询时传递 count(*) as name41_with_null;不包含 null - 查询时包含 column 名是对的
sum(age) as age5, max(age) as max5, min(age) as min5, avg(age) as avg5
from table as t, sex as s
where t.id = 1
And
(t.price > 10
or t.price = Null ❌ // 5、关键词错了:IS 操作符主要用于判断某个值是否为 NULL,且 t.price = null 的比较结果始终是 UNKNOWN, 因为 NULL 表示未知的值,无法确定它是否和另一个值相等 - t.price is null
or t.price between 5 to 10 ❌ // 6、关键词错了 between-and:t.price between 5 and 10
or t.price in [1,2] ❌ // 7、关键词错了,应该是():t.price in (1,2)
or year(s.time) = 2025)
group by age
sort age desc ❌ // 8、关键词错了 sort by
// 作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别?
- 第四遍 2025.2.16 -
path.join() 纯拼接路径
path.resolve() 解析绝对路径,默认绝对路径为 /
process.cwd() 当前 node 执行所在环境路径
__dirname 当前文件所属路径
- 第二遍 2025.1.29 -
⭐️ // path.resolve():绝对路径解析
- 第二遍 1.26 -
⭐️ // path.resolve():把 / 当做根路径
- 第一遍 2025.1.22 -
⭐️ // path.join(a,b):纯拼接
❌ // 1、path.resolve(a,b):是「绝对路径解析」,把 / 当做根目录 -> /a/b
❌ // 2、process.cwd():node 执行时所在目录
// 2020.03 改变原数组+结束循环+性能排序
- 第三遍 2025.2.16 -
改变原数组: pop shift push unshift sort reverse splice fill
结束循环:使用 return break 无法退出循环 forEach map reduce filter
性能排序: for > for...of > forEach > map > for...in
- 第二遍 2025.1.21 -
- 第一遍 -
// 2021.06 BFC特点、触发、存在/解决的问题
- 第三遍 2025.2.16 -
BFC:①独立容器 ②不和外部的 float 重叠 ③从上至下,垂直排列 ④计算盒子空间时,包含浮动元素
触发:①float: 不为 none ②position: absolute/fixed ⑤根元素
③overflow: hidden/scroll/auto ⭐️ // overflow不为visible
④display: flex/table-cell/inline-block ⭐️ // 少写了 table-captoin
存在的问题: BFC 内部的上下盒子 margin 会重叠 解决方案:给父元素 display: flex ⭐️ // 外边距重叠
- 第二遍 2025.1.21 -
- 第一遍 -
// 作业57:“假装成功”的意义是什么
- 第三遍 2025.2.16 -
心态能够改变行为 -> 行为同时也能够改变心态,扩展占用空间的姿势可以提升控制激素的分泌、抑制压力激素的分泌
⭐️ // 控制激素-睾丸酮,压力激素-皮质醇
Fake it until you become it! You deserved to be here.
- 第二遍 2025.1.21 -
⭐️ // 提高睾丸酮,降低皮质醇 - 提高自信心,降低压力 fake it until you become it
- 第一遍 -
// 作业34:写出强缓存、协商缓存的流程及相关属性
- 第三遍 2025.2.16 -
请求链接 url 有缓存时,直接使用缓存,强缓存生效
如果本地有缓存,但缓存失效 - 服务器状态码为 304 时,使用缓存并刷新缓存有效时间,协商缓存生效
If-modified-since/Last-modified If-none-match/Etag
Cache-control:max-age=2000; 秒[优先级高]
Expires deadline 事件
- 第二遍 2025.1.21 -
⭐️ // 刷新本地缓存时间
- 第一遍 -
// 作业26:给出一个场景,要求A、B请求执行结束后,再执行C请求,其中A、B请求同时开始,怎么实现
- 第三遍 2025.2.16 -
Promise.all([A(), B()]).then(C);
(async function () {
await Promise.all([A(), B()]);
C();
})()
- 第二遍 2025.1.21 -
- 第一遍 -
// 作业17:对斐波那切数列进行尾调用优化
- 第三遍 2025.2.16 -
function fib(n) {
if(n<2){
return n
}
return fib(n-1)+fib(n-2)
} 0 1 1 2 3 5
function optiFib(a = 0, b = 1, n) {
if(n===0) {
return a
}
return optiFib(b, a+b, n-1)
}
- 第二遍 2025.1.20 -
- 第一遍 -
// 2021.09 Object属性排序、与Map的区别
- 第五遍 2025.2.16 -
Object属性排序: 正整数>字符串>负数>浮点数>Symbol
Map区别:①键:任意值 VS Object只有字符串/Symbol ②记录插入顺序 VS Object 无法记录 ③Map 可迭代对象 ④ 数据量大的时候使用 Map,内存小,增删快 ⑤ 数量计算方便 m.size() VS Object.keys(obj).length
- 第四遍 2025.1.28 -
- 第三遍 2025.1.25 -
- 第二遍 2025.1.21 -
Object属性排序: 正整数>字符串>负数>Symbol ❌ // 1、少写了浮点数
- 第一遍 -
// 作业20:浏览器打开一个 URL 发生了什么
- 第五遍 2025.2.16 -
1、浏览器进程 UI线程 - 将用户输入内容、链接转换成完整的 url
2、IPC 协议传输给 网络线程 查询是否有缓存 - 有缓存,且有效,直接返回 ⭐️ // 强缓存生效
- 无缓存/有缓存但失效,进行 DNS 解析,得到 ip
- TCP 队列排队,三次握手,建立连接
- 组装请求头,包括 cookie 等信息
- 发送请求 301/302 Moved Permanently/Temperaily 重定向,使用响应头中的 Location 重新导航
304 使用缓存,并刷新缓存有效时间
200 若是 o-stream 下载类型,触发下载,结束导航;若是 html 和 渲染进程建立管道
3、渲染进程:主线程、预解析线程、合成线程
- 建立管道后,边下载边解析,并使用预解析线程,遇到 js、css提前下载
- 下载后,解析还没完成,出现 解析白屏
- 下载后,会通知浏览器进程更新 页面、安全锁、url、前进后退按钮
主线程:解析 DOM(DOM树 - 样式解析(CSS样式表 - 布局(布局树 - 分层(分层树 - 合成(合成指令 ⭐️ // 绘制(绘制指令列表
合成线程:栅格化(图块-位图,GPU 进程加速
浏览器进程 UI线程:合成(帧
- 第四遍 2025.1.28 -
- 第三遍 2025.1.25 -
- 第二遍 2025.1.20 -
❌ // 1、协议名错了:IPC - 进程通信协议
❌ // 2、少了:有缓存但缓存失效
❌ // 3、应该是先判断状态码,200的状态码下再判断类型
❌ // 4、少写了 304,使用本地缓存,并刷新缓存有效时间
- 第一遍 -
// 2021.07 bind、apply/call三者异同+apply/call实现bind
- 第五遍 2025.2.15 -
同:改变函数中 this 指向;不传第一个参数时,this 指向 window
异:bind 改变 this 指向后返回函数副本;apply、call 改变 this 指向后立即执行;apply 的第二个参数是 类数组/数组,call 的第二个参数是接收多个参数
fun.bind(thisArg,...args)
Function.prototype.fakeBind = function(thisArg, ...args1) {
const func = this;
return function F(...args2) {
if(func instanceof F) {
return new func(...args1, ...args2);
}
func.call(thisArg, ...args1, ...args2);
}
}
- 第四遍 2025.1.25 -
- 第三遍 2025.1.24 -
❌ // 1、bind 实现应该绑定在原型对象上实现: Function.prototype.fakeBind - 这样 this 才是指向调用 bind 的函数
- 第二遍 2025.1.19 -
❌ // 1、「bind 有多个参数,第一个是 this 指向,后面接收多个参数 」...arg1
❌ // 2、return new cb(...arg1, ...arg)
❌ // 3、cb.apply(thisArg, [...arg1, ...arg])
- 第一遍 -
❌ // 1、遗漏了判断是否为 new 调用
/**
return function F(...arg2) {
if(func instanceof F) {
return new func(...arg1, ...arg2);
}
func.call(thisArg, ...arg1, ...arg2);
}*/
// 2021.06 CSS 选择器 - 权重/匹配方式
- 第四遍 2025.2.15 -
#id > .red a[href] :hover LVHA> div ::after> *
从右向左 解析
!important > 行内 > 外联/内联(只和下载完顺序有关)
- 第三遍 1.28 -
- 第二遍 1.25 -
- 第一遍 1.20 -
!important > 内联 > 行内 ❌ // 1、行内 > 内联/外联
// 作业39:shim 和 polyfill 区别
- 第四遍 2025.2.15 -
shim 垫片,提供新的 API,解决兼容性问题,利用浏览器原生功能实现功能,优雅降级
polyfill 补丁,不提供新的 API,利用原生功能实现浏览器功能 ⭐️ // 实现浏览器原生 API 缺少的功能
- 第三遍 1.28 -
⭐️ // shim:优雅降级
⭐️ // polyfill:实现浏览器原生 API 中缺少的功能
- 第二遍 1.25 -
⭐️ // 实现浏览器原生 API 缺少的功能
- 第一遍 1.20 -
❌ // 1、记反了,shim 是处理兼容性问题-优雅降级,polyfill 是实现浏览器不支持的原生API-实现原生API中缺少的功能
// 作业42:TED 如何和大脑正确交流沟通
- 第四遍 2025.2.15 -
1、I want it, I like it , I have chosen it!
大脑只会对我们给它的语言和图像,大脑也天然追逐快乐(把想做的事情关联到快乐,把不想做的事情、停留在原地绑定到痛苦)
2、Make it familiar! 大脑喜欢熟悉的东西
- 第三遍 1.28 -
- 第二遍 1.25 -
- 第一遍 1.20 -
❌ // 1、少写了:I like it!I want it!I've chosen it! 大脑天然逃避痛苦、追逐快乐:将想要做的事情和巨大的快乐联系起来;将停滞在当下的痛苦细细描述
❌ // 2、大脑喜欢熟悉的事情:让大脑对想要做的事情变熟悉
// 作业43:TED 怎么提升自信
- 第四遍 2025.2.15 -
1、repetition!repetition!repetition! 重复一千遍,做好准备
2、不批评做得不好的事情,而是夸奖做得好的事情! 做自己最好的引导者
3、用自己的语言解释世界 - 世界上没有客观,只有主观
- 第三遍 1.28 -
- 第二遍 1.25 -
- 第一遍 1.20 -
❌ // 1、少写:重复训练!用足够多次的练习带来信心
❌ // 2、少写:而是不断夸奖正确行为 - 自己的或别人的 - 引导自己去学习和改变
2、用自己的语言解释世界
// 作业76:显卡的作用?
- 第四遍 2025.2.15 -
合成图像,放入到后缓存区
- 第三遍 2025.2.10 -
- 第二遍 2025.2.2 -
⭐️ // 合成新的图像,并将图形保存到后缓存区
// 作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次?
- 第五遍 2025.2.15 -
异步:React 合成事件 + 生命周期函数 中,react 提供了批量执行机制,在执行事件处理函数执行过程中,如果遇到 setState 会投入一个队列中,执行结束后,统一处理 setState ,修改 state 一次,触发一次渲染
同步:原生事件,setState 执行一次,触发一次渲染
- 第四遍 2025.2.10 -
- 第三遍 2025.2.2 -
❌ // 1、除了合成事件,还有「生命周期函数」中都是异步
⭐️ // 批量处理机制 + 更新一次 state ,触发一次重新渲染
⭐️ // 原生事件:setState 一次,就会触发一次渲染
- 第一遍 -
❌ // 1、除了合成事件,还有生命周期函数
// 作业61:常见的数据结构有哪些
- 第五遍 2025.2.15 -
按逻辑结构划分,结构型:栈,队列,数组,链表
非结构型:树,图
- 第四遍 2025.2.9 -
- 第三遍 2025.2.2 -
❌ // 1、少写了一个类型:数组
// 作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例
- 第六遍 2025.2.15 -
高阶函数 - 输入/输出是函数,map
高阶组件 - 输入是组件
const high = (My) => {
return class extends React.Component {
constructor(props) {
super(props);
}
render() {
return <My {...this.props} />
}
}
}
副作用 - 函数编程的说法,除了返回值,还会对外部产生影响,IO下载交互、全局变量等 ⭐️ // 改变输入参数
- 第五遍 2025.2.9 -
- 第四遍 2025.2.2 -
⭐️ // 副作用,少写了:除了返回值
render() {
return Child
❌ // 1、<Child {...this.props} />
}
// 作业55:flex 常见缩写
- 第六遍 2025.2.15 -
flex: none; 0 0 auto
flex: 1; 1 1 0
flex: inital; 0 1 auto
flex: auto; 1 1 auto
- 第五遍 2025.2.9 -
❌ // 1、把 0 0 auto 的缩写写错,应该是 flex: none;
- 第四遍 2025.2.2 -
❌ // 1、少写了一个 flex: initial; // 0 1 auto
- 第二遍 -
❌ // 1、把 0 0 auto 的缩写写错,应该是 flex: none;
- 第一遍 -
❌ // 1、把 0 0 auto 的缩写写错,应该是 flex: none;
// 作业74:起舞弄清影 是那首诗?谁写的?下一句是什么?
- 第四遍 2025.2.15 -
苏轼 《水调歌头 明月几时有》
明月几时有,把酒问青天,不知天上宫阙,今夕是何年?
我欲乘风归去,又恐琼楼玉宇,高处不胜寒。
起舞弄清影,何似在人间。
转朱阁,低绮户,照无眠。
不应有恨,千里共婵娟。 ⭐️ // 不应有恨,何事长向别时圆。人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟
- 第三遍 2025.2.9 -
⭐️ // 顺序反了 - 起舞弄清影,何似在人间。转朱阁,低绮户,照无眠。
⭐️ // 不应有恨,何事长向别时圆。人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。
- 第二遍 2025.2.2 -
宋 苏轼/苏东坡 ❌ // 1、题目记不起来:《水调歌头 明月几时有》
❌ // 2、乱序:起舞弄清影,何似在人间
// 作业25:简单写下 请求创建过程
- 第六遍 2025.2.15 -
function xmlRequest(url) {
const xhr = new XMLHttpRequest();
xhr.onerror = ()=>{};
xhr.ontimeout = ()=>{};
xhr.onreadystatechange = function(res) {
switch(res.readySate) {
case 0:
break;
case 4:
if(this.status === 200 || this.status === 301) { ⭐️ // 304
console.log(this.responseText);
}
break;
}
}
xhr.open('GET', url, true);
xhr.timeout = 3000;
xhr.responseType = 'text';
xhr.setRequestHeader(key, value);
xhr.send();
}
- 第五遍 2025.2.9 -
case 4:
if(res.status === 200 || res.status === 301) { ⭐️ // status 在this上
console.log(this.responseText)
}
- 第四遍 2025.2.2 -
xhr.onreadystate = function (res) { ❌ // 1、函数名称错误:onreadystatechange
switch(this.readyState) { ❌ // 2、属性不在 this 上,在 res 上:res.readyState
case 0:// 还未开始请求
❌ // 3、switch 中 0 表示请求还没开始,要使用 break 跳出循环
}}
- 第二遍 -
❌ // 1、属性名称记错 :res.readyState
- 第一遍 -
❌ // 1、open 的参数顺序记错了:xhr.open('GET', url, true)
// 2021.04 02 网络协议 - IP/UDP/TCP协议
- 第二遍 2025.2.15 -
应用层 HTTP
传输层 TCP/UDP:通过端口找程序
网络层 IP:通过 ip 找电脑
网际层 MAC:通过 mac 找网络服务
传输顺序 data]TCP]IP]---data]TCP]---data
- 第一遍 2025.1.9 -
// 作业56:对那些没有 deadline 的事情,怎么解决拖延症
- 第二遍 2025.2.15 -
有 deadline 的事情,脑中有一个怪兽可以警醒
但人生很多事情是,不做出行动就不会有改变,要做出行动,掌握到主动性才可以有所改变
想要解决人生的拖延症,就要明确人生是有限的,人就活这么多天,不去做,就少一天
- 第一遍 2025.1.9 -
// 作业52:道可道,非常道 翻译一下,有什么含义
- 第二遍 2025.2.15 -
如果真理是能够被语言描述的,那就不是永恒不变的道了
语言是有局限性的,真理只能身教,无法言传
- 第一遍 2025.1.9 -
// 作业51: 如何停止过度思考?如何停止焦虑
- 第二遍 2025.2.15 -
思考是一件非常容易上瘾的事情,不想过度思考的话,就把思维当做一条线,出现、get it、let it go
如果焦虑,应该是害怕自己会失败,那就把可能导致失败、会导向成功的可能性都列出来
- 第一遍 2025.1.9 -
// 作业50:学习的感觉是?
- 第二遍 2025.2.15 -
学如飞鸟过,脑内了无痕
- 第一遍 2025.1.9 -
// 作业48: input 和 textarea 对比(宽度设置,初始值设置);option 标签的初始值设置
- 第二遍 2025.2.15 -
input:size宽度、value 初始值、maxLength 最大长度 ⭐️ // 单行,单位为字符数
textarea:cols宽度、标签中间内容 为初始值、rows 高度 ⭐️ // 多行,单位为字符数
option:有 value 时取 value,没有 value 属性时,取标签中间内容
- 第一遍 2025.1.9 -
// 作业44:怎么能够不把所有事情、语言往心里去
- 第二遍 2025.2.14 -
1、It is not about me. 和我无关的事情,看看说话人的目的
2、It is about me. 给自己一些同情,同时学会表达自己的情绪,没有指责,只是表达
you will always keep your value
- 第一遍 2025.1.9 -
// 作业29:Promise的进度通知
- 第二遍 2025.2.14 -
class MyPromise extends Promise {
constructor(executor) {
let handles = [];
super((resolve, reject) => {
return executor(resolve, reject, (state) => {
handles.forEach(it => it(state));
})
})
this.handles = handles;
}
notify(fn) {
this.handles.push(fn);
}
}
const p = new MyPromise((resolve, reject, notifyFunc) => {
function count(n) {
if(n>0){
setTimeout(() => {
count(n-1);
notifyFunc(`${n*20} remained`)
}, 0)
⭐️ // 这里的写法可以优化,需要间隔时间执行的只有 count(n-1)
/**
notifyFunc(`${n*20} remained`);
setTimeout(() => count(n-1), 0);
*/
} else {
resolve()
}
}
count(5);
})
p1.notify((it) => { console.log(it) })
p1.then(() => console.log('completed'))
- 第一遍 2025.1.9 -
// 作业10:分别用两种方式新建一个映射实例、一个集合实例
- 第二遍 2025.2.14 -
const m1 = new Map([['a', 1], ['b', 2], ['c', 2]]);
const m2 = new Map().set('a', 1).set('b', 2).set('c', 3);
const s1 = new Set([1,2,3]);
const s2 = new Set({ *[Symbol.iterator]() {
yield 1;
yield 2;
yield 3
} })
- 第一遍 2025.1.9 -
// 2021-06 Position属性 - 占位?相对什么定位?
- 第二遍 2025.2.14 -
static:占位,原文件流 // ⭐️ 标准文档流
relative:占位,原文件流 // ⭐️「相对自己本身的定位」
absolute:不占位,相对于上级 position 非 static 属性的节点
fixed:不占位,相对于 window、视口
sticky:占位,超过阈值后不占位-fixed
- 第一遍 2024.12.25 -
// 2019.10 三栏,两边固定,中间自适应
- 第四遍 2025.2.14 -
.left,.right{ width: 100px }
1、.left,.right{ position: absolute;top: 0; } .right{ right:0 } .middle{ margin: 0 100px }
2、.outer{ display:flex; } .middle{ flex:1 }
3、.outer{ display:table;width:100%; } .left,.right,.middle{ display: table-cell }
.outer{ display:grid;grid-template-columns:100px auto 100px; }
4、.left,.right,.middle{ float: left } .middle{ width:cacl(100%-200px) }
- 第三遍 2025.1.24 -
- 第二遍 2025.1.19 -
- 第一遍 -
float 会导致 right 换行在第二行 - 要调整 左中右 的DOM布局(BFC也不行了)
2、flex
.outer { display: flex; justify-content: space-between; } // ⭐️ 横向布局可不写
.middle { flex: 1 }
4、calc
.left, .right { float: left } // ❌ 1、三浮动,否则 right 就会因为向左靠齐换行了
.middle { width: calc(100% - 400px) }
// 2019.10 两栏,左边固定,右边自适应的布局
- 第四遍 2025.2.14 -
.left { width: 200px };
1、marign-left+float:.left{ float: left } .right { marign-left: 200px }
2、marign-left+absolute:.left{ position: absolute } .right { marign-left: 200px }
3、BFC:.left{ float: left } .right{ overflow: hidden }
4、flex:.outer{ display: flex } .right{ flex:1; } 1 1 auto ⭐️ // 1 1 0
5、calc:.left, .right{ float: left } .right{ width: calc(100% - 200px) }
6、table+grid:.outer{ display: table; width: 100% } .left, .right{ display: table-cell }
.outer{ display: gird; grid-tenplate-columns: 200px auto; }
- 第三遍 2025.1.24 -
- 第二遍 2025.1.19 -
.outer {
display: grid;
grid-template-column: 100px auto; ❌❌ // 1、grid-template-columns
}
- 第一遍 -
4、flex
.outer { display: flex }
❌ // 1、父盒子变成弹性盒子后, right 要变成可拉伸 .right { flex: 1 }
// 2022.08 包管理工具
- 第五遍 2025.2.14 -
npm:将依赖打平提升到 node_modules;npm>5开始有 lock文件;package-lock.json .npmrc
yarn classic:将依赖打平提升到 node_modules;为了解决早期 npm 的加载慢问题,并行加载多个依赖;yarn.lock .yarnrc
pnpm:依赖依旧结构化存储在node_modules;内存依赖存储,物理地址单一存储;pnpm-lock.yml .npmrc ⭐️ // 内容寻址存储
yarn berry:不再使用node_modules;依赖查找表 .pnp.cjs+.yarn/cache zip存储;yarn.lock .yarnrc.yml ⭐️ // 即插即用
- 第四遍 2025.1.24 -
- 第三遍 2025.1.19 -
⭐️ // pnpm:内容寻址存储
⭐️ // yarn berry:老是想不到 - 即插即用
- 第二遍 -
❌ // 1、yarn v2/Berry:即插即用,且lock文件和配置文件分别为 yarn.lock + .yarnrc.yml
- 第一遍 -
❌ // 1、pnpm:「不是pnpm.yml,而是 pnpm-lock.yml」
// ⭐️ yarn v2/Berry:「即插即用」
// 2024.10 怎么实现跨域
- 第四遍 2025.2.14 -
1、jsonp:只能处理 GET 请求,依赖于script的 src 可以引入第三方资源
2、CORS:跨域资源共享 Access-Control-Allow-Origin 服务端允许访问的域名。可多个
3、websocket 双全工双向实时通讯协议,不受同源协议限制
4、代理:nginx ⭐️ // 同源协议只存在于浏览器端,服务端之间没有限制
- 第三遍 2025.1.24 -
- 第二遍 -
- 第一遍 -
// ❌ 1、CORS:哪些域名可以访问资源 - 是为了让浏览器知道哪些域名可以访问,而不是从客户端角度描述
// 2024.11 简单的发布 - 订阅模式实现 EventEmitter
- 第五遍 2025.2.14 -
class EventEmitter {
constructor() {
this.events = {};
this._max = 10;
}
add(event, listener) {
if(!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
if(this.events[event].length > this._max) {
throw Error('max exceed')
}
}
remove(event, listener) {
if(!listener) {
delete this.events[event];
}
if(Array.isArray(this.events[event])) {
const index = this.events[event].indexOf(listener);
if(index > -1) {
this.events[event].splice(index, 1);
}
}
}
once(event, listener){
const onceF = (...args) => {
listener(...args);
this.remove(event, onceF);
}
this.add(event, onceF);
}
emit(event) {
const args = [].shift.call(arguments);
if(Array.isArray(this.events[event])) {
const static = [...this.events[event]];
static.forEach((it, index) => {
const arg = args[index];
Array.isArray(arg) ? it(...arg) : it(arg);
})
}
}
setMax(v) {
this._max = v;
}
}
- 第四遍 2025.1.24 -
- 第三遍 2025.1.19 -
- 第二遍 -
❌ // 1、remove:这里要判断 index 存在才去删除:if(index>-1){ ... }
emit(event) {
const args = arguments.slice(1);
const list = this.events[event];
❌❌ // 2、不能直接赋值,要拷贝下来,才能避免 const list = [...this.events[event]];
list.forEach((it, index) => {
const arg = args[index];
Array.isArray(arg) ? it(...arg) : it(arg);
})
}
- 第一遍 -
❌ // 4、add:少了计算监听事件数量 if(this.events[event].length > this._max) { throw Error('exceed') }
❌ // 1、remove:这里要判断 index > -1,也就是 listener 已存在才删除
❌ // 2、emit:这里一定要注意,由于 once 的存在,this.events[event] 在执行过程是动态改变的,所以这里一定要将 this.events[event] 拷贝下来再处理 let static = [...this.events[event]]
❌ // 3、emit:args 还是要传进函数的 Array.isArray(args) ? it(...args) : it(args)
// 2021.07 数组扁平化(一层、全部展开、指定深度)
- 第七遍 2025.2.12 -
一层:arr.flat() [].concat(...arr) [].concat.call([], ...arr) [].concat.apply([], arr)
全部展开:arr.flat(Infinity) arr.toString().split(',')
function expense(arr, deep = 1) {
return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur)&& deep>1 ? expense(cur, deep - 1) : cur ), [])
}
- 第六遍 2025.2.9 -
- 第五遍 2025.1.31 -
全部展开:arr.flat(Infinity)、arr.toString().split(',')、JSON.parse(JSON.stringify(arr))
❌ // JSON.parse(JSON.stringify(arr)) 是用于深拷贝的,不能展开
- 第四遍 2025.1.25 -
- 第三遍 2025.1.24 -
❌ // 1、少写了 arr.flat(Infinity)
❌ // 2、JSON.stringify 不会展开数组
❌ // 3、全部展开递归函数中,pre 的值不要使用 push 来更新 - concat 既能处理数组,也能处理非数组
- 第二遍 2025.1.19 -
❌ // 1、deep 传递的时候要减一
// 作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情?
- 第八遍 2025.2.12 -
挂载阶段 componentDidMount、render、constructor 初始化 state 和 事件处理函数的 this、getDerivedStateFromProps
更新阶段 componentDidUpdate ⭐️ // (prevProps, prevState)
render、getDerivedStateFromProps、shouldComponentUpdate(nextProps, nextState)
卸载阶段 componentWillUnMount
useEffect - 模拟生命周期 componentDidMount+componentDidUpdate+componentWillUnMount
useLayoutEffect - 在 commit 之后,会阻塞帧的渲染 ⭐️ // 阻塞重绘
- 第七遍 2025.2.9 -
- 第六遍 2025.1.31 -
挂载阶段 ❌ // 1、少写了 constructor - 初始化 state 和绑定事件处理函数的 this
- 第五遍 2025.1.25 -
- 第四遍 2025.1.24 -
❌ // 1、shouldComponentUpdate 写错在挂载阶段,实际应该归属于更新阶段
❌ // 2、阶段划分是:挂载-更新-卸载,其中更新阶段记错了 - 应该是当组件的 state/props 变化,进入更新阶段,重新执行 render 函数来更新 DOM
- 第三遍 2025.1.19 -
❌ // 1、挂载阶段:少写了 constructor - 初始化 state 和绑定时间处理函数的 this
❌ // 2、除了类组件函数的生命周期,还要意识到函数组件也通过 hooks 模拟生命周期
// 作业78:使一个标签看不见的几种样式及其区别
- 第四遍 2025.2.12 -
display: none; 不占位,DOM 节点还在,触发回流重绘
VS v-if 移除 DOM 节点
opacity: 0; 占位,只是透明,可以触发节点上的绑定事件,只重绘
visibility: hidden; 占位,但不能触发节点上的绑定事件,只重绘;v-show
- 第三遍 2025.1.23 -
- 第二遍 2025.1.19 -
⭐️ // display: none 触发回流+重绘
⭐️ // visibility: hidden 触发重绘(不影响结构)
- 第一遍 -
❌ // 1、display: none 元素仍在 DOM 中,会触发回流+重绘; v-if 移除了 DOM 节点
❌ // 2、visibility: hidden 依旧占位,不影响结构,会触发重绘
// 作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期
- 第六遍 2025.2.11 -
①computed:有缓存值,除非依赖值变化,否则不重新计算
watch:监听,改变一次,触发执行一次
methods:每次组件渲染都会重新执行
②父子组件挂载顺序:父 beforeCreate created beforeMount 子 beforeCreate created beforeMount mounted 父 mounted
卸载 父 beforeDestroy 子 beforeDestroy destroyed 父 destroyed
③每个组件实例化时,如果 data 不是函数,会导致多个实例间共享;使用函数后,每个实例间有独立的作用域
④ beforeCreate created:此时data/computed/watch/methods都初始化了 beforeMount mounted:挂载到 DOM 上 beforeUpdate:VDOM 已生成 updated beforeDestroy:清理 destroyed
- 第五遍 2025.1.23 -
- 第四遍 2025.1.18 -
- 第三遍 -
❌ // 1、大虐,卸载是 beforeDestroy 和 destroyed
❌ // 2、大虐,和第一个一样的错误,名称错啦! beforeDestroy+destroyed,一般在 beforeDestroy 中清理一些 - 定时器、订阅时间、监听事件等,防止内存溢出
- 第二遍 -
❌ // 1、computed:咦,和上次错误的逻辑一样,不是「存储的值」是否变化,是「依赖的值」是否变化
❌❌ // 2、和上次错得一样 - 因为上次答案就是错的 😅,这就纠正了,下次不许再错了:子组件在父组件执行 父 beforeMounte 后触发
- 第一遍 -
❌ // 1、computed:这里其实对 computed 缓存的内容理解有误差,不是「值」,是「计算属性依赖的值」,同时 computed 必须return 返回
❌ // 2、watch:执行回调
❌ // 3、methods:每次页面发生变化,都会被调用
❌ // 子组件挂载完成后,父组件才会挂载 - 子组件在父组件执行 父 beforeMount 后触发
⭐️ // 这里补充:每个组件实例都有自己的「作用域」,不会互相影响
// 作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count,setCount]=useState(0)具体说明
- 第六遍 2025.2.11 -
JIT 运行时编译,将 JSX 编译为 React.createReactElement 方法 ⭐️ // React.createElement
render阶段:生成 fiberNode 树 + 遍历执行全部 hooks + 收集副作用链 + Diff
- hooks: useState useEffect
commit阶段:将Diff计算出的结果应用到真实 DOM + 生命周期函数+副作用链表
- componentDidMount/componentDidUpdate/componentWillUnMount
- useLayoutEffect
count 值改变,找到对应节点,修改值,触发 React 渲染整棵树,渲染某组件时,执行 fiberNode.memorizedState 存储的所有 hooks,如果有副作用,则收集副作用链 fiberNode.queue ⭐️ // fiberNode.updateQueue
计算 Diff 更新到真实 DOM,执行副作用链
- 第五遍 2025.1.23 -
- 第四遍 2025.1.18 -
render: fiber树 + hooks按序全部执行 + 副作用链表收集 + Diff
// ⭐️ FiberRootNode HostRootFiber beginWork-completeWork wip.tag
commit:// ⭐️ useLayoutEffect
- 第三遍 -
❌ // 0、渲染之前,React 已经通过 JIT 将 JSX 描述的 UI 编译为 React createElement方法
render阶段:❌ // 1、FiberRootNode 才是「根节点」,「HostRootFiber」只是新的 fiber 树的遍历起始点,也就是 FiberRootNode.current
❌ // 2、「遍历AST逐个生成 fiberNode」可以更详细得表述为「遍历执行 beginWork,根据不同的 wip.tag 进行不同标签的处理,执行到叶子节点 completeWork,生成 WIP Tree」
❌ // 3、render 阶段也是有生命周期在执行的,例如每次渲染执行前的 shouldComponentUpdate
❌ // 4、遍历执行 hooks 时,如果遇到有副作用的节点,除了被副作用链表收集,react 也会在节点 Node 上标记 fiberNode.subtreeFlags
⭐️ // fiberNode.updateQueue
⭐️ // 生命周期是类组件的概念
⭐️ // useLayoutEffect、ref引用也在这个时机
❌ // 1、「触发更新」其实应该描述成「更新 count 状态对应的节点」
❌ // 2、这里应该将「如果有副作用」改写为「要注意是否有 useEffect 的作用:根据依赖数组是否变化确定是否要更新」
- 第二遍 -
❌ // 1、在 render 之前,运行时就会执行 JIT 先将 JSX 描述的 UI 编译为 React createElement 方法
1.render :创建一颗完整的Fibernode的虚拟树 + diff
❌ // 2、中间少写了两个关键步骤:存储 hooks + 标记、收集副作用链表
❌ // 3、创建 FiberRootNode 后,创建 fiber 作为 HostRootFiber,也就是 FiberRootNode.current
❌ // 4、以单个组件为例,fiberNode 存储 hooks,按序执行时标记、收集副作用链表
❌ // 5、更新时会重新调用渲染函数,按序执行所有 hooks,所以也算是流程的一部分
- 第一遍 -
// 2021.06 子盒子在父盒子中水平垂直居中有几种方法?
- 第三遍 2025.2.11 -
①.father { display: flex; justify-content: center; align-items: center; }
②.father { display:table-cell; text-align: center; vertical-align: middle; }
.son { display: inline-block }
③.son { position: absolute; top:0; right:0; bottom:0; left:0; margin: auto; }
④.son { position: absolute; transform: traslate(-50%, -50%); top: 50%; left: 50%; }
- 第二遍 2025.2.9 -
- 第一遍 2025.1.30 -
④ .son { display: table-cell; text-align: center; vertial-align: middle; }
❌ // 1、不是给子元素 添加 table-cell,这个方法的核心是把 父盒子 设置为 table-cell,使得另外两个属性生效 - 而子盒子作为行内块类型,垂直、居中 .father { display: table-cell; text-align: center; vertical-align: middle; } .son { display: inline-block; }
// 作业24:写出事件循环系统的流程图,常见宏任务、微任务有哪些。并写出几个常见题目的输出结果
- 第三遍 2025.2.11 -
事件 - 同步 - 主线程执行 - 清空调用栈之前,去事件队列中获取函数执行 - 循环执行清空事件队列
- 异步 - Event Table - 异步函数有结果后,将回调函数投入事件队列
宏任务:IO下载交互、UI渲染、setTimeout/setTimeInterval、js代码执行
微任务:Promise.then、async/await、MutationObserver
125364
script start - async1 start - async2 - promise1 - script end
- async1 end - promise2 - promise3
- setTimeout
- 第二遍 2025.2.9 -
⭐️ // Event Queue + 触发时机 - 等异步任务有了结果,将回调函数投入事件队列
- 第一遍 2025.1.30 -
⭐️ // Event Queue,等异步任务有了结果,将回调函数投入事件队列
⭐️ // IO表示下载和交互
1 5 2 6 3 4 ❌ // 125364
// 作业3:迭代器原理
- 第四遍 2025.2.11 -
使用 next 方法可以遍历可迭代对象的所有值
- 第三遍 2025.2.9 -
- 第二遍 2025.1.30 -
提供暂停和恢复代码执行能力,[Symbol.iterater] 默认迭代器
yield 暂停,next 恢复执行 {done, value}
❌ // 迭代器使用 next 方法在可迭代对象中遍历数据
- 第一遍 2024.12.25 -
// 作业75:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序?
- 第四遍 2025.2.11 -
组件 - 返回可以被渲染的 JSX ⭐️ // 写想要做什么,而不是怎么做
hook - 返回类型不受限制,但是只是提供状态处理逻辑,hook之间独立,不会共享状态;且如果 hook 内没有使用其他 hook ,不建议抽离为 hook
渲染时,某些值改变,触发组件重新渲染,会遍历执行 fiberNode.memorisedState 存储的整个 hooks 链表,保证每个值都是最新的、准确的
- 第三遍 2025.1.22 -
- 第二遍 -
- 第一遍 -
// ❌ 修改下题目:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序?
React 组件代码表达的是 目标,而不是具体实现 - 描述的是想要做什么,而不是怎么做
hook 中如果没有内置 hook,则不建议使用 hook
自定义 hook 只是共享状态逻辑,而不是状态本身 - 每次调用都有独立的状态
返回值:组件一般是返回一段 React 能够显示的内容,例如JSX结构,hook可以返回任何值
// 2021.04 05-3 白屏优化?
- 第四遍 2025.2.11 -
1、硬件加速 DNS缓存、优化、TCP连接加速、服务器
2、代码 js:不写内联代码,避免阻塞主线程解析DOM,使用 async/defer
css:模块化,使用link开启预解析提前下载、GPU加速、减少层级(因为css从右向左解析、避免回流
html:精简结构,减少层级
压缩代码 + tree-shaking
减少 HTTP 请求次数、减小请求大小
- 第三遍 2025.1.20 -
- 第二遍 2025.1.19 -
css:从右向左解析,所以减少层级;媒体查询,减少加载代码体量;调用 GPU 等优化方式,减少回流重绘
❌ // 1、使用 link 加载而非 @import,可以触发预加载
js:defer、async 加载 ❌ // 2、尽量不使用「内联代码」
- 第一遍 -
白屏:网络进程文件加载完毕,浏览器开始刷新页面,但是主线程渲染流程还没有结束,页面短暂出现解析白屏
❌ // 1、少写 - 硬件加速:DNS 解析优化,例如缓存、预加载;TCP、服务器优化
2、css 从右到左的解析方式,尽可能减少层级,使用 link 而非 @import,从而触发预加载,使用 css 优化/GPU 加速减少回流重绘 ❌ // 2、少写 - 对于大文件的 css,利用媒体查询拆分不同用途
3、js 加载使用 defer/async 避免阻塞 ❌ // 3、少写 - 尽量不使用 内联代码
// 2024.05 rem是基于什么原理进行适配的?
- 第四遍 2025.2.11 -
em - font-size 值相对于父元素;width/height/padding/margin 相对于当前元素的 font-size
rem - 相对于根元素,html
- 第三遍 2025.1.22 -
- 第二遍 2025.1.19 -
- 第一遍 -
rem 相对于根元素的 font-size - html(body) ❌ // 1、不是 body-只是html的一个子标签,就是 html
em - 当前节点的字体大小相对于父元素的 font-size
- 当前节点的 width/height/padding/border/margin 相对于当前元素的 font-size
// 2024.12 模拟实现promise
- 第五遍 2025.2.11 -
const S = {
pending: 'pending',
resolve: 'fulfilled',
reject: 'rejected'
}
function MyPromise(executor) {
this.status = S.pending;
this.value = null;
this.reason = null;
this.resolveCalls = [];
this.rejectCalls = [];
const resolve = (v) => {
if(this.status === S.pending) {
this.status = S.resolve;
this.value = v;
this.resolveCalls.forEach(it => it(v))
}
}
const reject = (r) => {
if(this.status === S.pending){
this.status = S.reject;
this.reason = r;
this.rejectCalls.forEach(it => it(r))
}
}
return try{
executor(resolve, reject);
}catch(e){
reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r };
return new MyPromise((resolve, reject) => {
const resovleF = () => {
setTimeout(() => {
try{
const res = onResolved(this.value);
resolve(res);
}catch(e){
reject(e)
}
}, 0)
}
const rejectF = () => {
setTimeout(() => {
try{
const res = onRejected(this.reason);
resolve(res);
}catch(e){
reject(e)
}
}, 0)
}
switch(this.status) {
case S.pending:
this.resolveCalls.push(resovleF);
this.rejectCalls.push(rejectF);
break;
case S.resolve:
resovleF();
break;
case S.reject:
rejectF();
break;
}
})
}
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
MyPromise.prototype.finally = function(call) {
return this.then(v => { call(); return v; }, r => { call(); throw r; })
}
MyPromise.resolve = function (v) {
return new MyPromise((resolve, reject) => {
if(v instanceof MyPromise) {
return v.then(resolve, reject)
}
resolve(v);
})
}
MyPromise.reject = function (r) {
return new MyPromise((resolve, reject) => reject(r))
}
MyPromise.all = function(arr){
return new MyPromise((resolve, reject) =>{
return arr.reduce((pre, cur) => {
cur.then((res, index) => {
pre.push(res);
if(index === arr.length-1) {
resolve(pre)
}
}, reject)
return pre
}, [])
})
}
MyPromise.race = function(arr){
return new MyPromise((resolve, reject) =>{
arr.forEach(it => MyPromise.resolve(it).then(resolve, reject))
})
}
- 第四遍 2025.1.20 -
- 第三遍 2025.1.19 -
- 第二遍 -
class MyPromise { ❌ // 1、模拟 Promise 的时候,一般都是用函数
MyPromise.prototype.then = function(onResolve, onReject) {
// ...
let that = this;
return new MyPromise((resolve, reject) => {
const resolveFun = () => {
setTimout(() => {
const res = onResolve(that.value);
resolve(res);
❌❌ // 2、遗漏捕获错误
/**
try{
const res = onResolve(that.value);
resolve(res);
}catch(e){ reject(e) }
*/
}, 0)
}
const rejectFun = () => {
setTimeout(() => {
const res = onReject(that.reason);
reject(res);
❌❌ // 3、遗漏捕获错误 + onReject 返回的依旧是 resolve 的 promise
/**
try{
const res = onReject(that.reason);
resolve(res);
}catch(e){ reject(e) }
*/
}, 0)
}
// ...
})
}
- 第一遍 -
MyPromise.prototype.then = function (onResolve, onReject) {
// ...
return new MyPromise((resolve, reject) => {
const resolveFun = () => {
setTimeout(() => {
try{
const res = onResolve(this.value);
resolve(res);
}catch(reject); ❌ // 6、catch 不能这么写 catch(e){ reject(e) }
}, 0)
}
const rejectFun = () => {
setTimeout(() => {
try{
const res = onReject(this.reason);
resolve(res);
}catch(reject); ❌ // 6、catch 不能这么写 catch(e){ reject(e) }
}, 0)
}
// ...
})
}
MyPromise.resolve = (value) => {
return new MyPromise((resolve, reject) => {
if(value instanceof MyPromise) {
return value.then(resolve, reject); ⭐️ // 3、这里的 return 可以删除,不影响
}
resolve(value);
})
}
MyPromise.reject = (reason) => {
⭐️ // 2、简写成 return new MyPromise((resolve, reject) => reject(reason));
}
MyPromise.all = function (arr) {
return new MyPromise((resolve, reject) =>{
return arr.reduce((pre, cur, index) => {
⭐️ // 4、这里的 return 可以删除,不影响
cur.then(res => {
pre.push(res);
if(index === arr.length -1) {
resolve(pre);
}
}).catch(reject);
❌ // 1、reject 是作为 then 的第二个函数 -> }, reject)
return pre;
}, [])
})
}
MyPromise.race = function (arr) {
return new MyPromise((resolve, reject) => {
⭐️ // 5、这里科利简写 arr.forEach(it => MyPromise.resolve(it).then(resolve, reject))
})
}
// 作业85:Lambdas是什么
- 第二遍 2025.2.11 -
匿名函数
- 第一遍 2025.1.22 -
匿名函数
// 作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的
- 第四遍 2025.2.11 -
原型链:一个函数的原型对象是另一个函数的实例
原型链继承:Sub.prototype = new Super();
- 优:方法不需要反复重新创建
- 缺:属性和方法都共享,修改一个实例的引用类型,其他实例也改变了
组合继承:- 优:每个实例都有私有的属性 - 缺:父级实例每次都要执行2次
function Sub(){
Super.apply(this, arguments);
}
Sub.prototype = new Super();
寄生组合式继承:子级的原型对象是父级原型对象的副本
function iherit(Sub, Super){
let proto = Object.create(Super.prototype);
Object.defineProperty(proto, "constructor", {
enumerable: false,
value: Sub
});
Sub.prototype = proto;
}
- 第三遍 2025.1.22 -
- 第二遍 2025.1.19 -
寄生组合式继承: 父类的原型对象的副本,是子类的原型
function inherit(Sub, Super) {
let proto = Object.create(Super.prototype);
proto.constructor = Sub;
❌❌ // 1、Object.defineProperty(proto, "constructor", {enumberable: false, value: Sub})
Sub.prototype = proto;
}
- 第一遍 -
寄生组合式继承: 父类的原型对象副本是子类的原型对象
function inherit(Sub, Super) {
const proto = Object.create(Super.prototype);
❌ // 1、Object.defineProperty(对象,对象属性,对象值),所以应该写成 Object.defineProperty(proto, constructor, { enumerable: false, value: Sub })
Object.defineProperty(proto, {
enumerable: false,
value: { constructor: Sub }
}); // 修复 自定义原型对象会导致的 constructor 指向问题
Sub.prototype = proto;
}
// 作业53:胜利者一无所获 说的是什么
- 第四遍 2025.2.10 -
反战 海明威 《战国春梦》 质疑胜利的本质意义,指出战争胜利者失去了,美好的品质,例如善良
鼓励人们追求内在美好品质 ⭐️ // 质疑赢家通吃逻辑
- 第三遍 2025.1.22 -
- 第二遍 2025.1.19 -
⭐️ // 质疑胜利的本质
- 第一遍 -
// ❌ 《战地春梦》
// 作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口)
- 第四遍 2025.2.10 -
<canvas id='drawing' width=200 height=200></canvas>
const drawing = document.getElementById('drawing');
2D:左上角,矩形
if(drawing.getContext) {
const context = drawing.getContext('2d');
context.beginPath();
context.fillStyle/strokeStyle = 'red';
context.fillRect/strokeRect(10, 10, 30, 50);
context.drawImage/fillText('string', 10, 10);
context.moveTo/lineTo();
context.stroke();
}
3D: 左下角
if(drawing.getContext) {
const context = drawing.getContext('webgl');
context.viewport(drawing.width/2, 0, drawing.width/2, drawing.height/2); // 右下角
}
- 第三遍 2025.1.22 -
- 第二遍 2025.1.19 -
3D:左下角,context.viewPoint() ❌❌ // 1、viewport
- 第一遍 -
2D:
context.beginStroke(); ❌ // 1、context.beginPath() - 创建路径
3D: ❌ // 2、viewport
context.viewpoint(drawing.width/2, 0, drawing.width/2, drawing.height/2); // 右下角1/4
// 作业71:React为什么要合成事件?和原生事件有什么不同?
- 第五遍 2025.2.10 -
合成事件 - React 对原生事件的封装,抹平浏览器之间的 API 差异 + 绑定事件使用事件委托机制,在渲染时,绑定在最外层节点(一般是 document) ⭐️ // 更好的说法:跨浏览器兼容 + 在挂载时,会在节点绑定事件处理函数
优点:专注于代码,抹平差异 + 统一绑定时间处理函数,减少对 DOM 绑定的操作,优化性能
差异:
事件执行时机:React 冒泡;原生:先捕获,执行,再冒泡,一般比合成事件早执行
事件绑定方式:React JSX 绑定事件处理函数,事件委托绑定;原生:addEventListener ⭐️ // onclick
事件对象:合成事件是统一的对象;原生对象各个浏览器之间有差异
- 第四遍 2025.1.22 -
- 第三遍 2025.1.19 -
⭐️ // 跨浏览器兼容
- 第二遍 -
⭐️ // + 是对浏览器原生事件的封装
❌ // 1、合成事件的绑定,不是在「构建时」,事件委托发生在「组件挂载时」
❌ // 2、差异少写了:事件绑定方式 合成事件-JSX中属性指定事件处理函数;原生事件 标签的onclick或者addEventListener
- 第一遍 -
❌ // 1、内核是对的,不是“高级集合”,是「React对浏览器原生事件的封装」,是「一个跨浏览器兼容的事件系统」
// 优点/原因:① 跨浏览器兼容性 ②性能优化:通过事件委托到 document,减少大量事件绑定的开销
❌ // 2、差异需要具体的描述:
// ①事件对象(有一套标准的属性和方法,用于事件信息 VS 由浏览器提供,属性方法因浏览器而异)
// ②事件绑定方式(React中JSX属性中指定事件处理函数来绑定 VS 原生标签的 onclick/addEventListener)
// ③事件执行顺序(内部事件系统决定,冒泡执行,一般比原生晚 VS 先捕获再冒泡)
// 作业66:React如何处理错误
- 第八遍 2025.2.10 -
React 提供两个函数处理 render + commit 阶段的错误,避免错误溢出影响整个应用,影响 UI 展示
static getDerivedStateFromError - 返回 state 用于更新 UI
componentDidCatch - 抓取 err ,用于对错误进行记录、处理
class ErrorBoundaries extends React.Component {
constructor(props) {
super(props);
this.state = { hasErr: false };
}
static getDerivedStateFromError() {
return { hasErr: true };
}
componentDidCatch(e, info) {
console.log(info)
}
render() {
if(this.state.hasErr) {
return <div> There are errs. </div>
} else {
return this.props.children;
}
}
}
<ErrorBoundaries> <My /> </ErrorBoundaries>
- 第七遍 2025.2.9 -
- 第六遍 2025.1.29 -
React 提供两个函数处理错误 static getStateFromError()、componentDidCatch,用于捕获
❌ // 1、静态函数名称错了 getDerivedStateFromError
render+commit 阶段的错误,阻止错误蔓延到整个应用影响 UI 展示
几个情况不会捕获:异步、回调、ssr、错误边界内部
class Boundaries extends React.Component{
constructor() {
super(props);
this.state = { hasError: false }
}
static getStateFromError() { ❌ // 2、getDerivedStateFromError
return { hasError: true }
}
componentDidCatch(e, info) {
console.log(e)
}
render() {
if(this.state.hasError) {
return <p> There are errs. </p>
} else {
return this.props.children;
}
}
}
- 第五遍 2025.1.22 -
- 第四遍 2025.1.18 -
// 作业6:new操作符实例化一个对象的过程
- 第四遍 2025.2.10 -
function newF() {
const obj = new Object();
const ctor = [].shift.call(arguments);
obj.__proto__ = ctor.prototype;
const res = ctor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}
- 第三遍 2025.2.9 -
- 第二遍 1.28 -
function newFun () {
let obj = new Object();
const ctor = [].shift.call(arguments);
obj.__proto__ = ctor; ❌ // 1、不是构造函数,是构造函数的原型对象 obj.__proto__ = ctor.prototype;
const res = ctor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}
- 第一遍 1.20 -
❌ // 1、不是调整 constructor 的指向
LTN①⑤
①⑤ 错题重做 - 23题
2.9 - 14 题 2.10 - 9题 = 2h57min
✅作业6:new操作符实例化一个对象的过程 ✅作业66:React如何处理错误 ✅作业3:迭代器原理 ✅2021.06 子盒子在父盒子中水平垂直居中有几种方法? ✅作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情? ❌作业87:Nextjs 中预渲染是什么?有哪些模式?写一个动态路由的page ✅作业24:写出事件循环系统的流程图,常见宏任务、微任务有哪些。并写出几个常见题目的输出结果
Promise.resolve().then(() => {
console.log(1);
Promise.resolve().then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
})
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
------
async1 = async () => {
console.log('async1 start');
await async2();
console.log('async1 end');
}
function async2(){
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
})
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
return new Promise(function(resolve) {
resolve();
})
}).then(() => {
console.log('promise3');
})
console.log('script end');
❌作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接 ✅2021.07 数组扁平化(一层、全部展开、指定深度) ✅作业25:简单写下 请求创建过程 ✅作业74:起舞弄清影 是那首诗?谁写的?下一句是什么? ❌作业55:flex 常见缩写 ✅作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例 ✅作业61:常见的数据结构有哪些 ✅作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次? ✅作业76:显卡的作用? ✅作业90:金玉满堂是什么意思?在花卉中的说法是什么? ✅作业91:三次握手的过程和具体包的作用 ❌作业92:前端中的序列化是什么?常见序列化方法 ❌作业93:闭包的作用和原理 ❌2021.07 事件流 + 事件模型 ✅2021.06 链表 141. 环形链表 ✅2021.09 get和post有什么区别
// 2021.09 get和post有什么区别
- 第二遍 2025.2.10 -
get:参数在请求行上;参数长度有限制 2kb;浏览器默认缓存 GET 请求结果;回退无害,参数在链接中,重新发起请求
post:参数在请求体中;参数长度无限制;默认不缓存,可以手动设置;回退有害,参数不存储在链接中
- 第一遍 2025.2.8 -
❌ // 1、从四个方面描述:数据参数 + 参数长度 + 回退安全性 + 缓存
get:
请求参数在链接上; ⭐️ // 也就是请求行
参数大小限制;❌ // 2、参数长度:get - 大小限制2kb
❌ // 4、回退安全性:get - 回退无害,参数被保留在浏览器历史记录
❌ // 6、缓存:get - 请求会被浏览器主动 cache
post:
请求参数在请求体;
数据大小限制很大 2kb;❌ // 3、参数长度:post - 没有限制
❌ // 5、回退安全性:post - 回退时,post请求会被再次提交,参数不会被保留
❌ // 7、缓存:post - 不会缓存,除非手动设置
// 2021.06 链表 141. 环形链表
- 第二遍 2025.2.10 -
function cricle(head) {
try{
JSON.stringify(head);
return false
}catch(e){
return true
}
}
function circle2(head) {
while(head) {
if(head.circle){
return true
}
head.circle = true;
head = head.next;
}
return false
}
- 第一遍 2025.2.8 -
function circle(head) {
try{
JOSN.stringify(head);
return false;
} catch(e){
return true;
}
}
function circle2(head) {
while(head.next) { ❌ // 1、 while(head) 即可
if(head.circle) {
return true
}
head.circle = true;
head = head.next;
}
return false
}
// 2021.07 事件流 + 事件模型
- 第二遍 2025.2.10 -
事件流: 捕获 从上至下,直到具体节点 window-document-body-html-... ;冒泡 从内至外,直到最外层节点
事件模型: 现代事件模型 先捕获,再具体节点执行事件处理函数,最后冒泡
❌ // 1、现代事件模型:是事件委托,利用事件冒泡,将事件处理程序添加到父元素;DOM2级事件模型才是我写的:捕获+处理+冒泡
- 第一遍 2025.2.8 -
先捕获,再冒泡
❌ /** 缺少详细描述
捕获:从 window->document->html->body 传递到最具体的节点
冒泡:从具体节点逐渐向上传播到DOM最高层父节点
现代事件模型:事件委托-利用事件冒泡,将事件处理程序添加到父元素
DOM2级事件模型:捕获+处理+冒泡
*/
// 作业93:闭包的作用和原理
- 第二遍 2025.2.10 -
闭包:内部函数引用外部函数的变量,外部函数执行完后,原本应该清理变量,但因为引用,就会以 closure 存储,以便存储值,后续访问 ⭐️ // 活动对象没有被销毁,词法作用域
作用:保存变量值、私有变量 ❌ // + 函数工厂、回调函数
- 第一遍 2025.2.8 -
外部函数执行完毕后,会清理执行栈和内存将变量清理;但是如果内部函数引用了变量,变量将不被清理,以 closure 存储在内存 ⭐️ // 活动对象没有被销毁,词法作用域
用于保存变量持续的值 ❌ // 保存状态 + 数据封装/私有变量 + 函数工厂 + 回调函数
// 作业92:前端中的序列化是什么?常见序列化方法
- 第二遍 2025.2.10 -
序列化:将对象等转换为方便存储、传输的结构 ⭐️ // 将数据结构、对象转换
JSON.stringify、new FormData(form)
❌ // 1、JSON.stringify 无法处理 Function、无法处理循环引用;new FormData 收集表单数据并序列化,转换成键值对格式
- 第一遍 2025.2.8 -
将不同的数据结构转换为 JSON
❌ // 1、序列化是将数据结构/对象转换为可以存储/传输的格式的过程
JSON.stringify ⭐️ // 特殊点:无法处理 Function 和 互相引用,循环引用时会报错
❌ // 2、少写了 new FormData(form) 收集表单数据并序列化,转换成键值对的格式
// 作业91:三次握手的过程和具体包的作用
- 第二遍 2025.2.10 -
客户端和服务端建立 TCP 连接,SYN 序列编号包,ACK 确认包(标志位)⭐️ // SYN 同步序列编号包
客户端发起 SYN
服务端 SYN + ACK
客户端 ACK
- 第一遍 2025.2.8 -
客户端:SYN
服务端:SYN + ACK
客户端:ACK
SYN - ...不记得了 ❌ // 1、SYN是同步序列编号包
ACK - 确认信息,有序列号 ❌ // 2、ACK确认包
// 作业90:金玉满堂是什么意思?在花卉中的说法是什么?
- 第二遍 2025.2.10 -
形容财富很多,比喻人学识丰富
玉堂春富贵:玉兰 海棠 迎春 牡丹 桂花
- 第一遍 2025.2.8 -
金玉满堂:很多内涵,夸奖 ⭐️ // 形容财富很多、比喻学识丰富
海棠、(其他的不记得了)
❌ // 1、花卉中的说法 玉堂春富贵 - 玉兰(玉)、海棠(堂)、迎春(春)、牡丹(富)、桂花(贵)
// 作业76:显卡的作用?
- 第三遍 2025.2.10 -
合成图像,存储在后缓冲区
前后缓冲区替换
- 第二遍 2025.2.2 -
用于绘制帧
在后区渲染好之后,直接替换前区帧
⭐️ // 合成新的图像,并将图形保存到后缓存区
// 作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次?
- 第四遍 2025.2.10 -
react 中 合成事件+生命周期函数 中都是异步 - 批量更新机制,在执行事件处理函数时,遇到 setState 先用队列存储,执行完函数后,将队列中的 state 统一计算处理,修改 state 一次,触发一次渲染
在原生事件中,是同步,触发一次 setState 重新渲染一次
- 第三遍 2025.2.2 -
React 合成事件 - 异步;原生事件 - 同步
❌ // 1、除了合成事件,还有「生命周期函数」中都是异步
React 在处理 setState 的时候,在执行回调函数时,会创建一个队列存储,执行完回调函数,统一处理队列中的 setState ,有个合并处理机制 ⭐️ // 批量处理机制 + 更新一次 state ,触发一次重新渲染
在原生事件中不受 React 管控,直接执行 ⭐️ // setState 一次,就会触发一次渲染
- 第一遍 -
❌ // 1、除了合成事件,还有生命周期函数
// 作业61:常见的数据结构有哪些
- 第四遍 2025.2.9 -
按照逻辑结构分
结构型:数组、链表、栈、队列
非结构型:树、图
- 第三遍 2025.2.2 -
结构化数据:队列、栈、链表 ❌ // 1、少写了一个类型:数组
非结构化数据:树、图
// 作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例
- 第五遍 2025.2.9 -
高阶函数:输入或者输出的值是函数,map
高阶组件:输入参数是组件
副作用:函数式编程的概念,除了返回值,还会修改外部其他值,例如 IO、全局变量
const f = (My) => {
return class extends React.Component{
constructor(props) {
super(props);
}
render() {
return <My {...this.props}/>
}
}
}
- 第四遍 2025.2.2 -
高阶函数:输入/输出是函数的函数,例如 map
高阶组件:输入是组件
副作用:在函数内部会修改外部参数、全局参数、IO ⭐️ // 除了返回值
function fun(Child){
return class extends React.Component {
constructor(props) {
super(props);
}
render() {
return Child
❌ // 1、<Child {...this.props} />
}
}
}
// 作业55:flex 常见缩写
- 第五遍 2025.2.9 -
flex: 0; // 0 0 auto ❌ // 1、把 0 0 auto 的缩写写错,应该是 flex: none;
flex: initial; // 0 1 auto
flex: auto; // 1 1 auto
flex: 1; // 1 1 0
- 第四遍 2025.2.2 -
flex: none; // 0 0 auto
flex: 1; // 1 1 0
flex: auto; // 1 1 auto
❌ // 1、少写了一个 flex: initial; // 0 1 auto
- 第二遍 -
❌ // 1、把 0 0 auto 的缩写写错,应该是 flex: none;
- 第一遍 -
❌ // 1、把 0 0 auto 的缩写写错,应该是 flex: none;
// 作业74:起舞弄清影 是那首诗?谁写的?下一句是什么?
- 第三遍 2025.2.9 -
起舞弄清影,何似在人间
苏轼 《水调歌头 明月几时有》
明月几时有,把酒问青天,不知天上宫阙,今夕是何年。
我欲乘风归去,又恐琼楼玉宇,高处不胜寒。
转朱阁,低绮户,照无眠。⭐️ // 顺序反了 - 起舞弄清影,何似在人间。转朱阁,低绮户,照无眠。
起舞弄清影,何似在人间。
⭐️ // 不应有恨,何事长向别时圆。人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。
千里共婵娟。
- 第二遍 2025.2.2 -
宋 苏轼/苏东坡 ❌ // 1、题目记不起来:《水调歌头 明月几时有》
我欲乘风归去,又恐琼楼玉宇,高处不胜寒
不知天上宫阙,今夕是何年,何似在人间 ❌ // 2、乱序:起舞弄清影,何似在人间
/**
明月几时有?把酒问青天。不知天上宫阙,今夕是何年。
我欲乘风归去,又恐琼楼玉宇,高处不胜寒。
起舞弄清影,何似在人间。
转朱阁,低绮户,照无眠。
不应有恨,何事长向别时圆。
人有悲欢离合,月有阴晴圆缺,此事古难全。
但愿人长久,千里共婵娟。
*/
// 作业25:简单写下 请求创建过程
- 第五遍 2025.2.9 -
function request(url) {
const xhr = new XMLHttpRequest();
xhr.ontimeout = ()=>{};
xhr.onerror = ()=>{};
xhr.onreadystatechange = function(res) {
switch(res.readyState) {
case 0:
break;
case 4:
if(res.status === 200 || res.status === 301) { ⭐️ // status 在this上
console.log(this.responseText)
}
}
}
xhr.open('GET', url, true);
xhr.timeout = 3000;
xhr.responseType = 'text';
xhr.setRequestHeader(key, value);
xhr.send();
}
- 第四遍 2025.2.2 -
function request(url) {
let xhr = new XMLHttpRequest();
xhr.onerror = () => {}
xhr.ontimeout = () => {}
xhr.onreadystate = function (res) { ❌ // 1、函数名称错误:onreadystatechange
switch(this.readyState) { ❌ // 2、属性不在 this 上,在 res 上:res.readyState
case 0:// 还未开始请求
❌ // 3、switch 中 0 表示请求还没开始,要使用 break 跳出循环
case:4
if(this.status === 200 || this.status === 304) {
console.log(this.responseText)
}
}
}
xhr.open('GET', url, true); // 允许异步
xhr.timeout = 3000;
xhr.responseType = 'text';
xhr.setRequestHeader(key, value);
xhr.send();
}
- 第二遍 -
❌ // 1、属性名称记错 :res.readyState
- 第一遍 -
❌ // 1、open 的参数顺序记错了:xhr.open('GET', url, true)
// 2021.07 数组扁平化(一层、全部展开、指定深度)
- 第六遍 2025.2.9 -
一层: arr.flat()/[].concat(...arr)/[].concat.call([], ...arr)/[].concat.apply([], arr)
全部: arr.flat(Infinity)/arr.toString().split(',')/
function expense(arr, deep) {
return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) && deep>1 ? expense(cur, deep-1) : cur), [])
}
- 第五遍 2025.1.31 -
一层:arr.flat()、[].concat(...arr)、[].concat.call([], ...arr)、[].concat.apply([], arr)
全部展开:arr.flat(Infinity)、arr.toString().split(',')、JSON.parse(JSON.stringify(arr))
❌ // JSON.parse(JSON.stringify(arr)) 是用于深拷贝的,不能展开
function expense(arr, deep = 1) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) && deep > 1 ? expense(cur, deep - 1) : cur)
}, [])
}
- 第四遍 2025.1.25 -
- 第三遍 2025.1.24 -
❌ // 1、少写了 arr.flat(Infinity)
❌ // 2、JSON.stringify 不会展开数组
❌ // 3、全部展开递归函数中,pre 的值不要使用 push 来更新 - concat 既能处理数组,也能处理非数组
- 第二遍 2025.1.19 -
❌ // 1、deep 传递的时候要减一
// 作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接
- 第四遍 2025.2.9 -
mysql2 是给node环境的数据库驱动,可以直接连接数据库,但是需要写比较多的 SQL
Sequelize ORM 可以将表和行解析为对象,将数据库的操作以函数式呈现
❌ // 1、不是解析,是映射:将表和行映射为对象模型
mysql2
import { createPool } 'mysql2/promise';
const pool = createPool({host, user, password, database});
const connect = await pool.getConnect();
const [rows] = await connect.execute('SELECT * FROM table');
sequelize
import { Sequelize, DataType, Modal } from 'sequelize';
const se = new Sequelize({ host, user, password })
❌ // 2、实例化的参数错误 new Sequelize(database, user, password, { host: 'localhost', dialect: 'mysql2'})
方法一
class Usr extends Modal {}
Usr.init({
name: DataType.STRING
})
❌ // 3、init 还需要第二个参数传递连接实例,模型名称
/**
Usr.init({ name: DataTypes.STIRNG }, {
sequelize: se, // 传递连接实例
modalName: 'Usr' // 模型名称
})
*/
方法二
se.define('table_name', {
name: {
type: DataType.STRING
}
}, {
underscore: false
})
- 第三遍 2025.1.31 -
mysql2 是适用于node场景的 mysql 驱动,可以直连数据库,但是需要写很多 SQL
⭐️ // 提供了 mysql2/promise 模块,允许 async/await 、管理多个数据库连接;没有内置的对象关系映射ORM
import { createPool } from 'mysql2' ❌ // 1、依赖包错了 mysql2/promise
const pool = createPool({host, user, password}); ❌ // 2、少写了 database
const connection = await pool.getConnection(); ❌ // 3、获取连接的函数错了 await pool.getConnect()
const data = connection.execute('SELECT * FROM table') ❌ // 4、需要使用 await:const [rows] = await connection.execute('SELECT * FROM table')
--
Sequelize 数据库模块,ORM 把整个数据库表对象化,可以通过函数式编程直接调用使用,执行事务
⭐️ // ORM 把数据库的表和行映射为对象模型
import { sequelize, Datatypes, Modal } from 'sequelize'
const se = new sequelize(database, user, password, {
host: 'localhost',
dialect: 'mysql2'
})
创建连接后,会一直保持连接状态,直到调用 se.close() 关闭连接
class se extends Modal {}
se.define('table', {
id: Datatypes.number
}, {
underScore: false;
})
❌ // 5、有两种模型定义方法:一种是 define,另一种 extends Modal + init
/**
const User = se.define('table', {
id: Datatypes.number
}, {
underScore: false;
})
--
class User extends Modal {}
User.init({
id: Datatypes.number
}, {
sequelize: se, // 传递连接实例
modalName: 'User' // 模型名称
})
最后调用: User.findAll({ attribute: ['id', 'name'] })
*/
- 第二遍 2025.1.26 -
❌ // 1、mysql2 的依赖包是 mysql2/promise
❌ // 2、mysql2 创建连接池不需要 await:const pool = createPool({host, user, password, database})
❌ // 3、mysql2 获取连接时需要 await:const connection = await pool.getConnect()
❌ // 4、mysql2 查询数据时需要 await:const [rows] = await connectiom.execute('SQL')
❌ // 5、sequelize 实例化创建连接 const s = new sequelize(database, user, password, { host, dialect })
❌ // 6、sequelize 模型定义的底层 - 先扩展,再init定义
- 第一遍 2025.1.22 -
❌ // 1、mysql2 提供的连接的两个特点: mysql2/promise 支持 async/await + 管理多个数据库连接池
❌ // 2、sequelize 将数据库的表和行映射为对象模型,直接通过 js 操作
// 作业24:写出事件循环系统的流程图,常见宏任务、微任务有哪些。并写出几个常见题目的输出结果
- 第二遍 2025.2.9 -
事件 -同步 - 主线程执行 - 调用栈执行 - 调用栈清空前,调用事件队列中的函数 - 直到清空
-异步 - Event Table - 触发后,回调函数加入事件队列 ⭐️ // Event Queue + 触发时机 - 等异步任务有了结果,将回调函数投入事件队列
宏任务:IO、UI渲染、js执行、setTimeout/setTimeInterval
微任务:Promise.then、async/await、MutationObserver
125364
script start - async1 start - async2 - promise1 - script end - async1 end - promise2 - promise3- setTimeout
- 第一遍 2025.1.30 -
时间循环系统
事件:同步类型 - 主线程执行 - 执行完成,清理执行栈之前,从事件队列中获取事件执行 - 所有微任务执行完毕后,进入下一个事件循环
异步类型 - Event Table - 将回调函数投入事件队列 ⭐️ // Event Queue
⭐️ // 等异步任务有了结果,将回调函数投入事件队列
宏任务 : UI渲染、IO下载、js代码、setTimeout/setTimeInterval ⭐️ // IO表示下载和交互
微任务: MutationObserver、Promise.then、async/await
1 5 2 6 3 4 ❌ // 125364
script start - async1 start - async2 - promise1 - script end
- async1 end - promise2 - promise3 - setTimeout
// 作业87:Nextjs 中预渲染是什么?有哪些模式?写一个动态路由的page
- 第四遍 2025.2.9 -
预渲染:主要是服务端提前渲染SSR、客户端提前构建SSG、增量客户端构建ISR
❌ // 1、名称错误:服务端渲染SSR、静态生成SSG、增量静态生成ISR
模式:服务端渲染 - 在请求时,服务端将引用数据插入,返回给客户端后,客户端只需要渲染,不需要js执行
⭐️ // getServerSideProps
客户端构建 - 在构建时,提前将页面渲染完成,并使用 revalidate 设置时间,到时间后重新构建
⭐️ // generateStaticParams
❌ // 2、缺少说明一下结果:客户端、服务端生成的结果都是 HTML,得说明一下
SSR
import { getServerSideState } from 'next'
❌ // 3、函数名称、依赖包错误:import { getServerSideProps } from 'next/server'
function Blog() { return <div>11</div> }
export default Blog;
export function getServerSideState(props) {
return <Blog {...this.props} />
}
❌ // 4、SSR 的核心是在服务端提前获取数据 -> 传入组件
/**
export function getServerSideProps() {
const res = await fetch('xxxx');
const data = await res.json();
return { props: data }
}
export default function Blog({data}) {return <div>{data}</div>}
*/
SSG
function Blog({data}) { return <div>{data}</div> }
export function generateParams() {
❌ // 5、函数名称错误:generateStaticParams
return [{data: 'first'}, {data: 'second'}]
}
export const revalidate = 60; // 秒
export default Blog;
- 第三遍 2025.1.31 -
预渲染:包含服务端渲染 SSR、静态生成SSG、ISR 增量静态生成
SSR:getServerSideProps 客户端每次请求,都在服务端构建 HTML,动态引入 data 数据,返回给客户端直接渲染,而不是交给客户端js
SSG+ISR:getDerivedStateParams + revalidate 在构建时,直接生成 HTML,超过更新时间后,后台直接重新构建
❌ // 1、记错函数名称,13.x 之前是 getStaticProps,现在已经改为 generateStaticParams,用于生成动态路由的参数列表
SSR
import { getServerSideProps } from 'next/server'
function Blog({ data }) { return <div>{data}</div> }
async function getServerSideProps() {
const data = await fetch('xxxx');
return { props: { data: data.json() } }
❌ // 2、data.json() 是异步的,异步解析数据,处理 json 数据转换为 js 对象 - const res = await data.json;
}
export default Blog;
SSG
function Blog({ data }) { return <div>{data}</div> }
async function getDerivedStateParams() {
const data = await fetch('xxxx');
return { props: { data: data.json() } }
❌ // 3、return 的应该是个路由列表 例如 return [{slug: 'first'}, {slug: 'second'}]
}
const revalidate = 60; // 秒
❌ // 4、export const revalidate = 60; // 页面重新验证时间
export default Blog;
- 第二遍 2025.1.26 -
❌ // 1、SSR getServerSideProps: fetch 数据需要 await: const data = await fetch('xxx')
❌ // 1、SSR getServerSideProps: 异步解析数据需要 await: const res = await data.json()
- 第一遍 2025.1.22 -
❌ // 1、SSG generateStaticParams 用于生成动态路由的参数列表
❌ // 2、SSG revalidate 用于定义页面重新验证时间 - 页面将重新生成,运行时生成/更新静态页面以增量更新
// 作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情?
- 第七遍 2025.2.9 -
挂载阶段 constructor(初始化state和事件处理函数的this) render getDerivedStateFromProps componentDidMount
更新阶段 shouldComponentUpdate(nextProps, nextState) componentDidUpdate(prevProps, prevState) render getDerivedStateFromProps
卸载阶段 componentWillUnMount
useEffect:模拟三个componentDidMount componentDidUpdate componentWillUnMount
useLayoutEffect:commit 阶段后同步执行,阻塞重绘
- 第六遍 2025.1.31 -
类组件
挂载阶段
getDerivedStateFromProps
componentDidMount - 首次渲染执行
render
❌ // 1、少写了 constructor - 初始化 state 和绑定事件处理函数的 this
更新阶段
getDerivedStateFromProps
componentDidUpdate - 更新后执行
shouldComponentUpdate(nextProps, nextState) - 返回布尔值判断是否需要重新渲染
render
卸载阶段
componentWillUnMount - 卸载前执行
函数组件
useEffect 模拟 componentDidMount回调函数执行一次 ⭐️ // 内部函数
模拟 componentDidUpdate 依赖更新,再次执行回调函数
模拟 componentWillUnMount 返回一个清理函数,在组件卸载前执行
useLayoutEffect 在 commit 阶段后,同步执行函数,阻塞重绘
- 第五遍 2025.1.25 -
- 第四遍 2025.1.24 -
❌ // 1、shouldComponentUpdate 写错在挂载阶段,实际应该归属于更新阶段
❌ // 2、阶段划分是:挂载-更新-卸载,其中更新阶段记错了 - 应该是当组件的 state/props 变化,进入更新阶段,重新执行 render 函数来更新 DOM
- 第三遍 2025.1.19 -
❌ // 1、挂载阶段:少写了 constructor - 初始化 state 和绑定时间处理函数的 this
❌ // 2、除了类组件函数的生命周期,还要意识到函数组件也通过 hooks 模拟生命周期
// 2021.06 子盒子在父盒子中水平垂直居中有几种方法?
- 第二遍 2025.2.9 -
1、.father { display: flex; justify-content: center; align-items: center; }
2、.father { display: table-cell; text-align: center; vertical-align: middle; }
.son { display: inline-block; }
3、.son { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; }
4、.son { position: absolute; transform: translate(-50%, -50%); top: 50%; left: 50%; }
- 第一遍 2025.1.30 -
①flex .father { display: flex; justify-content: center; align-items: center; }
② .son { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; }
③ .son { position: absolute; transform: translate(-50%, -50%); left: 50%; top: 50%; }
④ .son { display: table-cell; text-align: center; vertial-align: middle; } ❌ // 1、不是给子元素 添加 table-cell,这个方法的核心是把 父盒子 设置为 table-cell,使得另外两个属性生效 - 而子盒子作为行内块类型,垂直、居中 .father { display: table-cell; text-align: center; vertical-align: middle; } .son { display: inline-block; }
// 作业3:迭代器原理
- 第三遍 2025.2.9 -
使用 next 访问可迭代对象的所有值,不需要知道对象类型 ⭐️ // 遍历
- 第二遍 2025.1.30 -
提供暂停和恢复代码执行能力,[Symbol.iterater] 默认迭代器
yield 暂停,next 恢复执行 {done, value}
❌ // 迭代器使用 next 方法在可迭代对象中遍历数据
- 第一遍 2024.12.25 -
// 作业66:React如何处理错误
- 第七遍 2025.2.9 -
static getDerivedStateFromError + componentDidCatch 处理 render + commit 阶段的错误,是为了错误不蔓延到整个应用,影响 UI 展示
class ErrorBoundaries extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false }
}
static getDerivedStateFromError() { // 返回新的 state
return { hasError: true }
}
componentDidCatch(e, info) {
console.log(info);
}
render() {
if(this.state.hasError) {
return <div> There are errs. </div>
} else {
return this.props.children;
}
}
}
- 第六遍 2025.1.29 -
React 提供两个函数处理错误 static getStateFromError()、componentDidCatch,用于捕获
❌ // 1、静态函数名称错了 getDerivedStateFromError
render+commit 阶段的错误,阻止错误蔓延到整个应用影响 UI 展示
几个情况不会捕获:异步、回调、ssr、错误边界内部
class Boundaries extends React.Component{
constructor() {
super(props);
this.state = { hasError: false }
}
static getStateFromError() { ❌ // 2、getDerivedStateFromError
return { hasError: true }
}
componentDidCatch(e, info) {
console.log(e)
}
render() {
if(this.state.hasError) {
return <p> There are errs. </p>
} else {
return this.props.children;
}
}
}
- 第五遍 2025.1.22 -
- 第四遍 2025.1.18 -
// 作业6:new操作符实例化一个对象的过程
- 第三遍 2025.2.9 -
function newf() {
const ctor = [].shift.call(arguments);
const obj = new Object();
obj.__proto__ = ctor.prototype;
const res = ctor.call(obj, ...arguments);
return typeof res === 'object' ? res : obj;
}
- 第二遍 1.28 -
function newFun () {
let obj = new Object();
const ctor = [].shift.call(arguments);
obj.__proto__ = ctor; ❌ // 1、不是构造函数,是构造函数的原型对象 obj.__proto__ = ctor.prototype;
const res = ctor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}
- 第一遍 1.20 -
❌ // 1、不是调整 constructor 的指向
①⑤ 74题 2025.1.27 - 2.4 开始
2.8 - LTN2 ✅2021.05 29 WebSocket ✅2021.05 XSS跨域脚本攻击 和 CSRF跨站请求伪造攻击 ✅2019.07 深拷贝和浅拷贝的区别?怎么实现深拷贝? ✅2020.03 模块化 ✅2021.06 跨页面通信 ✅2019.07 数组去重(算法整理) ✅2021.07 柯里化函数实现
2.8 - LTN1 ✅作业16(2):arguments 对象对待命名参数和设置了默认值的命名参数有什么不同?数组作为参数传入函数得到的arguments 是什么?怎么实现arguments是数组?怎么收集独立参数?在对象字面量和数组字面量中,扩展运算符有什么不同的表现? ✅作业 89:mysql 中类型的区别:varchar 和 char,date datetime ❌作业90:金玉满堂是什么意思?在花卉中的说法是什么? ❌作业91:三次握手的过程和具体包的作用 ❌作业92:前端中的序列化是什么?常见序列化方法 ❌作业93:闭包的作用和原理 ❌2021.07 事件流 + 事件模型 ❌2021.06 链表 141. 环形链表 ❌2021.09 get和post有什么区别
// 作业 89:mysql 中类型的区别:varchar 和 char,date datetime
- 第一遍 2025.2.8 -
varchar 按照内容长度存储
char 需要指定固定存储长度,不论存储什么都是这么大内存占用 ⭐️ // 内容长度不够时,使用空格填充到指定长度
date YYYY-MM-DD ⭐️ // 3字节
datetime YYYY-MM-DD HH:MM:SS ⭐️ // 8字节
// 作业16(2):arguments 对象对待命名参数和设置了默认值的命名参数有什么不同?数组作为参数传入函数得到的arguments 是什么?怎么实现arguments是数组?怎么收集独立参数?在对象字面量和数组字面量中,扩展运算符有什么不同的表现?
- 第三遍 2025.2.8 -
arguments 默认会同步命名参数,修改 aguments[1] 就会就该对应的命名参数
但是不会同步 设置了默认值的命名参数
数组直接传入:arguements=[[1,2,3]] ->(...arr)
(first, ...rest)
[...arr] => 扩展运算符,一一传入可迭代对象的值
{...obj} => 扩展运算符,复制可枚举属性
- 第一遍 -
❌ // 1、数组直接传入的 arguments 错了,应该是 [[1,2,3]]
❌ // 2、{...obj} 作用描述有问题,复制的是 可枚举属性
// 2021.07 柯里化函数实现
- 第二遍 2025.2.8 -
function curry(fn, ...args1){
if(fn.length <= args1.length) {
fn(...args)
} else {
return (...args2) => curry(fn, ...args1, ...args2)
}
}
// 2019.07 数组去重(算法整理)
- 第三遍 2025.2.8 -
1、[...new Set(arr)] Array.from(new Set(arr))
2、arr.filter((it, index) => it === arr[index]) ⭐️ // 判断条件 index === arr.indexOf(it)
3、
function unique(arr){
const hash = {};
return arr.filter(it => hash[it] ? false : hash[it] = true)
}
- 第一遍 -
❌ // 1、Set 不是数组类型,需要再次转换成数组 [...new Set(arr)] Array.from(new Set(arr))
// 2021.06 跨页面通信
- 第三遍 2025.2.8 -
同源页面:①localStorage setItem+window.onstorage=()=>{},只能处理非当前页面
②new BroadCastChannel('b') 两个页面实例化相同广播,b.postMessage('xxx')+b.onmessage=()=>{}
非同源页面:
③postMessage(H5 API:使用 const win = window.open(url); win.postMessage('xxx'); + window.onMessage=()=>{}
④iframe 使用 origin 配置成同源
- 第一遍 -
❌ // 1、构造函数的名称记错:BroadCastChannel
// 2020.03 模块化
- 第三遍 2025.2.8 -
CommonJS:同步,适合服务端,运行时,输出值的拷贝,require+exports
AMD:异步,依赖前置,并行加载多个依赖,按照加载先后顺序执行,无法控制执行时机,define(['a.js'], function(a){ a.call(); }) ⭐️ // 加载完了立即执行调用,无法按序执行
CMD:异步,依赖就近,并行加载多个依赖,按照书写顺序执行,define(function(require, exports, model) {
let a = require('./a.js'); ⭐️ // 加载后按序执行
a.call();
})
ES6:异步,服务端+客户端通用解决方案,编译时,输出值的引用,export default import as from
- 第一遍 -
❌ // 1、少写了 AMD - 加载模块后直接执行,无法保证执行顺序;CMD - 加载后,直调调用才按序执行
// 2019.07 深拷贝和浅拷贝的区别?怎么实现深拷贝?
- 第三遍 2025.2.8 -
基础类型:深、浅拷贝都是拷贝一个副本
引用类型:浅拷贝拷贝的是引用地址,修改新值会影响原值;深拷贝-引用地址指向的数据,修改新值不会影响原值
浅:[...arr]/Array.from(arr)/arr.concat()/arr.slice()/Object.assign({}, obj)/{...obj}
深:JSON.parse(JSON.stringify(target))/
function isObj(t) {
const type = typeof t;
return t !== null && (type === 'function' || type === 'object');
}
function handleSpecial(t, type) {
switch(type) {
case '[object Date]':
return new Date(t.getTime());
break;
case '[object RegExp]':
return new RegExp(t.source, t.flags);
break;
case '[object Symbol]':
return Symbol.for(t.description);
break;
default:
const ctor = t.constructor;
return new ctor(t);
}
}
function clone(target, m = new weakMap()) {
if(!isObject(target)) {
return target
}
const detailType = Object.prototype.toString.call(target);
const specialType = ['Map', 'Set', 'Array', 'Object'].map(it => `[object ${it}]`);
if(!specialType.includes(detailType)) {
return handleSpecial(target, detailType);
}
const ctor = target.constructor;
let res = new ctor();
if(m.has(target)) {
return target;
}
m.set(target, res);
if(detailType === '[object Map]') {
target.forEach((value, key) => {
res.set(key, clone(value, m))
})
} else if (detailType === '[object Set]') {
target.forEach(it => {
res.add(clone(it, m))
})
} else if(Array.isArray(target)) {
target.forEach((it, index) => {
res[index] = clone(it, m);
})
} else {
for(let i in target) {
res[i] = clone(target[i], m);
}
}
return res
}
- 第一遍 -
❌ // 1、属性名写错了:正则类型的 t.flags
❌ // 2、 方法错了:object 不是可迭代对象,不能使用 for...of,这里应该改为 for...in
// 2021.05 XSS跨域脚本攻击 和 CSRF跨站请求伪造攻击
- 第三遍 2025.2.8 -
脚本攻击的原因:浏览器给同源协议开了两个后门:①可以引入第三方数据 ②通过CORS可以请求第三方资源
XSS跨域脚本攻击:
定义-前端页面注入代码,修改服务端数据
解决:①禁止渲染时 DOM 执行 js 代码,例如 img、script
②服务端对用户输入等数据过滤、转义
③HttpOnly 禁止js访问cookie,js引擎会禁用 document.cookie 等
④CSP 内容安全策略 Content-Security-Policy 客户端信任的引入资源域名 meta+响应头 ⭐️ // script-src 'self'
CSRF跨站请求伪造攻击:
定义-冒用用户信息获取服务端信任,修改服务端数据
①cookie Samesite
②验证码+CSRF token
③请求头 referer
- 第二遍 -
⭐️ // CSP - 从客户端角度讲的,哪些域名的资源可以被信任;CORS - 从服务端角度讲的,哪些域名可以访问这个资源
- 第一遍 -
❌ // 1、少写了:XSS的解决方案中,在「渲染时」禁止标签执行 js 代码
// 2021.05 29 WebSocket
- 第三遍 2025.2.8 -
全双工双向实时通讯协议
由于 HTTP 的请求-应答模型,无法实现实时通讯
1、使用新的协议 ws:80 wss:443
2、使用新的发现服务方式 URI,而不是ip+端口
3、不受同源协议限制、使用二进制帧,双向通讯 ⭐️ // 在语义语法上完全不兼容
4、基于 HTTP 进行连接,GET 请求头中 Connection: update,如果响应头中 Update: websocket,切换协议
- 第二遍 -
❌ // 1、少写了:修改了协议名 ws:80 wss:443
- 第一遍 -
⭐️ // 2、少写了全名:全双工 - 双向通信协议
2.2 - LTN2 ❌作业25:简单写下 请求创建过程 ✅作业49:牛顿三大定律、热力学两大定律 ✅作业54:举杯邀明月,对影成三人。的上一句 ❌作业74:起舞弄清影 是那首诗?谁写的?下一句是什么? ❌作业55:flex 常见缩写 ❌作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例 ❌作业61:常见的数据结构有哪些 ❌作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次? ❌作业76:显卡的作用? ✅作业77:重绘和重排是什么?有什么区别? ✅作业79:地球四季的成因是什么? ✅2019.07 显示省略号 ✅2021.04 25 HTTP/2 特性
// 2021.04 25 HTTP/2 特性
- 第三遍 2025.2.2 -
语义上完全兼容 HTTP/1.1,语法上完全不一样
1、压缩头部字段,HPACK字典表算法
2、使用二进制帧,双向传输数据流-模拟虚拟的流,多路复用-多个请求响应复用一个连接,多个请求响应之间没有了顺序关系,解决了队头堵塞问题
3、服务器推送
4、使用HTTPS
5、设置优先级
- 第一遍 -
❌ // 1、少写一个特性:服务器推送
// 2019.07 显示省略号
- 第三遍 2025.2.2 -
.line {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.lines {
display: -webkit-box-;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
⭐️ // line-height: 10px; height: 30-padding;
}
- 第一遍 -
❌ // 1、css 属性名错误:-webkit-box-orient: vertical;
// 作业79:地球四季的成因是什么?
- 第二遍 2025.2.2 -
和太阳旋转的椭圆形无关
和太阳直射夹角 + 穿过大气层厚度有关(强度
⭐️ // 直射点的移动 + 太阳照射时长 - 夹角和地面角度的改变,改变了太阳辐射面积和经过大气层的路程衰减的辐射能量强度
// 作业77:重绘和重排是什么?有什么区别?
- 第二遍 2025.2.2 -
解析DOM树 - 样式计算 css - 布局树 - 分层树 - 绘制
重绘只需要修改分层+合成
⭐️ // 触发方式主要是 字体、背景、边框颜色
重排需要布局+分层+合成,性能消耗大
⭐️ // 字体大小、padding/margin、激活伪类、style样式的修改、resize、页面初始渲染、DOM增删、位置内容的修改、DOM布局查询
// 作业54:举杯邀明月,对影成三人。的上一句
- 第三遍 2025.2.2 -
花间一壶酒,独酌无相亲
// 作业49:牛顿三大定律、热力学两大定律
- 第三遍 2025.2.2 -
牛顿三大定律:加速度定律 a=F/m、惯性定律、作用力和反作用力
热力学两大定律:能量守恒定律、熵增定律(在自然条件下,变得混乱是趋势
- 第一遍 -
❌ // 1、少写了惯性定律 - 公交车记忆:刹车-加速度、乘客-惯性、地上痕迹-摩擦
1.31 - LTN1 - 8题
✅作业70:Vue 和 React 的 Diff 算法比较 ❌作业72:react 的 声明周期有哪些,在不同生命周期中做什么事情? ✅作业80:对DOM 树的理解 - 定义和作用 ✅作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更? ❌作业87:Nextjs 中预渲染是什么?有哪些模式?写一个动态路由的page ❌作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接 ❌2021.07 数组扁平化(一层、全部展开、指定深度) ✅2021.07 bind、apply/call三者异同+apply/call实现bind
// 2021.07 bind、apply/call三者异同+apply/call实现bind
- 第五遍 2025.1.31 -
同:改变函数的 this 指向 + 不传第一个参数时,函数的 this 指向 window
异:bind 返回函数副本;apply/call 立即执行函数
apply 的第二个参数是类数组或者数组;call 接收多个参数
Function.protytype.fakeBind = function(thisArg, ...args1) {
let func = this;
return function F(...args2) {
if(func instanceof F) {
return new func(...args1, ...args2)
}
func.call(thisArg, ...args1, ...args2)
}
}
- 第四遍 2025.1.25 -
- 第三遍 2025.1.24 -
❌ // 1、bind 实现应该绑定在原型对象上实现,这样才能 this 指向调用函数
- 第二遍 2025.1.19 -
❌ // 1、bind 函数接收多个参数 - 第一个是 this 指向;后面接收多个参数
// 作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更?
- 第二遍 2025.1.31 -
13.x 开始 Link 中不再写 a 标签用于跳转 - 因为 Link 会被解析为 a 标签
文件路由系统 - 从 pages 改为 src/app/page.tsx,Layout.tsx global.css 可以直接修改全局布局,方便布局
后端路由系统 - src/app/api/route.tsx
- 第一遍 2025.1.22 -
❌ // 1、少写了后端路由系统
// 作业80:对DOM 树的理解 - 定义和作用
- 第三遍 2025.1.31 -
DOM 树是 HTML 的树形对象结构,属性对应于 DOM 节点的各种特性 ⭐️ // 「对象化表示」,是按照 HTML 结构转换的树形结构的对象模型
js - DOM 树提供了 JS 访问、修改 HTML 的交互接口
浏览器 - 浏览器渲染依赖于 DOM 结构 ⭐️ // 浏览器根据 DOM 结构构建页面布局
框架 - 框架利用 VDOM 比较得到最小更新 UI 量,再改动真实 DOM,提高了 Diff 性能 ⭐️ // 提高页面更新性能
- 第二遍 2025.1.25 -
- 第一遍 2025.1.24 -
❌ // 1、除了js,少写了浏览器 - 渲染页面的依据和框架视角 - VDOM Diff 计算提高更新性能
// 作业70:Vue 和 React 的 Diff 算法比较
- 第六遍 2025.1.31 -
Vue 双端指针指针算法 + 静态节点跳过 + 找不到匹配节点,先判断同类型+同 key 的节点是否同索引,索引相同则复用 + 列表组件,同类型同key 的节点复用
React:分层比较 + 同层级同类型节点优先比较 - 不同节点类型,直接重新渲染;同类型同key 移动复用;同类型 修改属性
⭐️ // React 是精确到节点属性级别的 Diff 计算,适合大型动态复杂场景;Vue 基于组件级别的 Diff 计算
- 第五遍 2025.1.25 -
❌ // 1、Vue 少写了静态节点跳过 + 基于组件级别的更新算法
❌ // 2、React 是基于单个节点属性级别,适合大型冬天复杂场景
- 第四遍 2025.1.24 -
❌ // 1、Vue - 找不到匹配的节点后,先找同类型+同key的节点进行索引比较,一致则复用,不一致则移动到新位置修改复用
- 第三遍 2025.1.18 -
❌ // 1、Vue - 双端指针算法比较后,是找同类型和同key的节点比较
❌ // 2、Vue - 少写了:静态节点跳过比较
- 第二遍 2025.1.13 -
1.30 - LTN1 - 2题
✅作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序) ✅2023.07 clickhouse、mysql、mongodb异同
LTN3 ❌作业3:迭代器原理 ✅作业41:TED 如何解决焦虑
LTN4 ✅作业4:什么是生成器?有什么作用? ✅作业18:多进程浏览器会开启几个进程(5个点)?相同站点的页面,可以复用么? ✅作业19:发起请求后,得到 301,有效信息是哪些? ✅作业21:下面这段代码输出结果是什么?为什么
console.log(11, fun1);
function fun1(n1, n2) { arguments[1] = 10; return n1 + n2; }
var fun1 = 1;
console.log(22, fun1)
✅作业22:分别输出什么?
function foo(){
var a=1;
let b=2;
{
let b=3;
var c=4;
let d=5;
console.log(a);
console.log(b);
}
console.log(b);
console.log(c);
console.log(d);
}
foo();
❌作业24:写出事件循环系统的流程图,常见宏任务、微任务有哪些。并写出几个常见题目的输出结果
Promise.resolve().then(() => {
console.log(1);
Promise.resolve().then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
})
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
------
async1 = async () => {
console.log('async1 start');
await async2();
console.log('async1 end');
}
function async2(){
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
})
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
return new Promise(function(resolve) {
resolve();
})
}).then(() => {
console.log('promise3');
})
console.log('script end');
✅作业27:写一个 sleep 函数 ✅作业28:写出 js 按什么顺序执行
<script src='./a.js' defer></script>
<script src='./b.js'></script>
<script>
console.log('event start');
document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded finish');
})
window.addEventListener('load', () => {
console.log('onload finish');
})
</script>
<body>
<div>
<script>
let i = 0;
while(i<1000) {
i++;
}
console.log('compute finished');
</script>
</div>
</body>
✅作业35:写出HTPPS和HTTP的差异?对称加密和非对称加密有什么异同?混合加密是怎么做的? ✅2019.07 css中的动画特性可以用js实现,那为什么还要用css来实现? ❌2021.06 子盒子在父盒子中水平垂直居中有几种方法? ✅2024.10 第十一章 期约与异步函数 小结 ✅2019.06 随机给一个盒子添加一个十六进制的颜色
// 2019.06 随机给一个盒子添加一个十六进制的颜色
function addColor() {
let res = '#';
const arr = ['0', ...,'9','a', ... 'f'];
Array(6).forEach(it => {
const index = Math.floor(Math.random() * 16);
res += arr[index];
})
return res;
}
// 2019.07 css中的动画特性可以用js实现,那为什么还要用css来实现?
- 第一遍 2025.1.30 -
js 实现动画: 占用主线程执行;低版本浏览器可能需要写兼容性代码;
css 实现动画: 有些 transform、opacity 只需要合成线程,并且可以开启 GPU 加速;加载完 css 就可以开始渲染动画,不用等 js 执行;优雅降级
⭐️ // 避免回流重绘 + 自然降级 + 硬件GPU加速 + 预扫描加载快
// 作业35:写出HTPPS和HTTP的差异?对称加密和非对称加密有什么异同?混合加密是怎么做的?
- 第一遍 2025.1.30 -
HTTPS = HTTP + SSL/TLS,把 TCP 层改为 SSL/TLS 层
对称加密 - 只有一个密钥用于加密、解密
非对称加密 - 公钥可以发放,客户端加密使用公钥,私钥用于解密,只有服务端独有
混合加密 - 使用非对称加密商讨出对称加密使用的密钥 ⭐️ // 协商
// 作业28:写出 js 按什么顺序执行
- 第一遍 2025.1.30 -
b.js - event start - compute finished - a.js - DOMContentLoaded finish - onload finish
// 作业27:写一个 sleep 函数
- 第一遍 2025.1.30 -
async function sleep(delay){
return new Promise((resolve) => setTimeout(() => resolve(), delay));
⭐️ // 不用写 async + setTimeout 可以简写成 setTimout(resolve, delay)
}
await sleep(100);
// 作业22:分别输出什么?
- 第一遍 2025.1.30 -
1 3 2 4 error
// 作业21:下面这段代码输出结果是什么?为什么
- 第一遍 2025.1.30 -
11 function
22 1
执行上下文中,函数内先创建 auguments、再函数提升、最后变量提升
// 作业19:发起请求后,得到 301,有效信息是哪些?
- 第一遍 2025.1.30 -
Location 重新导航
// 作业18:多进程浏览器会开启几个进程(5个点)?相同站点的页面,可以复用么?
- 第一遍 2025.1.30 -
浏览器进程、渲染进程*n、插件进程*n、网络进程、GPU进程
同协议、同域名、同端口 可以复用当前渲染进程
// 作业4:什么是生成器?有什么作用?
- 第一遍 2025.1.30 -
生成器:暂停、恢复代码执行,* 声明,yield next
自定义可迭代对象、模拟协程
// 2023.07 clickhouse、mysql、mongodb异同
- 第二遍 2025.1.30 -
clickhouse: 列式数据库,支持异步操作,读写性能都好,适合大型动态场景 ⭐️ // 高吞吐、低延迟
mysql:关联型数据库,支持事务操作,读写性能好,在并发操作时,会出现数据一致性问题
mongodb:文档型数据库,存储半结构化/非结构化数据,读的性能好,写的性能弱,在异步操作时,有数据一致性问题 ⭐️ // 分布式场景下一致性问题
- 第一遍 2025.1.22 -
// 作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序)
- 第二遍 2025.1.30 -
SELECT DISTINCT t.name,
CONCAT('(', t.interval, ')') AS interval,
UPPER(t.type) AS type,
COUNT(*) AS with_null,
COUNT(t.name) AS without_null,
AVG(t.gap) AS avg // -- MAX MIN SUM
FROM Times as t, Issues as i
WHERE t.id = 1 AND
(t.price > 10
OR it.price IS NULL
OR it.price BETWEEN 5 AND 10,
OR it.price in (1,2),
OR YEAR(it.date) = 2025)
GROUP BY t.date
SORT BY t.date
- 第一遍 2025.1.23 -
// 作业41:TED 如何解决焦虑
- 第二遍 2025.1.30 -
1、列出所有可以解决问题的方案,去做 do it badly ⭐️ // anything worth doing worth doing badly the first time
2、给自己一些同情 ⭐️ // 我们不会和天天批评自己的人交朋友,我们也不要做自己这样的朋友
3、找到意义 ⭐️ // 即使别人不知道,自己一定要认可自己的行为
- 第一遍 2024.12.25 -
1.29 - LTN1 - 4题
❌作业66:React如何处理错误 ✅作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用 ✅作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询 ✅作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别?
// 作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别?
- 第二遍 2025.1.29 -
path.join() 纯拼接
path.resolve() 拼接绝对路径 / ⭐️ // 绝对路径解析
process.cwd() 获取 node 执行环境的当前目录
__dirname 当前文件的所处目录
- 第一遍 2025.1.22 -
// 作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询
- 第二遍 2025.1.29 -
INNER JOIN 内联查询 取AB表都满足条件的行,即交集 ⭐️ // 内连接
LEFT OUTER JOIN 左表查询 取左表满足条件的行及右表补充的行
RIGHT OUTER JOIN 右表查询 取右表满足条件的行及左表补充的行
UNION 连接两次查询 ⭐️ // 拼接
封装 CREATE VIEW mySearch AS SELECT ...
下次使用的时候直接 FROM mySearch
- 第一遍 2025.1.22 -
// 作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用
- 第二遍 2025.1.29 -
Redux 将全局状态保存在 store 对象树中,通过 action 才能 dispatch 改变 store ⭐️ // 改变 state
RTK - Redux tollkit 官方推荐依赖
reducer: 使用 旧的state 和 action 计算得到 新的state
import { crateStore } from 'redux';
const reducer = (oldState = { value: 1 }, action) => {
if(action.type === 'add') { return { value: oldState.value + 1 } }
}
const store = createStore(reducer);
store.dispatch({ type: 'add' });
store.subscribe(() => { console.log(store.getState()) })
- 第一遍 2025.1.22 -
1.28 - LTN1 - 2题
✅作业20:浏览器打开一个 URL 发生了什么 ✅2021.09 Object属性排序、与Map的区别
// 作业20:浏览器打开一个 URL 发生了什么
- 第三遍 1.28 -
1、浏览器进程 UI线程:将用户输入内容或者链接转换成 URL
2、通过 IPC 协议传递给网络进程
网络进程:① 查询本地缓存,有缓存且有效则 强缓存生效
②无缓存/有缓存但已过期,DNS 解析得到 IP
③TCP 队列排队,三次握手,进行连接
④组装请求:请求头、cookie 等信息
⑤发起请求
响应 状态码:301、302 重定向,获取响应头中的 Location 进行重新跳转
304 Not Modified,使用缓存,并刷新本地缓存时间
200 响应类型是 os-stream 下载类型,直接触发下载,停止导航
响应类型是 html,和浏览器进程建立管道,边下载边解析
3、渲染进程
①分为主线程、合成线程、预解析线程,解析 html 时,开启预扫描线程扫描 js、css,提前下载
②管道传输完毕后,通知浏览器进程,更新 页面、前进后退、url、安全锁
③下载完成,解析完成之前 - 解析白屏
主线程:解析DOM(DOM树) - 计算样式表 - 布局树 - 分层树 - 绘制(指令列表
合成线程: - 栅格化(图块合成位图)[会开启 GPU 加速]
浏览器进程 UI线程:- 合成(帧
- 第二遍 1.20 -
❌ // 1、IPC 协议名错了 2、有缓存但缓存失效也继续请求 3、请求的响应,应该先判断状态码再判断类型
// 2021.09 Object属性排序、与Map的区别
- 第三遍 1.28 -
正整数 > 字符串 > 负数 > 浮点数 > Symbol
区别:有序;属性可以是任意类型;m.size();大数据,占用内存小,增删快;可迭代对象
- 第二遍 1.21 -
1.27 - LTN1 - 8题
✅作业2:支持迭代器的原生语言特性有哪些? ✅作业43:TED 怎么提升自信 ✅作业42:TED 如何和大脑正确交流沟通 ✅作业39:shim 和 polyfill 区别 ❌作业6:new操作符实例化一个对象的过程 ✅2019.07 为什么要清除浮动?怎么清除浮动? ✅2021.06 CSS 选择器 - 权重/匹配方式
// 2021.06 CSS 选择器 - 权重/匹配方式
- 第二遍 1.28 -
!important > 行内 > 内联、外联(只和加载顺序有关
#id > .red a[href] :hover LVHA > div ::after > *
- 第一遍 1.20 -
// 2019.07 为什么要清除浮动?怎么清除浮动?
- 第二遍 1.28 -
对于浮动元素,块级元素把它当做不存在;行内块元素,环绕布局
父组件中的子组件浮动时,会造成高度塌陷,父级元素的背景无法撑开,padding和border展示异常
清除浮动:①给父级元素添加高度 ②父盒子里最后添加一个盒子 .box {clear:both} ③父盒子添加伪元素 .father::before{ content:''; display: block; clear: both; } ④BFC .father{ overflow: hidden } - BFC 的盒子计算高度时包含浮动元素
- 第一遍 1.20 -
❌ // 1、写错了:是给父元素添加BFC + 父盒子设置高度(宽度不行
// 作业39:shim 和 polyfill 区别
- 第二遍 1.28 -
shim:垫片,提供新API,使用原生功能实现,处理兼容问题 ⭐️ // 优雅降级
polyfill:补丁,实现浏览器原生功能,不提供新的API ⭐️ // 实现原生 API 中缺少的功能
- 第一遍 1.20 -
// 作业42:TED 如何和大脑正确交流沟通
- 第二遍 1.28 -
1、I like it, I want it, I've chosen it!
2、Make it familiar!
大脑只会响应我们给它的语言 + 图像(具体的);大脑天然追寻快乐,所以把想做的事情绑定到巨大的快乐中,不想做的事情、停留在原地,绑定痛苦;大脑喜欢熟悉的东西,做得越多越能够坚持
- 第一遍 1.20 -
// 作业43:TED 怎么提升自信
- 第二遍 1.28 -
1、repetition!repetition!repetition!
2、不要批评自己,鼓励、表扬做得好的地方 - 引导自己
3、用自己的语言解释世界
- 第一遍 1.20 -
// 作业2:支持迭代器的原生语言特性有哪些?
- 第三遍 1.28 -
Array.from ...扩展运算符 数组解构 for...of new Set new Map yield * Promise.all/race
- 第二遍 1.25 -
- 第一遍 1.20 -
❌ // 1、少写了:yield * 操作符 - 只能在生成器中使用;不是对象解构,是「数组解构」
LTN①④
❌2019.07 为什么要清除浮动?怎么清除浮动? ❌2023.07 clickhouse、mysql、mongodb异同 ❌作业70:Vue 和 React 的 Diff 算法比较 ❌作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询 ❌作业87:Nextjs 中预渲染是什么?有哪些模式?实现一个动态路由的预渲染-一个客户端一个服务端 ❌作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接
LTN①④ 错题重做 - 23题 - 2025.1.25-1.26 4h50m
✅作业2:支持迭代器的原生语言特性有哪些? ✅作业43:TED 怎么提升自信 ✅作业42:TED 如何和大脑正确交流沟通 ✅作业39:shim 和 polyfill 区别 ✅作业6:new操作符实例化一个对象的过程 ❌2019.07 为什么要清除浮动?怎么清除浮动? ✅2021.06 CSS 选择器 - 权重/匹配方式 ✅作业20:浏览器打开一个 URL 发生了什么 ✅2021.09 Object属性排序、与Map的区别 ✅2021.07 bind、apply/call三者异同+apply/call实现bind ✅2021.07 数组扁平化(一层、全部展开、指定深度) ❌2023.07 clickhouse、mysql、mongodb异同 ✅作业66:React如何处理错误 ❌作业70:Vue 和 React 的 Diff 算法比较 ✅作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情? ✅作业80:对DOM 树的理解 - 定义和作用 ✅作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用 ✅作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序) ❌作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询 ✅作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别? ✅作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更? ❌作业87:Nextjs 中预渲染是什么?有哪些模式?实现一个动态路由的预渲染-一个客户端一个服务端 ❌作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接
// 作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接
- 第二遍 1.26 -
mysql2: 专为 node 设计的 mysql 驱动程序,可以直接连接数据库,要写大量 SQL
⭐️ // 提供了 mysql2/promise 模块,允许 async/await + 可以管理多个数据库的连接
Sequelize:ORM 将数据库映射成对象,提供封装的函数处理,上手快
⭐️ // 被映射成对象的是 数据库的 表和行
import { createPool } from 'mysql2';
❌ // 1、依赖包错了,是从 import mysql from 'mysql2/promise'
const pool = await createPool(database, host, password, {});
❌ // 2、创建连接池时不需要 await,只是创建连接 + 传递对象而不是具体顺序的参数 const pool = mysql.createPool({host, user, password, database})
const mysql = pool.connect();
❌ // 3、获取连接时需要 await 来等待连接成功 + 函数错了 const connection = await pool.getConnect();
❌ // 4、查询数据 const [rows] = await connection.execute('SELECT * FROM table WHERE ...')
创建连接后,会一直保持连接状态,直到手动关闭 mysql.close() ❌ // 5、错误,是 Sequelize 的情况
import { sequelize, modal, DataTypes } from 'sequelize';
⭐️ // 导入 Sequelize Modal - 用首字母大写
sequalize.init(database, { host, password })
❌ // 6、创建连接不是用 init 函数(这是定义模型属性的方法),是实例化创建连接 const sequelize = new Sequelize(database, user, password, { host: 'localhost', dialect: 'mysql' })
❌ // 7、这里就是上面记错的:创建连接后,将一直保持打开状态,并对所有查询使用相同状态,关闭连接需要调用实例关闭 sequelize.close()
class MyModal extends modal {
constructor() {
return {
date: {
type: DataTypes.DATE,
allowNull: false
}
}
}
}
❌ // 8、定义模型是扩展 Modal - 先继承Modal,再init定义模型
/**
class MyModal extends Modal{}
MyModal.init({
date: {
type: DataTypes.DATE,
allowNull: false
}
}, {
sequelize,
modalName: 'MyModal'
})
*/
this.modal.MyModal.findAll()
- 第一遍 2025.1.22 -
mysql2: mysql 驱动,轻量,需要手动写大量原生 SQL
⭐️ // 专为 Node 设计的 MySQL 驱动程序
❌ // 1、少写了:可以直接连接 MySQL 服务器 以及 提供的两个连接特点:①提供了 mysql2/promise 模块,支持 async/await 的异步操作 + ②支持创建连接池,管理多个数据库的连接
❌ // 2、缺点少写了:没有内置的对象关系映射 ORM 功能
Sequelize:提供统一事务操作,使用函数包装 SQL,更适合函数式编程
❌ // 3、强大的 ORM 功能,可以将数据库的表和行「映射为对象/模型」,直接通过 js 操作 - 会被自动转换成 SQL
连接方式 ?
❌ // 4、代码写法真的不记得了
import mysql from 'mysql2/promise';
const pool = mysql.createPool({host, user, password, database}); // 创建连接池
const connection = await pool.getConnection(); // 获取连接
const [rows] = await connection.execute('SELECT * FROM users WHERE id = 1'); // 查询
import { Sequelize, DataType, Modal } from 'sequelize';
const sequelize = new Sequelize(database, user, password, {
host: 'localhost',
dialect: 'mysql'
})
// 创建连接,一直保持连接状态,直到实例关闭 sequelize.close();
// 模型是 Sequelize 的本质
class User extends Modal {}
User.init({
name: { type: DataType.STRING, allowNull: false },
age: { type: DtaType.NUMBER }
}, {
sequelize,
modelName: 'User'
})
// 作业87:Nextjs 中预渲染是什么?有哪些模式?实现一个动态路由的预渲染-一个客户端一个服务端
- 第二遍 1.26 -
预渲染机制主要包含 SSR服务器渲染、SSG 静态生成、ISR 增量静态生成
SSR-在请求时,在服务端将需要引入的动态数据打包进 html,返回给浏览器直接解析,而不是交给客户端的 js 处理
SSG-在构建时,生成 html + revalidate 配置验证有效时间,时间到期后,框架后台重新渲染
SSG
export default function Blog ({ params }) => {
return <div> { params.slug } </div>
}
export async function generateStaticParams() {
return [ {slug: 'first'}, {slug: 'second'} ]
}
export const revalidate = 60;
SSR
⭐️ // import { getServerSideProps } from 'next/server';
export async function getServerSideProps() {
const data = fetch('xxxx'); ❌ // 1、少写了 await:const data = await fetch('xxxx')
❌ // 2、const res = await data.json() - 异步解析数据,处理JSON数据转换为 js 对象
return { props: data.json() }
}
export default function Blog (props) => {
return <div> {props.text} </div>
}
- 第一遍 2025.1.22 -
❌ // 1、什么是预渲染 - 预渲染机制主要是 SSR服务端渲染、SSG「静态生成」、ISR 增量静态生成
-SSG 构建时预渲染 + 静态资源增生 ISR ❌ // 2、名词记错了:SSG 静态生成、ISR 层两静态生成
getStaticProps ❌ // 3、这是13.x以下版本使用的函数 - 已改为 generateStaticParams,主要用于生成动态路由的参数列表
revalide - 设置预渲染的更新时间,超过设定时间后,框架在后台重新渲染,生成新的后,下次访问使用新的渲染
❌ // 4、单词错了 revalidate - 是「页面重新验证时间」- 页面将重新生成,运行时 生成/更新 静态页面以增量更新 ISR
-SSR 服务端预渲染 - 在服务端先构建把数据注入 js 包裹进 HTML,返回给浏览器时直接进行解析渲染即可,不交给客户端的 js 处理了 ⭐️ // 在每次请求时,在服务端重新渲染 HTML 并嵌入拉取到的动态数据
getStatexxxx ❌ // 5、getServerSideProps
export function Blog(props) {return <div>{props.text}</div>}
export const revalid = 60;
export function getStaticProps() {}
export function getStatexxxx() {}
❌ // 6、上述单词都错了,准确写法如下:
/**
SSG - src/app/[slug]/page.js
export default function Blog({ params }) { return <div> hello,{params.slug} </div> }
export const revalidate = 60;
export async function generateStaticParams(){
return [{slug: 'first'}, {slug: 'second'}]; // 返回生成动态路由的参数列表
}
SSR
import { getServerSideProps } from 'next/server';
export default function SSRPage({ data }){return <div>{ data }</div>}
export async function getServerSideProps(context){
const res = await fetch('xxxx');
const data = res.json();
return {
props: { data } // 作为组件的 props 传递给当前页面的组件
}
}
*/
// 作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更?
13.x 开始 Link 标签在底层被解析为 a标签,所以之后使用的时候 Link 里面不用写 a 标签了
文件路由系统:src/app/page.tsx layout.tsx 用于统一全局布局
后端路由系统:src/app/api/xxx/route.tsx
- 第一遍 2025.1.22 -
Nextjs 13.x 开始,Link 中不再使用 a 标签,因为框架把 Link 标签直接解析为 a 标签
路由从之前的 pages/ 路由模式 改为 src/app 下
优点:自定义统一布局 layout.tsx + global.css
❌ // 1、只写了「文件路由系统」的变更,还少写了「后端路由系统」的变更 从 pages/api 变更为 src/app/api
// 作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别?
- 第二遍 1.26 -
path.join() 拼接路径
path.resolve() 拼接绝对路径 ⭐️ // 把 / 当做根路径
process.cwd() 当前 node 指令执行的当前环境
__dirname 当前文件所在目录
- 第一遍 2025.1.22 -
path.join(a,b):用于拼接两个地址 a/b ⭐️ // 纯拼接
path.resolve(a,b):解析地址 a/b ❌ // 1、是「绝对路径解析」,把 / 当做根目录 -> /a/b
process.cwd():文件的当前路径 ❌ // 2、node 执行时所在目录
__dirname:文件的当前路径
// 作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询
- 第二遍 1.26 -
INNER JOIN 内联查询,AB表都满足条件的行,相当于取交集
LEFT OUTER JOIN 左联查询,A 表满足,及B表相关
RIGHT OUTER JOIN 右联查询,B表满足,及A表相关
UNION 拼接两次查询结果
封装 CREATE VIEW mySearch SELECT a from table ❌❌ // 1、少了一个 AS
下次使用 SELECT a from mySearch
- 第一遍 2025.1.22 -
INNER JOIN:求交集,两个表都符合条件的行 ⭐️ // 内连接
LEFT OUTER JOIN:左表所有符合筛选条件及关联的右表的数据
RIGHT OUTER JOIN:右表所有符合筛选条件及关联的左表的数据
UNION:拼接上下两个查询
封装一个 SQL:create mySelect SELECT name from table;
❌ // 1、create View MY AS 关键词:Create View ... AS
select name from mySelect
// 作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序)
- 第二遍 1.26 -
SELECT DISTINCT t.date,
CONCAT('(', t.title, t.source,')') AS title,
UPPER(t.weekday) AS week,
COUNT(*) AS total_with_null,
COUNT(t.date) AS tota_without_null,
SUM(t.interval) AS sum,
MAX(t.interval) AS max,
MIN(t.interval) AS min,
AVG(t.interval) AS avg,
FROM ltn_time AS t, ltn_data AS d
WHERE d.id = 1,
AND (d.price > 10
OR d.price IS NULL
OR d.price BETWEEN 5 AND 10
OR d.price IN (1, 2)
OR YEAR(d.date) = 2025)
GROUP BY d.date
SORT BY d.source DESC
- 第一遍 2025.1.23 -
❌ // 1、由于 from 中有两张表,如果查询的 column 名在两张表都存在,有重名就会报错 - 所以查询的时候最好声明表名 t.name1 t.age
SELECT constinct name1, age, ❌ // 2、关键词错了 distinct
concat(sex, height) as answer2, // ⭐️ concat(t.sex, '(', t.height, ')') as answer2
toUpperCase(weight3) as weight3, ❌ // 3、函数名错了 upper
count(name) as name42, count() as name41, ❌ // 4、关键词参数错了 包含 null - 查询时传递 count(*) as name41_with_null;不包含 null - 查询时包含 column 名是对的
sum(age) as age5, max(age) as max5, min(age) as min5, avg(age) as avg5
from table as t, sex as s
where t.id = 1
And
(t.price > 10
or t.price = Null ❌ // 5、关键词错了:IS 操作符主要用于判断某个值是否为 NULL,且 t.price = null 的比较结果始终是 UNKNOWN, 因为 NULL 表示未知的值,无法确定它是否和另一个值相等 - t.price is null
or t.price between 5 to 10 ❌ // 6、关键词错了 between-and:t.price between 5 and 10
or t.price in [1,2] ❌ // 7、关键词错了,应该是():t.price in (1,2)
or year(s.time) = 2025)
group by age
sort age desc ❌ // 8、关键词错了 sort by
// 作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用
- 第二遍 2025.1.26 -
Redux 核心是将整个应用的状态全部由 store 管理,只能通过 action dispatch 变化
reducer 通过旧的 state 和 action 生成新的 state - 返回新的 state 而不是改变
RTK Redux ToolKit 官方推荐的核心工具包
import { createStore } from 'redux';
const reducer = (oldState = { value: 'old' }, action) => {
if(action.type === 'add') {
return { value: 'new' }
}
}
const store = createStore(reducer); ⭐️ // dispatch subscribe getState
store.dispatch({ type: 'add' });
store.subscribe(() => { console.log(store.getState()); }) ⭐️ // 可以更新 UI
- 第一遍 2025.1.22 -
将全局状态以对象树的形式存储在 store 中;
reducer:(oldState, action) => newState
action:用于描述 state 变化,redux 中要改变 state 只能通过把 action dispatch 到 store
RTK:redux toolkit 核心包
const { createStore } from 'redux';
const reducer = (state={val:0}, action) => {if(action.type){return {val: state.val+1}}}
const store = createStore(reducer); // dispatch getState ? ❌ // 1、少一个 subscribe
store.dispatch({type: true});
❌ // 2、通过 subscribe 更新 UI 来响应 state:store.subcribe(() => {console.log(store.getState())})
// 作业80:对DOM 树的理解 - 定义和作用
- 第二遍 2025.1.25 -
DOM 树是 HTML 文件结构的对象结构表示,标签的属性以对象属性存储 ⭐️ // 对象化表示,树形结构对象模型
对 js 而言:DOM 树提供了可以访问、修改 HTML 的交互接口 ⭐️ // 编程接口
对框架而言:VDOM 树提供了机会可以先从 VDOM 计算最小更新量 Diff 来更新最新UI,提高更新性能
对浏览器而言:浏览器根据 DOM 渲染 HTML 结构 ⭐️ // DOM 是渲染页面的依据
- 第一遍 2025.1.24 -
用对象树描述 HTML,标签的属性就是 DOM 节点的属性值
⭐️ // DOM 是 HTML 文档的「对象化表示」,是按照 HTML 结构转换的树形结构的对象模型
提供了浏览器对 HTML 操作入口,获取 DOM 节点进行操作 ⭐️ // 提供了一个编程接口 - DOM 是 js 可以访问、修改 HTML 的基础
❌ // 1、除了 js,还少写了 浏览器 + 框架 视角
/**
对浏览器而言,DOM 是「渲染页面的依据」,浏览器根据 DOM 结构构建页面布局
对框架而言,框架通过比较 虚拟DOM 和实际 DOM 树的差异,以最小 UI 更新量,提高页面更新性能
*/
// 作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情?
- 第五遍 2025.1.25 -
挂载阶段 ⭐️ // 就是组件的创建和首次插入 DOM
constructor 初始化 state 和绑定事件处理函数的 this
getDerivedStateFromProps、render、componentDidMount 首次渲染
更新阶段
shouldComponentUpdate(nextProps, nextState)
getDerivedStateFromProps、render、componentDisUpdate(prevProps, prevState)
卸载阶段
componentWillUnMount 卸载前,清理监听、定时任务等
⭐️ // 函数组件 useEffect useLayoutEffect
- 第四遍 2025.1.24 -
类组件
挂载阶段:⭐️ // 组件的创建 + 首次插入 DOM
shouldComponentUpdate(nextProps, nextState) - 自定义布尔值,是否重新渲染 ❌❌❌❌ // 3、应该归属于更新阶段
getDerivedStateFromProps - 从 props 更新 state
render - 初次渲染
constructor - 初始化 state 和 绑定事件处理函数的 this
渲染阶段: ❌❌❌❌ // 1、不是渲染阶段,是更新阶段 - 当组件的 state/props 发生变化,进入更新阶段,重新执行 render 函数来更新 DOM
componentDidMount - 第一次渲染执行 ❌❌❌❌ // 2、应该归属于 挂载阶段
componentDidUpdate(prevProps, prevState) - 更新时渲染 ⭐️ // 更新后
render - 重新渲染
卸载阶段:componentWillUnMount - 卸载前
函数组件
useEffect - 模拟componentDidMount,依赖空数组[],初次渲染时执行内部函数
- 模拟componentDidUpdate,更新依赖数组内容,重新执行内部函数
- 模拟componentWillUnMount,返回一个清理函数,在卸载前被调用
useLayoutEffect - 在commit挂载到 DOM 之后执行,但是是同步的,阻塞重绘,直到执行完毕
- 第三遍 2025.1.19 -
挂载阶段 ⭐️ // 创建组件+首次挂载
❌❌❌ // 1、少写:constructor - 初始化 state 和 绑定事件处理函数的 this
getDerivedStateFromProps - 从 props 更新 state
render - 触发渲染,生成 VDOM,diff 后挂载到真实 DOM
componentDidMount - 挂载DOM后 ⭐️ // 只在首次渲染执行
更新阶段 ⭐️ // 当组件的 state/props 更新
shouldComponentUpdate(nextProps, nextState) - 返回布尔值,自定义是否需要重新渲染
getDerivedStateFromProps - 从 props 更新 state
render - 更新,渲染,diff 后挂载到真实 DOM
componentDidUpdate(prevProps, prevState) - 更新后
卸载阶段
componentWillUnMount - 卸载前,一般用于清理,避免内训泄漏
❌❌❌ // 2、上面的都是「类组件」的生命周期,应该还要意识到「函数组件」也是通过 hooks 来模拟生命周期了
/**
useEffect - 模拟 componentDidMount - 组件首次渲染时,内部函数执行
- 模拟 componentDidUpdate - 依赖数组传入 state/props,变化时内部函数再次执行
- 模拟 componentWillUnMount - 可以返回一个清理函数,组件卸载时执行
useLayoutEffect 在DOM 更新后,副作用在 commit 阶段同步执行,阻塞「重绘」
*/
- 第二遍 -
❌❌ // 1、不应该以渲染阶段(Render Phase:render-commit)来进行回答,应该以生命周期阶段(Lifecycle Phases:挂载-更新-卸载)来回答
render:
类组件 shouldComponentUpdate(nextProps, nextState) - 自定义是否需要更新
❌❌ // 1、shouldComponentUpdate 不是在 render 阶段执行的,是在更新阶段,用于确定当前组件是否需要渲染
按序执行所有 hooks
函数组件 useEffect - 根据依赖是否变更,确定是否有副作用
commit:Diff 计算出的最小更新 UI 应用到真实 DOM 后,执行几个生命周期:
执行副作用链表
类组件 componentDidMount - 首次,挂载后执行 ⭐️ // 一般用于初始化数据
类组件 componentDidUpdate - 更新,挂载后执行 ⭐️ // 参数是(prevProps, prevState) 一般用于操作 DOM 和执行副作用
类组件 componentDidUnMount - 卸载前更新 ❌❌ // 2、不是 Did 是 Will,componentWillUnMount 清理定时器、取消监听,避免内存泄漏
函数组件 useLayoutEffect - 和 useEffect 的唯一差别是时机,回调在挂载到 DOM 之后执行,并且是同步的,会阻塞渲染主线程
❌❌ /** 3、重新简单梳理下 类组件的三个生命周期阶段
挂载阶段-创建组件+首次挂载到DOM:初始化constructor()、static getDerivedStateFromProps->纯函数render()->挂载后componentDidMount
更新阶段-重新执行render构建新VDOM:static getDerivedStateFromProps->shouldComponentUpdate->render()->更新后componentDidUpdate()
卸载阶段:卸载前componentWillUnMount
*/
❌❌ // 4、函数组件 主要是使用 hooks 来模拟实现生命周期 useEffect + useLayoutEffect
- 第一遍 -
react 的生命周期主要是针对 类组件 来讲的,函数组件通过 useEffect 等hooks 实现
❌ // 1、对也是对的,但是函数组件的生命周期还是可以「展开讲讲」
① componentDidMount - 首次渲染,在 commit 阶段,Diff 计算出的最小更新量 应用到真实 DOM 后触发,一般用于 初始化数据
② componentDidUpdate - 更新,commit 阶段,更新真实 DOM 后触发
❌ // componentDidUpdate(prevProps, prevState),一般用于「操作DOM」和「执行副作用」
③ componentDidUnMount - 卸载组件之前调用,一般用于清理定时器、解除监听等,避免内存泄漏
❌ // componentWillUnMount
④ shouldComponentUpdate - 自定义组件是否重新渲染,传入的参数为 新的状态和属性
⭐️ // shouldComponentUpdate(nextProps, nextState)
❌ // 2、不止上面四个,还有 constructor()、render(),分三个阶段来描述:
// ⑤constructor - 类组件的构造函数,用于初始化组件状态 state + 绑定事件处理函数 + super(props) 传递 props
// ⑥render - 渲染函数,返回一个React元素
❌ // 3、分为三个阶段,分别执行对应的生命周期
// 挂载阶段:⑤constructor()、①componentDidMount、⑥render()
// 更新阶段:④shouldComponentUpdate、⑥render()、②componentDidUpdate
// 卸载阶段:③componentWillUnMount
❌ // 4、函数组件的生命周期
❌ // ① useEffect - 模拟了三个生命周期
// 模拟componentDidMount - 当组件首次渲染时,useEffect 内部的函数执行
// 模拟componentDidUpdate - 通过在 useEffect 的依赖数组中传入状态或属性,依赖发生变化时,内部函数再次执行
// 模拟componentWillUnMount - useEffect 可以 return 一个函数作为清理函数,会在组件卸载时执行
❌ // ② useLayoutEffect - [commit阶段]在DOM更新后,浏览器下一次重绘前,同步执行 - 会阻塞重绘
// useEffect - [commit阶段]在DOM更新后,异步执行 - 适合不依赖DOM布局的副作用,例如数据获取、订阅事件等
// 作业70:Vue 和 React 的 Diff 算法比较
- 第五遍 2025.1.25 -
Vue:双端指针算法 - 同类型+同key 索引 - 移动复用 ❌❌❌❌❌ // 1、少写了:静态节点跳过 ;是基于组件级别的更新算法
React:分层比较 + 同类型优先比较(不同标签直接重新渲染;相同标签,更新属性;列表组件-同类型同key复用 ❌❌❌❌❌ // 2、少写了:是基于单个节点属性级别的,适合大型动态复杂场景
- 第四遍 2025.1.24 -
Vue 1、双端指针算法匹配
2、找不到匹配的点,则判断索引是否一致可复用 ❌❌❌❌ // 1、找不到匹配的点,找到同类型+同 key的节点,进行索引比较,一致则复用,不一致则移动到新位置修改使用
3、列表组件 key+同标签
4、静态节点优化跳过
React 分层比较+同类型优先比较,同类型同key复用,同类型则更新属性,不同类型重新渲染
⭐️ // React 是精确到单个节点属性级别,适合大型动态复杂场景; Vue 基于组件级别的更新算法
- 第三遍 2025.1.18 -
React:⭐️ // 精确到「单个节点属性级别」,适合大型复杂动态场景
分层比较 + 同类型优先比较
- 分层比较时,不同类型则重新渲染
- 相同类型,则更新属性
- 列表组件:同层级相同类型 + key 相同,则移动到当前位置来复用
Vue:⭐️ // 基于「组件级别」更新的算法
- 双端比较算法,找寻匹配的节点
- 找不到匹配的,先查找索引,相同索引则直接复用 ❌❌❌ // 2、找到同类型+同key来比较
- 列表组件:同类型 + key 相同,复用
❌❌❌ // 1、静态节点跳过比较 - 不要遗漏了,是个很重要的性能优化
- 第二遍 2025.1.13 -
React Diff:
比较虚拟 fiber 树和真实 UI 对应的 fiber 树,深度优先遍历
⭐️ // 是精确到单个节点属性级别的算法,适合大型复杂动态场景
分层同类型优先比较:如果同层级,标签相同,则更新当前节点属性即可;
如果标签不同,则标记重新渲染
如果同层级,标签相同,且 key 相同,则直接复用,移动到当前位置
⭐️ // 「列表组件,根据 key 确定新老节点关系」
Vue Diff:
⭐️ // 是基于组件级别更新的算法,适合简单场景
双端指针法:比较两棵 VDOM 树,分别从首尾双端进行比较,如果标签相同则修改属性;标签不同则重新渲染
❌❌ // 1、双端比较算法是直接找匹配节点,不进行 标签修改
如果找不到匹配的 DOM 节点,则按 key 查找相同标签节点,进行复用
❌❌ // 2、key 是第三层比较逻辑,第二层比较逻辑是「索引」 - 若索引没有变化,则不更新当前节点;如果索引有变化,判断是否「同类型 + 同key」,移动到新位置
❌❌ // 3、性能优化,编译时标记的静态节点跳过 - 虽然老生常谈,但是这其实是一大优化
- 第一遍 -
React:
❌ // 1、简单描述下 React 的 Diff 算法是「精确到单个节点的属性级别」,适合「大型复杂场景」,例如大量动态组件和频繁更新的场景
1、从根节点开始比较,采用分层比较
❌ // 2、对,这里可以标题记忆 「① 分层比较 + 同类型比较优先策略」
2、同类型节点,则比较属性的变更;不同类型则停止比较,标记为需要重新渲染
3、同类型+同key 的节点进行复用
❌ // 3、这里主要是指:「列表组件」中,根据 key 确定新老树子节点的关系
❌ // 4、少写了「② DFS 深度优先遍历子节点」
Vue:
❌ // 5、描述:「更新基于组件级别」
1、比较新老两棵 VDOM 树,采用双指针,分别从新老两棵树的头尾开始比较,适用于小型应用快速计算出最小更新量 UI
❌ // 6、双指针其实很不明确,「双端比较算法-双向指针」
2、同类型+同key 的节点进行复用
❌ // 7、是会复用,但是场景是「双端比较法没有找到足够匹配的节点,就根据节点的索引进行比较」
// 8、检索「同类型+同key 的节点」,「索引位置」没有变化,就认为这个节点不需要更新;若有变化,就「移动到新位置」,「复用旧列表」
3、跳过比较静态编译标记的静态节点,加快比较速度
// 作业66:React如何处理错误
- 第六遍 2025.1.25 -
React 提供了两个函数用于处理错误,为了让错误不影响到整个应用,能够正常展示 UI,捕获 render commit 阶段的错误
static getDerivedStateFromError 返回 state 用于更新 UI;componentDidCatch 用于操作错误
class Boundaries extends React.component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(e, info) {
console.log(info);
}
render() {
if(this.state.hasError) {
return <div> There are Errs. </div>
} else{
return this.props.children;
}
}
}
<Boundaries> <My /> </Boundaries>
- 第五遍 2025.1.22 -
react 提供了两个函数用于处理渲染流程中 render+commit 抛出的错误,避免错误影响 UI 的渲染
static getDerivedStateFromError(修改UI) + componentDidCatch(错误处理)
class Boundaries extends React.Component {
constructor(props) {
❌❌❌❌❌ // 1、派生类使用 this 需要调用 super(props); - 初始化组件的内部状态和绑定 this 关键字
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(e) {
console.log(e.message);
}
render() {
if(this.state.hasError) {
return <div> There are errs. </div>
} else {
return this.props.children;
}
}
}
<Boundaries> <My /> </Boundaries>
- 第四遍 2025.1.18 -
错误边界 static getDerivedStateFromError(可以更新state来修改UI,不能使用 this - 不绑定具体的组件) + componentDidCatch(处理错误),可以将 render + commit 阶段的错误进行处理,不会让错误蔓延至整个应用 UI
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(e) {
return { hasError: true };
}
componentDidCatch(e) {
console.log(e.message);
}
render() {
if(this.state.hasError) {
return <div>There are errs.</div>
} else {
return this.props.children;
}
}
}
<ErrorBoundary> <MyComponent/> </ErrorBoundary>
// ⭐️ 错误边界不能捕获:事件回调、异步代码、ssr、错误边界内部错误
- 第三遍 -
react 提供了两个错误处理函数,一般被称为错误边界,用于处理在 render 和 commit 阶段的错误 ⭐️ // 用于避免错误传递到整个应用,影响 UI 的正常展示
static getDerivedStateFromError 静态方法,用于捕获错误后,有UI修改的时机
componentDidCatch 生命周期,用于捕获错误后提供记录的时机
class boundaries extends React.component{ ⭐️ // 组件名称应该用大写 Boundaries
constructor(props){
super(props);
this.state={hasError:false};
}
static getDerivedStateFromError(){
this.setState({hasError:true}); ❌❌❌ // 1、这个 getDerivedStateFromError 是不能使用 this 的 -> 因为它是一个静态方法,不与组件实例绑定,所以不能直接访问this(因为它是在类级别上被调用的,而不是在组件实例的上下文中) -> 这种方式是为了保持函数的纯粹性,只能通过返回一个对象来修改state return { hasError: true }
}
componentDidCatch(err){
console.log(err);
}
render(){
if(this.state.hasError){
return <div>It’s Err.</div>
}else{
return this.props.children;
}
}
}
错误边界不会捕获 1当前错误边界组件中的错误 2 异步函数 setTimeout setInterval,因为不在 react 的渲染流程中 ⭐️ // 还有一个事件回调中的错误,也不在 react 渲染流中
- 第二遍 -
React提供两个错误处理API,用于获取render和commit 期间生命周期当中的错误,避免错误传递影响到整个应用页面的展示。 ⭐️ // 是为了 UI 正常显示设计的
Static Getderivedstatefromerror 捕获错误,提供重新渲染UI的时机(需要返回 state 对象来更新)
Componentdidcatch 捕获错误,提供记录错误的时机。
class MyBoundaryError extends React.Component {
constructor() { ❌❌ // 1、错误边界这里需要传入 props - 才能传入子组件
❌❌ // 2、派生类使用 this 需要调用 super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(e) {
console.log(e);
}
render() {
if(this.state.hasError) {
return <div> There is error.</div>
} else {
return <p>Page.</p>
❌❌ // 3、如果没有错误,应该要渲染错误边界包裹的子组件
// return this.props.children;
// 可以看下使用:<MyBoundaryError><MyCom /></MyBoundaryError>
}
}
}
- 第一遍 -
React 提供了两个错误 API,使用两个 API 可以组装错误边界
❌ // 1、捕获的错误是: React render+commit 中的错误
// 2、两个 API 分别是: static getDerivedStateFromError() [callback UI 有错误时提供渲染时机]
// componentDidCatch()[组件实例方法 有错误时提供时机记录错误]
// 3、为什么要有专门的错误边界处理错误:因为它可以捕获渲染、生命周期、构造函数中的错误,防止错误传播到整个应用,使其他页面能够正常展示
// 4、不会被捕获的四种错误:①事件回调中的错误,因为不在react工作流里(错误处理API被设计的目的是避免错误影响 UI显示,只有commit阶段会造成UI变化)②异步代码(setTimeout)③SSR④在错误边界组件内部发生的错误
<ErrorBoundary><MyComponent /></ErrorBoundary>
class ErrorBoundary extends React.Component {
constructor(props){
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(err){
return { hasError: true }
}
componentDidCatch(err, errInfo) {
console.log(err, errInfo);
}
render() {
if(this.state.hasError) {
return <h1> Sth wrong.</h1>
}
return this.props.children;
}
}
// 2023.07 clickhouse、mysql、mongodb异同
- 第一遍 2025.1.25 -
clickhouse:列式数据库,读写性能都好,但是没有事务,适合高吞吐、低延迟的场景,例如日志 ⭐️ // 支持复制、分布查询
mysql:关系型数据库,读写性能不错,支持事务操作,但是在高并发下会有一致性问题
mongdb:文档型数据库,读的性能好,写的性能弱,在异步操作下会有一致性问题
❌❌ // 1、在分布式场景下可能出现一致性问题 ;适合半结构化/非结构化数据
- 第一遍 2025.1.22 -
❌ // 4、mysql 是关系型数据库-适合事务处理场景;MongoDB 是文档型数据库-半结构化/非结构化数据;ClickHouse 是列式数据库-适合高吞吐、低延迟的分析场景
clickhouse:各种数据存储,不支持事务操作,适合大型数据,例如操作记录等 ❌ // 1、少写了:读写性能都高,场景适合少了归类:高吞吐、低延迟场景,例如日志;操作少写了:支持复制和分布查询
mysql:结构化的数据存储,支持事务操作,查询、写入性能好,一致性好,适合小型应用 ❌ // 3、一致性描述错误:高并发时可能出现一致性问题
mongdb:类/半结构化的数据存储,写入性能好,一致性一般 ❌ // 2、性能描述错误:写的性能弱,读的性能好;适合数据少写了:半结构化数据,类似 XML HTML JSON 等,以及非结构化数据;一致性描述:在分布式场景下可能出现数据一致性问题
// 2021.07 数组扁平化(一层、全部展开、指定深度)
一层:
arr.flat() [].concat(...arr) [].concat.call([], ...arr) [].concat.apply([], arr)
全部展开:
arr.flat(Infinity) arr.toString().split(',')
function expense(arr, deep = 1) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur)&&deep > 1 ? expense(cur, deep - 1) : cur)
}, [])
}
// 2021.07 数组扁平化(一层、全部展开、指定深度)
- 第三遍 2025.1.24 -
一层:
1、arr.flat();
2、[].concat(...arr);
3、[].concat.call([], ...arr);[].concat.apply([], arr)
全部展开
❌❌❌ // 1、少写了 :arr.flat(Infinity)
Json.stringify(arr).split(',') ❌❌❌ // 2、错了: JSON.stringify 不会展开数组,arr.toString.split(',') - toString 将数组转换成字符串时,会打平所有数组
function deepFlat(arr, deep = 1) {
return arr.reduce((pre, cur) => {
pre.push(Array.isArray(cur) && deep>1 ? deepFlat(cur, deep - 1): cur);
❌❌❌ // 3、不能用 push, deepFlat 返回的是数组 -> 应该使用 concat
return pre;
}, [])
}
- 第二遍 2025.1.19 -
一层:arr.flat();[].concat(...arr);[].concat.call([], ...arr);[].concat.apply([], arr)
全部展开:arr.toString().split(','); ❌❌ // 1、少写了 arr.flat(Infinity)
function flatten(arr, deep = 1){
return arr.reduce((pre, cur) => {
pre.concat(Array.isArray(cur)&&deep>1 ? flatten(cur, deep) : cur)
❌❌ // 2、deep 传递的时候记得 -1
}, [])
}
- 第一遍 -
const arr = [1, 2, [3, 4], 5];
一层
1、arr.flat()
2、[].concat.call([], ...arr) // ❌ 1、对 concat 了解不够深,concat 会铺平一层,同样原理的还可以写成 [].concat(...arr) / [].concat.apply([], arr)
全部展开
1、arr.flat(Inifity) ❌ // 2、拼写错误 Infinity
2、Object.prototype.toString.call(arr).join(',') ❌ // 3、学杂了,这个写法会返回'[object Array]' 用于查询 arr 的类型 -> arr.toString().join(',')
3、
function spread(arr) {
return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? spread(cur) : cur), [])
}
指定深度
function spread(arr, deep) {
return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) && deep > 1 ? spread(cur, deep - 1) : cur), [])
// 2021.07 bind、apply/call三者异同+apply/call实现bind
- 第四遍 2025.1.25 -
三者都是用于处理函数的this 指向;如果不传递第一个参数的话,this指向window
bind的是返回函数的副本,apply和call 是立即执行函数
apply 第二个参数类数组或数组,call 接收多个参数
Function.prototype.fakeBind = function(thisArg, ...args1) {
const fun = this;
return function F(...args2) {
if(fun instanceof F) {
return new fun(...args1, ...args2);
}
fun.call(thisArg, ...args1, ...args2);
}
}
- 第三遍 2025.1.24 -
共同点是:1、都是用于改变函数的this指向 2、如果第一个参数不传,那么函数的this指向将指向window
差异点:1、bind是绑定修改函数的this指向后返回函数的副本;apply和call是修改函数的this指向后立即执行
2、apply的第二个参数是类数组或者数组,call 可以有多个参数依次传入
function fakeBind(thisArg, ...args1) { ❌❌❌ // 1、bind 实现应该绑定在原型对象上实现: Function.prototype.fakeBind - 这样 this 才是指向调用 bind 的函数
let func = this;
return function F(...args2) {
if(func instanceof F) {
return new func.call(thisArg, ...args1, ...args2);
}
func.call(thisArg, ...args1, ...args2);
}
}
- 第二遍 2025.1.19 -
同:都是用于改变函数内部 this 指向;不穿参数时,指向 window
异:bind-返回函数副本;apply/call-立即执行;apply第二个参数是类数组/数组;call 接收多个参数
function fakeBind(thisArg) { ❌❌ // 1、「bind 有多个参数,第一个是 this 指向,后面接收多个参数 」...arg1
const cb = this;
return function F(...arg) {
if(cb instanceof F) {
return new cb(...arg) ❌❌ // 2、return new cb(...arg1, ...arg)
}
cb.apply(thisArg, arg);❌❌ // 3、cb.apply(thisArg, [...arg1, ...arg])
}
}
- 第一遍 -
同:1、都是用于改变函数内部 this 指向的上下文
2、在第一个参数不传的时候,this 默认指向 window
异:1、bind 改变函数内部 this 指向后,返回函数副本,执行的话需要再次调用;
apply/call 都是改变函数内部 this 指向后直接执行
2、aplly 的第二个参数接收一个类数组/数组;call 的第二个参数开始,接收多个参数
Function.prototype.fakeBind = function(thisArg, ...arg1) {
const func = this;
return (...arg2) => {
❌ // 1、遗漏了判断是否为 new 调用
func.call(thisArg, ...arg1, ...arg2);
}
/**
return function F(...arg2) {
if(func instanceof F) {
return new func(...arg1, ...arg2);
}
func.call(thisArg, ...arg1, ...arg2);
}
*/
}
// 2021.09 Object属性排序、与Map的区别
- 第三遍 2025.1.25 -
正整数 字符串 负数 浮点数 Symbol
有序、属性可以是所有类型(字符串/Symbol、m.size()、可迭代类型、大小,增删快
- 第二遍 2025.1.21 -
Object属性排序: 正整数>字符串>负数>Symbol ❌❌ // 1、少写了浮点数
与Map的区别:①有序 ②属性值任意类型 ③可迭代对象 ④数据量大时占用空间小,增删快 ⑤访问数量方便:m.size() Object.keys(obj).length
- 第一遍 -
Object 属性排序:正整数 > 字符串 > 负数 > 浮点数 > Symbol
与Map的区别:
① Map的键可以是任何类型 VS Object 的键只有字符串/Symbol
② Map可以记录插入顺序 VS Object 无法记录,自有自己的默认排序方式
③ Map写法简单,长度为 m1.size() VS Object.keys(Object).length
④ Map在大量数据时占用空间小,增删快
⑤ Map是可迭代对象 VS Object不是
// 作业20:浏览器打开一个 URL 发生了什么
- 第三遍 2025.1.25 -
浏览器进程 UI线程 - 把用户输入的内容或者是链接补充成完整的 url
通过 IPC 协议传递给网络进程
网络进程:1、查找当前缓存是否存在存在,判断当前缓存是否在有效期内 - 直接返回缓存内容,强缓存生效
2、缓存不存在或者是缓存已失效的情况下,进行DNS解析获得IP
3、排队tcp队列进行三次握手连接
4、拼接请求,例如请求头、cookie等信息
5、发起请求
如果状态码为301或者302,则获取响应头中的location字段进行新的url,导航跳转
如果状态码为304,则代表继续使用缓存并刷新缓存时间
如果状态码为200,判断当前返回的响应类型是
Osc stream 下载类型触发下载导航结束
HTML 和渲染进程建立管道。边下载边解析。
渲染进程分为主线程 合成线程 预解析线程
1、在解析的过程当中提前扫描HTML文件,如果有js或者css文件,则使用预解析线程提前下载
2、下载完成后通知浏览器进程,对页面、url、前进后退、安全锁进行刷新
3、下载完成,但是解析还没有完成的过程当中出现的是解析白屏
渲染流程分为以下几步:
主线程中执行:解析dom,得到dom树 - 计算css样式表 - 布局,布局树 - 分层得到分层树 - 绘制,指令列表
合成线中执行:栅格化,会启用GPU加速,图块合成位图
浏览器UI线程:执行合成帧
- 第二遍 2025.1.20 -
1、浏览器进程-UI线程:将用户输入的内容/链接拼接成完整的url
2、网络进程:通过 RPC 传递给网络进程后,查找本地缓存 ❌❌ // 1、协议名错了:IPC - 进程通信协议
有缓存则直接返回 - 强制缓存;无缓存则继续进行导航 ❌❌ // 2、少了:有缓存但缓存失效
3、①DNS 解析-缓存,得到 ip
②TCP 队列排队,三次握手建立连接
③组装请求:包括cookie、请求头等
④发起请求
4、返回的类型是 stream 时,进入下载流程,不再导航 ❌❌ // 3、应该是先判断状态码,200的状态码下再判断类型
返回的类型是 html 时,判断状态码 301、302 时,使用响应头中的 Location 字段触发 重新导航
状态码是 200 时,和渲染进程建立管道
❌❌ // 4、少写了 304,使用本地缓存,并刷新缓存有效时间
5、渲染进程:边下载,边传输解析
①渲染进程分为:主线程、预解析线程、合成线程,解析时预扫描js css文件,提前下载
②下载完成时,通知浏览器进程,刷新页面、URL地址、安全锁、前进后退按钮
③下载完成后,解析完成前,出现解析白屏
6、主线程:解析DOM DOM树 - 样式计算 css样式表 - 布局树 - 分层树 - 绘制(指令列表
合成线程+GPU进程:-栅格化(位图转换成为图块
浏览器UI线程:-合成(帧
- 第一遍 -
1、浏览器进程-UI线程:将用户输入内容/链接转换为完整的URL
2、通过IPC协议传递给网络进程
① 查询本地是否有缓存:有缓存且在有效期内-直接返回缓存
②有缓存但无效/无缓存:DNS查询解析,得到ip
③TCP队列排队,连接-三次握手
④组装请求信息:请求头cookie等
⑤发起请求:301/302 重定向,取响应头中的Location作为新的url导航
304 Not Modified,使用缓存,并刷新本地缓存有效期
200 OK type类型为 o-stream 下载流
html 和渲染进程建立管道,边下载边传输
3、渲染进程(主线程、合成线程、预解析线程)
①和网络进程建立管道,并开启预解析,如果检测到js/css文件,提前下载
②传输完成后,渲染进程还没解析完之前,页面会出现短暂空白,就是解析白屏
③传输完成后,会通知浏览器进程更新 页面+url+前进后退+安全锁
注意:解析过程中,遇到js代码,会暂停DOM解析(因为JS可能会改变DOM
渲染流程:
主线程:解析DOM(DOM树)->样式计算(css样式表)->布局(布局树)->分层(分层树)->绘制(指令列表)
合成线程:->栅格化(图块->位图)【此处会开启GPU进程加速】
浏览器进程-UI线程:->合成展示
// 2021.06 CSS 选择器 - 权重/匹配方式
- 第二遍 1.25 -
!important > 行内 > 内联+外联(按照加载顺序排序
#id > .red a[href] :hover LVHA > div ::after > *
选择器从右向左解析
- 第一遍 1.20 -
!important > 内联 > 行内 ❌ // 1、行内 > 内联/外联
#id > .red [attr] :hover LVHA > div ::after > *
从右向左
// 2019.07 为什么要清除浮动?怎么清除浮动?
- 第二遍 1.25 -
块级元素把浮动元素当做不存在;行内元素围绕布局
高度塌陷:子元素浮动后,父元素的背景和 border padding 无法撑开 ❌❌ // 1、背景无法撑开 + padding、border 无法正常展示
清除浮动:1、给父元素最后一个子元素后添加一个 box .box{ clear:both }
2、给父元素添加伪元素 .father::before{ content: ''; display: block; clear: both; }
3、BFC 给父元素添加 .father { overflow: hidden; } - BFC 高度计算包含浮动元素
4、给父元素添加宽度、高度 ❌❌ // 2、宽度不对,高度+padding+border
- 第一遍 1.20 -
对于浮动元素,行内元素会环绕布局,块级元素会当做浮动元素不存在,且子元素浮动时,会导致父元素背景无法撑开 - 高度塌陷
清除浮动:①子元素其他盒子 BFC:overflow:hidden; ❌ // 1、给父元素添加 - BFC 内部高度计算时包含浮动元素 - 解决高度塌陷的关键
②给父盒子设置 padding border ❌ // 2、还有高度
③给父盒子末尾添加 box .box { clear: both }
④给父元素添加伪元素 .box::before{ display: block; clear: both; content: ''; }
// 作业6:new操作符实例化一个对象的过程
- 第二遍 1.25 -
创建空对象 - 将新对象的原型指向构造函数的原型对象 - 构造函数this指向新对象 - 执行构造函数为新对象添加属性和方法 - 返回新值/新对象
function fakeNew() {
let ctor = [].shift.call(arguments);
let obj = new Object();
obj.__proto__ = ctor.prototype;
const res = ctor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}
- 第一遍 1.20 -
1、创建空对象
2、设置constructor ❌ // 1、新对象/实例的 __proto__ 指向构造函数的原型对象
3、将构造函数this指向当前空对象
4、执行构造函数,为空对象添加属性和方法
5、返回对象
function new() {
let ctor = [].shift.call(arguments);
let obj = new Object();
Object.defineProperty(obj, 'prototype', {
enumerable: false,
value: ctor
}); ❌ // 1、obj.__proto__ = ctor.prototype;
const res = ctor.apply(this, arguments);
return typeof res === 'object' ? res : obj;
}
// 作业39:shim 和 polyfill 区别
- 第二遍 1.25 -
shim:垫片,会提供新的 API,利用浏览器原生功能实现,处理兼容问题,优雅降级
polyfill:补丁,实现浏览器原生功能(低版本),没有新的 API ⭐️ // 实现浏览器原生 API 缺少的功能
- 第一遍 1.20 -
shim:垫片,提供新的 API,用浏览器现有功能实现,
polyfill:补丁,不提供新的API,提供兼容性,用低浏览器版本的现有功能实现新功能
❌ // 1、记反了,shim 是处理兼容性问题-优雅降级,polyfill 是实现浏览器不支持的原生API-实现原生API中缺少的功能
// 作业42:TED 如何和大脑正确交流沟通
- 第二遍 1.25 -
1、I want it, I like it, I've chosen it!
2、Make it familiar!
You always keep your value.
大脑只会响应你告诉它的语言和图片,说得越详细越好
大脑天然追求快乐,把想要做的事情绑定到非常快乐,把不想做的事情绑定到痛苦
大脑喜欢熟悉的事情
- 第一遍 1.20 -
1、大脑只会对你给的语言和你想的画面有反应
❌ // 1、少写了:I like it!I want it!I've chosen it! 大脑天然逃避痛苦、追逐快乐:将想要做的事情和巨大的快乐联系起来;将停滞在当下的痛苦细细描述
❌ // 2、大脑喜欢熟悉的事情:让大脑对想要做的事情变熟悉
// 作业43:TED 怎么提升自信
- 第二遍 1.25 -
1、repetition!repetition!repetition!
2、不要批评,要用鼓励来引导
3、用自己的视角解释一切
- 第一遍 1.20 -
❌ // 1、少写:重复训练!用足够多次的练习带来信心
1、少批评自己
❌ // 2、少写:而是不断夸奖正确行为 - 自己的或别人的 - 引导自己去学习和改变
2、用自己的语言解释世界
// 作业2:支持迭代器的原生语言特性有哪些?
- 第二遍 1.25 -
for...of、Array.from、...扩展运算符、数组解构、创建 Set Map、yield *(只能在生成器函数中使用)、Promise.all/race
- 第一遍 1.20 -
... 扩展运算符、对象解构、for-of、Array.from、创建 Map、创建 Set、Promise.all/race
❌ // 1、少写了:yield * 操作符 - 只能在生成器中使用;不是对象解构,是「数组解构」
LTN①④ - 2025.1.20开始 12h
LTN1 - 43题 8h33m
✅2019.10 两栏,左边固定,右边自适应的布局 ✅2019.10 三栏,两边固定,中间自适应 ✅2020.07 对象分类、Promise按序执行、实现map ✅2021.04 05-3 白屏优化? ❌2021.07 bind、apply/call三者异同+apply/call实现bind ❌2021.07 数组扁平化(一层、全部展开、指定深度) ✅2022.08 包管理工具 ❌2023.07 clickhouse、mysql、mongodb异同 ✅2024.05 rem是基于什么原理进行适配的? ✅2024.10 怎么实现跨域 ✅2024.11 观察者模式 EventEmitter ✅2024.12 模拟实现promise ✅作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的 ✅作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口) ✅作业53:胜利者一无所获 说的是什么 ✅作业58:React和Vue是怎么描述UI的 ✅作业62:fiber架构是什么?优点和实现方式? ✅作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count, setCount]=useState(0)具体说明 ✅作业64:Vue渲染怎么实现的? ✅作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期 ❌作业66:React如何处理错误 ✅作业67:React 怎么做的性能优化? ✅作业68:React hooks 的原理是什么?useEffect useState模拟实现 ❌作业70:Vue 和 React 的 Diff 算法比较 ✅作业71:React为什么要合成事件?和原生事件有什么不同? ❌作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情? ✅作业75:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序? ✅作业78:使一个标签看不见的几种样式及其区别 ❌作业80:对DOM 树的理解 - 定义和作用 ❌作业81:Redux 核心是什么?reducer、action、dispatch、store、RTK 怎么理解?简单写一个 redux 使用 ❌作业82:写一个复杂的 SQL: ①只返回某列不同的值,相同的忽略 ②拼接A列和B列为字符串后输出 ③将某一列转换为大写输出 ④计算某列行数,一个包含null的个数,一个不包含null个数 ⑤求和、求最大、求最小、求平均某列的值 ⑥给表起别名 ⑦查询条件涵盖 id为1,同时price大于10,或者是NULL,或者是 5-10之间,或者是 1或2,或者年份为2025 ⑧按照某列分组 ⑨按照某列排序(降序) ❌作业83:INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 有什么区别?UNION作用是什么?怎么封装一个 SQL 查询 ❌作业84:path.join()和path.resolve()的区别?process.cwd()和__dirname的区别? ✅作业85:Lambdas是什么作业 ❌作业86:Nextjs 中 Link 的变更是哪个版本开始的,有什么变更?从这个版本开始对路由有什么变更? ❌作业87:Nextjs 中预渲染是什么?有哪些模式?实现一个动态路由的预渲染-一个客户端一个服务端 ❌作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接 ✅2024.09 第八章 对象、类和面向对象编程 小结 ✅2024.11 第十七章 事件
// 2019.10 三栏,两边固定,中间自适应 .left, .right { width: 100px }
- 第三遍 2025.1.24 -
float\BFC 不行
1 position .left, .right { position: absolute; top: 0; }
.rgiht { right: 0; }
.middle { margin: 0 100px; }
2 flex .outer { display: flex; } // 可以使用 justify-content: space-around;
.middle { flex: 1; }
3 table .outer { display: table; width: 100%; }
.left, .right, .middle { display: table-cell; }
grid .outer { display: grid; grid-template-columns: 100px auto 100px; }
4 cacl .left, .right, .middle { float: left; }
.middle { width: cacl(100% - 200px); }
- 第二遍 2025.1.19 -
float\BFC 都因为浮动换行,不能用
1、
.left, .right { position: absolute; top: 0; }
.right { right: 0 }
middle { margin: 0 100px }
2、flex
.outer { display: flex }
.middle { flex:1 }
3、table/grid
.outer { display: table; width: 100%; }
.left, .right, .middle { display: table-cell; }
.outer { display: grid; grid-template-columns: 100px auto 100px; }
4、calc
.left, .right, .middle { float: left }
.middle { width: calc(100% - 200px) }
- 第一遍 -
float 会导致 right 换行在第二行 - 要调整 左中右 的DOM布局(BFC也不行了)
1、position
.left, .right { position: absolute; top: 0; }
.right { right: 0 }
.middle { margin: 0 200px }
2、flex
.outer { display: flex; justify-content: space-between; } // ⭐️ 横向布局可不写
.middle { flex: 1 }
3、table/grid
.outer { display: table; width: 100%; }
.left, .right, .middle { display: table-cell }
.outer { display: grid; grid-template-columns: 200px auto 200px; }
4、calc
.left, .right { float: left } // ❌ 1、三浮动,否则 right 就会因为向左靠齐换行了
.middle { width: calc(100% - 400px) }
// 2019.10 两栏,左边固定,右边自适应的布局 .left{ width: 100px }
- 第三遍 2025.1.24 -
1 .left { float: left; }
.right { margin-left: 100px; }
2 .left { position: absolute; }
.right { margin-left: 100px; }
3 BFC .left { float: left; }
.right { overflow: hidden; }
4 flex .outer { display: flex; }
.right { flex: 1; } // 1 1 0
5 table .outer { display: table; width: 100%; }
.left, .right { display:table-cell; }
grid .outer { display: grid; grid-template-columns: 100px auto; }
6 cacl .left, .right { float: left }
.right { width: calc(100% - 100px) }
- 第二遍 2025.1.19 -
1、margin-left - float
.left { float: left }
.right { margin-left: 100px }
2、margin-left - position
.left { position: absolute }
.right { margin-left: 100px }
3、BFC
.left { float: left }
.right { overflow: hidden }
4、flex
.outer { display: flex }
.right { flex: 1 }
或者
.left { flex: 0 0 100px }
5、table/grid
.outer {
display: table;
width: 100%;
}
.left, .right {
display: table-cell;
}
.outer {
display: grid;
grid-template-column: 100px auto; ❌❌ // 1、grid-template-columns
}
6、calc
.left, .right { float: left }
.right { width: cacl(100% - 100px) }
- 第一遍 -
1、margin-left float
.left { float: left }
.right { margin-left: 200px }
2、margin-left position
.left { position: absolute }
.right { margin-left: 200px }
3、BFC
.left { float: left }
.right { overflow: hidden }
4、flex
.outer { display: flex }
// .left { flex: 0 0 200px }
❌ // 1、父盒子变成弹性盒子后, right 要变成可拉伸 .right { flex: 1 }
5、table/grid
.outer { display: table; width: 100%; }
.left, .right { display: table-cell }
.outer { display: grid; grid-template-columns: 200px auto; }
6、calc
.left, .right { float: left }
.right { width: calc(100% - 200px) }
// 2020.07 对象分类、Promise按序执行、实现map
- 第三遍 2025.1.24 -
let list = [{name:'Alice', age: 32}, name:{'qiu', 4}]
function classify(arr, property){
return arr.reduce((pre, cur) => {
const key = cur[property];
if(!pre[key]) {
pre[key] = [];
}
pre[key].push(cur);
return pre;
}) ⭐️ // 少写了 reduce 的初始值 - 一个空对象 {}
}
function promise(arr, init) {
return arr.reduce((pre, cur) => pre.then(cur), Promise.resolve(init));
}
Array.prototype.fakeMap = function(cb, thisArg) {
return this.reduce((pre, cur, index, array) => {
pre[index] = cb.call(thisArg, cur, index, array);
return pre;
}, [])
}
- 第二遍 2025.1.19 -
const arr = [{age:1, name:'a'}, {age:2}]
function classify(arr, property) {
return arr.reduce((pre, cur) => {
const key = cur[property];
if(!pre[key]) {
pre[key] = [];
}
pre[key].push(cur);
return pre;
}, {})
}
function executor(arr, init) {
return arr.reduce((pre, cur) => pre.then(cur) , Promise.resolve(init))
}
Array.prototype.fakeMap = function(cb) {
❌❌ // 1、map 函数接收两个参数,第一个是 callback,第二个是 thisArg - 用来指定 this
const arr = this;
return arr.reduce((pre, cur, index, array) => {
const res = cb(cur, index, array); ❌❌ // cb.call(thisArg, cur, index, array);
pre.push(res);
⭐️ // reduce filter map forEach 会跳过 empty 的数组项( map 和 reduce 都跳过)
return pre;
}, [])
}
- 第一遍 -
const arr = [{name:1, age:2}, {name:1, sex:0}]
function classify(arr, property) {
return arr.reduce((pre, cur) => {
const key = cur[property];
if(!pre[key]){
pre[key] = [];
}
pre[key].push(cur);
return pre;
},{})
}
function order(arr, init) {
return arr.reduce((pre, cur) => pre.then(cur), Promise.resolve(init))
}
Array.prototype.fakeMap = function(cb) {
const arr = this; // ⭐️ 可以直接使用 this
return arr.reduce((pre, cur, index, array) => {
const res = cb(cur, index, array); ❌ // 1、注意 map 除了 callback,第二个参数是可选的的 thisArg,用于指定前面函数的 this
pre[index] = res; // ⭐️ 可以直接赋值
return pre;
}, [])
}
/**
Array.prototype.fakeMap = funciton (cb, thisArg) {
return this.reduce((pre, cur, index, array) => {
pre[index] = cb.call(thisArg, cur, index, array);
return pre;
}, [])
}
*/
// 2022.08 包管理工具
- 第四遍 2025.1.24 -
npm 将依赖拍平提升到node_modules,npm>5 package-lock.json,.npmrc
yarn v1/Classic 将依赖拍平提升到node_modules,针对早期npm版本优化,并行加载多个依赖,yarn.lock,.yarnrc
pnpm 内容寻址存储,依赖只存储一次,结构化嵌套存储在node_modules,pnpm-lock.yml,.npmrc
yarn v2/Berry 即插即用,存储在 .pnp.cjs+.yarn/cache(zip),yarn.lock,yarnrc.yml ⭐️ //不再使用 node_modules
- 第三遍 2025.1.19 -
npm:拍平、提升至 node_modules;package-lock.json,.npmrc
yarn v1/Classic:拍平、提升至 node_modules;针对早期 npm 问题,并行加载多个依赖;yarn.lock,.yarnrc
pnpm:保留结构化的 node_modules,内容依赖存储,只物理存储一次;pnpm-lock.yml,.npmrc ⭐️ // 内容寻址存储
yarn v2/Berry:不再使用 node_modules;依赖查找表 .pnp.cjs+.yarn/cache(zip);yarn.lock,.yarnrc.yml ⭐️ // 老是想不到:即插即用
- 第二遍 -
npm:将安装包拍平提升到node_modules,package-lock.json(npm>5),.npmrc
yarn v1/Classic:将安装包拍平提升到node_modules,相对于npm早期版本,提供并行下载,yarn.lock,.yarnrc
pnpm:内存寻址存储,结构化存储 node_modules,只物理存储一次,节省安装空间,pnpm-lock.yml,.npmrc
yarn v2/Berry:不再有 node_modules,依赖查找表.pnp.cjs + .yarn/cache zip存储依赖,yarn-lock.yml,.yarnrc ❌❌ // 1、即插即用,且lock文件和配置文件分别为 yarn.lock + .yarnrc.yml
- 第一遍 -
npm:
将依赖提升、拍平存储在node_modules;7.x有针对peer dependencies的break change;package-lock.json(npm>5) + .npmrc
yarn v1/classic:
将依赖提升、拍平存储在node_modules;针对早期npm优化,可以并行加载多个依赖;yarn.lock + .yarnrc
pnpm:❌ // 「不是pnpm.yml,而是 pnpm-lock.yml」
保留结构化的node_modules,采用内容寻址存储,只物理存储一次,节省空间;pnpm.yml + .npmrc
yarn v2/Berry: // ⭐️「即插即用」
不再使用node_modules,使用依赖查找表 .pnp.cjs 和 /.yarn/cache 存储 zip文件;yarn.lock + .yarnrc.yml
// 2024.10 怎么实现跨域
- 第三遍 2025.1.24 -
1、jsonp,利用 script src 可以引入第三方资源,只能处理 GET 请求
2、CORS【推荐】跨域资源共享 - Access-Control-Allow-Origin 设置可以访问资源的域名
3、代理 nginx 反向代理 - 同源协议存在于浏览器端,服务器端不存在
4、websocket 不受同源协议限制
- 第二遍 -
jsonp:GET 请求是传输callbak-服务端将数据包裹成函数-拿到返回信息,触发callback 函数
nginx:同源协议限制是 浏览器侧,服务器端不受限
CORS:跨域资源共享 Access-Control-Allow-Origin 服务端允许访问资源的域名
wesocket:不受同源协议限制
- 第一遍 -
1、JSONP:只能处理 GET 类型的请求,利用 script 标签的 src 可以引入第三方资源
在客户端强求中指定 callback,在服务端将数据包裹成函数,返回给客户端,客户端执行 callback 函数
2、代理 nginx:同源协议限制只在客户端,服务端之间不存在
3、CORS 设置 Access-Control-Allow-Origin 可以信任的第三方资源 // ❌ 1、那些域名可以访问资源 - 是为了让浏览器知道哪些域名可以跨域资源共享,而不是从客户端角度描述
4、websocket 不受同源协议的限制
// 2024.11 观察者模式 EventEmitter
- 第四遍 2025.1.24 -
class EventEmitter {
constructor() {
this._max = 10;
this.events = {};
}
add(event, listener) {
if(!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener)
if(this.events[event].length > this._max) {
throw Error('max exceed');
}
}
remove(event, listener){
if(Array.isArray(this.events[event])) {
if(!listener) {
delete this.events[event];
} else {
const index = this.events[event].indexOf(listener);
if(index>-1) {
this.events[event].splice(index, 1);
}
}
}
}
once(event, listener) {
const onceFun = (...arg) => {
listener(...arg);
this.remove(event, onceFun);
}
this.add(event, onceFun);
}
emit(event){
if(Array.isArray(this.events[event])){
const args = arguments.slice(1);
const static = [...this.events[event]];
static.forEach((it, index) => {
const arg = args[index];
Array.isArray(arg) ? it(...arg) : it(arg);
})
}
}
}
- 第三遍 2025.1.19 -
add once remove emit
class EventEmitter {
remove(event, listener) {
if(Array.isArray(this.events[event])) {
if(!listener) {
delete this.events[event]
} else {
const index = this.events[event].indexOf(listener);
if(index > -1) {
this.events[event].splice(index, 1);
}
}
}
}
emit(event) {
if(Array.isArray(this.events[event])) {
let static = [...this.events[event]];
const args = arguments.slice(1);
static.forEach((it, index) => {
const arg = args[index];
Array.isArray(arg) ? it(...arg) : it(arg);
})
}
}
}
- 第二遍 -
class EventEmitter {
constructor() {
this.events = {};
this._max = 10;
}
add(event, listener) {
if(!this.events[event]){
this.events[event] = [];
}
!this.events[event].push(listener);
if(this.events[event].length >= this._max) {
throw Error('max exceed');
}
}
remove(event, listener) {
if(Array.isArray(this.events[event])) {
if(!listener) {
delete this.events[event];
} esle {
const index = this.events[event].indexOf(listener);
❌❌ // 1、这里要判断 index 存在才去删除:if(index>-1){ ... }
this.events[event].splice(index, 1);
}
}
}
once(event, listener) {
const onceFun = (...arg) => {
listener(...args);
this.remove(event, onceFun);
}
this.add(event, onceFun)
}
emit(event) {
const args = arguments.slice(1);
const list = this.events[event];
❌❌ // 2、不能直接赋值,要拷贝下来,才能避免 const list = [...this.events[event]];
list.forEach((it, index) => {
const arg = args[index];
Array.isArray(arg) ? it(...arg) : it(arg);
})
}
}
- 第一遍 -
add remove emit once setMax
class EventEmitter() {
constructor() {
this.events = {};
this._max = 10;
}
setMax(limit) {
this._max = limit;
}
add(event, listener) {
if(!this.events[event]) {
this.events[event] = [];
}
❌ // 4、少了计算监听事件数量 if(this.events[event].length > this._max) { throw Error('exceed') }
this.events[event].push(listener);
}
remove(event, listener) {
if(!listener) {
delete this.events[event];
return;
}
if(Array.isArray(this.events[event])) {
let index = this.events[event].indexOf(listener);
this.events[event].splice(index, 1);
❌ // 1、 这里要判断 index > -1,也就是 listener 存在才删除
}
}
once(event, listener) {
const onceFunc = (...args) => {
listener(...args);
this.remove(event, onceFunc);
}
this.add(event, onceFunc);
}
emit(event) {
const argsList = arguments.slice(1);
if(this.events[event]) {
❌ // 2、这里一定要注意,由于 once 的存在,this.events[event] 在执行过程是动态改变的
❌ // 所以这里一定要将 this.events[event] 拷贝下来再处理 let static = [...this.events[event]]
this.events[event].forEach((it, index) => {
const args = argsList[index];
Array.isArray(args) ? it(...args) : it();
❌ // 3、args 还是要传进函数的 Array.isArray(args) ? it(...args) : it(args)
})
}
}
}
// 作业68:React hooks 的原理是什么?useEffect useState模拟实现
- 第五遍 2025.1.23 -
hooks 目的是不写类组件也能够使用 state 和状态管理、模拟生命周期能力
原理是利用 闭包 实现 state 每次重新渲染都能够访问到最新值;同时每个 fiberNode 都存储对应的所有 hooks 链表,每次渲染全部执行,保证使用最新值渲染,在 commit 挂载到真实 DOM 后执行 hooks 收集到的副作用,模拟生命周期
function render() {
ReactDOM.render(<App />, document.getElementById('root'));
effectCursor = 0;
}
let memorizedState = {};
let effectCursor = 0;
function fakeUseEffect(cb, array) {
const lastArray = memorizedState[effectCursor];
const hasChanged = lastArray && array.some((it, index) => it !== lastArray[index]);
if(!lastArray || hasChanged){
cb();
memorizedState[effectCursor] = array;
}
effectCursor++;
}
let cursor = 0;
function fakeUseState(init) {
let currentCursor = cursor;
memorizedState[currentCursor] = memorizedState[currentCursor] ?? init;
const useValue = (newVal) => {
memorizedState[currentCursor] = newVal;
render();
}
cursor++;
return [memorizedState[currentCursor], useValue];
}
- 第四遍 2025.1.18 -
hooks 设计的目的:不使用类组件也可以使用 state、状态管理、模拟生命周期
原理:闭包-每次渲染都拿到最新值;hooks按序存储在 fiberNode.memorizedState,更新时按序执行全部 hooks,使用副作用链表收集副作用,在 commit 阶段挂载后执行副作用链表
const memorizedState = {};
let cursor = 0;
function fakeUseEffect = (cb, array) => {
const lastArr = memorizedState[cursor];
const hasChanged = lastArr && lastArr.some((it, index) => array[index] !== it);
if(!lastArr || hasChanged) {
memorizedState[cursor] = array;
cb();
}
cursor++;
}
function render() {
ReactDOM.createElement(<App/>);
❌❌❌❌ // 1、ReactDOM.render(<App />, document.getElementById('root'));
cursor = 0;
}
function fakeUseState = (init) => {
const current = cursor;
memorizedState[current] = memorizedState[current] ?? init;
const setValue = (newValue) => {
memorizedState[current] = newValue;
render();
}
return [memorizedState[current], setValue];
❌❌❌❌ // 2、cursor++; 忘记写游标自增了
}
- 第三遍 2025.1.13 -
react 的每个组件都会将 hooks 链表存储在 fiberNode.memorizedState
组件有更新时,会按需全部执行全部 hooks,保证每次都可以获得最新的属性和状态
❌❌❌ // 1、这里描述的是 hooks 的执行过程 - 大虐,第一遍/第二遍的时候都记得是用于给函数组件添加功能用的,这一次反而完全没想起来
❌❌❌/** 记忆 React hooks原理
先描述【目的】:16.8的新增功能,可以在不编写类组件的情况下「使用 state」及其他特性,「状态管理能力」、「生命周期替代能力」、逻辑复用能力
再描述【实现上述目的的方法】,也就是【原理】:怎么用来「使用 state + 状态管理能力」 - ①主要是利用「闭包」,组件每次渲染时,使用闭包保持其状态;怎么用来「生命周期替代」 - ②使用链表保存一系列hooks,存储在 fiberNode 上,「在 Fiber 树更新时,能从 hook 中计算出最终输出的状态和需要执行的相关副作用」
*/
// useEffect(() => {}, []);
⭐️ /** 注意其实要写下 render 函数,因为 useEffect 触发 render 后,对应的游标要置0
function render(){
ReactDOM.render(<App />, document.getElementById('root'));
count = 0;
}
*/
const memorized = {};
let count = 0;
function fakeUseEffect(callback, array) {
const lastArray = memorized[count];
const hasChanged = lastArray && array.some((it, index) => it === lastArray[index]);❌❌❌ // 2、这里应该是判断「不等于」,有不相等的,代表依赖数组变化,需要更新:!Object.is(it, lastArray[index])
if(!lastArray || hasChanged) {
callback();
❌❌❌ // 3、少写了「更新依赖数组」的逻辑:memorized[count] = array;
}
count ++;
}
// const [a, useA] = useState(init);
const memorized = {};
let count = 0;
function fakeUseState(init) {
const currentCount = count;
let value = memorized[currentCount] ?? init;
❌❌❌ // 4、使用 value 会导致返回后,value 不会自动更新,和 memorized[currentCount] 失去了联系,本行改为:memorized[currentCount] = memorized[currentCount] ?? init;
const setValue = (newValue) => {
memorized[currentCount] = newValue;
❌❌❌ // 5、调用 useValue 必须触发 render:render();
}
count++;
return [value, setValue]
❌❌❌ // 4、延续问题4,本行修改为:return [memorized[currentCount], setValue]
}
- 第二遍 -
Hooks的实现目的是为了实现和类组件一样拥有对组建流程进行自定义的能力。
❌❌ // 1、和第一遍的逻辑是一样的,都是想到了「函数组件」缺失「类组件的生命周期」
// 但是 hooks 不只是「生命周期替代能力」,还有「状态管理能力」、「逻辑复用能力」
// 函数组件也不止缺失「生命周期」,还包括 state(没有 hooks 的函数组件主要接收 props 来展示 UI)
在首次渲染时,react会为每个组件收集所有的hooks并以链表形式存储在每个节点的memorizestate当中。当组件被更新,react会重新调用渲染函数,所有的hooks会按标记需要重新渲染的执行一遍。
(Hooks是和副作用链表哪个是被标记要重新渲染,然后重新渲染执行的呀?我记得hooks是在首次渲染的时候或者是后续更新的时候都会被全部按序执行一遍的,而副作用收集的链表是在commit渲染把dom更新到真实dom之后。执行副作用链表的)
❌❌ // 2、质疑是对的: hooks 在组件更新时是「全部按序重新执行一遍」,副作用链表是「在 hooks 执行过程中,如果 useEffect 依赖有更新」,就更新到副作用链表
// 和第一遍一样想到链表:第一遍想到存储位置,第二遍描述收集、执行过程 - 而不是原理
// 原理是从「链表+闭包」描述的:利用闭包保存状态,使用链表保存一系列的 hooks 与 Fiber关联。在 Fiber 更新时,就能从 hooks 中计算出最终输出的状态 和执行相关的副作用。
// ⭐️ 1、function render(){
// ReactDOM.render(<App />, document.getElementById('root'));
// cursor = 0; // 这里注意在 render 函数中,将 effect 的游标置 0
//}
const memorizedState = {};
const cursor = 0; ❌❌ // 3、这里应该是用 let
function fakeUseEffect(cb, depsArray) {
const lastDeps = memorizedState[cursor];
const hasChange = lastDeps && depsArray.some((it, index) => !Object.is(it, lastDeps[index]));
if(!lastDeps || hasChange) {
cb();
memorizedState[cursor] = depsArray;
}
cursor++;
}
const memorizedState = {}; // ⭐️ 核心是链表,但是这里选择使用 [] 实现
const cursor = 0; ❌❌ // 4、这里应该是用 let
function fakeUseState(init){
const value = memorizedState[cursor] ?? init;
❌❌ // 5、cursor 应该使用其他变量保存,和下文中 setValue 形成了闭包 - 在异步情况下,cursor 可能会变化,导致索引错误 const currentCursor = cursor; 这里使用 currentCursor 进行索引
const setValue = function (newValue) {
memorizedState[cursor] = newValue;
❌❌ // 6、更新使用闭包保存的 memorizedState[currentCursor] = newValue;
❌❌ // 7、这里还要触发一次 render() 函数执行
}
return [value, setValue]
}
- 第一遍 -
之前渲染流程类组件中是使用生命周期管理的,函数式组件也通过 hooks 实现了管理
❌ // 1、描述得不准确:hooks 可以在不编写类组件的情况下使用 state 及React其他特性
在 render 阶段,组件执行时,会通过链表形式存储 hooks - FiberNode.memorizeState 存储第一个链表
❌ // 2、hooks 链表说得不错,链表是为了在更新时,以稳定的顺序执行,输出最终的状态和相关副作用
❌ // 3、还有一个 hooks 利用闭包保存状态,在组件每次渲染时通过闭包保持其状态
触发更新/渲染时,会执行
⭐️ // 注意 hooks:1、不要在循环、条件、嵌套、try/catch/finally 中使用 - 无法保证顺序的稳定
⭐️ // 2、只有在 函数组件和hooks 中能够调用 hooks
let states = [];
let cursor = 0;
function render() { ReactDOM.render(<App />, document.getElementById('root')) }
function useFakeState(init) {
❌ // 5、当前定义时的 cursor 需要被闭包保存 let currentCursor = cursor;
const states[cursor] = states[cursor] ?? init;
const setCurrentState = (new) => {
states[cursor] = new;
render();
}
❌ // 4、少处理了游标自增: cursor++;
return [states[cursor], setCurrentState];
}
let states = []; ⭐️ // 这里使用 allDeps 声明或许更合适
let cursor = 0;
function render() {
ReactDOM.render(<App />, document.getElementById('root'));
cursor = 0;
}
function fakeUseEffect(cb, depsArray) {
const lastDeps = states(cursor);
const hasChanged = lastDeps && depsArray.some((it, index) => it !== lastDeps[index]);
⭐️ // 这里的 some 回调可以使用 !Object.is(it, lastDeps[index])
if(!lastDeps || hasChanged) {
cb();
states[cursor] = depsArray;
}
cursor ++;
}
// 作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count, setCount]=useState(0)具体说明
- 第五遍 2025.1.23 -
1、JIT 将 JSX 编译成 React createElement
2、渲染 render:生成 fiber树 + fiberNode.memorized 存储 hooks + 收集副作用链表 fiberNode.updateQueue + diff 计算UI最小更新量
3、commit:将变更应用到真实 DOM + 执行生命周期 + 副作用链表
渲染前 shouldComponentUpdate
渲染 constructor render getDerivedStaeFromProps
commit 后 componentDidMount-初次渲染,componentDidUpdate-更新挂载后,componentWillUNMount-卸载前,useLayoutEffect-同步执行,阻塞重绘
useEffect:componentDidMount-没有依赖值,componentDidUpdate-依赖值,componentWillUNMount-返回一个函数作为清理函数
render 时收集到 hooks 链表,将副作用收集到 fiberNode.updateQueue
count 值更新,找到对应节点修改值,标记重新渲染 fiberNode.subTreeFlags
重新渲染,执行全部 hooks,根据依赖是否变化,收集副作用变化
将 VDOM 更新到真实 DOM,有副作用执行副作用
- 第四遍 2025.1.18 -
JIT 运行时编译成 React createElement 方法
- 渲染前 shouldComponentUpdate
render: fiber树 + hooks按序全部执行 + 副作用链表收集 + Diff
// ⭐️ FiberRootNode HostRootFiber beginWork-completeWork wip.tag
commit:应用到真实 DOM + 执行生命周期+副作用链表
- 挂载后 componentDidMount、componentDidUpdate // ⭐️ useLayoutEffect
在 render 阶段执行 useState 按序将 count 值存储
更新 count 值,触发重新渲染,组件渲染重新执行所有 hooks,标记 fiberNode.subTreeFlag
判断是否有相关的 useEffect 副作用,有的话收集到副作用链表 fiberNode.updateQueue
生成新的 VDOM 后,Diff 比较最小更新 UI,应用到真实 UI,执行副作用
- 第三遍 -
❌❌❌ // 0、渲染之前,React 已经通过 JIT 将 JSX 描述的 UI 编译为 React createElement方法
render阶段:fiber树 + hooks链表执行 + 收集副作用链表 + Diff
react 首次渲染或者每次更新时,都会遍历生成整棵 Fiber 树(先创建 FiberRootNode,再创建 FiberHostNode 作为根节点,遍历AST逐个生成 fiberNode),同时在每个 fiberNode.memorizedState 中存储 hooks 链表;❌❌❌ // 1、FiberRootNode 才是「根节点」,「HostRootFiber」只是新的 fiber 树的遍历起始点,也就是 FiberRootNode.current
❌❌❌ // 2、「遍历AST逐个生成 fiberNode」可以更详细得表述为「遍历执行 beginWork,根据不同的 wip.tag 进行不同标签的处理,执行到叶子节点 completeWork,生成 WIP Tree」
在状态更新时,遍历执行所有 hooks;❌❌❌ // 3、render 阶段也是有生命周期在执行的,例如每次渲染执行前的 shouldComponentUpdate
❌❌❌ // 4、遍历执行 hooks 时,如果遇到有副作用的节点,除了被副作用链表收集,react 也会在节点 Node 上标记 fiberNode.subtreeFlags
同时 React 创建一个 side-effects-list 用于存储副作用链表的收集;⭐️ // fiberNode.updateQueue
将新的 fiber树和当前UI对应的 fiber 进行 Diff 比较,得到最小UI更新
commit阶段:应用到真实DOM + 执行副作用链表 + 执行生命周期 ⭐️ // 生命周期是类组件的概念
将Diff计算出的最小 UI 更新到真实DOM,
然后执行副作用链表 +
生命周期 componentDidMount/componentDidUpdate/componentDidUnMount 中的操作 ⭐️ // useLayoutEffect、ref引用也在这个时机
执行完成后,将 FiberRootNode.current 切换指向新的 fiber树
const [count, setCount]=useState(0)
在首次渲染的时候,将 hooks 存储在当前 fiberNode 的 hooks 链表中
当调用 setCount 触发更新,fiber 定位到当前 hooks 标记更新 ❌❌❌ // 1、「触发更新」其实应该描述成「更新 count 状态对应的节点」
触发整棵 fiber 树重新创建,更新当前组件,触发组件的 hooks 链表被全部重新执行,如果有副作用则被收集
得到最新的、准确的状态和属性用于渲染
❌❌❌ // 2、这里应该将「如果有副作用」改写为「要注意是否有 useEffect 的作用:根据依赖数组是否变化确定是否要更新」
生成新的 fiber 树和当前UI对应的 fiber 树 Diff 计算得到最小更新量,应用到真实 DOM
执行相关副作用,渲染完成,切换 current 指向
- 第二遍 -
❌❌ // 3、在 render 之前,运行时就会执行 JIT 先将 JSX 描述的 UI 编译为 React createElement 方法
1.render :创建一颗完整的Fibernode的虚拟树 + diff
❌❌ // 1、中间少写了两个关键步骤:存储 hooks + 标记、收集副作用链表
-创建FiberRootnode,然后再创建hostfibernode,作为起点深度遍历按照标签的所属类型转换成对应的fibernode
❌❌ // 2、创建 FiberRootNode 后,创建 fiber 作为 HostRootFiber,也就是 FiberRootNode.current
-核心就是通过react.createelement创建。React element类型。
❌❌ // 4、以单个组件为例,fiberNode 存储 hooks,按序执行时标记、收集副作用链表
-diff 计算最小更新量
2.commit 将计算出来的最小更新量,UI更新到真实dom上 + 执行相关生命周期和副作用列表(hooks不在这边执行,hooks不作为单独的流程执行。)
❌❌ // 5、更新时会重新调用渲染函数,按序执行所有 hooks,所以也算是流程的一部分
3.将fiberrootnode.current指向新的fiberNode的树
- 第一遍 -
❌ // 1、「componentDidMount、componentDidUpdate」 都在「commit 阶段-修改更新到真实 DOM 后」执行,还有少写了:「componentWillUnMount、useLayoutEffect、ref引用」也在这个时机是执行
JSX 描述 UI
1、render:componentDidMount
❌ // 2、还有几个生命周期在「render 阶段」执行:「shouldComponentUpdate + 所有状态相关的 hooks(例如 useState、useMemo、useCallback等)」
❌ // 4、 需要描述下 render 的「主要任务」 - 可以作为记忆的纲要:①构建新的Fiber树②存储 hooks③副作用的收集和标记④Diff
① 创建 FiberHostNode 作为实例
❌ // 5、先创建「FiberRootNode 作为根节点」,再创建「HostRootFiber 作为起始点」,也就是根节点存储的当前树 WIP tree - FiberRootNode.current
② DFS 遍历:类组件-执行 render 函数/函数组件 - 编译成 FiberNode tree [hooks链表的第一个存储在 memoriesState]
❌ // 6、没有在这里执行 render 函数/函数组件,它们俩的执行时机是渲染之前-它们的执行就是重新渲染的时机
❌ // 7、「生成 WIP tree 的过程」其实是:执行「beginWork」-通过wip.tag来不同类型元素生成 fiberNode;遍历到叶子节点的时候,执行「completeWork」构建出 tree + flags 冒泡
❌ // 8、在此期间, 为「每个FiberNode,存储 hooks 链表」(memorizedSate) ,而不是整颗 tree
2、pre-commit:componentDidUpdate
❌ // 3、没有 pre-commit 阶段
③ 执行过程中,标记 flag 收集副作用链表 side-effects-list/EffectFalgs
❌ // 9、对,但是「标记 flag 收集副作用链表」是两件事,一个是对有副作用需要「在更新阶段执行」的节点进行标记 fiberNode.subtreeFlags,另一个是在 fiberNode.updateQueue「构建 side-effects-list 用于收集副作用链表」
④ 执行完副作用列表后,得到 WIP tree,将 FiberHostNode.current 切换到 WIP tree
❌ // 10、不是在这里执行的副作用列表(在commit更新DOM后执行),也不是在这里切换指针(commit全部完成后)
❌ // 12、render 阶段的最后,进行「Diff 比较」
3、commit-Diff
❌ // 11、Diff 在 render 阶段
❌ // 17、描述 commit 阶段的主要任务:「将更新应用到真实DOM,同步执行,不可中断 + 执行副作用、类组件生命周期」
❌ // 18、这个 commit 阶段涉及的生命周期 如 第1条 错误所示
⑤ 将 current tree(和真实DOM对应的) 和 WIP 树进行对比,将计算后的 更新 应用到真实 DOM
❌ // 19、这里前面描述的是 Diff,应用到真实 DOM 之后,还有两个步骤:①「执行生命周期」 ②「commit 工作都完成后,切换 FiberRootNode.current 指向 WIP 树」
Diff 算法 - 增量式更新:
❌ // 13、描述不对,Diff 算法主要是「找出最小更新量」来更新真实 DOM
1、分层遍历,对比标签是否一致,一致则复用标签修改属性;不一致则标记重新渲染(删除重建)
❌ // 14、少描述了:「先对比根节点,再遍历子节点」,之后才是具体的「同层比较」
2、生成 FiberNode 描述的增量修改 UI,可以根据优先级调度和时间分片,逐步渲染,提高 diff 效率
❌ // 15、这里的「增量式」描述是 fiber 架构在 commit 阶段做的事情
❌ // 16、提高 diff 效率 - 实际上对应的 Diff 操作是「对于列表组件,使用 key 从新老树中匹配对应节点比较」
// 作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期
- 第五遍 2025.1.23 -
computed:依赖值不变化,计算的属性则使用缓存,不重新计算
watch:监听的对象,改变一次,重新计算一次
methods:每次重新渲染都会重新执行
父子组件挂载顺序:父 beforeCreate-created-beforeMount -> 子 beforeCreate-created-beforeMount-mounted -> 父 mounted
父子组件卸载:父 beforeDestroy -> 子 beforeDestroy-destroyed -> 父 destroyed
data 不为函数时,组件被多次实例化后,每个实例会共享 data 作用域
为函数时,每个实例都有独立的作用域
beforeCreate 初始化,data methods 都没有
created 创建,data methods watch computed 都执行了
beforeMount 挂载前,已有 VDOM
mounted 挂载到真实 DOM
beforeUpdate 更新前,已有 VDOM
updated 挂载到真实 DOM
beforeDestroy 卸载前,清理定时器、监听函数等,避免内存泄漏
destroyed 卸载
- 第四遍 2025.1.18 -
computed - 依赖的值不变化,直接使用缓存值,不重新计算
watch - 监听,属性变化一次,执行一次
methods - 组件每次渲染都执行
父子组件挂载顺序:父beforeCreate created beforeMount -> 子beforeCreate created beforeMount mounted -> 父mounted
卸载:父beforeDestroy -> 子beforeDestroy destroyed -> 父destroyed
data 不使用函数,每次组件实例化后,引用的是一个地址的data
使用函数后,每个组件都有各自的作用域
beforeCreate:没有初始化
created:data methods watch computed 初始化
beforeMount:挂载前,已生成 VDOM
mounted:挂载后,可以访问DOM
beforeUpdate:更新前,已生成 VDOM
updated:更新后
beforeDestroy:卸载前,清理,避免内存泄漏
destroyed
- 第三遍 -
1、computed:有缓存,只要依赖的变量不变,就不会触发重新计算
watch:监听,修改一次触发一次重新计算
methods:每次组件重新渲染都会触发重新执行
2、父组件 beforecreate>父组件created>父组件beforemounte>子组件 beforecreate 子组件created 子组件beforemount 子组件mounted>父组件mounted
卸载:父组件beforeunmounte>子组件beforeunmounte>子组件unmounted>父组件unmounted
❌❌❌ // 1、大虐,卸载是 beforeDestroy 和 destroyed
3、vue中的组件会被复用,多次调用,如果data不用函数返回,会导致每个组件实例都共享 data对象;采用函数形式后,可以保证每个组件都有独立的作用域
4、beforecreate 什么都没有加载,data和methods都没有
created 加载了data和methods、computed、watch,但是还没有DOM
beforemount 挂载之前,此时已经生成了完整的虚拟DOM树
mounted 挂载后,此时可以访问 DOM
beforeupdate 修改前,还没应用到真实DOM ⭐️ // 此时虚拟DOM已经重新构建完成
updated修改后,此时修改已经更新到DOM
beforeunmounte还未卸载
unmounted 已卸载
❌❌❌ // 2、大虐,和第一个一样的错误,名称错啦! beforeDestroy+destroyed,一般在 beforeDestroy 中清理一些 - 定时器、订阅时间、监听事件等,防止内存溢出
- 第二遍 -
1、Compute需要返回值进行缓存。如果值不变的话就不会重新计算。
❌❌ // 1、咦,和上次错误的逻辑一样,不是「存储的值」是否变化,是「依赖的值」是否变化
Watch是监听的值变化一次则触发一次回调函数。
Methods当中的函数每次更新组件渲染一次,执行一次。
2、Created的时候,页面当中的dom还没有挂载,在其中,不可以操作dom组件。但是data methods,computer和watch都已经处理了。
Mounted的时候已经挂载了,这个时候是可以操作。Dom节点的
3、父组件before created,父组件created,子组件before created子组件created,子组件beforemount,子组件mount,父组件before mount,父组件mounted。
❌❌ // 2、和上次错得一样 - 因为上次答案就是错的 😅,这就纠正了,下次不许再错了:子组件在父组件执行 父 beforeMounte 后触发
卸载组件也是父组件before destroy子组件before destroy子组件destroyed的父组件destroyed。
4、 如果不返回函数式的结构的话,每一个组件被实例化后,所有的实例访问的data都是一个引用对象。通过函数形式返回后,每一个被实例化的组件都拥有。自己的data作用域。
- 第一遍 -
① computed 有缓存,值不变就不会重新计算触发更新
❌ // 1、这里其实对 computed 缓存的内容理解有误差,不是「值」,是「计算属性依赖的值」,同时 computed 必须return 返回
watch 主要是监听一个值,变化一次就执行一次 ❌ // 2、执行回调
methods 方法,在初始化时定义,后续渲染时被调用 ❌ // 3、每次页面发生变化,都会被调用
② created 时, data+methods+computed+watch 已经定义存在,但是 DOM 还没有挂载到真实DOM
mounted时,DOM已经更新到真实 DOM,可以访问/操作DOM了
③ 父 beforeCreated - 父 created - 子 beforeCreated - 子 created - 子 beforeMounted - 子 mounted - 父 beforeMounted - 父 mounted
❌ // 子组件挂载完成后,父组件才会挂载 - 子组件在父组件执行 父 beforeMounte 后触发
④ 因为组件会被多次调用、实例化,函数保证了每个组件实例都可以拥有自己的 data 数据
⭐️ // 这里补充:每个组件实例都有自己的「作用域」,不会互相影响
// 作业64:Vue渲染怎么实现的?
- 第五遍 2025.1.23 -
1、模版编译
generate 词法分析、语法分析得到 AST
optimize 静态节点优化
render 生成 render 函数
2、数据双向绑定
初始化时,通过 Object.defineProperty(obj, property,{get,set})/proxy 劫持数据的 get set
开始渲染,创建组件 Watcher,get访问数据,将正在执行的 Watcher 收集到 Dep 中
赋值、更新触发 set,通知所有订阅的 watcher - dep.notify()
3、render 生成 VDOM
4、patch:diff 计算出 UI 最小更新量
- 第四遍 2025.1.18 -
1、模版编译:parse语法分析、词法分析,得到AST;optimize标记静态节点;generate生成render函数
❌❌❌❌ // 1、generate-optimize-render 函数名称记错
2、双向数据绑定:
初始化时劫持数据属性,Object.defineProperty(target, property, {get, set})/Proxy
实例化组件 Watcher,触发get,在Dep收集当前正在执行的 Watcher // ⭐️ 订阅时机
修改属性时,触发set,更新相关 Watcher - dep.notify()
3、执行 render 函数,生成虚拟 DOM
4、Diff 比较新的虚拟DOM 和 映射真实DOM的DOM树,得到最小更新 UI // ⭐️ patch
- 第三遍 -
1、模板编译: ⭐️ // AOT 参与全程
generate:通过词法解析、语法解析将模板转换成AST
optimize:遍历AST,标记静态节点,方便后续Diff时跳过
render:将AST转换成render函数字符串
2、将js代码进行双向绑定—数据劫持和发布订阅过getter/proxy劫持,进行自定义 ❌❌❌ // 1、getter 被劫持是结果,「劫持方式是 Object.defineProperty(target, property, {get:()=>{}, set:()=>{}})」/Proxy
在Dep中存储正在进行中的Watcher(确认下Dep的结构是不是以变量为key,所有订阅的Watcher为值的格式
❌❌❌ // 2、「每一个响应式对象属性都会创建一个对应的 Dep 类的实例」 -> 我写的这里理解有误,同时数据劫持之后,触发订阅的时机是「渲染组件,实例化 Watcher 时,执行到 getter 触发对应的 Dep 收集正在执行的 Watcher」
等调用setter/proxy赋值时,通知在Dep中所有订阅的Watcher,即执行 dep.notify()
3、执行render函数,渲染虚拟DOM
4、patch:使用Diff算法计算虚拟DOM和当前UI对应的虚拟DOM的最小更新量,应用到真实DOM
- 第二遍 -
1.模板编译 将模板编译成render函数。执行的步骤分别是 parse optimize render
❌❌ // 1、执行步骤的最后一步是 generate
// 三个步骤分别作用(AOT参与全程):① parse 词法解析+语法解析 template 得到 AST ② optimize 对AST静态内容优化 ③ generate 递归转换 AST 得到 render 函数
2.先通过AOT进行预编译标记静态节点。拿到可编译文件后对代码进行语法分析,词法分析转换为抽象语法树AST
❌❌ // 2、AOT 预编译功能是针对 template 的,不是针对 js 的(第一次做的时候知道,这次做的时候误以为预编译是针对 js 的 -> 其实通过 AOT 和 JIT 的区别就可以推断,AOT 既然依赖的是静态编译,就是需要像模版语法这种,方便标记静态节点 - js 那么动态肯定是不行🚫的)
进行双向数据绑定。通过object.define property或者是proxy,对数据进行getter和setter的重写。
渲染组件时,也就是实例化watcher时。执行getter触发收集相关的正在执行的watcher 到deps
更新组件时也就是触发数据的setter函数。通知所有的订阅 deps.notify()
⭐️ // Dep 用于依赖收集,作为容器管理多个订阅者 Watcher
3.执行render函数生成vdom树
4.patch也就是diff算法通过双端比较算法计算出最小UI的更新量,更新到真实 DOM
- 第一遍 -
1、render
❌ // 1、整个渲染「不止是 render + patch」,应该分成4步,①html:先「模版编译,得到render函数」 ②js:「实例化组件,同时收集依赖」(也就是数据双向绑定-实现响应式数据) ③html+js:「执行渲染函数/更新,得到 VDOM」 ④Diff/patch:「计算最小更新,应用到真实 DOM」
主要任务:生成新的 VDOM 树
相关生命周期:created、beforeMounted
❌ // 2、Vue 的生命周期很难罗列,这里模版编译前,js 都没执行到,怎么会有生命周期
①模版语法,静态编译 AOT 优化,标记静态节点
❌ // 3、这里就是第一步:「模版编译,生成render函数」
// 分三步:①parse - 递归解析 template(词法/语法分析),「转换成AST」抽象语法树
// ②optimize - 对AST静态内容优化,「标记静态节点」 ③generate - 递归「转换AST,创建render字符串」
②DFS遍历,发布订阅模式+双向数据绑定 - 在 Dep 收集相关的 Watcher,更新时通知这些订阅的 Watcher
❌ // 4、这里说得很粗糙,还少了「实例化组件」来触发,第二步:「实例化组件+创建响应式数据」 - 也就是「数据双向绑定」,是通过「数据劫持」和「发布订阅」模式相结合实现的 - Object.defineProperty/Proxy 劫持重新定义 getter和setter
❌ // 5、没说 Dep 是怎么收集依赖的:在创建 Watcher 访问 getter 时,把当前正在计算的Watcher订阅到 Dep
❌ // 6、没说怎么触发更新:setter 被触发,通知 Dep 中的所有订阅者 - dep.notify()
❌ // 7、数据双向绑定也有缺点:Vue2使用的 Object.defineProperty 在递归处理 data 中的属性时,必须要指定具体属性,所以不在 data 中声明的对象无法拥有双向绑定 - Vue3 改为 Proxy 劫持
③生成 VDOM
❌ // 8、这是第三步,没有说清楚怎么生成 VDOM:执行渲染函数(更新时重新计算/执行渲染函数),得到 VDOM
2、patch
主要任务:利用 Diff 算法,计算出需要更新的UI + 将更新应用到真实DOM上
相关生命周期:Mounted
❌ // 9、这里 patch 后,VDOM 已经更新到真实 DOM,除了 mounted,updated 也会在更新后在此执行
Diff 算法:两棵树,每棵各双指针,进行往复的节点比较,找到最小更新量
❌ // 10、Diff 比较的是新旧两棵 VDOM树
// 作业67:React 怎么做的性能优化?
- 第四遍 2025.1.23 -
fiber 时间分片 + 优先级调度 ⭐️ // 少写了 Diff 增量式更新 + 双 VDOM 树
VDOM 渲染时复用策略
- eagerState:state 如果是新值,和旧值无关,则直接渲染,不查询旧值
- bailout:state+props 不变时,复用组件
- shouldComponentUpdate(nextProps, nextState) 通过返回布尔值来自定义是否需要重新渲染
- React.memo((props) => {<div>{props.text}</div>}) 通过浅比较 props,不变则直接复用
- useMemo(() => c*2, [c]) 缓存一个值,依赖不变则复用
- useCallback(() => c, [c]) 缓存一个函数,依赖不变则复用(每次渲染函数都会重新创建
- 第三遍 2025.1.18 -
eagerState策略、bailout策略、Diff增量式更新、时间分片+优先级调度
bailout:
1、可变、不可变分开写
2、自定义是否需要重新渲染:shouldComponentUpdate(nextProps, nextState)
3、props浅比较:React.memo((props) => <div>{props.text}</div>)
4、依赖不变则使用缓存
缓存值:useMemo(() => value*2, [value]);
缓存函数:useCallback(() => { value }, [value]) ⭐️ // 每次渲染,函数都是重新生成的,用了这个就缓存住了
- 第二遍 2025.1.12 -
由于react使用的jsx比较灵活,在编译时很难做预编译的优化,所以react在运行时 JIT 做了一些优化
主要是两个策略 ⭐️ // 主要是避免组件重新渲染
1、eagerState 策略:如果当前更新的state和旧值无关,react 会直接使用新值渲染,而不是访问旧值后再修改
2、bailout策略:判断组件的 state和props相等,即可复用节点,跳过重新渲染
针对 bailout,有好几个生命周期和hooks 方便命中
1、在写代码的时候,将可变的和不可变的分开写,方便命中
2、shouldComponentUpdate(nextProps,nextState)通过返回布尔值,自定义是否重新渲染当前类组件
⭐️ // shouldComponentUpdate 是为了「类组件」的性能优化设计的生命周期
/**
❌❌ 1、对于「函数组件」,React 提供了 React.memo 这个高阶函数来处理 -> 对组件的 props 进行浅比较,如果不变则跳过重新渲染
const MyMemo = React.memo((props) => { return <div>{props.time}</div> })
*/
3、useMemo 将组件缓存,只要依赖数组不变,就不进行重新渲染
4、useCallback 将函数缓存,只要依赖不变,就不进行重新计算
⭐️ // const expensiveMemoValue = useMemo(() => { return count * 2 }, [count])
// const expensiveCallback = useCallback(() => { setCount(count + 1) }, [count])
// 这里的记忆方法:shouldComponentUpdate(类组件)/React.memo(函数组件) 都是用于通过比较前后 props;2个函数组件的性能 hook:useMemo/useCallback
- 第一遍 -
React 每次更新都是重新生成一棵完整的树,更新颗粒度细
1、运行时优化:fiber 架构实现时间分片 + 优先级调度
❌ // 1、对,在运行时优化,但是这个「架构级别」的属于渲染层面的优化,性能方便还是两个优化策略
①Time Slice 原理:每次渲染新的一帧前,fiber 都会查询 shouldYield 判断是否要执行一个渲染单元
②用户交互产生的渲染任务,优先级高于非视口下的页面渲染
2、在是否复用组件时,两个优化策略:
❌ // 2、对,但是一般不叫做「是否复用组件」,而是「避免组件重新渲染」
①s? 策略:?
❌ // 3、eagerState:在useState 更新过程中,如果新值不依赖于旧状态,则直接使用新状态来更新组件
②bailout 策略:props+state不变,就可以进入该策略
❌ // 4、只需要组件的 props 不变,即可避免重新渲染[state可能会影响子组件的 props,还是要考虑的]
3、日常一些写法也可以优化
❌ // 5、两个策略中,bailout 策略可以通过调用 API 来实现
①把动态的变量和静态数据拆分组件
❌ // 6、React.memo - 针对函数组件,对组件的 props 浅比较,不变则跳过重新渲染
const MyComponent = React.memo((props) => {return <div>{props.text}</div>}) // props.text 的值/引用不变,就不会重新渲染
❌ // 7、shouldComponentUpdate - 针对类组件,在组件更新前调用,自定义是否需要重新渲染
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.text !== nextProps.text; // 不同时,重新渲染
}
render() {return <div>{this.props.text}</div>}
}
❌ // 8、useMemo/useCallback - 依赖项不变,返回之前的缓存
const expensiceValue = useMemo(() => { return count*2 }, [count])
const handleClick = useCallback(() => {setCount(count + 1)}, [count])
❌ // 9、除了两个策略,还有 VDOM + Diff - 减少对真实 DOM 的操作,避免不必要的回流重绘
// 作业78:使一个标签看不见的几种样式及其区别
- 第二遍 2025.1.23 -
display: none; 不占位,DOM 节点还在VDOM树中,触发回流重绘
- v-if 直接移除 DOM 节点
opacity: 0; 占位,透明,绑定的事件还是可以触发的
visibility: hidden; 占位,看不见且绑定的事件无法触发,只触发重绘 ⭐️ // v-show
- 第二遍 2025.1.19 -
display: none; 不占位,隐藏 DOM ⭐️ // 触发回流+重绘
- v-if 不占位,且移除 DOM
opacity: 0; 依旧占位,且能够被触发事件
visibility: hidden; 依旧占位,但是不能够被触发事件 ⭐️ // 触发重绘(不影响结构)
- v-show same
- 第一遍 -
display: none; 不占位,直接移除 DOM 节点 v-if ❌ // 1、元素仍在 DOM 中,会触发回流+重绘; v-if 移除了 DOM 节点
opacity: 0; 依旧占位,但是透明、看不见节点,绑定的事件依旧可以触发
visibility: hidden; 不占位,DOM 节点还在,但是看不见节点,绑定的事件无法触发 v-show ❌ // 2、依旧占位,不影响结构,会触发重绘
// 作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口)
- 第三遍 2025.1.22 -
<canvas id="drawing" width=200 height=200></canvas>
const drawing = document.getElementById('drawing');
2D:左上角 矩形
if(drawing.getContext()) {
const context = drawing.getContext('2d');
context.fillStyle/fillRect/strokeStyle/strokeRect/fillText/drawImage
context.beginPath()/moveTo()/lineTo()/stroke()
}
3D:左下角
if(drawing.getContext()) {
const context = drawing.getContext('webgl');
}
context.viewport(drawing.width/2, 0, drawing.width/2, drawing.height/2) // 右下角1/4
- 第二遍 2025.1.19 -
<canvas id='drawing' width=200 height=200></canvas>
const drawing = document.getElementById('drawing');
2D: 左上角,矩形
if(drawing.getContext){
const context = drawing.getContext('2d');
context.fillStyle/fillRect/strokeStyle/strokeRect/fillText/drawImage
context.beginPath()/lineTo/moveTo/stroke()
}
3D:左下角,context.viewPoint() ❌❌ // 1、viewport
if(drawing.getContext){
const context = drawing.getContext('webgl');
}
- 第一遍 -
2D:
const context = drawing.getContext('2d');
坐标原点:左上角
唯一形状:矩形
基本操作:
context.fillStyle = '#333';
context.fillRect(10, 10, 20, 30); // 在坐标(10, 10)画一个 #333 的长20,高30 的矩形
context.strokeStyle = 'red';
context.strokeRect(20, 20, 30, 40); // 在坐标(20, 20)画一个边框红色的长30,高40 的矩形
context.beginStroke(); ❌ // 1、context.beginPath() - 创建路径
context.lineTo(x, y);
context.moveTo(x, y);
context.stroke();
context.fillText('string', x, y);
context.drawImage...
3D:
const context = drawing.getContext('webgl');
坐标原点:左下角
定义视口:
❌ // 2、viewport
context.viewpoint(drawing.width/2, 0, drawing.width/2, drawing.height/2); // 右下角1/4
// 作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的
- 第三遍 2025.1.22 -
原型链:一个函数的原型是另一个函数的实例
原型链继承:优-函数共享,不用再重复创建;缺-属性共享,修改一个,其他实例也改变了
Sub.prototype = new Super();
组合继承:优-每个实例都有独立的作用域;缺-父级构造函数被执行两次
function Sub() { Super.apply(this, arguments) } Sub.prototype = new Super();
寄生组合式继承:父级的原型对象副本是子级的原型对象
function inherit(Sub, Super){
let proto = Object.create(Super.prototype);
Object.defineProperty(proto, "constructor", {
enumerable: false,
value: Sub
})
Sub.prototype = proto;
}
- 第二遍 2025.1.19 -
原型链:一个函数的原型对象,是另一个函数的实例
原型链继承: Sub.prototype = new Super();
优点:函数不再需要每次都创建,实例共享函数方法
缺点:所有实例共享属性,修改一个会改变所有其他访问实例
组合继承: function Sub() { Super.apply(this, arguments) }
Sub.prototype = new Super();
优点:每个实例拥有各自的属性
缺点:父级构造函数每次都要执行2次
寄生组合式继承: 父类的原型对象的副本,是子类的原型
function inherit(Sub, Super) {
let proto = Object.create(Super.prototype);
proto.constructor = Sub;
❌❌ // 1、Object.defineProperty(proto, "constructor", {enumberable: false, value: Sub})
Sub.prototype = proto;
}
- 第一遍 -
原型链:一个函数的原型是另一个函数的实例
原型链继承
Sub.prototype = new Super();
优点:共享属性和方法
缺点:所有属性共享,修改一个实例会影响其他实例
组合继承
function Sub(){ Super.apply(this, arguments) }
Sub.prototype = new Super();
优点:每个实例都会有独立的属性
缺点:Super 父类构造函数每次都要执行2遍
寄生组合式继承: 父类的原型对象副本是子类的原型对象
function inherit(Sub, Super) {
const proto = Object.create(Super.prototype);
❌ // 1、Object.defineProperty(对象,对象属性,对象值),所以应该写成 Object.defineProperty(proto, constructor, { enumerable: false, value: Sub })
Object.defineProperty(proto, {
enumerable: false,
value: { constructor: Sub }
}); // 修复 自定义原型对象会导致的 constructor 指向问题
Sub.prototype = proto;
}
// 作业62:fiber架构是什么?优点和实现方式?
- 第五遍 2025.1.22 -
三大主流程:Scheduler 调度器 - 优先级调控,在渲染时宏观掌控
Reconciler 协调器 - 时间分片,使用 shouldYield 判断是否有足够时间,render 阶段
Render 渲染器 - commit阶段,应用到真实 UI
Scheduler、Reconciler 都是在内存中计算的,会被以下一些情况打断:报错;时间分片不够;优先级更高的任务
Render 会更新 UI,同步,不可打断
优点:1、优先级调度-Scheduler+中断恢复机制-优先处理一些用户交互,非视口的渲染优先级延后
2、时间分片-Reconciler+中断恢复机制-react每次更新都会重新创建整棵fiber树-对比-更新,之前同步执行,会长时间占用主线程,阻塞重绘,fiber 将渲染任务拆分成多个渲染任务单元,在重绘前+空闲时间执行
3、渐进式渲染-all-会优先处理高优先级渲染,每次重绘前 shouldYield 判断时间是否够进行一次渲染单元
4、增量式 Diff-不会一次性比较整棵树,而是跟随程序进展逐步计算-渲染,提高算法的性能 // ⭐️ 根据任务的执行进度,逐步比较和更新部分节点
- 第四遍 2025.1.18 -
Scheduelor ? - 管理优先级,在渲染过程中宏观把控 ❌❌❌❌ // 1、调度器
Reconciler 协调器 - 根据 shouldYield 计算时间分片:是否有足够时间,对应 render 阶段
Render 渲染器 - 对应 commit 阶段,更新 UI
Scheduelor 和 Reconciler 都是在内存中计算的,异步处理,可随时被打断:报错;更高优先级;时间分片不够
1、渐进式渲染[all]:先处理视口部分渲染,逐步渲染整个页面
❌❌❌❌ // 2、少:应该先说明 react 每次更新都要递归构建整棵 fiber 树+比较+更新,长时间占用主线程,会阻塞重绘 + 改:[Scheduelor+中断恢复机制]
2、时间分片[Reconciler+中断恢复机制]:将整个渲染分为多个渲染任务单元 - 在下一个渲染前,计算时间分片是否足够执行一个单元,或在空闲时间执行渲染任务
3、优先级调控[Scheduelor+中断恢复机制]:在渲染过程中,根据优先级,优先处理交互,延迟非视口等渲染任务
❌❌❌❌ // 3、改:[Scheduelor+优先级调度机制]
4、Diff 增量式[all]:可以根据优先级和时间分片,拆分任务小区域计算,提高 diff 效率
❌❌❌❌ // 4、修改描述:不是一次性比较整棵树,而是根据任务执行进度,逐步比较、更新部分节点 - 减少了每次比较的范围,提高了效率
- 第三遍 -
fiber 分成三大部分 // ⭐️ 不是「三大部分」,是「三个主流程」
Scheduler 调度器 - 在渲染过程中不直接参与渲染,用于优先级执行调度 // ⭐️ 宏观调控
Commit - 对应 render 阶段
❌❌❌ // 1、不是「Commit」,是「Reconciler」,叫做「协调器」,作用是「调用 shouldYield 判断当前 Time Slice 是否有剩余时间」
上述两部分都是异步的,随时可能会因为一些原因被打断,例如①报错 ②时间切片剩余时间不够 ③有更优先级
❌❌❌ // 2、不是因为「异步」才可以被打断,是以为「他们都在内存中执行,不会更新宿主环境 UI」
Reconciliator - 对应 commit 阶段,是同步的,不可被打断
❌❌❌ // 3、不是「Reconciliator」,是「Render」「渲染器」
❌❌❌ /** 上述三个主流程记忆法:
Scheduler 调度器 - 优先级宏观调控
Reconciler 协调器 - render 阶段,用于判断时间切片剩余时间
Render 渲染器 - commit 阶段(更新UI)*/
优点:
Fiber 架构提供了时间分片 + 优先级调度
1、之前 react 的解析、渲染流程是同步的,会占用主线程,阻塞页面更新;现在fiber 将渲染任务拆分成一个个小的渲染任务单元,利用浏览器的空余时间执行小单元
❌❌❌ /** 1、第一个优点的关键词是「渐进式渲染」。
描述内容和上述很像,但是「占用主线程」不是重点,是「长时间」占用主线程。
不是「react 的解析、渲染」在占用,是「react 每次更新都要递归构建整棵 fiber 树、比较、更新」,重点要突出导致访问卡顿感的原因是渲染卡住了。
实现方式:Scheduler调度器 + 中断恢复机制*/
2、在处理用户交互时,通过优先级调度优先处理交互,提升用户交互体验,比如不在视口的渲染任务可以延后执行
❌❌❌ // 2、第二个优点的关键词是「优先级调度」。实现方式:Scheduler 调度器 + 优先级调度机制
3、Diff 算法优化:比较两棵 fiber 树的差异,计算出最小UI更新,应用到真实 DOM,减少了对 DOM 操作带来的性能影响
❌❌❌ /** 3、第三个优点,现在描述的是怎么实现 diff,但是 fiber 对 diff 的优化主要是「增量式Diff」。
「不是一次性比较整棵树,而是根据任务的执行进度,逐步比较和更新部分节点,根据时间和优先级安排,逐步更新,减少了每次比较的范围,提高了效率」。
实现方式:Scheduler调度器 + 中断恢复机制 + Reconciler协调器 + 双缓存机制*/
❌❌❌ /** 4、少一个优点「时间分片」。
「有效利用浏览器空闲时间,在每帧开始之前检查剩余时间,是否执行一个小的渲染任务单元」。
实现方式:Reconciler协调器 + 中断恢复机制*/
实现方式:⭐️ // 核心是对的,结合 fiber 的三大主流程一起
1、实现了更好的时间切片:提供了渲染任务暂停、恢复的能力
2、任务管理+优先级调度
- 第二遍 -
Fiber架构分为三个部分
第一个是schedule调度器,在整个渲染流程当中不直接参与渲染,但作为总把控。
⭐️ // 主要用于优先级调度,宏观调控
第二个是 Reconciler ,对应的是render阶段。
上述两个部分都是异步执行的,随时会被以下几种情况打断1️⃣错误2️⃣更高优先级的任务,比如用户交互。3️⃣时间分片时间不够。
第三个是render。对应的是commit阶段。
这个部分是同步无法被打断,直到渲染到真实页面的dom。
优点
1.在Fiber架构出现以前,react的渲染更新流程是同步执行的。耗时比较久会阻塞。页面渲染性能差。
通过fiber实现了时间分片和优先级调度。拥有了暂停和恢复执行渲染任务的能力,将整个渲染任务拆分成了很多小的渲染单元,在针和针的渲染间隙进行执行,尽可能不影响渲染帧的执行。
2.同时优先级调度任务可以让react优先处理和用户的交互。将一些没那么重要的渲染任务稍后执行,例如不再是口中的画面的图块处理。
⭐️ // 总结:「渐进式渲染」、「暂停恢复渲染能力」、「优先级调度」、「时间分片管理」
1.双fiber node的虚拟dom树
2.优先级调度器,对任务进行统一调度。
⭐️ // 「优先级调度器」其实是两样,「任务调度」 + 「优先级管理」
3.模拟了生成器和yield。有了暂停和恢复渲染任务的能力。
❌❌ // 1、这里缺少一个 Diff 算法的执行优化:增量式
4.在每次渲染之前都会调用 shouldYield 判断当前是否拥有足够多的时间处理小的渲染单元任务。
⭐️ // 这里就是时间分片的原理
- 第一遍 -
fiber 是为了解决CPU瓶颈 - 连续的 js 执行会占用主线程,导致渲染阻塞,低于60帧/秒出现卡顿
❌ // 1、不是「连续的 js 执行」会长时间占用主线程,是「React递归构建整颗 DOM 树、比较、更新」
❌ // 2、明确 React 由于每次构建的都是整颗树 + 之前是同步渲染 - fiber 做到了异步渲染
优点:时间分片 + 优先级调度
1、将React的代码执行分成很多小模块,在帧和帧之间的小间隙里面,检测时间是否充足用于执行模块 - 不会占据很长时间的主线程,不影响渲染
❌ // 3、分成小模块的自然也不只是「代码执行」,是「渲染工作」被分解成了「小任务单元」 + 同时可以「暂停 + 恢复」 -> 使得 在每帧开始前检查剩余时间,能否执行的也就是「小渲染任务单元」
2、标记优先级,如果遇到用户交互,则优先处理,提高渲染效率和用户体验
❌ // 4、少写了:渐进式渲染
❌ // 上述写的都是原理,并不是具体实现 1、双缓存机制 current tree 和 work-in-progress tree 2、任务调度和优先级管理 3、Diff 算法优化为增量式 Diff
// 作业58:React和Vue是怎么描述UI的
- 第四遍 2025.1.22 -
React: JSX=HTML+JS,花括号{},类组件+函数组件,state-组件自变量+props外部传入自变量
Vue: 模版语法,双花括号,指令 v-if/v-else/v-else-if/v-for/v-modal/v-bind ⭐️ // v-show
- 第三遍 2025.1.18 -
React: JSX=HTML+JS、类组件+函数组件、state-组件的自变量;props-组件传入的变量、花括号{}插入js
Vue: 模版语法 + 双花括号 + 指令 v-if/v-elseif/v-show/v-model/v-bind
⭐️ // v-else-if + v-for
- 第二遍 -
React - JSX=HTML+JS,单个{}将js插入展示
❌❌ // 1、少写了 ①自定义组件-函数组件+类组件 ②state-状态,组件内部自变量;props-属性,外部传入自变量
Vue - 模版语法,双个将js插入展示 + 指令 v-if、v-show
⭐️ // 指令还有 v-for v-bind v-else/v-else-if v-on v-model等
- 第一遍 -
React: JSX - 由js+HTML组成,函数组件+类组件
// ❌ 少写了: 可以自定义组件 + 使用花括号{}将js嵌入JSX {note}
// 少写了: state-状态,组件内部自变量
// 少写了: props-属性,其他组件传入的自变量
Vue: 模版 - 花括号嵌入模版+指令<div v-if="show">
// ❌ 模拟语法 - 结合数据+预先定义的模版
// 少写了: 使用双大括号展示数据 + 指令v-if="show"/v-for="item in items"
// 作业75:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序?
- 第三遍 2025.1.22 -
组件返回可以用于渲染的 JSX;hook 可以返回任何值
组件应该写想要做什么,而不是怎么做 - 抽离到 hooks 中
hooks:如果你想要写得 hook 中没有使用其他 hook,建议不要使用 hook
hooks 之间是不共享 state 的,只是使用相同的修改 state 逻辑
在渲染时,有更新,触发组件重新渲染,渲染时会按序执行所有 hooks
- 第二遍 -
React 组件用于返回可以渲染的UI,一般是 JSX
- 组件用于描述做什么,而不是怎么做
如果 hook 中没有 hooks ,则不建议抽离成 hook
- hook 只是共享状态逻辑,并不是直接共享状态
- hook 可以返回任何值
顺序:组件更新时,会按序执行所有 hooks
- 第一遍 -
React 组件用于复用 html 结构 - 清晰的输入和明确的输出,尽可能不要有副作用,写注释
hook:hook用于复用代码逻辑,内部没有 内置hook 可以不使用 hook 来复用,函数即可
// ❌修改下题目:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序?
React 组件代码表达的是 目标,而不是具体实现 - 描述的是想要做什么,而不是怎么做
hook 中如果没有内置 hook,则不建议使用 hook
自定义 hook 只是共享状态逻辑,而不是状态本身 - 每次调用都有独立的状态
返回值:组件一般是返回一段 React 能够显示的内容,例如JSX结构,hook可以返回任何值
渲染:组件重新渲染时,会执行所有 hook 链表,所以组件一直可以接收到最新的 props 和 state
// 作业53:胜利者一无所获 说的是什么
- 第三遍 2025.1.22 -
海明威 战地春梦 反战,战争胜利的一方失去了美好的品质(善良等
质疑赢家通吃逻辑,质疑胜利的意义,鼓励人们追求内在美好的品质
- 第二遍 2025.1.19 -
海明威 战地春梦 反战 战争胜利者失去了美好的品质
鼓励人们追求内在美 ⭐️ //质疑胜利的本质
- 第一遍 -
海明威 在《战国春秋》中用于反战,战胜方失去了美好的品质、善良、公序 // ❌ 《战地春梦》
质疑胜利的本质,如果胜利者并不得获得很多,那要追求什么?
鼓励人们追求内在美,包括善良、纯真等
// 2024.05 rem是基于什么原理进行适配的?
- 第三遍 2025.1.22 -
rem 相对于根元素 html
em font-size 相对于父级的 font-size;height width padding margin 是相对于当前元素的 font-size
- 第二遍 2025.1.19 -
rem - 根元素 html
em - font-size 属性相对于父级的 font-size; width/height/padding/margin 相对与本元素的 font-size
- 第一遍 -
rem 相对于根元素的 font-size - html(body) ❌ // 1、不是 body-只是html的一个子标签,就是 html
em
- 当前节点的字体大小相对于父元素的 font-size
- 当前节点的 width/height/padding/border/margin 相对于当前元素的 font-size
// 作业85:Lambdas是什么
匿名函数
// 作业71:React为什么要合成事件?和原生事件有什么不同?
- 第四遍 2025.1.22 -
react合成事件:①浏览器处理兼容性,对事件系统的封装,开发者专注于写代码 ②借用事件委托,在组件挂载时,将事件冒泡到最顶层,一般是 document,节省了对DOM元素绑定的开销
不同:
事件绑定方式:合成事件-JSX属性绑定事件处理函数;原生事件-onclick属性、addEventListener
事件执行顺序:合成事件-由react统一调度,一般是冒泡;原生事件-先捕获,后冒泡,一般早于合成事件
事件对象:合成事件-封装事件,统一;原生事件-各个浏览器有差异
- 第三遍 2025.1.19 -
合成事件:①React 对原生事件的封装,抹平浏览器间的差异②通过事件委托机制,冒泡到顶层标签绑定,一般是 document ⭐️ // 跨浏览器兼容
优点:抹平差异,关注功能实现;事件委托机制-挂载到DOM时冒泡到 document 绑定,节省对DOM绑定的性能
事件绑定方式:JSX 绑定事件处理函数;onclick 或者 addEventListener
事件执行顺序:合成事件由React统一调控,一般是冒泡;原生事件一般是先捕获再冒泡,比合成事件执行得早
事件对象:合成事件统一对象,包括一些属性和方法;原生事件不同浏览器间有差异
- 第二遍 -
React 的合成事件,其实是一个处理了浏览器兼容性的事件处理系统 ⭐️ // + 是对浏览器原生事件的封装
功能:①处理各个浏览器的差异,使用者可以只关注代码实现;
②利用事件委托,所有的合成事件在构建时,统一冒泡到最外层,一般是 document 标签上绑定,大量减少了绑定事件的性能消耗
❌❌ // 1、不是在「构建时」,事件委托发生在「组件挂载时」
差异:
事件对象:合成事件-统一了对象类型;原生事件-根据浏览器不同有一定差异
事件执行机制:合成事件-由React内部事件系统统一管理执行,一般是在冒泡阶段执行;
原生事件-捕获阶段就会触发,比合成事件早;同步执行,触发后直接执行;
❌❌ // 2、少写了:事件绑定方式 合成事件-JSX中属性指定事件处理函数;原生事件 标签的onclick或者addEventListener
- 第一遍 -
合成事件的定义:React 统一了各个浏览器内核的原生 API 差异,抹平差异后,用户只需要关注功能实现,而不需要再关注宿主环境
合成事件是原生事件的高级集合
❌ // 1、内核是对的,不是“高级集合”,是「React对浏览器原生事件的封装」,是「一个跨浏览器兼容的事件系统」
// 优点/原因:① 跨浏览器兼容性 ②性能优化:通过事件委托到 document,减少大量事件绑定的开销
❌ // 2、“不同”需要具体的描述:
// ①事件对象(有一套标准的属性和方法,用于事件信息 VS 由浏览器提供,属性方法因浏览器而异)
// ②事件绑定方式(React中JSX属性中指定事件处理函数来绑定 VS 原生标签的 onclick/addEventListener)
// ③事件执行顺序(内部事件系统决定,冒泡执行,一般比原生晚 VS 先捕获再冒泡)
// 2021.04 05-3 白屏优化?
- 第三遍 2025.1.20 -
硬件: DNS 缓存、优化;TCP 优化,服务器优化
代码:1、html 精简代码,优化结构
2、css:尽可能使用 link 而不是@import,触发预加载,提前下载;从右向左解析,减少层级;媒体查询,减小 css 文件大小;利用 GPU 加速,减少回流重绘
3、js:尽可能不写内联代码,defer、async,不阻塞渲染
4、压缩文件
5、HTTP 请求减少数量和大小
- 第二遍 2025.1.19 -
网络进程下载完成,触发浏览器刷新页面,渲染进程解析还没完成,出现解析白屏
1、DNS 缓存、优化,服务器、TCP优化
2、html:减少结构层级,精简代码
css:从右向左解析,所以减少层级;媒体查询,减少加载代码体量;调用 GPU 等优化方式,减少回流重绘
❌❌ // 1、使用 link 加载而非 @import,可以触发预加载
js:defer、async 加载 ❌❌ // 2、尽量不使用「内联代码」
3、压缩代码
4、HTTP 请求数量减少、大小减少
- 第一遍 -
白屏:网络进程文件加载完毕,浏览器开始刷新页面,但是主线程渲染流程还没有结束,页面短暂出现解析白屏
❌ // 1、少写 - 硬件加速:DNS 解析优化,例如缓存、预加载;TCP、服务器优化
1、html 精简代码,简化结构,减短DOM解析时间
2、css 从右到左的解析方式,尽可能减少层级,使用 link 而非 @import,从而触发预加载,使用 css 优化/GPU 加速减少回流重绘 ❌ // 2、少写 - 对于大文件的 css,利用媒体查询拆分不同用途
3、js 加载使用 defer/async 避免阻塞 ❌ // 3、少写 - 尽量不使用 内联代码
4、压缩代码
5、减少 http 请求次数、大小
// 2024.12 模拟实现promise
- 第四遍 2025.1.20 -
const Status = {
pending: 'pending',
resolve: 'fulfilled',
reject: 'rejected'
}
function MyPromise(executor){
this.status = Status.pending;
this.value = null;
this.reason = null;
this.resolveCalls = [];
this.rejectCalls = [];
const resolve = (value) => {
if(this.status === Status.pending) {
this.status = Status.resolve;
this.value = value;
this.resolveCalls.forEach(it => it(value));
}
}
const reject = (reason) => {
if(this.status === Status.pending) {
this.status === Status.reject;
this.reason = reason;
this.rejectCalls.forEach(it => it(reason));
}
}
try{
executor(resolve, reject);
}catch(e){
reject(e);
}
}
MyPromise.prototype.then = function(onResolve, onReject) {
onResolve = typeof onResolve === 'function' ? onResolve : v=>v;
onReject = typeof onReject === 'function' ? onReject : r => { throw r };
return new MyPromise((resolve, reject) => {
const resolveFun = () => {
setTimeout(() => {
try{
const res = onResolve(this.value);
resolve(res);
}catch(e){
reject(e);
}
}, 0)
}
const rejectFun = () => {
setTimeout(() => {
try{
const res = onReject(this.reason);
resolve(res);
}catch(e){
reject(e);
}
}, 0)
}
switch(this.status) {
case Status.pending:
this.resovleCalls.push(resolveFun);
this.rejectCalls.push(rejectFun);
break;
case Status.resolve:
resolveFun();
break;
case Status.reject:
rejectFun();
break;
}
})
}
MyPromise.prototype.catch = function(onReject) {
return this.then(null, onReject);
}
MyPromise.prototype.finally = function(callback) {
return this.then(value => { callback(); return value; },
reason => { callback(); throw reason; })
}
MyPromise.resolve = function(value) {
return new MyPromise((resolve, reject) => {
if(value instanceof MyPromise) {
return value.then(resolve, reject); ⭐️ // 可以省略 return
}
resolve(value);
})
}
MyPromise.reject = function(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
})
}
MyPromise.all = function(arr) {
return new MyPromise((resolve, reject) => {
return arr.reduce((pre, cur, index) => {
cur.then(res => {
pre.push(res);
if(index === arr.length - 1){
resolve(pre);
}
}, reject);
return pre;
}, [])
})
}
MyPromise.race = function(arr) {
return new MyPromise((resolve, reject) => {
arr.forEach(it => MyPromise.resolve(it).then(resolve, reject))
})
}
- 第三遍 2025.1.19 -
then catch finally resolve reject all race
1、promise 使用函数来实现,而不是 class
2、then 中 onResolve、onReject 都会返回 fulfilled 状态的 promise
const Status = {
pending: 'pending',
resolve: 'fulfilled',
reject: 'rejected'
}
MyPromise.prototype.then = function(onResolve, onReject){
onResolve = typeof onResolve === 'function' ? onResolve : v => v;
onReject = typeof onReject === 'function' ? onReject : r => { throw r };
return new MyPromise((resolve, reject) => {
const resolveFun = () => {
setTimeout(() => {
try{
const res = onResolve(this.value);
resolve(res);
}catch(e){ reject(e) }
}, 0)
};
const rejectFun = () => {
setTimeout(() => {
try{
const res = onReject(this.reason);
resolve(res);
}catch(e){ reject(e) }
}, 0)
};
switch(this.status) {
case Status.pending:
this.resolveCalls.push(resolveFun);
this.rejectCalls.push(rejectFun);
break;
case Status.resolve:
resolveFun();
break;
case Status.reject:
rejectFun();
break;
}
})
}
- 第二遍 -
const State = {
pending: 'pending',
resolve: 'fulfilled',
reject: 'rejected'
}
class MyPromise { ❌❌ // 1、模拟 Promise 的时候,一般都是用函数
constructor(executor) {
this.state = State.pending;
this.value = null;
this.reason = null;
this.resolveCalls = [];
this.rejectCalls = [];
const resolve = (value) => {
if(this.state === State.pending) {
this.state = State.resolve;
this.value = value;
this.resolveCalls.forEach(it => it(value));
}
}
const reject = (reason) => {
if(this.state === State.pending) {
this.state = State.reject;
this.reason = reason;
this.rejectCalls.forEach(it => it(reason));
}
}
try{
executor(resolve, reject);
}catch(e){
reject(e);
}
}
}
MyPromise.prototype.then = function(onResolve, onReject) {
onResolve = typeof onResolve === 'function' ? onResolve : v => v;
onReject = typeof onReject === 'function' ? onReject : r => { throw r };
let that = this;
return new MyPromise((resolve, reject) => {
const resolveFun = () => {
setTimout(() => {
const res = onResolve(that.value);
resolve(res);
❌❌ // 2、遗漏捕获错误
/**
try{
const res = onResolve(that.value);
resolve(res);
}catch(e){ reject(e) }
*/
}, 0)
}
const rejectFun = () => {
setTimeout(() => {
const res = onReject(that.reason);
reject(res);
❌❌ // 3、遗漏捕获错误 + onReject 返回的依旧是 resolve 的 promise
/**
try{
const res = onReject(that.reason);
resolve(res);
}catch(e){ reject(e) }
*/
}, 0)
}
switch(that.status) {
case Status.pending:
that.resolveCalls.push(resolveFun);
that.rejectCalls.push(rejectFun);
break;
case Status.resolve:
resolveFun();
break;
case Status.reject:
rejectFun();
break;
}
})
}
MyPromise.prototype.catch = function(onReject) {
return this.then(null, onReject)
}
MyPromise.prototype.finally = function(cb) {
return this.then(value => {cb(); return value;},
reason => {cb(); throw reason;})
}
MyPromise.resolve = function(value) {
return new MyPromise((resolve, reject) => {
if(value instanceof MyPromise) {
value.then(resolve, reject);
} else {
resolve(value);
}
})
}
MyPromise.reject = function(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
MyPromise.all = function(arr) {
return new MyPromise((resolve, reject) => {
arr.reduce((pre, cur, index) => {
cur.then(res => {
pre.push(res);
if(index === arr.length) {
resolve(pre);
}
}, reject);
return pre;
}, [])
})
}
MyPromise.race = function(arr) {
return new MyPromise((resolve, reject) => {
arr.forEach(it => MyPromise.resolve(it).then(resolve, reject));
})
}
- 第一遍 -
then catch finally resolve reject all race
const Status = {
pending: 'pending',
resolve: 'fulfilled',
reject: 'rejected'
}
function MyPromise(executor) {
this.status = Status.pending;
this.value = null;
this.reason = null;
this.callbacksResolve = [];
this.callbacksReject = [];
const resolve = (value) => {
if(this.status === Status.pending) {
this.status = Status.resolve;
this.value = value;
this.callbacksResolve.forEach(it => it(value));
}
}
const reject = (reason) => {
if(this.status === Status.pending) {
this.status = Status.reject;
this.reason = reason;
this.callbacksReject(it => it(reason))
}
}
try{
executor(resolve, reject);
}catch(e) {
reject(e);
}
}
MyPromise.prototype.then = function (onResolve, onReject) {
onResolve = typeof onResolve === 'function' ? onResolve : v => v;
onReject = typeof onReject === 'function' ? onReject : r => {throw r};
return new MyPromise((resolve, reject) => {
const resolveFun = () => {
setTimeout(() => {
try{
const res = onResolve(this.value);
resolve(res);
}catch(reject); ❌ // 6、catch 不能这么写 catch(e){ reject(e) }
}, 0)
}
const rejectFun = () => {
setTimeout(() => {
try{
const res = onReject(this.reason);
resolve(res);
}catch(reject); ❌ // 6、catch 不能这么写 catch(e){ reject(e) }
}, 0)
}
switch(this.status) {
case Status.pending:
this.callbacksResolve.push(() => resolveFun());
this.callbacksReject.push(() => rejectFun());
break;
case Status.resolve:
resolveFun();
break;
case Status.reject:
rejectFun()
break;
}
})
}
MyPromise.prototype.catch = function (onReject) {
return this.then(null, onReject);
}
MyPromise.prototype.finally = function(callback) {
return this.then((value) => { callback(); return value; },
(reason) => { callback(); throw reason; })
}
MyPromise.resolve = (value) => {
return new MyPromise((resolve, reject) => {
if(value instanceof MyPromise) {
return value.then(resolve, reject);
}
resolve(value);
})
⭐️ // 3、这里的 return 可以删除,不影响
}
MyPromise.reject = (reason) => {
return new MyPromise((resolve, reject) => {
reject(reason);
})
⭐️ // 2、简写成 return new MyPromise((resolve, reject) => reject(reason));
}
MyPromise.all = function (arr) {
return new MyPromise((resolve, reject) =>{
return arr.reduce((pre, cur, index) => {
⭐️ // 4、这里的 return 可以删除,不影响
cur.then(res => {
pre.push(res);
if(index === arr.length -1) {
resolve(pre);
}
}).catch(reject);
❌ // 1、reject 是作为 then 的第二个函数 -> }, reject)
return pre;
}, [])
})
}
MyPromise.race = function (arr) {
return new MyPromise((resolve, reject) => {
arr.forEach(it => {
return MyPromise.resolve(it).then(resolve, reject);
})
⭐️ // 5、这里科利简写 arr.forEach(it => MyPromise.resolve(it).then(resolve, reject))
})
}
LTN2 - 10题 - 2025.1.20 - 1.21 131min
✅作业17:对斐波那切数列进行尾调用优化 ❌作业20:浏览器打开一个 URL 发生了什么 ✅作业26:给出一个场景,要求A、B请求执行结束后,再执行C请求,其中A、B请求同时开始,怎么实现 ✅作业34:写出强缓存、协商缓存的流程及相关属性 ✅作业57:“假装成功”的意义是什么 ✅2021.06 BFC特点、触发、存在/解决的问题 ✅2020.03 改变原数组+结束循环+性能排序 ❌2021.09 Object属性排序、与Map的区别 ✅2021.07 防抖节流 ✅2024.12 模拟实现jsonp
// 2024.12 模拟实现jsonp
- 第二遍 2025.1.21 -
function jsonp(url, params, cb) {
const suffix = Object.keys(params).map(it => `${key}=${params[key]}`).join('&');
const totalUrl = url + url.includes('?') ? '&' : '?' + suffix + `&callback=${cb}`;
const dom = document.createElement('script');
window[cb] = function() { ⭐️ // 带参数 data
cb();
document.body.removeChild(dom);
}
dom.src = totalUrl;
document.body.appendChild(dom);
}
- 第一遍 -
function jsonp(url, params, cb){
const suffix = Object.entries(params).map(([key, value]) => `${key}=${value}`).join('&');
const allUrl = url + (url.includes('?') ? '&' : '?') + suffix + (suffix ? '&' : '') + `callback=${cb}`;
const dom = document.createElement('script');
window[cb] = function (...args) {
cb(...args);
document.body.removeChild(dom);
}
dom.src = allUrl;
document.body.appendChild(dom);
}
// 2021.07 防抖节流
- 第二遍 2025.1.21 -
function debounce(fn, delay){
let time;
return (...arg) => {
if(time) {
clearTimeout(time);
}
time = setTimeout(() => {
fn(...arg);
}, delay);
}
}
// 间隔多少秒才能执行一次
function throttle(fn, delay) {
let flag = false;
let time; ⭐️ // 已经不需要 time 来控制执行时机了
return (...args) => {
if(flag) {
return
}
flag = true;
fn(...args);
time = setTimeoute(() => {
flag = false;
}, delay)
}
}
- 第一遍 -
function debounce(fn, delay) {
let timeId = null;
return function (...args) {
if(timeId) {
clearTimeout(timeId);
}
timeId = setTimeout(() => {
fn(...args);
}, delay)
}
}
function throttle(fn, delay) {
let flag = true;
return function(...args) {
if(!flag) {
return;
}
flag = false;
fn(...args);
setTimeout(() => {flag = true}, delay)
}
}
// 2020.03 改变原数组+结束循环+性能排序
- 第二遍 2025.1.21 -
可以改变原数组的函数:shift pop unshift push sort reverse splice fill
return 和 break 不可以结束的循环:filter map forEach reduce
性能循环: for循环 for-of forEach map for-in
- 第一遍 -
可以改变原数组的数组方法有:shift unshift pop push reverse sort splice fill
不可以通过return和break结束循环 filter map reduce forEach
排序:for>for...of>forEach>map>for...in
// 2021.06 BFC特点、触发、存在/解决的问题
- 第二遍 2025.1.21 -
BFC 特点:①独立容器 ②从上而下,垂直排列 ③和外部浮动元素不重合 ④计算高度时包含浮动元素-高度塌陷
触发:①float不为none ②overflow不为none:hidden/auto/scroll ⭐️ // overflow不为visible
③position:absolute/fixed ④display:flex/table-captoin/table-cell/inline-block ⑤根元素
存在的问题和解决:BFC内两个盒子,上下 margin 重叠 - 外边距重叠,解决方案给父元素 display:flex
- 第一遍 -
特点:①独立容器 ②从上到下,垂直排列 ③不和外部的浮动元素重合
触发:①float 非 none ②overflow 非 visible:hidden/auto/scroll ③display: flex/inline-block/table-caption/table-cell ④position: absolute/fixed ⑤根元素
问题:同一个BFC中相邻的两个盒子会出现 外边距重叠,只显示一个 margin
解决方案:给他们的父元素添加 display:flex
// 作业57:“假装成功”的意义是什么
- 第二遍 2025.1.21 -
意识能够改为行为,行为也能够改变心态 言灵 ⭐️ // 提高睾丸酮,降低皮质醇 - 提高自信心,降低压力 fake it until you become it
- 第一遍 -
客观从物理层面说,肢体是可以改变人体内的睾丸酮和皮质醇(压力激素)
假装成功可以降低压力、提高自信心
人是有“自我预言”机制的,你越相信,你越能做到
you derserve better, fake it until you become it!
// 作业34:写出强缓存、协商缓存的流程及相关属性
- 第二遍 2025.1.21 -
强缓存:根据 URL 在本地内存、磁盘超找缓存 - 找到并返回就是强缓存生效
协商缓存:本地有缓存,但是已过期 cache-control: max-age=2000 / Expires: deadline ,进协商缓存流程
If-modified-since/Last-modified、If-none-match/ETag - 304 协商缓存生效 ⭐️ // 刷新本地缓存时间
- 第一遍 -
1、url 在本地查找是否存在缓存,存在的话直接使用[强缓存生效]
2、存在缓存,但是缓存失效 If-none-match/Etag、Last-Modified/If-modified-since ,如果服务器返回 304,代表缓存内容正确,使用缓存并刷新本地的缓存有效期[协商缓存生效]
有效期:Cache-Control: max-age=2000;(单位是秒)、Expires: deadline具体时间点;
// 作业26:给出一个场景,要求A、B请求执行结束后,再执行C请求,其中A、B请求同时开始,怎么实现
- 第二遍 2025.1.21 -
Promise.all([A(), B()]).then(C);
async function() {
await Promise.all([A(), B()]);
C();
}
- 第一遍 -
1、Promise.all([A(), B()]).then(C);
2、async function () {
await Promise.all([A(), B()]);
C();
}
// 作业17:对斐波那切数列进行尾调用优化
- 第二遍 2025.1.20 -
斐波那契数列正常写法
function fib(n) {
if(n<2){
return n;
}
return fib(n-1) + fib(n-2);
}
0 1 1 2 3
优化写法
function betterFib(a = 0, b = 1, n) {
if(n===1){
return 0
}
return betterFib(a+b, a, n-1);
}
- 第一遍 -
function fib(n) {
if(n<2)return n;
return fib(n-1)+fib(n-2);
} 0 1 1 2 3 5 8
function optimizeFib(a=0, b=1, n){
if(n === 0) return a;
return optimizeFib(b, a+b, n-1)
}
LTN3 - 11题 - 2025.1.20 65min
❌作业43:TED 怎么提升自信 ❌作业42:TED 如何和大脑正确交流沟通 ✅作业40:写出以下值
1、Number(null)
2、Number(undefined)
3、isNaN() 检测那些值为 true?
4、Number、parseInt、parseFloat 检测 空字符串 得到?
5、1+'2'+'2'
1+ +'2'+'2'
1+ -'1'+'2'
'A'-'B'+'2'
'A'-'B'+ 2
6、let i = 0, age = 30;
i++ + age;
++i + age;
7、 12.34 | 0;
12.43 >> 0;
8、null === undefined;
❌作业39:shim 和 polyfill 区别 ✅作业23:写出js 代码的执行顺序。词法作用域的特点是什么?
function bar(){
console.log(myName);
}
function foo(){
var myName = 'hi 坑';
bar();
}
var myName = 'bye 坑';
foo();
❌作业6:new操作符实例化一个对象的过程 ✅作业32:什么是队头阻塞?原因是什么?怎么解决? ✅作业16(1):箭头函数和普通函数的区别是什么?(3点) ✅2019.07 盒模型有哪些?有什么区别? ❌2019.07 为什么要清除浮动?怎么清除浮动? ❌2021.06 CSS 选择器 - 权重/匹配方式
// 2019.07 盒模型有哪些?有什么区别?
box-sizing: content-box; // W3C标准盒模型 witdh 只包含内容
box-sizing: border-box; // IE盒模型 width包含 内容+padding+border
// 作业16(1):箭头函数和普通函数的区别是什么?(3点)
不能使用 new.target super arguments;不能用作构造函数;没有prototype
// 作业32:什么是队头阻塞?原因是什么?怎么解决?
队头阻塞:HTTP/1.1 请求-应答模式导致的,一个请求响应不返回,其他请求响应需要等待
⭐️ // HTTP/1.1 域名分片 + 并发连接
解决方式:HTTP/2 采用二进制帧-双向传输序列,虚拟的流,一个连接中可以处理多个请求响应-多路复用,多个请求响应之间没有顺序关系,解决了队头阻塞(在应用层解决,但传输层TCP还存在队头阻塞
HTTP/3 将TCP换为UDP ⭐️ // QUIC 协议
// 作业23:写出js 代码的执行顺序。词法作用域的特点是什么?
bye 坑
词法作用域:只和代码所写位置有关
js代码执行顺序:①执行上下文:this
var 变量作用域 - 先创建 arguments,再函数提升,再变量提升
let const 词法作用域 - 作用域链:多个执行上下文的变量作用域组成的链表
②可执行代码 AST
// 作业40:写出以下值
1、0
2、NaN
3、{} NaN undefined
4、Number - 0,含有非数字字符时返回NaN;parseInt、parseFloat - NaN,不以数字开头的字符串返回NaN
5、'122' '32' '02' 'NaN2' NaN
6、30 32
7、12 位操作符只能操作 32 位的整数,运算时将两遍值转换成整数
8、true undefined派生自null,在js中规定两者相等性比较为 true
LTN5 - 2题 - 2025.1.20 10min
❌作业2:支持迭代器的原生语言特性有哪些? ✅作业5:什么是生成器对象?有什么特点?什么时候会被执行?
// 作业5:什么是生成器对象?有什么特点?什么时候会被执行?
生成器对象:生成器函数返回的对象
特点:默认是可迭代对象
在创建时,生成器对象保持一个暂停状态,第一次调用 next 函数时被执行 ⭐️ // 恢复执行
LTN①③
LTN ①③ - 错题重做 28题 - 2025.1.18 - 1.19 5h10m
✅作业58:React和Vue是怎么描述UI的 ❌作业62:fiber架构是什么?优点有哪些? 是怎么实现的? ✅作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count, setCount]=useState(0)说明 ❌作业64:Vue渲染怎么实现的? ✅作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期 ✅作业66:React如何处理错误 ✅作业67:React 怎么做的性能优化? ❌作业68:React hooks 的原理是什么?useEffect useState模拟实现 ❌作业70:Vue 和 React 的 Diff 算法比较 ✅作业71:React为什么要合成事件?和原生事件有什么不同? ❌作业72:react 的 声明周期有哪些,在不同生命周期中做什么事情? ✅作业78:使一个标签看不见的几种样式及其区别 ✅2024.12 模拟实现promise ✅2024.11 观察者模式 EventEmitter ✅2022.08 包管理工具 ✅2024.09 第八章 对象、类和面向对象编程 小结 ✅2024.11 第十七章 事件 ❌作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口) ✅作业53:胜利者一无所获 说的是什么 ✅2024.10 怎么实现跨域 ❌2021.07 bind、apply/call三者异同+apply/call实现bind ❌2021.07 数组扁平化(一层、全部展开、指定深度) ❌2021.04 05-3 白屏优化? ❌作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的 ✅2024.05 rem是基于什么原理进行适配的? ❌2020.07 对象分类、Promise按序执行、实现map ❌2019.10 两栏,左边固定,右边自适应的布局 ✅2019.10 三栏,两边固定,中间自适应
LTN ①③ - 76题
2025.1.7 开始
(LTN1 盒子) 2025.1.10-1.17 - 21h31min
✅作业16(2):arguments 对象对待命名参数和设置了默认值的命名参数有什么不同?数组作为参数传入函数得到的arguments 是什么?怎么实现arguments是数组?怎么收集独立参数?在对象字面量和数组字面量中,扩展运算符有什么不同的表现? ✅作业25:简单写下 请求创建过程 ✅作业49:牛顿三大定律、热力学两大定律 ✅作业54:举杯邀明月,对影成三人。的上一句 ✅作业74:起舞弄清影 是那首诗?谁写的?下一句是什么? 作业36:多分支项目的大致情况 作业37:大盘的大致情况 作业38:部署两个项目的大致情况 ✅作业55:flex 常见缩写 ❌作业58:React和Vue是怎么描述UI的 ✅作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例 ✅作业61:常见的数据结构有哪些 ❌作业62:fiber架构是什么?优点有哪些? 是怎么实现的? ❌作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count, setCount]=useState(0)具体说明 ❌作业64:Vue渲染怎么实现的? ❌作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期 ❌作业66:React如何处理错误 ❌作业67:React 怎么做的性能优化? ❌作业68:React hooks 的原理是什么?useEffect useState模拟实现 ❌作业70:Vue 和 React 的 Diff 算法比较 ❌作业71:React为什么要合成事件?和原生事件有什么不同? ❌作业72:react 的 声明周期有哪些,在不同生命周期中做什么事情? ✅作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次? ✅作业75:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序? ✅作业76:显卡的作用? ✅作业77:重绘和重排是什么?有什么区别? ❌作业78:使一个标签看不见的几种样式及其区别 ✅作业79:地球四季的成因是什么? ❌2024.12 模拟实现promise ❌2024.11 观察者模式 EventEmitter ❌2022.08 包管理工具 ✅2021.06 跨页面通信 ✅2021.05 XSS跨域脚本攻击 和 CSRF跨站请求伪造攻击 ✅2021.05 29 WebSocket ✅2021.04 25 HTTP/2 特性 ✅2020.03 模块化 ✅2019.07 数组去重(算法整理) ✅2019.07 显示省略号 ✅2019.07 深拷贝和浅拷贝的区别?怎么实现深拷贝? 红宝书需复习章节: ✅2024.09 第六章 集合引用类型 小结 ❌2024.09 第八章 对象、类和面向对象编程 小结 ✅2024.10 第九章 代理与反射 小结 ✅2024.10 第十章 函数 小结 ✅2024.10 第十二章 BOM 小结 ✅2024.10 第十四章-第十五章 DOM、DOM扩展 ✅2024.11 第十六章 DOM2和DOM3 ❌2024.11 第十七章 事件
// 2019.07 深拷贝和浅拷贝的区别?怎么实现深拷贝?
- 第二遍 -
基本类型:深拷贝、浅拷贝都是复制值的副本,修改新值不会影响原值
引用类型:浅拷贝复制的引用地址,修改新值会影响原值;深拷贝复制的引用地址指向的数据,修改新值不会影响原值
[...arr] arr.concat() arr.slice() Array.from(arr) {...obj} Object.assign({}, obj)
Json.parse(Json.stringify(obj))
const isObj = (t) => {
const type = typeof t;
return t !== null &&(type === 'function' || type === 'object');
}
function specificFun(t, type) {
switch(type) {
case '[object Date]':
return new Date(t.getTime());
break;
case '[object RegExp]':
return new RegExp(t.source, t.flags);
break;
case '[object Symbol]':
return Symbol.for(t.description);
break;
default:
const ctor = t.constructor;
return new ctor(t);
}
}
function clone(target, m = new WeakMap()) {
if(!isObj){
return target;
}
const type = Object.prototype.toString.call(target);
const specificTypes = ['Map', 'Set', 'Array', 'Object'].map(it => `[object ${it}]`);
if(!specificTypes.includes(type)){
return specificFun(target, type);
}
const ctor = target.construcor;
const res = new ctor();
if(m.has(target)) {
return target
}
m.set(target, res);
if(type === '[object Map]') {
target.forEach((value, key) => {
res.set(key, clone(value, m));
})
} else if(type === '[object Set]') {
target.forEach(it => {
res.add(clone(it, m));
})
} else if(Array.isArray(target)) {
target.forEach((it, index) => {
res[index] = clone(it, m);
})
} else {
for(let i in target) {
res[i] = clone(target[i], m)
}
}
return res;
}
- 第一遍 -
对于基础类型的数据深拷贝和浅拷贝都是数据的副本,修改新的数据不会影响原来的数据
对于引用类型的数据:浅拷贝拷贝指向数据的地址,修改后会影响原有数据;深拷贝的是引用地址指向的数据,修改新数据并不会影响原数据
浅拷贝:[...arr] Array.from(arr) arr.concat() arr.slice() {...obj} Object.assign({}, obj)
深拷贝:Json.parse(Json.stringify(obj))
function isObj(t) {
const type = typeof t;
return (t !== null) && (type === 'function' || type === 'object');
}
function handleNormal(t, type) {
switch(type) {
case '[object Date]':
return new Date(t.getTime());
break;
case '[object RegExp]':
return new RegExp(t.source, t.flag);
❌ // 1、正则类型的 t.flags
break;
case '[object Symbol]':
return Symbol.for(t.description);
break
default:
const ctor = t.constructor;
return new ctor(t);
break;
}
}
function clone(target, m = new WeakMap()) {
if(!isObj(target)) {
return target;
}
const deepType = ['Map', 'Set', 'Array', 'Object'].map(it => `[object ${it}]`);
const type = Object.prototype.toString.call(target);
if(!deepType.includes(type)) {
return handleNormal(target, type);
}
const ctor = target.constructor;
let res = new ctor();
if(m.has(target)) {
return target
}
m.set(target, res);
if(type === '[object Set]') {
target.forEach(it => {
res.add(clone(it, m));
})
}
if(type === '[object Map]') {
target.forEach((value, key) => {
res.set(key, clone(value, m));
})
}
if(Array.isArray(target)) {
for(let i = 0, len = target.len; i<len;i++) {
res[i] = clone(target[i], m);
}
} else {
for(let i of target) {
❌ // 2、 object 不是可迭代对象,不能使用 for...of,这里应该改为 for...in
res[i] = clone(target[i], m)
}
}
return res;
}
// 2019.07 显示省略号
- 第二遍 -
.line {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.lines {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
padding: 8px;
line-height: 30px;
height: 82px;
}
- 第一遍 -
.line {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.lines {
overflow: hidden;
display: -webkit-box;
-webkit-box-direction: vertical; ❌ // 『不是 direction 是 orient: -webkit-box-orient: vertical;』
-webkit-line-clamp: 3;
// 解决 padding 带来的露出问题
line-height: 30px;
height: 90 - padding距离;
}
// 2019.07 数组去重(算法整理)
- 第二遍 -
arr = [1,1,2,2,2,3];
1、set: [...new Set(arr)] // ⭐️ Array.from(new Set(arr))
2、filter+indexOf: arr.filter((it, index) => index === arr.indexOf(it))
3、hash:
function unique(arr) {
let hash = {};
return arr.filter((it) => hash[it] ? false : hash[it]=true);
}
- 第一遍 -
// ⭐️ 125最重要,其实 4 和 5 是一个逻辑, 3 和 2 很像
1、new Set([...arr]) // ❌ [...new Set(arr)] Array.from(new Set(arr))
2、arr.filter((it, index) => arr.indexOf(it) === index);
5、function unique(arr){
let hash = {};
return arr.filter(it => hash[it] ? false : hash[it] = true)
}
3、function unique(arr) {
return arr.reduce((pre, cur) => {
if(!pre.includes[cur]) {
pre.push(cur);
}
return pre;
}, [])
}
4、function unique(arr) {
let hash = {};
let res = [];
arr.forEach(it => {
if(!hash[it]) {
res.push(it);
hash[it] = true;
}
})
return res
}
// 2020.03 模块化
- 第二遍 -
CommonJS:同步加载,适用于服务端编码,输出的是值的拷贝,运行时输出,require+exports
AMD:RequireJS,异步加载,并行加载,依赖前置,加载完了立即执行调用,无法按序执行
define(['a.js', 'b.js'], function(a, b) { a.cb();b.cb() })
CMD:SeaJS,异步加载,并行加载,依赖就近,加载后按序执行
define(function(require, exports, module) { let a = require('a.js'); a.cb(); })
ES6模块化:异步加载,是客户端和服务端的通用解决方案,输出的是值的引用,编译时输出,import default as export from
- 第一遍 -
CommonJS:同步加载,适用于服务器端,输出的是值的拷贝,运行时加载,require + exports
AMD:异步加载,可以并行加载多个模块,依赖前置 define(['a.js', 'b.js'], function (a, b) {})
❌ // 少写了 :加载模块后直接执行,无法保证执行顺序
CMD:异步加载,可以并行加载多个模块,依赖就近 define(function(require, exports, module){
let a = require('a.js'); a.callback();
})
❌ // 少写了 :加载后,直到调用才按需执行
ES6模块化:异步加载,是客户端和服务端的通用解决方案,输出的是值的引用,编译时加载, import as from default export
// 2021.04 25 HTTP/2 特性
- 第二遍 -
在语义上完全兼容,在语法上大力改造
①压缩头部信息,使用 HAPCK 字典表算法
②采用二进制帧 - 双向传输序列,虚拟的流 - 一个连接可以处理多个请求响应,多个请求响应之间没有了顺序关系 - 解决了队头阻塞的问题
③服务端推送数据
④设置请求优先级
⑤安全性升级:使用HTPPS
- 第一遍 -
和HTTP/1.1相比,语义层面完全兼容,语法层面大力改造
1、压缩头部信息,采用 HPACK 字典表算法
2、不再使用纯文本传送,而是二进制帧 - 双向传输序列 - 形成虚拟的流,在一个连接上可以处理多个请求响应(多路复用),多个请求响应之间没有了顺序,解决了队头阻塞
3、设置请求优先级
4、采用HTTPS
❌ // 少写一个 5、服务器推送
// 2021.05 29 WebSocket
- 第二遍 -
全双工双向通讯协议,由于 HTTP/1.1 的请求-应答模型限制,无法实现实时通讯
和 HTTP 差异
① 在语义、语法上完全不兼容,没有同源协议的限制,使用二进制帧
② 不再使用 ip+端口号 形式发现服务,而是 URI
③ 跑在 HTTP 基础上,GET 请求是发送 Connection:Update; 如果响应中带有 update:websocket; 则升级为 websocket
// ⭐️ 协议名称改为 ws:80 wss:443
- 第一遍 -
WebSocket和HTTP/1.1在语法、语义上完全不相同,HTTP/1.1因为请求-应答的模式无法实现实时通讯
❌ // 1、学名:全双工 - 双向通信协议
①使用不同的协议头 ws:80 wss:443
②使用不同的识别方式,websocket使用的是URI,HTTP使用的是IP+端口号
③websocket使用二进制帧,不受同源协议的限制
④websocket跑在HTTP协议之上,使用GET请求进行连接。请求头中携带Connection:upgrade,如果响应头中返回Upgrate:websocket 则建立连接
// 2021.05 XSS跨域脚本攻击 和 CSRF跨站请求伪造攻击
- 第二遍 -
网络攻击的根本,是因为浏览器给同源协议开的两个后门:①可以引入第三方资源->XSS②可以请求第三方数据->CSRF
XSS 跨域脚本攻击:在前端页面注入恶意代码,以修改界面或者服务端数据(存储、反射、映射
解决方法:①纯客户端渲染,服务端对数据进行转义和过滤
②渲染时禁止DOM执行js代码,例如 src/href 标签属性
③对 cookie 设置 HttpOnly,只有http协议能够访问cookie,浏览器 js 引擎会禁止访问 document.cookie
④CSP内容安全策略:设置白名单,规定那些资源是可以信任的,Content-Security-Policy 域名
// ⭐️ CSP 是从客户端角度讲:设置那些域名的资源是可以被信任的 srcipt-src 'self'
// ⭐️ CORS 是从服务端的角度讲:设置那些域名可以访问这个资源 Access-Control-Allow-Origin
CSRF 跨站请求伪造:仿造用户信息,获取服务端信任,修改服务端数据
解决方法:①校验请求头中的 referer ,确认请求来源
②对 cookie 设置 samesite,只有当前域名能够访问
③在请求中校验第三方请求无法伪造的 csrf token
④验证码
- 第一遍 -
网络攻击的根本原因是浏览器给同源策略开的两个后门:①可以引入第三方数据 ②通过CORS向第三方资源发起请求
❌ // 1、可以引用第三方资源
XSS跨域脚本攻击:定义-在前端页面中恶意注入恶意代码,修改服务器数据
解决方案 ①纯前端渲染,服务端对输入、存储进行转义、过滤
②对 cookie 设置 httpOnly,只有 http 协议可以访问 cookie,js 引擎会禁止 js 对 cookie 的访问,例如document.cookie
③浏览器提供了白名单CSP:content-security-policy 设置的是可以访问该资源的域名
❌ // 2、少了一项:在渲染时禁止标签执行JavaScript的代码,例如a标签script的标签
CSRF跨站请求伪造攻击:定义-冒用用户的cookie信息获取服务器的信任,修改服务器数据
①对请求来源进行校验,例如请求头中的 referer
②开启 cookie 中的 samesite 配置,禁止其他非同源页面对 cookie的访问
③CSRF token,在请求中校验第三方无法模拟的 token
// 2021.06 跨页面通信
- 第二遍 -
同源页面:
- localStorage,只能处理非当前页面的传值[页面A传值 localStorage.setItem('a', 1); 页面B接收 window.onstorage=()=>{}]
- new BroadCastChannel[页面A传值 let b1 = new BroadCastChannel('b1'); b1.postmessage('aa'); 页面B接收 let b1 = new BroadCastChannel('b1'); b1.onmessage=()=>{}]
非同源页面:
- postMessage(H5 API)[页面A传值 let win = window.open('xxx');win.postmessage('aaa'); 页面B接收 window.onmessage=()=>{}]
- iframe,将 origin 设置成同源域名,使用同源页面的两个方法通信
- 第一遍 -
同源页面:
1、localStorage:只能处理非当前页面
发送 localStorage.setItem(key, value) + 接收 window.onstorage
2、BroadChannel:❌ // 构造函数名字记错了:BroadCastChannel
发送 let b = new BroadChannel('one'); b.postMessage(value);
接收 创建同名频道,接收数据 b.onmessage
非同源页面:
3、postMessage(H5 API)
发送 let win = window.open(url); win.postMessage(value)
接收 在打开的页面中直接获取 window.onmessage
4、iframe:将 origin 设置为同源,然后使用方法1/2
// 作业79:地球四季的成因是什么?
地球绕着太阳公转轨迹是椭圆的,但是四季的成因和太阳与地球的距离没啥关系
主要是太阳直射点位置 + 夹角(大气层厚度)
⭐️ // 直射点的移动 + 太阳照射时间的长短;夹角,与地面角度的改变,包括改变了太阳辐射面积 + 经过大气的路程衰减的辐射能量强度
// 作业77:重绘和重排是什么?有什么区别?
浏览器渲染流程:解析DOM-计算样式表-布局树 layout-分层树-绘制-栅格化-合成
重绘:只需要重新修改每层的样式+合成
- 不涉及到布局修改的样式:字体、背景、边框颜色
重排:需要重新计算布局+修改每层的样式+合成
- 字体大小、padding/margin大小、激活伪类、style样式的修改、resize ⭐️ // 1、页面初始渲染 2、可见DOM的增删 3、位置、内容的修改 4、DOM 布局的查询
// 作业76:显卡的作用?
用于渲染帧
分成前后区,浏览器从前区获取显示的帧;在后区进行渲染,直到渲染完成,跟前区交换用于显示新帧
⭐️ // 作用:合成新的图像,并将图像保存到后缓存区
// 作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次?
- 第二遍 -
在合成事件中的 setState 是异步的,setState 批量处理机制:在 react 中,对 setState 进行了优化处理,例如一个事件处理函数中,会把 setState 放到一个队列中,函数执行完毕后,统一处理 setState,更新一次 state,触发一次重新渲染
⭐️ // 批量更新机制
⭐️ // 异步:合成事件 + 生命周期函数;同步:原生事件,例如 addEventListener、setTimeout/setInterval
在原生事件中的 setState 是同步的,不受 react 管控,原生事件 setState 一次,就会触发一次渲染
- 第一遍 -
合成事件中的 setState 是异步的,冒泡到顶层容器才触发
❌ // 1、除了合成事件,还有「生命周期函数」中都是异步,原因是 React 对多个 setState 调用进行合并,在一个事件处理函数中,收集多个 setState 到一个更新队列,这个事件处理函数结束后,根据合并后的状态,合并为一个更新,触发一次重新渲染
原生事件中的 setState 是同步的,不在 React 管控中,捕获时触发 ✅
⭐️ // 2、补充:原生事件,比如 addEventListener、setTimeout/setTimeInterval,在其中调用 setState,状态更新会立即生效,并且会触发组件的重新渲染
React 做了 setState 合并,在执行过程中,收集变更,执行完事件函数后再触发 setState 更新
⭐️ // 3、学名「批量更新机制」:为了减少不必要的重新渲染次数
// 作业61:常见的数据结构有哪些
- 第二遍 -
按照结构分
线性结构:链表、数组、栈/队列
非线性结构:树、图
- 第一遍 -
❌ // 按逻辑结构分为:线性结构和非线性结构
结构型类型:数组、栈/队列、链表
非结构型类型:树、图
// 作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例
- 第三遍 -
高阶函数:输入参数或者输出结果是函数的函数
高阶组件:输入参数是组件
副作用:是函数式编程的概念,除了返回值,函数会造成其他影响,例如修改全局变量、修改输入变量、IO变化等
const a = (MyComponent, required) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { permission: false };
}
componentDidMount() {
const check = check('id');
this.setState({ permission: check === required })
}
render() {
if(this.state.permission) {
return <MyComponent {...this.props} />
} else {
return <div> You don't have permission. </div>
}
}
}
}
- 第二遍 -
高阶函数就是输入输出的参数是函数。最常见的就是map。
高阶组件就是输入的参数也是组件。
副作用是函数式编程当中的说法,含义是除了return的返回值。在函数当中还会影响到外部值,比如说传入的参数,全局变量。Io下载等
const my = (MyComponents, requiredType) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { permit: false }
}
// ❌❌ 1、这里少写了一个 componentDidMount 用于初始化的时候计算 state 中的值
componentDidMount() {
const myPermit = check('id'); // 查询
this.setState({ permit: myPermit === requiredType })
}
render() {
if(this.state.permit) {
return this.props.children;
// ❌❌ 2、子组件作为参数传入了 return <MyComponents {...this.props} />
} else {
return <div> Error. </div>
}
}
}
}
- 第一遍 -
高阶函数 - 输入参数是函数的函数 arr.map(it => +it),map 就是高阶函数
❌ // 高阶函数 - 以函数作为【输入或者输出】的函数
高阶组件 - 输入参数是组件的组件,做一些包装,可以统一添加监控、报错等功能
❌ // 高阶组件 - 接收组件作为参数,并返回一个新组件
// - 常用于1、包装一层功能2、属性代理/组合3、动态行为添加
副作用 - 除了返回值,在函数执行过程中会影响其他值、IO等
❌ // 副作用是函数式编程的概念 - 除了函数的返回值外,还对外部环境产生了影响(例如:1、修改全局变量 2、改变输入参数 3、进行IO操作等)
高阶函数-权限校验示例:
class Wrap extends React.Component {
constructor(props) {
super();
}
render() {
return <div>{props}</div>
}
} // 写法是对的,但是传入的 props 不是明确的组件 ❌
// 作业55:flex 常见缩写
- 第三遍 -
flex: none; // 0 0 auto
flex: 1; // 1 1 0
flex: auto; // 1 1 auto
flex: initial; // 0 1 auto
- 第二遍 -
// 所以我错的不是缩写不清晰,是每次 0 0 auto 的简写是 none ,而我写成 0
flex: 0; // 0 0 auto ❌❌ 「缩写其实是 flex: none;」
flex: initial; // 0 1 auto
flex: 1; // 1 1 0
flex: auto; // 1 1 auto
- 第一遍 -
flex: 0; // 0 0 auto ❌ 「缩写其实是 flex: none;」
flex: 1; // 1 1 0
flex: auto; // 1 1 auto
flex: initial; // 0 1 auto
// 作业74:起舞弄清影 是那首诗?谁写的?下一句是什么?
苏轼
不知天上宫阙,今夕是何年
起舞弄清影,何似在人间
/** ⭐️ 欣赏一下原文
《水调歌头 明月几时有》
明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。
转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。
*/
// 作业54:举杯邀明月,对影成三人。的上一句
- 第二遍 -
花间一壶酒,独酌无相亲
- 第一遍 -
起舞弄清影,独酌无相亲 ❌ 『花间一壶酒,独酌无相亲』
// 作业49:牛顿三大定律、热力学两大定律
- 第二遍 -
惯性定律、加速度定律 a=F/m、作用力与反作用力
能量守恒、熵增定律
- 第一遍 -
+ 加速度 a=F/m + 作用力与反作用力 ❌ 『惯性定律』
能量守恒 + 熵增定律
// 作业25:简单写下 请求创建过程
- 第三遍 -
function request(url){
const xhr = new XMLHttpRequest();
xhr.ontimeout = () => {};
xhr.onerror = () => {};
xhr.onreadystatechange = function(res) {
switch(res.readyState) {
case 0: // 请求没开始
break;
case 4:
if(this.status === 200 || this.status === 304) {
console.log(this.responseText);
}
break;
}
}
xhr.open('GET', url, true);
xhr.timeout = 3000;
xhr.responseType = 'text';
xhr.setRequestHeader('animal', 'qiu');
xhr.send();
}
- 第二遍 -
function request(url) {
let xhr = new XMLHttpRequest();
xhr.onerror = () => {};
xhr.ontimeout = () => {};
xhr.onreadystatechange = function(res) {
switch(res.state) { ❌❌ // 1、res.readyState
case 0: // 请求未开始
break;
case 4:
if(this.status === 200 || this.status === 304) {
console.log(this.responseType);
}
break;
}
}
xhr.open('GET', url,true);
xhr.timeout = 3000;
xhr.responseType = 'text';
xhr.setRequestHeader('a', 'qiu');
xhr.send();
}
- 第一遍 -
function require(url) {
let xhr = new XMLHttpRequest(); // 1、实例 ⭐️「使用const」
// 2、回调
xhr.onerror = () => {};
xhr.ontimeout = () => {};
xhr.onreadyStateChange = function (res) { // ⭐️「原生函数全小写字母 onreadystatechange」
switch(res.readyState) {
case 0: // 未开始
break;
case 4:
if(this.status === 200 || this.status === 304) {
console.log(this.responseText);
}
break; // ⭐️「记得写 break」
}
}
// 3、创建
xhr.open(url, 'GET', true); // true 表示异步 ❌ 『xhr.open('GET', url, true)』
// 4、属性设置
xhr.timeout = 3000;
xhr.responseType = 'text';
xhr.setRequestHeader(key, value);
// 5、发送请求
xhr.send();
}
// 作业16(2):arguments 对象对待命名参数和设置了默认值的命名参数有什么不同?数组作为参数传入函数得到的arguments 是什么?怎么实现arguments是数组?怎么收集独立参数?在对象字面量和数组字面量中,扩展运算符有什么不同的表现?
- 第二遍 -
arguments 和命名参数始终同步,例如修改 arguments[0] 也会改变第一个命名参数
但是 arguments 和设置了默认值的命名参数不同步,修改了 arguments[0] 并不能修改到命名参数
数组传入 - arguments=[[1,2,3]]
arguments是数组 - (...arr),使用扩展运算符
收集独立参数 - (first, ...rest),使用扩展运算符 + 最后一个参数,rest 是数组
对象字面量 {...obj} - 创建一个新对象,拷贝所有可枚举属性
数组字面量 [...obj] - 将可迭代对象一一传入
- 第一遍 -
arguments 默认同步命名参数,比如在函数内修改了 arguments[0],如果对应的是命名参数,那么值会同步
但是 arguments 不同步设置了默认值的命名参数
数组作为参数 - arguments:[1,2,3]❌『[[1,2,3]]』
可以用扩展运算符转换 (...arr)
收集独立参数 (first, second, ...rest) -> rest 是一个数组 只能作为最后一项收集
扩展运算符 - 对象字面量 {...obj} 浅拷贝对象中所有实例属性❌『创建一个新对象,并复制所有可枚举属性』
扩展运算符 - 数组字面量 [...obj] 将可迭代对象一一返回
(LTN2 盒子) 2025.1.9 - 2h13m
✅作业10:分别用两种方式新建一个映射实例、一个集合实例。 ✅作业29:Promise的进度通知 ✅作业44:怎么能够不把所有事情、语言往心里去 ✅作业45:原生+canvas 显示一张png格式的图片 ❌作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口) ✅作业48: input 和 textarea 对比(宽度设置,初始值设置);option 标签的初始值设置 ✅作业50:学习的感觉是? ✅作业51: 如何停止过度思考(当下的力量) ✅作业52:道可道,非常道 翻译一下 ❌作业53:胜利者一无所获 说的是什么 ✅作业56:对那些没有 deadline 的事情,怎么解决拖延症 ✅2024.12 模拟实现instanceof ❌2024.10 怎么实现跨域 ❌2021.07 bind、apply/call三者异同+apply/call实现bind ❌2021.07 数组扁平化(一层、全部展开、指定深度) ❌2021.04 05-3 白屏优化? ✅2021.04 02 网络协议 - IP/UDP/TCP协议 ✅2019.07 h5新标签和语义化,块/行内元素
// 2019.07 h5新标签和语义化,块/行内元素
语义化:1、样式不同 2、更好的SEO
h5新标签:header footer aside article address nav datalist fieldSet/legend detail time hgroup section
input 新属性:placeholder autocomplete autofocus accesskey required multiple
input type 新属性:tel email url number date time datetime week month range search
块元素: div table h1-h6 td ol li ul dd dt table-caption p // ⭐️ tr dl caption address hr
行内元素: span br i u a strong em select textarea button // ⭐️ b label
行内块元素: input img td
// 2021.04 02 网络协议 - IP/UDP/TCP协议
应用层(HTTP
传输层(TCP、UDP - 定位某一个应用端口
网络层(IP - 定位某一台机器 ip
网际链路层(MAC - 底层,在网络中定位域
data 传输流程: data]TCP -> data]TCP]IP ->....-> data]TCP -> data
// 2024.12 模拟实现instanceof
function fakeInstanceOf(target, Ctor) {
let proto = Object.getPrototype(target); // 等价于 target.__proto__
while(proto) {
if(proto === Ctor.prototype) {
return true;
}
proto = Object.getPrototype(proto);
}
return false;
}
// 作业56:对那些没有 deadline 的事情,怎么解决拖延症
deadline 是第一生产力
但是人生很多事情是不去尝试就无法推进的,比如创业、比如换工作
但是要意识到,人类出生都是向死而生的,我们并没有永恒的生命,如果你不做自己想做的事情,那就又少活了一天
// 作业52:道可道,非常道 翻译一下
如果真理是可以被说出来的,那它就不是永恒不变的道了
形容语言的局限性,很多道理难以言传
// 作业51: 如何停止过度思考(当下的力量)
思考是一种上瘾行为,我们要是沉迷于思考,就失去了对时间的控制
解决方法:抽离出来,看到自己脑海中的思想,get it,不跟着走,let it go,回到当下
// 作业50:学习的感觉是?
学如飞鸟过,脑内了无痕
// 作业48: input 和 textarea 对比(宽度设置,初始值设置);option 标签的初始值设置
input:单行标签;size 设置宽度,单位为字符数;value 设置初始值;maxLength 设置最大长度
textarea:多行;cols 设置宽度,单位为字符数;标签中间的内容就是初始值;rows 设置高度
option 初始值有 value 取 value 值, 没有 value 取标签中间内容
// 作业45:原生+canvas 显示一张png格式的图片
html 部分:<canvas width=200 height=200 id='drawing'></canvas>
js 部分:
const drawing = document.getElementById('drawing');
if(drawing.getContext) {
const imgURI = drawing.toDataURL('image/png');
const dom = document.createElement('img');
dom.src = imgURI;
ducoment.body.appendChild(dom);
}
// 作业44:怎么能够不把所有事情、语言往心里
1、It's not about me. 看看别人说这句话的目的
2、It's about me. 被说到心里了才会痛苦 - 真诚地做自己,不怕展示自己的软弱,学会表述自己的感受,不指责
you'll always keep your value. 你不会因外部流言蜚语有任何改变,你一直是你
// 作业29:Promise的进度通知
class MyPromise extends Promise {
constructor(executor) {
const handlers = [];
super((resolve, reject) => {
return executor(resolve, reject, (notes) => {
handlers.forEach(it => it(notes));
})
});
this.handlers = handlers;
}
notify(fn) {
this.handlers.push(fn);
}
}
const p1 = new MyPromise((resolve, reject, notifyCb) => {
function count(n) {
if(n>0) {
notifyCb(`${n*20} remained`);
setTimeout(() => count(n - 1), 0);
} else {
resolve()
}
}
count(5);
})
p1.notify((it) => console.log(`${it} ---`));
p1.then(() => console.log('completed'));
// 作业10:分别用两种方式新建一个映射实例、一个集合实例
const m1 = new Map([['a', 1], ['b', 2]]);
const m2 = new Map().set('a', 1).set('b', 2);
const s1 = new Set([1, 2, 3]);
const s2 = new Set({*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}})
(LTN3 盒子)2025.1.8 - 1h56min
❌作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的 ✅作业14:Proxy 的原型是什么?怎么撤销代理关联?是否可逆?写几个常见的代理模式对应的捕获器 ✅作业30:简单写出一个请求头和响应头。HTTP/1.1中唯一要求请求头必须提供?写出常见状态码。 ✅作业31:什么是长连接?长连接的属性怎么写?哪个版本开始启用长连接? ✅作业33:Cookie 怎么设置有效期?有优先级吗?HttpOnly 是什么含义? ❌2024.05 rem是基于什么原理进行适配的? ✅2021.07 柯里化函数实现 ❌2020.07 对象分类、Promise按序执行、实现map ❌2019.10 两栏,左边固定,右边自适应的布局 ❌2019.10 三栏,两边固定,中间自适应 ✅2019.07 引用css,link和@import的区别 ✅2019.07 隐式转换:{}+{}=?
// 2019.07 隐式转换:{}+{}=?
'[object Object][object Object]'
// 2019.07 引用css,link和@import的区别
link:xml标签,没有兼容问题;可以处理 rss 等其他事务;浏览器可以解析到预加载;js可以正常修改DOM
@import:css2.0 提出,低版本需要兼容代码;只能加载 css;遇到再下载;由于加载顺序问题,js很难修改到样式
// 2021.07 柯里化函数实现
function curry(fn, ...args1) {
if(args1.length >= fn.length) {
return fn.apply(this, args1); // ⭐️ 这里可以直接写成 return fn(...args1)
} else {
return (...args2) => { // ⭐️ 函数体只有一条指令,可以省略花括号{}
curry(fn, ...args1, ...args2);
};
}
}
// 作业33:Cookie 怎么设置有效期?有优先级吗?HttpOnly 是什么含义?
Cache-control: max-age=2000; // 单位是秒(优先级高)
Expires: deadline; // 具体时间
HttpOnly 的含义是只有 HTTP 协议可以访问当前 Cookie,浏览器不可以访问,js引擎会禁用方法,例如 document.cookie
// 作业31:什么是长连接?长连接的属性怎么写?哪个版本开始启用长连接?
长连接:浏览器和服务器三次握手建立 TCP 连接后,不断开,可以处理多个请求响应
Connection: keep-alive
HTTP/1.1 后默认开启长连接
// 作业30:简单写出一个请求头和响应头。HTTP/1.1中唯一要求请求头必须提供?写出常见状态码
请求头 GET /index HTTP/1.1
响应头 HTTP/1.1 200 OK
HTTP/1.1中唯一要求请求头必须提供 Host
常见状态码:
200 OK 请求成功
204 No Content 没有响应体
301 Moved Permanently 永久重定向
302 Moved Templately 临时重定向
304 Not Modified 缓存内容不变,可以继续使用
(If-none-match/ETag、If-modified-since/Last-modified)
404 Not Found 请求资源在服务器上找不到
400 Bad Request 请求头有语法错误
403 Forbidden 请求资源禁止访问
500 服务器错误
// 作业14:Proxy 的原型是什么?怎么撤销代理关联?是否可逆?写几个常见的代理模式对应的捕获器
Proxy 的原型是 undefined, 所以不能使用 instanceof 来查询代理对象,会报错
const { proxy, revoke } = Proxy.revocable(target, handler); revoke();
撤销关联不可逆
const handler = {
get(...args) { return Reflect.get(...args) },
set: Reflect.set,
has // 对应 in 操作符
apply // 对应执行函数
constructor // 对应实例化 new 操作符
}
(LTN4盒子) 2025.1.7 - 45min
✅作业15:类由哪些组成,不同定义的内容在继承时有什么特点?类是怎么实现继承的? ✅2021.07 怎么判断数组类型?4种方法 ✅2021.04 03 为什么很多网站第二次打开速度会很快 ✅2019.07 for循环+计时器,如何实现i按序输出 ✅2019.06 函数参数传值修改 ✅2019.06 检测数据类型 + 布尔转换为0的值
// 2019.06 检测数据类型 + 布尔转换为0的值
typeof 检测基本数据类型为字符串,对于 Array、Object 都返回 'object';对函数返回'function'
instanceof 检测对象的原型链中是否有指定构造函数的原型对象,一般用于检测引用类型
Object.prototype.toString.call 可以将对象转换成 '[object 类型]' 的字符串(可被改写)
constructor 指向构造函数(可被改写)
Array.isArray 可以准确检测数组
布尔值转换为false的值:''空字符串、undefined、null、NaN、0、false
// 2019.06 函数参数传值修改
[1,2,3]
{a:1}
100 100 100 Error 55 100
// 2019.07 for循环+计时器,如何实现i按序输出
1、let - 会为每次的循环保留上下文
for(let i = 0; i<10; i++) { setTimout(() => console.log(i), 0) }
2、立即执行(闭包)
for(var i = 0; i<10; i++) {
(function(a) { setTimout(() => console.log(a), 0) })(i)
}
3、setTimout 参数(闭包)
for(var i = 0; i<10; i++) { setTimout(() => console.log, 0, i) }
// 2021.04 03 为什么很多网站第二次打开速度会很快
1、DNS缓存 - 查询一次后在本地会缓存
2、同源站点可以复用渲染进程,HTTP/1.1 后TCP连接都是默认长连接,节省了建立连接的时间
3、GET 类型的请求在第一次打开后浏览器会默认缓存(也可以自定义设置),第二次可以直接复用缓存
4、还有一些资源在第一次请求后本地有缓存,但缓存过期,会触发协商缓存请求,如果资源没过期-接口返回304,浏览器可以直接使用本地资源、刷新缓存时间,而不需要等待接口返回内容重新下载
// 2021.07 怎么判断数组类型?4种方法
1、Array.isArray() - 最准确
2、instanceof - 查找对象的原型链上是否有指定构造函数的原型对象。在多个全局上下文时,判断可能有误
3、Object.prototype.toString.call(obj) === '[object Array]' - 可被改写
4、obj.constructor === Array - 可被改写
typeof 主要用于检测基础类型,检测数组会得到 'object',无法区分
// 作业15:类由哪些组成,不同定义的内容在继承时有什么特点?类是怎么实现继承的?
类由 constructor、static 静态函数、原型函数
constructor:用于创建对象的构造函数,每个子类继承时都被实例化
static 静态函数:只属于当前类的方法,不被继承
原型函数:子类可以继承的方法
通过 extends 关键词继承 constructor和原型函数,在子类的 constructor 可以通过 super 调用父类构造函数
LTN ①② 错题重做 2024.12.31
(错题重做,做对了不记录,记录二次错)
LTN ①②
2024.12.26 - 12.30 做LTN1 - 123+min
✅作业17:对斐波那切数列进行尾调用优化
✅作业20:浏览器打开一个 URL 发生了什么
✅作业26:给出一个场景,要求A、B请求执行结束后,再执行C请求,其中A、B请求同时开始,怎么实现
✅作业34:写出强缓存、协商缓存的流程及相关属性
作业36:多分支项目的大致情况
作业37:大盘的大致情况
作业38:部署两个项目的大致情况
❌作业55:flex 常见缩写
✅作业57:“假装成功”的意义是什么
❌作业58:React和Vue是怎么描述UI的
❌作业59:什么是高阶函数?什么是高阶组件?什么是副作用?简单举例
❌作业60:虚拟DOM在 Vue 和 React 中的使用?分别有什么提升性能的方法?
❌作业61:常见的数据结构有哪些
❌作业62:fiber架构优点? 是怎么实现的?
❌作业63:React渲染怎么实现的
❌作业64:Vue渲染怎么实现的?
❌作业65: ①computed/watch/methods 的区别?②created和mounted之间的区别 ③父子组件挂载顺序 ④vue组件中的data为什么是函数
❌作业66:React如何处理错误
❌作业67:React 怎么做的性能优化?
❌作业68:React hooks 的原理是什么?
❌作业69:useEffect useState模拟实现
❌作业70:Vue - patch 和 React 的reconciliation - Diff 算法
❌作业71:React为什么要合成事件?和原生事件有什么不同?原理是什么?
❌作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情?
❌作业73:react 的 setState 是同步还是异步?为什么React有时候有两次 setState,却只执行一次?
✅2024.12 模拟实现jsonp
❌2024.12 模拟实现promise
❌2024.11 观察者模式 EventEmitter
✅2021.09 Object属性排序、与Map的区别
✅2021.07 防抖节流
✅2021.07 常见Webpack问题
❌2021.06 跨页面通信
✅2021.06 BFC特点、触发、存在/解决的问题
❌ 2021.05 XSS跨域脚本攻击 和 CSRF跨站请求伪造攻击
❌2021.05 29 WebSocket
✅2020.03 改变原数组+结束循环+性能排序
❌2019.07 深拷贝和浅拷贝的区别?怎么实现深拷贝?
// 2021.07 常见Webpack问题
/** 1、webpack的几个核心概念理解:Chunk、Module、plugin
2、常见配置项:entry、output、module、resolve等
自定义 loader 怎么配置?
3、Code Splitting 和 Tree Shaking 的区别?懒加载怎么实现?
4、html-webpack-plugin 作用?
5、sourceMap不同环境的区别?怎么开启配置?
6、热更新怎么实现?
7、webpack原理/执行过程?
开发插件的桥梁?*/
1、Chunk:由多个Module组成
Module:万物皆模块
plugin:在构建过程中,特定时机注入代码执行
2、entry[配置入口文件]: 'src/main.js'; 或者 { main: 'src/main.js', sub: 'src/sub.js' }
output[配置输出]: { filename: '[name]-[hash].js', path: path.resolve(_dirname, 'dist') }
module[依赖解析、转换配置]: { rules: [ { test: '/.\js/', use: ['style-loader', 'css-loader', 'sass-loader'] }, { test: '/.\css/' } ] }
resolve[依赖查找配置]:{
alias: { "@c": 'src/component' },
extensions: ['jsx', 'js', 'josn'],
modules: [path.resolve(_dirname, 'src/my'), 'node_modules']
}
配置自定义 loader 的两种方法
①如上,在 resolveModules 中添加自定义 loader 的路径 + 然后直接在 module.rules 直接引用即可 use:['style-loader', 'my-loader']
②在 use 中具体地址引用:use:['style-loader', path.resolve(_dirname, 'src/my/my-loader.js'), 'sass-loader']
3、coding splitting 按需加载,适用于单页应用,在加载首页时以entry配置入口分组,只加载当前使用的代码,其他的延迟/操作后再加载,提高渲染效率
⭐️ // 1、开启方式是 splitChunk 插件配置 - 加快响应速度
tree shaking 按需打包,按照 entry 为入口,递归查找实际被引用的代码进行打包,其他部分不进行打包,减小包大小
懒加载:利用 code splitting 按需加载首次渲染需要的代码,提高渲染效率
4、html-webpack-plugin:在 webpack 全部打包完成后,在 html 模版中将打包后文件引入,方便直接访问
5、生产环境:devtool:hidden-source-map; 不引入sourcemap
本地/测试以缓解:devtool:cheap-eval-source-map; (不提供列信息)方便代码调试
6、①在启动 webpack 的时候,通过 dev-server 在内存中启动一个服务,同时注入 HMR 相关代码,使用 websocket 协议 和浏览器保持实时通讯
②有代码更新时,通过监听 webpack 的 compiler done 事件,在编译结束后,得到 mainfest.json 和 chunk.js 文件,通过 websocket 将 mainfest 配置文件主动推送给 浏览器
③浏览器在 mainfest 中获得 ChunkId 等信息,发起 ajax 请求向内存中的服务获取 chunk.js 的模块代码文件
④执行 window.webpackHotUpdate->执行hotApply热更新函数,替换原有模块代码,不用刷新整个页面(模块代码通过 webpack.__require__来执行)
⭐️ // 2、第4步,其实就是开始 render
7、webpack原理/执行过程
①初始化配置参数:将shell脚本和配置文件中的配置整合,得到初始化配置
②利用得到的参数初始化 webpack Compiler,加载所有插件,执行 .run 开始构建
③根据entry配置为入口,依次执行 module.rules 配置的loader转换、解析,递归遍历所有依赖模块
④得到转换后的文件和依赖关系,按照entry配置,组合成一个一个chunk,输出到下载文件列表
⑤根据 output 输出配置,将列表中的文件输出到指定位置
Compiler:代表了webpack的生命周期,整个构建流程中就一个
Compilication:代表了一次构建,更新一次代码就创建一个
// 作业60:虚拟DOM在 Vue 和 React 中的使用?分别有什么提升性能的方法?
❌ // 1、主要区别从 虚拟DOM 的「具体实现」体现
React:
1、先通过渲染得到一个新的 VDOM 树
❌ // 2、需要详细描述一下:React使用JSX描述UI,JSX被编译为React createElement方法 -> 执行后返回一个由 React Element描述的UI - FiberNode构成,作为新的VDOM
2、和当前的 VDOM 比较,得到需要更新的节点,再修改真实 DOM
❌ // 3、当前的 VDOM 也是 FiberNode,比较,计算出 FiberNode 描述的 UI 更新 - Diff
可以减少对真实 DOM 的操作和访问,减少回流重绘消耗的性能
由于 React 使用 JSX ,灵活性比较大,编译时优化很难实现,在运行时做了很多优化策略。
❌ // 4、运行时优化是对的,主要策略是 fiber架构(时间分片 + 优先级调度)
Vue:
1、和 React 一样,也是通过渲染得到新的 VDOM 树
❌ // 5、一样的问题,要详细描述具体实现:Vue 使用模版语法描述UI,编译为 render 函数 -> 执行后返回 VDOM 描述的 UI「注意这里的差异」
2、和当前 VDOM 树比较,再修改真实节点
❌ // 6、描述是对的,专业一点说这里是 patch 的过程:Diff 主要是用的是 双向指针 + 静态解析
由于 Vue 使用的是模版语法,在编译时可以通过 AOT 进行优化。
编译分为两种:
❌ // 7、简单比较一下两种编译方式:JIT 应用首次加载会比AOT慢,体积也会更大
AOT 预编译,标记静态节点,运行时直接执行,速度比较快,占用内存空间小
❌ // 8、描述错误:编译后,下一步是「代码构建」-也就是准备执行,主语是「宿主环境」获得,宿主环境获得的是编译后的代码,可以直接执行 - 例如Vue3的静态/动态节点分析/跳过
JIT 即时编译,运行时需要先编译再执行,代码占用的空间会比较大,因为会有编译相关的代码需要存储
❌ // 9、少写了:代码依旧是在「宿主环境」中执行 - React
2024.12.25 +1天 做LTN2 - 140min
✅作业3:迭代器原理 ❌作业25:简单写下 请求创建过程 ✅作业41:TED 如何解决焦虑 ❌作业49:牛顿三大定律、热力学两大定律 ❌ 作业54:举杯邀明月,对影成三人。的上一句 ❌LTN2.1 显示省略号 ✅LTN2.2 position属性 - 都是什么占位?相对于什么定位 ❌LTN2.3 HTTP/2特性 ✅LTN2.4 红宝书19章 ✅LTN2.5 红宝书18章 ❌LTN2.6 模块化 ❌LTN2.7 包管理工具 ✅LTN2.8 给定时间切换状态 ❌LTN2.9 数组去重 ✅LTN2.10 数组乱序 洗牌算法
// LTN2.10 数组乱序 洗牌算法
// 从最后一个元素开始,从数组中随机选出一个位置,交换,直到第一个元素。
function chaos(arr) {
const length = arr.length;
for(let i = length -1 ; i >= 0; i--){
let random = Math.floor(Math.random()*length);
[arr[random], arr[i]] = [arr[i], arr[random]]
}
return arr
}
// while 循环来写
function chaos(arr) {
let cur = arr.length - 1;
while(cur >= 0) {
let random = Math.floor(Math.random()*arr.length);
[arr[random], arr[i]] = [arr[i], arr[random]];
cur --;
}
return arr
}
// LTN2.8 给定时间切换状态
async function (promise, time) {
const timePro = new Promise((resolve, reject) => { setTimout(reject, time, new Error('time out!')) });
return await Promise.race([promise, timePro])
}
// LTN2.2 position属性 - 都是什么占位?相对于什么定位
static - 标准文档流,占位
relative - 标准文档流,占位,相对定位 // ⭐️「相对自己本身的定位」
absolute - 脱离文档流,绝对定位,相对于上一个 position 非 static 的父级元素
fixed - 脱离文档流,粘性定位,相对视口
sticky - 原本是标准文档流,超过一个阈值后,变成 fixed
// 作业41:TED 如何解决焦虑
1、do it badly - anything worth doing worth doing badly the first time
2、给自己一些同情,我们都不会和一个天天批评自己的朋友长久交往 - 你也不要做自己这样的朋友
3、找到意义 - 即使别人不知道,自己一定要认可自己做得事情,觉得有价值
- 如何停止过度思考 -
“思考”和抽烟、喝酒一样,是一件上瘾行为 - 想要不上瘾,就是允许念头在脑中出现,get it then let it go
不跟着念头走,回到当下
焦虑是一种对失败的恐惧 - 写下可能导致自己失败的事情 - 每个失败的可能都找到应对方案,do it
// 作业3:迭代器原理
迭代器:调用 next() 将可迭代对象的值一一返回,不用关注对象类型,只要是可迭代对象都有默认迭代器 [Symbol.iterator]
2024.12.25 先做LTN4 - 62min
✅作业1:写一个只能被迭代指定次数的类 ✅作业7:原始值包装类型和原始类型在使用 typeof 和 instanceof 进行类型判断时,有什么异同? ✅作业9:数组自带排序 sort() 为什么经常要传比较函数?写一个降序的比较函数。如果数组元素全是数值,请给出简写形式 ✅作业11:写出一个用 yield 实现的递归算法,从0数到指定n ✅作业12:画出一个原型链,到 null 为止 ✅作业8:ES6新增了两个创建数组的静态方法:Array.from()和Array.of()两个有什么区别? ✅作业10:说出WeakMap/WeakSet和Map、Set的区别,为什么有这两个弱类型,经常用在什么场景 ❌作业16(2):arguments 对象对待命名参数和设置了默认值的命名参数有什么不同?数组作为参数传入函数得到的arguments 是什么?怎么实现arguments是数组?怎么收集独立参数?在对象字面量和数组字面量中,扩展运算符有什么不同的表现?
// 作业10:说出WeakMap/WeakSet和Map、Set的区别,为什么有这两个弱类型,经常用在什么场景
WeakMap/WeakSet 分别是 Map、Set 的弱类型,特点是键/值只能是对象类型,并且在对象是空对象,没有引用时,垃圾回收程序会清理当前弱类型
目的:避免内存泄漏
场景:比如监听 DOM 节点,如果节点被删除,弱类型会被清理
// 作业8:ES6新增了两个创建数组的静态方法:Array.from()和Array.of()两个有什么区别?
Array.from() - 用于将类数组(arguments+Nodelist)/可迭代对象转换成数组类型,浅拷贝
Array.of() - 用于将一连串参数转换成数组类型,主要用于替代 Array.prototype.slice.call(arguments)
// 作业12:画出一个原型链,到 null 为止
function Person() {}
let person = new Person();
person.__proto__ === Person.prototype
Person.prototype.constructor === Person
Person.prototype.__proto__ === Object.prototype
Object.prototype.constructor === Object
Object.prototype.__proto__ === null
// 作业11:写出一个用 yield 实现的递归算法,从0数到指定n
function * nTimes(n) {
if(n>0) {
yield* nTimes(n-1);
yield (n-1);
}
}
// 作业9:数组自带排序 sort() 为什么经常要传比较函数?写一个降序的比较函数。如果数组元素全是数值,请给出简写形式
sort函数会把数组中的值转换成字符串后比较,比如排序后会出现 [1,10,15,3,5]
// 降序
[].sort((a,b) => {
return a > b ? -1 : a < b ? 1 : 0
})
// 简写
[].sort((a, b) => b - a)
// 作业7:原始值包装类型和原始类型在使用 typeof 和 instanceof 进行类型判断时,有什么异同?
String Boolean Number
原始类型-使用 typeof v 可以得到具体类型的字符串 VS 使用 v instanceof S/B/N 都是 false
原始值包装类型 - 使用 typeof v 得到 'object' VS v instanceof S/B/N 得到 true
typeof 用于判断基础数据类型(null array object 都是 'object',函数是 'function')
instanceof 在对象的原型链上查找是否有指定构造函数的原型对象 [Symbol.hasinstance]
// 作业1:写一个只能被迭代指定次数的类
class nTimes{
constructor(n) {
this.limit = n;
}
[Symbol.iterator](){
let max = this.limit;
let times = 0;
return {
next() {
if(times < max) {
return {value: times++, done: false};
} else {
return {value: undefined, done: true};
}
}
return() {
return {value: undefined, done: true}
}
}
}
}
// 答题核心:迭代器返回的是迭代器对象(包含next()、return()) + next 返回一个 done+value组成的对象