You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/** * https://github.com/reactjs/rfcs/pull/107 * @param {*} type * @param {object} props * @param {string} key */exportfunctionjsx(type,config,maybeKey){letpropName;// 1. key的默认值是nullletkey=null;// Currently, key can be spread in as a prop. This causes a potential// issue if key is also explicitly declared (ie. <div {...props} key="Hi" />// or <div key="Hi" {...props} /> ). We want to deprecate key spread,// but as an intermediary step, we will use jsxDEV for everything except// <div {...props} key="Hi" />, because we aren't currently able to tell if// key is explicitly declared to be undefined or not.if(maybeKey!==undefined){// 2. 将key转换成字符串key=''+maybeKey;}if(hasValidKey(config)){// 2. 将key转换成字符串key=''+config.key;}// 3. 将key传入构造函数returnReactElement(type,key,ref,undefined,undefined,ReactCurrentOwner.current,props);}
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */exportfunctioncreateElement(type,config,children){letpropName;// Reserved names are extractedconstprops={};letkey=null;letref=null;letself=null;letsource=null;if(config!=null){if(hasValidKey(config)){key=''+config.key;// key转换成字符串}}returnReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props);}
Learning-notes
前端学习笔记 & 踩坑日记 & 冷知识,记录一些工作中遇到的问题,长期更新
1.
isNaN()
和Number.isNaN()
的区别Number.isNaN()
方法确定传递的值是否为NaN
,并且检查其类型是否为Number
。它是isNaN()
的更稳妥的版本。和
isNaN()
相比,Number.isNaN()
不会自行将参数转换成数字,只有在参数是值为NaN
的数字时,才会返回true
,否则返回false
。2. CSS 实现文本溢出省略
3. 复制到粘贴板
使用:
4. 什么是
抽象渗漏
?抽象渗漏指的是在代码中暴露了底层的实现细节,这些底层实现细节应该被屏蔽掉。
举例:在数组内查找某个值是否存在的时候,我们通常会使用到
indexOf
方法,该方法成功时返回下标,失败时返回-1
,这里用-1
作为失败时的返回值,而这种细节应该被屏蔽掉。所以更加推荐使用
includes
这种不会暴露代码底层实现细节的方法:5. 高性能向下取整
核心是利用了位运算:
6. 高性能判断奇偶
跟上条一样,也是利用位运算:
7. SEO 优化
8. 冷知识:浏览器地址栏也能运行代码
做法是以
javascript:
开头,然后跟要执行的语句。比如:做法是以
data:text/html,
开头,然后跟要执行的语句。比如:9. 冷知识:你不知道的 setTimeout
大多数浏览器都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647,换算一下相当于 24.8 天。那么这就意味着 setTimeout 设置的延迟值大于做个数字就会溢出。
10. 冷知识:Math.min 和 Math.max
执行 Math.min 而不传参数的时候,得到的结果是 Infinity,执行 Math.max 而不传参数的时候,得到的结果是-Infinity:
11. 我们整天挂在嘴边的闭包到底是什么?
这里收集了不同文献中的原话,具体怎么理解看你自己:
《JavaScript 高级程序设计》
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
《Node 深入浅出》
在 JavaScript 中,实现外部作用域访问内部作用域中变量的方法叫做闭包(closure)。
《JavaScript 设计模式与开发实践》
局部变量所在的环境被外界访问,这个局部变量就有了不被销毁的理由。这时就产生了一个闭包结构,在闭包中,局部变量的生命被延续了。
《你不知道的 JavaScript(上卷)》
内部的函数持有对一个值的引用,引擎会调用这个函数,而词法作用域在这个过程中保持完整,这就是闭包。换句话说:当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域外执行,这时就产生了闭包。
12. 节流与防抖
11. 冷知识:pr(pull Request)和 mr(merge Request)有什么区别?
答:没有区别。
一般我们执行分支合并,需要执行下面两个命令:
Github 选择了第一个命令来命名,叫
Pull Request
。Gitlab 选择了最后一个命令来命名,叫
Merge Request
。反正都不咋地……这起的什么狗屁名字
正确的起名应该是:
12. 判断一个对象是普通对象还是通过类创建的
13. 判断是否在浏览器环境
14. 判断是否为移动端
15. 判断页面是否在 iframe 框架里
16. 实现一个 compose 函数
17. 处理数字精度问题
顺便说一下,关于处理精度问题的解决方案,目前市面上已经有了很多较为成熟的库,比如
bignumber.js
、decimal.js
、以及big.js
等,这些库不仅解决了浮点数的运算精度问题,还支持了大数运算,并且修复了原生 toFixed 结果不准确的问题。我们可以根据自己的需求来选择对应的工具。最后提醒一下:这玩意儿也就面试的时候写一下,强烈建议业务中还是用现成的库,出了问题我可不负责的嗷,唉,我好菜啊
18. 垂直居中 textarea
难点
根本就不能通过 css 来实现输入的垂直居中
网上的那些就会复制答案,什么 flex 都来了
只能用 js 来实现
思路
通过动态调整 paddingTop 来偏移文本内容。
需要注意的是,多行的时候,需要计算行数
可以通过 set Height 0,然后滚动高度就是输入文字的总高度,算完之后把高度复原
行数 = 文字总高度 / 行高
所以,设置行高很重要,默认是 normal,normal 是字符串,没办法计算的,所以自己手动设一个 lineheight 吧
19. interface 和 type 的区别
相同点:
不同点:
20. gulp 和 webpack 的区别
21. 手写 getQueryString
22. 手写 Array.flat(Infinity)
23. 算法 — 有效的括号
24. 图片加载失败处理方式
图片为空很容易判断:
图片加载失败,使用图片自带的 error 事件处理即可:
注意
有些
加载 404 的图片不会走error
事件,而是走了load
事件,那么我们可以通过直接添加一个占位底图来实现,这样如果能加载就会覆盖占位图,如果不能加载那就会显示底下的底图25. 判断对象中是否存在某个属性的三种方法
1. hasOwnProperty()
hasOwnProperty
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(不包含原型上的属性):2. in 操作符
in 操作符
会返回一个布尔值,指示对象自身属性中是否具有指定的属性(包含原型上的属性):3. Reflect.has()
Reflect.has
作用与in 操作符
相同:26. 实现深拷贝
1. 简易版
这个方法有些缺点,懂的都懂,不再废话了
2. 加强版
3. 非主流版
structuredClone
:原生 js 的深拷贝,因为是新出的,所以兼容差的要死,不建议使用目前只有浏览器可以用,node 环境还不支持,并且只有最新几个版本的浏览器才能用
对了,而且这个方法不能拷贝函数,遇到函数会直接报错,嘻嘻嘻
4. 终极版
27. 让指定方法最多只能被调用 1 次
28. 判断是否为原生函数
不知道 lodash 为啥实现的如此复杂,可能是因为 lodash 太老了吧,都多少年了……
29. 不创建新变量的前提下,交换两个变量
方法一:四则运算
注意:由于
IEEE 754
标准的存在,第一种方法并不是一定安全的,可能会出现精度问题。方法二:位运算
方法三:解构
30. 猜打印顺序
猜一猜下面代码的打印顺序:
先说答案:顺序是
1、2、a2、a1
解释:js 在对对象的 key 进行遍历的时候,会先判断 key 的类型,如果是 number 类型,则会放在前面,并且进行排序,如果是 string 类型,则放在后面,不进行排序(对 number 排序是为了方便内存寻址,string 不能进行四则运算,所以排序没有意义)。
31. 猜打印结果
结果:11
解释:普通的十进制数字,没啥好解释的
结果:0.11
解释:如果数值前面的整数部分为 0,那么 js 允许我们省略
结果:11
解释:如果小数点后面的小数部分为 0,那么 js 允许省略
结果:9
解释:如果数值前面以 0 开头,那么 js 会把它当成八进制,逢八进一
结果:80
解释:因为八进制的数值里面不可能出现数字 8,所以这种情况下是无效的八进制,js 会当成十进制进行处理
结果:9
解释:0o 开头的数值也会被当成八进制处理
结果:报错
解释:0o 开头的数值会被当成八进制处理,但是八进制的数值里面不可能出现数字 8,所以直接报错了
结果:3
解释:0b 开头的数值会被当成二进制处理
结果:17
解释:0x 开头的数值会被当成十六进制处理
结果:1100
解释:科学计数法,表示 11 * (10 ** 2)
结果:报错
解释:在数字转字符串的过程中,toString 方法被当成小数点后面的小数部分了,所以报错了,正确写法如下:
32. 隐藏元素之 display、visibility、opacity
相同点:都能控制元素在视图中的可见性
不同点:直接看图
33. TCP 与 UDP 的区别
相同点
TCP
与UDP
都是运行在运输层的协议TCP
与UDP
的通信都需要开放端口不同点
34. 关于代码质量引发的一些哲学问题
1、为了封装而封装,硬套设计模式,这就是代码越写越乱的典型(负优化)
2、大筐里有 4 种萝卜,作者觉得这样很乱,于是往大筐里又套 4 个小筐,把萝卜放到小筐里(犯了形而上学的错误,只是对代码量进行了转移,并没有减少,甚至为了转移后的联系,增加了很多额外代码)。
3、奥卡姆剃刀原理,
如无必要、勿增实体
,没有必要把一段简单的switch case
或者几行if else
判断,直接在拦截器里可以搞定的事情,拆成 n 个子模块,而且为了联系上下文还要写一堆无用代码来桥接。4、泰斯勒定律,复杂性守恒原理,
复杂度不会凭空增加或消除,只能对复杂性进行转移
,这里是转移了复杂性,但是因为上下文的联系,不得不增加额外代码,这就增加了复杂性,所以转移的目的没有任何意义。5、责任链设计模式,作者只掌握了形式,并没有掌握精髓。
6、其他评论说的对,这个场景的模式选择的不对,
策略模式
更加合适。35. 老掉牙的面试题:React diff 是什么?可以省略吗?
回答:可以省略,但是强烈不推荐(废话文学,面试的时候直接说不可以就好了)
下面看满分答案:
在 react 组件开发的过程中,
key
是一个常用的属性值,多用于列表开发. 这里从源码的角度,分析key
在react
内部是如何使用的,key
是否可以省略.ReactElement 对象
我们在编程时直接书写的
jsx
代码,实际上是会被编译成 ReactElement 对象,所以key
是ReactElement对象
的一个属性.构造函数
在把
jsx
转换成ReactElement对象
的语法时,有一个兼容问题. 会根据编译器的不同策略,编译成 2 种方案.最新的转译策略: 会将
jsx
语法的代码,转译成jsx()
函数包裹jsx
函数: 只保留与key
相关的代码(其余源码这里不讨论)传统的转译策略: 会将
jsx
语法的代码,转译成React.createElement()函数包裹React.createElement()函数
: 只保留与key
相关的代码(其余源码这里不讨论)可以看到无论采取哪种编译方式,核心逻辑都是一致的:
key
的默认值是null
key
,则将key
转换成字符串类型.ReactElement
这个构造函数,并且将key
传入.源码看到这里,虽然还只是个皮毛,但是起码知道了
key
的默认值是null
. 所以任何一个reactElement
对象,内部都是有key
值的,只是一般情况下(对于单节点)很少显式去传入一个 key.Fiber 对象
react
的核心运行逻辑,是一个从输入到输出的过程(回顾reconciler 运作流程
). 编程直接操作的jsx
是reactElement对象
,我们的数据模型是jsx
,而react内核
的数据模型是fiber树形结构
. 所以要深入认识key
还需要从fiber
的视角继续来看.fiber
对象是在fiber树构造循环
过程中构造的,其构造函数如下:可以看到,
key
也是fiber
对象的一个属性. 这里和reactElement
的情况有所不同:reactElement
中的key
是由jsx
编译而来,key
是由开发者直接控制的(即使是动态生成,那也是直接控制)fiber
对象是由react
内核在运行时创建的,所以fiber.key
也是react
内核进行设置的,程序员没有直接控制.注意:
fiber.key
是reactElement.key
的拷贝,他们是完全相等的(包括null
默认值)。接下来分析
fiber
创建,剖析key
在这个过程中的具体使用情况.fiber
对象的创建发生在fiber树构造循环
阶段中,具体来讲,是在reconcileChildren
调和函数中进行创建.reconcileChildren 调和函数
reconcileChildren
是react
中的一个明星
函数,最热点的问题就是diff算法原理
,事实上,key
的作用完全就是为了diff算法
服务的.调和函数源码(只摘取了部分代码):
单节点
这里先看单节点的情况
reconcileSingleElement
(只保留与key
有关的逻辑):可以看到,对于单节点来讲,有 2 个重点:
key
是单节点是否复用的第一判断条件(第二判断条件是type
是否改变,比如div
改变为span
).key
不同,其他条件是完全不看的key
随着element
对象被传入fiber
的构造函数.所以到这里才是
key
的最核心作用, 是调和函数中, 针对单节点是否可以复用的第一判断条件
.对于单节点来讲,
key
是可以省略的,react
内部会设置成默认值null
. 在进行diff
时, 由于null === null
为true
, 前后render
的key
是一致的, 可以进行复用比较.如果单节点显式设置了
key
,且两次render
时的key
如果不一致,则无法复用.多节点
继续查看多节点相关的逻辑:
在
reconcileChildrenArray
中, 有 3 处调用与fiber
有关(当然也和key
有关了), 它们分别是:updateSlot
createChild
updateFromMap
针对多节点的
diff算法
可以分为三个步骤(请回顾算法章节React 算法之调和算法
):第一次循环:比较公共序列
第二次循环:比较非公共序列
oldFiber
队列遍历完了, 证明newChildren
队列中剩余的对象全部都是新增.newChildren
队列即可, 没有额外的diff
比较.oldFiber
队列没有遍历完, 需要将oldFiber
队列中剩余的对象都添加到一个map
集合中, 以oldFiber.key
作为键.newChildren
队列时, 需要用newChild.key
到map
集合中进行查找, 如果匹配上了, 就将oldFiber
从map
中取出来, 同newChild
进行diff
比较.清理工作
map
集合中还有剩余的oldFiber
,则可以证明这些oldFiber
都是被删除的节点, 需要打上删除标记.通过回顾
diff算法
的原理, 可以得到key
在多节点情况下的特性:newChildren
中的每一个对象(即reactElement
对象)都需要同旧队列oldFiber
中有相同key
值的对象(即oldFiber
对象)进行是否可复用的比较.key
就是新旧对象能够对应起来的唯一标识.key
或者直接使用列表index
作为key
, 表现是一样的(key=null
时, 会采用index
代替key
进行比较). 在新旧对象比较时, 只能按照index
顺序进行比较, 复用的成功率大大降低, 大列表会出现性能问题.oldFiber
队列有 100 个,newChildren
队列有 100 个(但是打乱了顺序). 由于没有设置key
, 就会导致newChildren
中的第 n 个必然要和oldFiber
队列中的第 n 个进行比较, 这时它们的key
完全一致(都是null
), 由于顺序变了导致props
不同, 所以新的fiber
完全要走更新逻辑(理论上比新创建一个的性能还要耗).key
使用不当还会造成bug
item
内部又是一个组件, 且其中某一个item
使用了局部状态(比如class组件
里面的state
). 当第二次render
时,fiber
对象不会delete
只会update
导致新组件的state
还沿用了上一次相同位置的旧组件的state
,造成了状态混乱。总结
在
react
中key
是服务于diff算法
, 它的默认值是null
, 在diff算法
过程中, 新旧节点是否可以复用, 首先就会判定key
是否相同, 其后才会进行其他条件的判定. 在源码中, 针对多节点(即列表组件)如果直接将key
设置成index
和不设置任何值的处理方案是一样的, 如果使用不当, 轻则造成性能损耗, 重则引起状态混乱造成 bug.36. 扁平数组转 tree 结构
要求:输入 list,输出对应的 result
实现:
测试结果:
The text was updated successfully, but these errors were encountered: