如果只是知道知识的表面,那只是了解。换个角度,加强深度,也能得心应手才是掌握
最近看的东西杂七杂八的。也做了一些笔记,发现有些知识点,面试或者项目开发上可能会遇到的比较多,或者比较重要,就整理出来发篇文章。每个知识点不会很全面,知识针对某一个方面进行稍微深入的记录,或者换一个角度记录。如果有错或者其他建议,欢迎评论区留言
可能很多人都会被问到过,浏览器解析 css 的时候,为什么是从右往左解析,而不是从左往右解析?关于这个问题,通俗一点的来说就是:如果 css 从左往右解析,浏览器会更累。
为什么这么说呢?首先给出下面的代码
<style>
.demo p span{
color:#09f;
}
</style>
<div class="demo">
<ul>
<li>
<a href="javascript:;">这是a</a>
</li>
</ul>
<p>
<span>这个才是span</span>
</p>
</div>
根据 css 代码
假设解析 css 是从左向右的匹配,过程是:从 .demo 开始,遍历子节点 ul 和子节点 p,然后各自向子节点遍历。首先遍历到 ul 的分支,然后遍历到 li ,最后遍历到叶子节点a,发现不符合规则,需要回溯到ul节点,再遍历下一个 li ,下一个 a ,假如有 1000 个li ,1000个 a ,就有 1000 次的遍历与回溯,而这一些遍历与回溯都是无用功,还可能会造成性能问题。直到 .demo 向下找到了节点 p ,然后再向下遍历到了 p 下面的节点,找到 span ,这样给 span 加上渲染的样式,然后结束这个分支的遍历。这下才是有用功。
然后看看从右至左的匹配,过程是:先找到所有的最右节点 span ,对于每一个 span ,向上寻找节点 p,由 p 再向上寻找 .demo 的节点,最后找到根元素 html 则结束这个分支的遍历。这样的遍历就不会出现从左向右的匹配时,要遍历 .demo 下面的 ul,遍历 ul 下面的 li ,li 下面的 a 这些无用功的遍历。
显然,两种匹配规则的性能差别非常大。之所以会出现这样的情况,就是因为从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点);而从左向右的匹配规则的性能都浪费在了失败的查找上面,多了很多无用功(比如例子中,遍历 .demo 下面的 ul,遍历 ul 下面的 li ,li 下面的 a )。
知道了 CSS 是这样渲染的,有什么优化的建议吗?
1.CSS 能少写尽量少写,比如利用 CSS 某些属性继承的特性,或者抽取公用样式。减少CSS代码,使遍历查找的分支尽量少
2.CSS 路径尽量短写,尽量不要超过 4 层,比如上面例子 .demo p span 可以写成 .demo span 。这样找到 span 之后可以直接找 .demo 不需要经过 p。
首先 flex 属性用于设置或检索弹性盒模型对象的子元素如何分配空间,是 flex-grow、flex-shrink 和 flex-basis 属性的简写属性。默认值为0 1 auto。后两个属性可选。
如果元素不是弹性盒模型对象的子元素,则 flex 属性不起作用
至于每一个属性代表什么意思,下面列举具体例子。为了方便理解,先从 flex-basis 说起
flex-basis 属性用于设置弹性盒伸缩基准值,可以设置具体宽度或者百分比,默认值是 auto。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<style>
.main
{
width:240px;
height:300px;
border:1px solid black;
display:flex;
}
.item
{
flex:1 1 auto;
}
</style>
</head>
<body>
<div class="main">
<div style="background-color:coral;" class="item red">红</div>
<div style="background-color:lightblue;" class="item blue">蓝</div>
<div style="background-color:lightgreen;" class="item green">绿</div>
</div>
</body>
</html>
比如上面,由于只设置了 flex:1 1 auto; 那么 flex-basis 实际上就是 auto ,那么每个子元素的宽度实际上就父元素的宽度/子元素的数量。就是 240/3 。每一个子元素的宽度就是 80px。
flex-basis 可以设置一个具体的宽度,也可以是一个百分比。比如把红色的 div,设置成 flex-basis:40px;
效果如图,至于为什么红色是 96px,下面解释。
用来“瓜分”父元素的“剩余空间”
如果子元素总的宽度小于父元素,就会瓜分
上面的结果,为什么红色是 96px 呢?首先把 flex-grow 设置 0,看下每一个元素的初始大小是多少。
可以看到,红色的由于设置了 flex-basis:40px; 所以就是 40px。蓝色和绿色只有一个字体的宽度,就是 16px;
由于子元素的宽度就是 40+16+16=72 ,小于父元素的 240。如果把 flex-grow 设置 1,那么父元素的剩余宽度 (240-72=168)就会平均分配给子元素 (168/3=56)。所以最后子元素的宽度是:红色=40+56=96px 蓝色=16+56=72px 绿色=16+56=72px。这就解释了,为什么 红色设置了 flex-basis:40px; 最后得出宽度是 96px;
如果给其中一个元素,比如蓝色的 flex-grow 设置 2 ,那么蓝色的子元素,瓜分剩余父元素的宽度就是红色或者绿色的两倍。(也可以理解为,把父元素的剩余宽度分成四份 168/4=42 ,蓝色子元素占其中两份,红色和绿色各占一份。因为红蓝绿三个子元素的 flex-grow 分别是 1:2:1。加起来就是 4 )。最后算出来的宽度就是:蓝色=16+42*2=100,红色=40+42=82,绿色 16+42=58
用来“消化”超出的空间
既然知道了 flex-grow 是用来瓜分剩余的父元素的。想必大家都想到了,如果子元素的总宽度大于父元素呢,这时候就需要 flex-shrink 来对子元素进行相应的缩小了
比如把 子元素的 flex-basis 都改成 100px;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<style>
.main
{
width:240px;
height:300px;
border:1px solid black;
display:flex;
}
.item
{
flex:1 1 100px;
}
</style>
</head>
<body>
<div class="main">
<div style="background-color:coral;" class="item red">红</div>
<div style="background-color:lightblue;" class="item blue">蓝</div>
<div style="background-color:lightgreen;" class="item green">绿</div>
</div>
</body>
</html>
但是最终每个子元素最后的宽度都是 80px ,这个结果的计算方式也非常简单。
首先三个子元素的 flex-basis 都是 100 ,那么子元素的总宽度就是 300,比父元素的宽度(240)多了 60。那么这 60 就需要子元素“消化掉”,由于设置了 flex:1 1 100px; 子元素的 flex-shrink 实际上都是 1 。那么就是把 60 分成 3份,每一份 20,那么子元素的实际宽度就是:预设宽度(100) - “消化掉的宽度” (20)= 80,所以每一个子元素都是 80 。
如果把其中一个子元素,比如绿色的子元素 flex-shrink 设置为 2 。其实这个和 flex-grow 类似,把多出来的 60 分成四份给子元素“消化掉”,每一份就是 15,然后 绿色的占两份,红色和蓝色各占一份(因为红蓝绿三个子元素的 flex-shrink 分别是 1:1:2。加起来就是 4)。那么最后计算出来的宽度就是:红色=100-15=85,蓝色=100-15=85,绿色:100-15*2=70
1.关于宽度计算,是根据 flex-grow 还是 flex-shrink。只要关注子元素的总宽度和父元素宽度对比就行了。如果父元素宽度足够:flex-grow有效,flex-shrink无效。如果父元素宽度不够:flex-shrink 有效,flex-grow无效。 2.注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。
如果要细分数据类型,目前应该是定义了 8 种数据类型。其中基本数据类型有 7 种 Number、String、Boolean、Null、undefined、Symbol、bigInt。引用数据类型有 Object。Object 常用包含 Array,Function,Date,RegExp,Map,Set,WeakMaps,WeakSets等。
简单的概括了有什么数据类型之后,下面看下目前有什么办法可以检测数据类型
确切地说 typeof 操作符适合用来判断一个变量是否为 string、number、function、boolean、symbol、 bigint或 undefined 的比较适合
但是如果用 typeof 检测 null,以及除了 function 外的引用数据类型,均返回 object
由此得知,用 typeof 检测一个变量是否为 string、number、function、boolean、symbol、 bigint或 undefined。typeof 比较适合。如果是其他数据,typeof 作用不大。
由于 typeof 检测基本数据类型比较有用,除了 null 。检测引用数据类型,只能检测 function。其他情况作用不大。
instanceof 的原理是检测对象的 prototype 是否在另一个要检测对象的原型链上。换句话说就是检测一个对象是否是另一个对象的实例
比如 Sou instanceof Parent
。比如判断 Son 是否为 Parent 的实例。
let Parent =function(){}
let Sou=new Parent()
Sou instanceof Parent //true 因为Son 就是 Parent
写到这里,可能大家就会知道了,instanceof 是用来判断两个对象是否存在实例关系的。至于引用类型的检测,就是利用这个原理。
接着上面代码,用 instanceof 检测 Sou 是否是 Object
Sou instanceof Object //true
也可以检测其他的引用类型
[] instanceof Array //true
function fn(){}
fn instanceof Function //true
如果是上面的简单用法,貌似没什么问题。但是 Array 和 Function 是存在于 Object 原型链上的,所以用 instanceof 检测 Object,可能就会有问题了
[] instanceof Object //true
function fn(){}
fn instanceof Object //true
因为很多时候,我们要知道的不仅仅是一个变量是否是引用类型,还要知道具体是哪一种引用类型。
constructor 属性会返回对象的构造函数,返回值就是函数的引用
"守候".constructor // 返回函数 String
(3).constructor // 返回函数 Number
false.constructor // 返回函数 Boolean
Symbol(3).constructor // 返回函数 Symbol
42n.constructor // 返回函数 BigInt
function fn(){}
fn.constructor // 返回函数 Function
[1,2,3,4].constructor // 返回函数 Array
{name:'守候'}.constructor // 返回函数 Object
new Date().constructor // 返回函数 Date
看到 Number 那里,是有一个括号把 3 包起来的。这里为了让浏览器不会混淆 . 的作用。比如
3.constructor; 3.14.constructor
这样难以区分 . 到底是小数点还是点运算符
constructor 判断数据类型,基本可以满足需求。但无法判断 null 和 undefined 两种类型,因为这两者没有 constructor。使用 constructor 判断 null 和 undefined 就会报错。
还有一个问题就是如果构造函数的实例,constructor 会丢失,所以一般也不会用 constructor 检测构造函数的类型
let Parent =function(){}
let Sou=new Parent()
Sou.constructor===Object //false
Sou.constructor===Parent //true
想要避免这个问题也不难,就是重写 constructor。只是一般不会这样做
Sou.constructor=Object
Sou.constructor===Object //true
上面的几种方法,貌似多多少少都有一点缺陷,那么接下来使用 Object.prototype.toString.call() 的方式可以说是目前最好的一个方案了,都能返回正确的数据类型
Object.prototype.toString.call 目前发现的一个问题就是无法检测一个数字是否是 NaN,如果是 NaN,就会返回 [object Number]。要检测 NaN,可以使用 Number.isNaN 方法进行检测
ES6+ 引入的 async 函数,使得异步操作变得更加方便。但是如果滥用确实会造成问题,比如 async/await 地狱。至于什么是 async/await 地狱。先看下面代码
(async () => {
// 根据用户 ID 获取用户信息和好友列表
let userID=123
let userInfo = await getUserInfo(userID) // 获取用户信息
let friendList = await getFriendList(userID) // 获取好友列表
// 开始渲染
})()
上面的代码很简单,就是根据用户的 ID 获取用户信息和好友列表。然后页面开始渲染的等待时间,就是 getUserInfo 的请求时间 加上 getFriendList 的请求时间。问题就出来了,getFriendList 和 getUserInfo 之前并没有依赖关系,getFriendList 并不需要等 getUserInfo 请求回来之后,才能执行请求。
既然 getUserInfo 和 getFriendList 没有依赖关系。优化的方式就简单了,就是把两个顺序的请求变成并发请求。这样开始渲染的等待时间就会变短。
(async () => {
let userID=123
// 先同时发起请求
let userInfo = getUserInfo(userID)
let friendList = getFriendList(userID)
// 再等请求回来
await userInfo
await friendList
// 开始渲染
})()
这种场景,更建议用 Promise.all
(async () => {
let userID=123
Promise.all([getUserInfo(userID),getFriendList(userID)]).then(res=>{
// 开始渲染
})
})()
先看代码
//es5 写法
function fn1(userName) {
let _userName=userName||'守候'
console.log(_userName)
}
//es6 写法
function fn2(userName='守候') {
let _userName=userName
console.log(_userName)
}
es6支持之前,一般是 fn1 的写法。支持之后,一般转变成 fn2 的写法。但是两种写法不一致。
1.fn1 只要 userName 的值为 “假” 的情况(false,null,undefined,'' 空字符串, 0,NaN),_userName 都会赋值为 “守候”
2.fn2 只有 userName 的值为 undefined 的情况,_userName 才会赋值为 “守候”
上面代码还只是很简单的说明的例子。实际开发中代码可能会比较复杂,比如需要对 userName 使用 replace 方法,如果往 fn2 传入 null 就报错了
这次分享这 5 个点,暂时到这里了。如果之后还发现有另外的收获会第一时间再写文章。如果大家都该文章有什么建议,看法,或者文章有什么错误,改进的地方欢迎大家评论区留言。