LTN做题
LTN①④ -
LTN5 - 2题
作业2:支持迭代器的原生语言特性有哪些? 作业5:什么是生成器对象?有什么特点?什么时候会被执行?
LTN3 - 11题
作业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 选择器 - 权重/匹配方式
LTN2 - 10题
作业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
LTN1 - 43题
作业75:React 组件怎么写才好?hook怎么写才好 作业36:多分支项目的大致情况 作业37:大盘的大致情况 作业38:部署两个项目的大致情况 作业58:React和Vue是怎么描述UI的 作业60:虚拟DOM在 Vue 和 React 中的使用?分别有什么提升性能的方法? 作业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 的原理是什么? 作业69:useEffect useState模拟实现 作业70:Vue 和 React 的 Diff 算法比较 作业71:React为什么要合成事件?和原生事件有什么不同? 作业72:react 的 声明周期有哪些,在不同生命周期中做什么事情? 作业78:使一个标签看不见的几种样式及其区别 作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口) 作业53:胜利者一无所获 说的是什么 作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的 作业80:对DOM 树的理解 - 定义和作用 作业81: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 中预渲染是什么?有哪些模式?写一个动态路由的page 作业88:mysql2 和 Sequelize 的使用场景?有哪些差异?简单写一个连接 ①2024.12 模拟实现promise ②2024.11 观察者模式 EventEmitter ③2024.10 怎么实现跨域 ④2024.05 rem是基于什么原理进行适配的? ⑤2023.07 clickhouse、mysql、mongodb异同 ⑥2022.08 包管理工具 ⑦2021.10 LTN做题 ⑧2021.07 bind、apply/call三者异同+apply/call实现bind ⑨2021.07 数组扁平化(一层、全部展开、指定深度) ⑩2021.04 05-3 白屏优化? ①①2020.07 对象分类、Promise按序执行、实现map ①②2019.10 两栏,左边固定,右边自适应的布局 ①③2019.10 三栏,两边固定,中间自适应
红宝书
①2024.09 第八章 对象、类和面向对象编程 小结 ②2024.11 第十七章 事件
LTN ①③ - 错题 28题
❌作业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
// 2022.08 包管理工具
- 第二遍 -
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.11 观察者模式 EventEmitter
- 第二遍 -
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)
})
}
}
}
// 2024.12 模拟实现promise
- 第二遍 -
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);
}, 0)
}
const rejectFun = () => {
setTimeout(() => {
try{
const res = onReject(this.reason);
resolve(res);
}catch(reject);
}, 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))
})
}
// 作业79:地球四季的成因是什么?
地球绕着太阳公转轨迹是椭圆的,但是四季的成因和太阳与地球的距离没啥关系
主要是太阳直射点位置 + 夹角(大气层厚度)
⭐️ // 直射点的移动 + 太阳照射时间的长短;夹角,与地面角度的改变,包括改变了太阳辐射面积 + 经过大气的路程衰减的辐射能量强度
// 作业78:使一个标签看不见的几种样式及其区别
display: none; 不占位,直接移除 DOM 节点 v-if ❌ // 1、元素仍在 DOM 中,会触发回流+重绘; v-if 移除了 DOM 节点
opacity: 0; 依旧占位,但是透明、看不见节点,绑定的事件依旧可以触发
visibility: hidden; 不占位,DOM 节点还在,但是看不见节点,绑定的事件无法触发 v-show ❌ // 2、依旧占位,不影响结构,会触发重绘
// 作业77:重绘和重排是什么?有什么区别?
浏览器渲染流程:解析DOM-计算样式表-布局树 layout-分层树-绘制-栅格化-合成
重绘:只需要重新修改每层的样式+合成
- 不涉及到布局修改的样式:字体、背景、边框颜色
重排:需要重新计算布局+修改每层的样式+合成
- 字体大小、padding/margin大小、激活伪类、style样式的修改、resize ⭐️ // 1、页面初始渲染 2、可见DOM的增删 3、位置、内容的修改 4、DOM 布局的查询
// 作业76:显卡的作用?
用于渲染帧
分成前后区,浏览器从前区获取显示的帧;在后区进行渲染,直到渲染完成,跟前区交换用于显示新帧
⭐️ // 作用:合成新的图像,并将图像保存到后缓存区
// 作业75:React 组件代码表达的是什么?hook怎么写才好,自定义hook会共享状态么?组件和hook的返回值有什么不同?在渲染时,他们是怎么个顺序?
- 第二遍 -
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
// 作业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、学名「批量更新机制」:为了减少不必要的重新渲染次数
// 作业72:react 的 生命周期有哪些,在不同生命周期中做什么事情?
- 第二遍 -
❌❌ // 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布局的副作用,例如数据获取、订阅事件等
// 作业71:React为什么要合成事件?和原生事件有什么不同?
React 的合成事件,其实是一个处理了浏览器兼容性的事件处理系统 ⭐️ // + 是对浏览器原生事件的封装
功能:①处理各个浏览器的差异,使用者可以只关注代码实现;
②利用事件委托,所有的合成事件在构建时,统一冒泡到最外层,一般是 document 标签上绑定,大量减少了绑定事件的性能消耗
❌❌ // 1、不是在「构建时」,事件委托发生在「组件挂载时」
差异:
事件对象:合成事件-统一了对象类型;原生事件-根据浏览器不同有一定差异
事件执行机制:合成事件-由React内部事件系统统一管理执行,一般是在冒泡阶段执行;
原生事件-捕获阶段就会触发,比合成事件早;同步执行,触发后直接执行;
❌❌ // 2、少写了:事件绑定方式 合成事件-JSX中属性指定事件处理函数;原生事件 标签的onclick或者addEventListener
- 第一遍 -
合成事件的定义:React 统一了各个浏览器内核的原生 API 差异,抹平差异后,用户只需要关注功能实现,而不需要再关注宿主环境
合成事件是原生事件的高级集合
❌ // 1、内核是对的,不是“高级集合”,是「React对浏览器原生事件的封装」,是「一个跨浏览器兼容的事件系统」
// 优点/原因:① 跨浏览器兼容性 ②性能优化:通过事件委托到 document,减少大量事件绑定的开销
❌ // 2、“不同”需要具体的描述:
// ①事件对象(有一套标准的属性和方法,用于事件信息 VS 由浏览器提供,属性方法因浏览器而异)
// ②事件绑定方式(React中JSX属性中指定事件处理函数来绑定 VS 原生标签的 onclick/addEventListener)
// ③事件执行顺序(内部事件系统决定,冒泡执行,一般比原生晚 VS 先捕获再冒泡)
// 作业70:Vue 和 React 的 Diff 算法比较
- 第二遍 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、跳过比较静态编译标记的静态节点,加快比较速度✅
// 作业68:React hooks 的原理是什么?useEffect useState模拟实现
- 第三遍 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 ++;
}
// 作业67:React 怎么做的性能优化?
- 第二遍 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 的操作,避免不必要的回流重绘
// 作业66:React如何处理错误
- 第三遍 -
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;
}
}
// 作业65: ①computed/watch/methods 的区别? ②父子组件挂载顺序 ③vue组件中的data为什么是函数 ④ 常见生命周期
- 第三遍 -
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渲染怎么实现的?
- 第三遍 -
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树
// 作业63:React渲染怎么实现的?涉及到哪些生命周期/hooks?以 const [count, setCount]=useState(0)具体说明
- 第三遍 -
❌❌❌ // 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 从新老树中匹配对应节点比较」
// 作业62:fiber架构是什么?优点有哪些? 是怎么实现的?
- 第三遍 -
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
// 作业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 不是明确的组件 ❌
// 作业58:React和Vue是怎么描述UI的
- 第二遍 -
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"
// 作业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
// 2021.04 05-3 白屏优化?
白屏:网络进程文件加载完毕,浏览器开始刷新页面,但是主线程渲染流程还没有结束,页面短暂出现解析白屏
❌ // 1、少写 - 硬件加速:DNS 解析优化,例如缓存、预加载;TCP、服务器优化
1、html 精简代码,简化结构,减短DOM解析时间
2、css 从右到左的解析方式,尽可能减少层级,使用 link 而非 @import,从而触发预加载,使用 css 优化/GPU 加速减少回流重绘 ❌ // 2、少写 - 对于大文件的 css,利用媒体查询拆分不同用途
3、js 加载使用 defer/async 避免阻塞 ❌ // 3、少写 - 尽量不使用 内联代码
4、压缩代码
5、减少 http 请求次数、大小
// 2021.07 数组扁平化(一层、全部展开、指定深度)
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
同: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);
}
*/
}
// 2024.10 怎么实现跨域
1、JSONP:只能处理 GET 类型的请求,利用 script 标签的 src 可以引入第三方资源
在客户端强求中指定 callback,在服务端将数据包裹成函数,返回给客户端,客户端执行 callback 函数
2、代理 nginx:同源协议限制只在客户端,服务端之间不存在
3、CORS 设置 Access-Control-Allow-Origin 可以信任的第三方资源 // ❌ 1、那些域名可以访问资源 - 是为了让浏览器知道哪些域名可以跨域资源共享,而不是从客户端角度描述
4、websocket 不受同源协议的限制
// 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 是第一生产力
但是人生很多事情是不去尝试就无法推进的,比如创业、比如换工作
但是要意识到,人类出生都是向死而生的,我们并没有永恒的生命,如果你不做自己想做的事情,那就又少活了一天
// 作业53:胜利者一无所获 说的是什么
海明威 在《战国春秋》中用于反战,战胜方失去了美好的品质、善良、公序 // ❌ 《战地春梦》
质疑胜利的本质,如果胜利者并不得获得很多,那要追求什么?
鼓励人们追求内在美,包括善良、纯真等
// 作业52:道可道,非常道 翻译一下
如果真理是可以被说出来的,那它就不是永恒不变的道了
形容语言的局限性,很多道理难以言传
// 作业51: 如何停止过度思考(当下的力量)
思考是一种上瘾行为,我们要是沉迷于思考,就失去了对时间的控制
解决方法:抽离出来,看到自己脑海中的思想,get it,不跟着走,let it go,回到当下
// 作业50:学习的感觉是?
学如飞鸟过,脑内了无痕
// 作业48: input 和 textarea 对比(宽度设置,初始值设置);option 标签的初始值设置
input:单行标签;size 设置宽度,单位为字符数;value 设置初始值;maxLength 设置最大长度
textarea:多行;cols 设置宽度,单位为字符数;标签中间的内容就是初始值;rows 设置高度
option 初始值有 value 取 value 值, 没有 value 取标签中间内容
// 作业47:2D绘图上下文(坐标原点、基本操作、唯一形状)和3D上下文(坐标原点、定义视口)
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');
坐标原点:左下角
定义视口:
context.viewpoint(drawing.width/2, 0, drawing.width/2, drawing.height/2); // 右下角1/4
// 作业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很难修改到样式
// 2019.10 三栏,两边固定,中间自适应 .left, .right { width: 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: 200px }
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
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;
}, [])
}
*/
// 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);
};
}
}
// 2024.05 rem是基于什么原理进行适配的?
rem 相对于根元素的 font-size - html(body) ❌ // 1、不是 body-只是html的一个子标签,就是 html
em
- 当前节点的字体大小相对于父元素的 font-size
- 当前节点的 width/height/padding/border/margin 相对于当前元素的 font-size
// 作业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 操作符
}
// 作业13:什么是原型链?原型链继承、组合继承、寄生组合式继承,分别有什么优缺点?分别是怎么实现的
原型链:一个函数的原型是另一个函数的实例
原型链继承
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;
}
(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 深拷贝和浅拷贝的区别?怎么实现深拷贝?
// 2020.03 改变原数组+结束循环+性能排序
可以改变原数组的数组方法有:shift unshift pop push reverse sort splice fill
不可以通过return和break结束循环 filter map reduce forEach
排序:for>for...of>forEach>map>for...in
// 2021.06 BFC特点、触发、存在/解决的问题
特点:①独立容器 ②从上到下,垂直排列 ③不和外部的浮动元素重合
触发:①float 非 none ②overflow 非 visible:hidden/auto/scroll ③display: flex/inline-block/table-caption/table-cell ④position: absolute/fixed ⑤根元素
问题:同一个BFC中相邻的两个盒子会出现 外边距重叠,只显示一个 margin
解决方案:给他们的父元素添加 display:flex
// 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:代表了一次构建,更新一次代码就创建一个
// 2021.07 防抖节流 ✅
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)
}
}
// 2021.09 Object属性排序、与Map的区别 ✅
Object 属性排序:正整数 > 字符串 > 负数 > 浮点数 > Symbol
与Map的区别:
① Map的键可以是任何类型 VS Object 的键只有字符串/Symbol
② Map可以记录插入顺序 VS Object 无法记录,自有自己的默认排序方式
③ Map写法简单,长度为 m1.size() VS Object.keys(Object).length
④ Map在大量数据时占用空间小,增删快
⑤ Map是可迭代对象 VS Object不是
// 2024.12 模拟实现jsonp
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);
}
// 作业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
// 作业57:“假装成功”的意义是什么
客观从物理层面说,肢体是可以改变人体内的睾丸酮和皮质醇(压力激素)
假装成功可以降低压力、提高自信心
人是有“自我预言”机制的,你越相信,你越能做到
you derserve better, fake it until you become it!
// 作业34:写出强缓存、协商缓存的流程及相关属性
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请求同时开始,怎么实现
1、Promise.all([A(), B()]).then(C);
2、async function () {
await Promise.all([A(), B()]);
C();
}
// 作业20:浏览器打开一个 URL 发生了什么
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线程:->合成展示
// 作业17:对斐波那切数列进行尾调用优化
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)
}
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组成的对象