huyaocode / webknowledge Goto Github PK
View Code? Open in Web Editor NEW前端知识点总结
License: MIT License
前端知识点总结
License: MIT License
then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this
//对传入的两个参数做判断,如果不是函数将其转为函数
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: v => v // onFulfilled = v => v
onRejected =
typeof onRejected === 'function'
? onRejected
: r => {
throw r
}
if(that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
else if(that.state === RESOLVED) {
onFulfilled(that.value)
}
else {
onRejected(that.value)
}
}
闭包这个概念是前端工程师必须要深刻理解的,但是网上确实有一些文章会让初学者觉得晦涩难懂,而且闭包的文章描述不一。
本文面向初级的程序员,聊一聊我对闭包的理解。当然如果你看到闭包联想不到作用域链
与垃圾回收
也不妨看一眼,希望读了它之后你不再对闭包蒙圈。
这里有个需求,即写一个计数器的函数,每调用一次计数器返回值加一:
counter() // 1
counter() // 2
counter() // 3
......
要想函数每次执行的返回不一样,怎么搞呢? 先简单的写一下:
var index = 1;
function counter() {
return index ++;
}
这样做的确每次返回一个递增的数。但是,它有以下三个问题:
三个痛点,让闭包来一次性优雅解决:
function counterCreator() {
var index = 1;
function counter() {
return index ++;
}
return counter;
}
// test
var counterA = counterCreator();
var counterB = counterCreator();
counterA(); // 1
counterA(); // 2
counterB(); // 1
counterB(); // 2
我的counterCreator
函数只是把上面的几行代码包起来,然后返回了里面的 counter
函数而已。却能同时解决这么多问题,这就是闭包的魅力! 6不6啊?
铺垫一些知识点,不展开讲。
函数每次执行,都会生成一个会创建一个称为执行上下文的内部对象(AO对象,可理解为函数作用域),这个AO对象会保存这个函数中所有的变量值和该函数内部定义的函数的引用。函数每次执行时对应的执行上下文都是独一无二的,正常情况下函数执行完毕执行上下文就会被销毁。
在函数定义的时候,他还获得[[scope]]。这个是里面包含该函数的作用域链,初始值为引用着上一层作用域链里面所有的作用域,后面执行的时候还会将AO对象添加进去 。作用域链就是执行上下文对象的集合,这个集合是链条状的。
function a () {
// (1)创建 a函数的AO对象:{ x: undfind, b: function(){...} , 作用域链上层:window的AO对象}
var x = 1;
function b () {
// (3)创建 b函数的AO对象:{ y: undfind , 作用域链上层:a函数AO对象}
var y = 2;
// (4)b函数的AO对象:{ y: 3 , 作用域链上层:a函数AO对象}
console.log(x, y); // 在 b函数的AO对象中没有找到x, 会到a函数AO对象中查找
}
//(2)此时 a函数的AO对象:{ x: 1, b: function(){...} , 作用域链上层:window的AO对象}
b();
}
a();
正常情况函数每次执行后AO对象都被销毁,且每次执行时都是生成新的AO对象。我们得出这个结论: 只要是这个函数每次调用的结果不一样,那么这个函数内部一定是使用了函数外部的变量。
如何确定哪些内存需要回收,哪些内存不需要回收,这依赖于活对象这个概念。我们可以这样假定:一个对象为活对象当且仅当它被一个根对象 或另一个活对象指向。根对象永远是活对象。
function a () {
var x = 1;
function b () {
var y = 2;
// b函数执行完了,b函数AO被销毁,y 被回收
}
b();
//a 函数执行完了,a函数AO被销毁, x 和 b 都被回收
}
a();
// 这里是在全局下,window中的 a 直到页面关闭才被回收。
// 生成闭包的函数
function counterCreator() {
// 被返回函数所依赖的变量
var index = 1;
// 被返回的函数
function counter() {
return index ++;
}
return counter;
}
// 被赋值为闭包函数
var counterA = counterCreator();
// 使用
counterA();
闭包的创造函数必定包含两部分:
// 被赋值的闭包函数
var counterA = counterCreator();
var counterB = counterCreator();
而上面这两句代码很重要,它其实是把闭包函数赋值给了一个变量,这个变量是一个活对象,这活对象引用了闭包函数,闭包函数又引用了AO对象,所以这个时候AO对象也是一个活对象。此时闭包函数的作用域链得以保存,不会被垃圾回收机制所回收。
当我们想重新创建一个新的计数器时,只需要重新再调用一次 counterCreator
, 他会新生成了一个新的执行期上下文,所以counterB
与counterA
是互不干扰的。
闭包的原理,就是把闭包函数的作用域链保存了下来。
带你手写一个简单的防抖函数,趁热打铁。
第一步,先把闭包的架子搭起来,因为我们已经分析了闭包生成函数内部一定有的两部分内容。
function debunce(func, timeout) {
// 闭包函数执行时依赖的变量,每次执行闭包函数时都能访问和修改
return function() {
// 这个函数最终会被赋值给一个变量
}
}
第二步: 把闭包第一次执行的情况写出来
function debunce(func, timeout) {
timeout = timeout || 300;
return function(...args) {
var _this = this;
setTimeout(function () {
func.apply(_this, args);
}, timeout);
}
}
第三步: 加上一些判断条件。就像我们最开始写计数器的index
一样,不过这一次你不是把变量写在全局下,而是写在闭包生成器的内部。
function debunce(func, timeout) {
timeout = timeout || 300;
var timer = null; // 被闭包函数使用
return function(...args) {
var _this = this;
clearTimeout(timer); // 做一些逻辑让每次执行效果可不一致
timer = setTimeout(function () {
func.apply(_this, args);
}, timeout);
}
}
// 测试:
function log(...args) {
console.log('log: ', args);
}
var d_log = debunce(log, 1000);
d_log(1); // 预期:不输出
d_log(2); // 预期:1s后输出
setTimeout( function () {
d_log(3); // 预期:不输出
d_log(4); // 预期:1s后输出
}, 1500)
闭包用到的真的是太多了,再举几个例子再来巩固一下:
例NodeJS模块化原理:
NodeJS 会给每个文件包上这样一层函数,引入模块使用require
,导出使用exports
,而那些文件中定义的变量也将留在这个闭包中,不会污染到其他地方。
(funciton(exports, require, module, __filename, __dirname) {
/* 自己写的代码 */
})();
一些使用闭包的经典例子:
最后,如果你对闭包有更好的理解或者我文章里写的不好的地方,还请指教。
“对于 TCP 头部来说,以下几个字段是很重要的:
序列号 (Sequence number),这个序号保证了 TCP 传输的报文都是有序的,对端可以通过序号顺序的拼接报文
确认号 (Acknowledgement Number),这个序号表示数据接收端期望接收的下一个字节的编号是多少,同时也表示上一个序号的数据已经收到
窗口大小 (Window Size),表示还能接收多少字节的数据,用于流量控制……”
这一段包括之后三步握手部分确实把确认号和ACK标识符区分开了,但是在输入URL到页面加载过程分析那一段的三步握手里出现了:
“端口建立 TCP 链接,三次握手如下:
客户端发送一个 TCP 的SYN=1,Seq=X的包到服务器端口
服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
客户端发送ACK=Y+1, Seq=Z”
这部分应该是把ACK标识符和确认号混淆了,应该是ACKbit为1,确认号为X+1,因为确认号就等于下一个client request的序列号,而当前client request序列号为X且request大小只有1bit,所以下一个SN就是X+1。
找到熟悉 / 可靠的人去帮你内推,然后与他保持联系。你可以知道更多信息,比如:
查看面试进度信息
填写推荐语
特殊情况紧急联系
处理速度更快
为什么要找内推
里说的那些,你办不到我们内推是任务,我当工作来完成,而且内推可以你直接使用我的内推投递链接或者校招码投递,这样我看不到你简历,保护你隐私。而且我尊重你的职位选择,我内推其实主要就是帮你填推荐语。(很多帮内推的都是往自己部门推,导致部门简历过多且可能不是你喜欢的职位)
我的微信: purple12369
我的邮箱:[email protected]
返回一个 memoized 回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
假如这里有个文章组件,我想观察当「文章内容」明确后, 用户对「文章标题」的修改频率如何。这个
具体实现:当「文章内容」的长度大于 0 时编辑「文章标题」就上报埋点,同时带上「文章标题」和「文章内容」的 字符长度。
点击链接,打开 console 看Demo:https://codepen.io/huyao/pen/LYbdomv?editors=0010
小胡写出了下面这一段代码,大家可以细看一下,有哪些地方需要优化?它有哪些地方不规范?
// 新建文章组件
function EditArticle() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [other, setOther] = useState("");
// 获取当前「标题」和「内容」的长度
const getTextLen = () => {
return [title.length, content.length];
};
// 上报当前「标题」和「内容」的长度
const report = () => {
const [titleLen, contentLen] = getTextLen();
if (contentLen > 0) {
console.log(`埋点 >>> 标题长度 ${titleLen}, 内容长度${contentLen}`);
}
};
/**
* 副作用
* 当「标题」长度变化时,上报
*/
useEffect(() => {
report();
}, [title]);
return (
<div className="App">
文章标题 <input value={title} onChange={(e) => setTitle(e.target.value)} />
文章内容 <input value={content} onChange={(e) => setContent(e.target.value)} />
其他不相关状态: <input value={other} onChange={(e) => setOther(e.target.value)} />
<MemoArticleTypeSetting getTextLen={getTextLen} />
</div>
);
}
enum ArticleType {
WEB = "前端",
SERVER = "后端",
}
// 子组件,修改文章类型(无需关注,它只是接受了父组件的一个参数而已)
const ArticleTypeSetting: FC<{ getTextLen: () => number[] }> = ({ getTextLen }) => {
console.log(" --- ArticleTypeSetting 组件重新渲染 --- ");
const [articleType, setArticleType] = useState<ArticleType>(ArticleType.WEB);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setArticleType(e.target.value as ArticleType);
console.log( "埋点 >>> 切换类型,当前「标题」和「内容」长度:", getTextLen() );
};
return (
<div>
<div>文章类型组件,当选择类型时上报「标题」和「内容」长度</div>
<div>
{[ArticleType.WEB, ArticleType.SERVER].map((type) => (
<div>
<input type="radio" value={type} checked={articleType === type} onChange={handleChange} />
{type}
</div>
))}
</div>
</div>
);
};
const MemoArticleTypeSetting = memo(ArticleTypeSetting);
注:这段代码是我为了模拟 useEffect 和 useCallback 同时使用而构思出来的,大家不用深究。当然有其他写法可以避免问题,我们这里只是借这个 Demo 描述一下这种类型的场景。
子组件 ArticleTypeSetting 是使用 memo 包裹的,这个组件是希望尽可能的减少渲染次数的(假装这个组件有性能问题,一般不用包)。但是,现在每当修改任意一个值(如 other),子组件都会重新渲染,这显然是没有达到优化的预期的。
这里不规范, useEffect 中使用了 report 函数,但是没有将它放到依赖数组中。我认为这是一件比较危险的事情,在 Hooks 中经常有过期状态的问题。插件已经帮你提示了,虽然现在你自测感觉没问题,但你很难保证在经过几轮轮修改之后,虽然你的代码一堆 warning 或 error,但跑起来没问题。
小胡于是对代码进行了一些修改:
// 获取当前「标题」和「内容」的长度
const getTextLen = useCallback(() => {
return [title.length, content.length];
}, [title, content]);
// 上报当前「标题」和「内容」的长度
const report = useCallback(() => {
const [titleLen, contentLen] = getTextLen();
if (contentLen > 0) {
console.log(`埋点 >>> 内容长度 ${titleLen}, 内容长度${contentLen}`);
}
}, [getTextLen]);
/**
* 副作用
* 当「标题」长度变化时,上报
*/
useEffect(() => {
report();
}, [title, report]);
有,当 「文章内容」修改了之后,会触发 useEffect 继续上报,这个问题比较隐晦,不再回归测试的话难以发现;并且编辑文章内容时子组件也在重新渲染。
我的初衷只是使用 useCallback 避免频繁调用,但当一个 useCallback 的依赖项变化后,这个 useEffect 会被执行,就像上面修改过后的代码一样,「文章内容」修改了之后,也会触发 useEffect 的,这就是「useCallback 带来的隐式依赖问题」。
这种做法确实可以解决这个问题,但是代码不宜维护,理由如下:
一旦一个组件这样写了之后,之后要有什么新的状态也只好放到这里面。
而在新写组件的时候,你不知道什么时候会碰到这个问题,因为一旦碰到了你只有使用 forceUpdate 来解决,要改相关状态的定义,每次使用的时候还要把 someSate 改为 ref.someState。
每次想更新视图时都需要 forceUpdate,官网是不推荐这种方式的,链接点我
const getTextLenRef = useRef<() => [number, number]>(() => [0, 0]);
// 获取当前「标题」和「内容」的长度
getTextLenRef.current = () => {
return [title.length, content.length];
};
// 上报当前「标题」和「内容」的长度
const report = () => {
const [titleLen, contentLen] = getTextLenRef.current();
if (contentLen > 0) {
console.log(`埋点 >>> 标题长度 ${titleLen}, 内容长度${contentLen}`);
}
};
/**
* 副作用
* 当「标题」长度变化时,上报
*/
useEffect(() => {
report();
}, [title]);
将函数绑定到 Ref上,ref 引用不论怎样都保持不变,而且函数每次 render ref 上又会绑定最新的函数,不会有闭包问题。我在开发一个复杂项目中,大量的使用了这种方式,这让我的开发效率提升。它让我专注于写业务,而不是专注于解决闭包问题。
这种处理方式的灵感来源于 Dan 的博客:使用 React Hooks 声明 setInterval
虽然把函数挂到 ref 上可以很好到解决这个问题,但是我在开发的时候我并不知道一个函数之后会不会碰到这个闭包问题,但我又不想所以函数全部都这样干。
我对这种方式抽象封装了一下,得到这样一个工具函数,它通过将函数挂到 ref 上,保证永远都是拿到最新状态的函数,往外暴露时使用 useCallback 包裹,保证函数引用不更新。
export function useRefCallback<T extends (...args: any[]) => any>(callback: T) {
const callbackRef = useRef(callback);
callbackRef.current = callback;
return useCallback((...args: any[]) => callbackRef.current(...args), []) as T;
}
使用时,简单的把原来有闭包问题的函数包裹一下,不需要传递依赖性,方便简单又好用。
点击链接,打开 console 看Demo:https://codepen.io/huyao/pen/XWNELYr?editors=0010
// 获取当前「标题」和「内容」的长度
const getTextLen = useRefCallback(() => {
return [title.length, content.length];
});
// 上报当前「标题」和「内容」的长度
const report = useRefCallback(() => {
const [titleLen, contentLen] = getTextLen();
if (contentLen > 0) {
console.log(`埋点 >>> 内容长度 ${titleLen}, 内容长度${contentLen}`);
}
});
/**
* 副作用
* 当「标题」长度变化时,上报
*/
useEffect(() => {
report();
}, [title, report]);
我认为其实最开始使用 useCallback 的理由中,只有「需要保存一个函数闭包结果,如配合 debounce、throttle 使用」这个是真正需要使用 useCallback 的,其他的都可能带来风险,比如:
当 useCallback 和 useEffect 组合使用时,由于 useCallback 的依赖项变化也会导致 useEffect 执行,这种隐式依赖会带来BUG或隐患。因为在编程中,函数只是一个工具,但现在一旦某个函数使用了 useCallback ,当这个函数的依赖项变化时所有直接或间接调用这个 useCallback 的都需要回归。所以我说这是成本高、有风险的事情。
而「为了避免子组件频繁渲染,使用 useCallback 包裹,保持引用不变」这种情况下,当 useCallback 的依赖项变化时,函数的引用也在更新,没有完全的避免子组件频繁渲染问题。
而「希望这个useCallback函数的某个依赖项变化时,引用了我这个函数的所有 useEffect 都得重新执行一下」这个理由,我认为它是有风险的,虽有有时候你确实希望这么做,但我认为这样在设计上就不对,副作用怎么调用应该由副作用来决定,不应该由依赖的函数来影响,当你真正碰上这个场景,你应该将所有应该主动的把触发 useEffect 执行的状态都放入依赖数组中。
在绝大多数情况下,开发者想要的仅仅只是避免函数的引用变化而已,而 useCallback 带来的隐式依赖问题会给你带来很大的麻烦,所以推荐使用 useRefCallback
,把函数挂到 ref 上,这样代码更不容易留下隐患或带来问题,还可以省去维护 useCallback 依赖项的精力。
而 useRefCallback 本质上就是帮你把函数挂在 ref 上,并方便你使用 ref.current。
export function useRefCallback<T extends (...args: any[]) => any>(callback: T) {
const callbackRef = useRef(callback);
callbackRef.current = callback;
return useCallback((...args: any[]) => callbackRef.current(...args), []) as T;
}
凡事都有方法论,但大多数人做选择则是简单的凭感觉。“感觉”这个东西往往是不够准确的,当你的大脑往某些方向多想一下的时候,当旁人说了几句话的时候,你凭拍脑袋得出的结论往往就会发生变化。当几个选择接近时,凭感觉更无法得出结论。
本文是介绍如何做选择,并侧重讲如何选择offer。
“白赚”的收入就叫“租”。
假如你现在有两个offer,A
offer年薪20W、B
offer年薪30W。我们假设这两个offer除了薪资不同,其他都差不多。你肯定会选择年薪30W的B
offer,即使B
offer 只有 21 W,你也会选 B
,因为 B
比 A
高啊。 这B
比A
高的部分,是你”白赚“的,这多出来的部分就叫租。
应届生选择offer往往会从以下几点考虑:
有幸拿到了涛哥的邀请函,免费去听了这场大会,就当涨涨见识。其实大多数我都没有听懂,只是大概知道了有这些东西,有了一些感悟。
最近在准备实习,没有对会议内容进行背景的预习。自己没有带着问题听,也没有相关的经验,有的术语也不熟。导致很多听了也就听了,从技术的角度来看其实收益不大。
去事件,去输出,去体验。让自己记住以后有碰到类似场景可以超这边靠拢。
每个人都是求知若渴的,而且他们有很多开发经验。而且感觉很多开发者都很帅气,都在不断的深入学习中
让听众听懂自己讲的内容才是关键。听众不关心你有多牛批,你这个东西有多牛批,你不是在做答辩,而是做分享,是为了让他人收获知识,增长眼界。有好几次听到讲师在着重去讲一些成果,那个时候心里是抵触的。
webpack作者可以把生活过的很好,severless讲师超级帅。他们都是活的很优雅的。我认为技术其实就是匠人的一把锤子,是匠人的手艺,匠人会做最好的东西。但匠人不会苦逼的说自己每天面对这些很难受,太多东西要学了什么的。 匠人会沉浸其中,会开心,会充实的。
MDN上关于stopPropagation方法的描述如下:The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.应该是阻止在捕获阶段或冒泡阶段继续传播,而不是阻止冒泡。
vu3中,ref 函数被推荐用来声明基础数据类型的响应式,仍然是通过Object.defineProperty实现的,而reactive定义的引用数据类型才是通过Proxy实现的,所以并不是完全替换的关系。
本人从事十年年大数据相关工作,做过用户增长,BI,大数据中台,知识图谱,AI中台,擅长大数据AI相关技术栈。在CSDN输出很多专栏,是CSDN博客专家,CSDN大数据领域优质创作者,2018年参与共建WeDataSphere开源社区,社区属性是数据相关综合社区,共建过DataSphereStudio(开发管理集成框架),Exchangis(数据交换工具),Streamis(流式应用开发管理系统),Apache Linkis (计算中间件) 。个人发起SolidUI数据可视化社区。Apache Asia 2022 讲师 ,Hadoop Meetup 2022 讲师,WeDataSphere Meetup 2022讲师。Apache Linkis Committer , EXIN DPO (数据保护官)。
2023年2月开始创业,全职运营SolidUI。
一句话生成任何图形。
随着文本生成图像的语言模型兴起,SolidUI想帮人们快速构建可视化工具,可视化内容包括2D,3D,3D场景,从而快速构三维数据演示场景。SolidUI 是一个创新的项目,旨在将自然语言处理(NLP)与计算机图形学相结合,实现文生图功能。通过构建自研的文生图语言模型,SolidUI 利用 RLHF (Reinforcement Learning Human Feedback) 流程实现从文本描述到图形生成的过程。
SolidUI Gitee https://gitee.com/CloudOrc/SolidUI
SolidUI GitHub https://github.com/CloudOrc/SolidUI
SolidUI 官网地址 https://cloudorc.github.io/SolidUI-Website/
Discord https://discord.gg/NGRNu2mGeQ
SolidUI v0.3.0 发版文章 https://mp.weixin.qq.com/s/KEFseiQJgK87zvpslhAAXw
SolidUI v0.3.0 概念视频 https://www.bilibili.com/video/BV1GV411A7Wn/
SolidUI v0.3.0 教程视频 https://www.bilibili.com/video/BV1xh4y1e7j6/
SolidUI 演示环境 http://www.solidui.top/ admin/admin
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.