手记
下载APP

[笔记整理]几个有点重要的知识点

前言

最近看的东西杂七杂八的。也做了一些笔记,发现有些知识点,面试或者项目开发上可能会遇到的比较多,或者比较重要,就整理出来发篇文章。每个知识点不会很全面,知识针对某一个方面进行稍微深入的记录,或者换一个角度记录。如果有错或者其他建议,欢迎评论区留言

1.css 为什么要从右往左解析?应该怎么优化?

可能很多人都会被问到过,浏览器解析 css 的时候,为什么是从右往左解析,而不是从左往右解析?关于这个问题,通俗一点的来说就是:如果 css 从左往右解析,浏览器会更累。

为什么这么说呢?首先给出下面的代码

<div class="demo">
	<ul><li>
			<a>这是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。

2.Javascript 检测数据类型有哪些方法?

如果要细分数据类型,目前应该是定义了 8 种数据类型。其中基本数据类型有 7 种 Number、String、Boolean、Null、undefined、Symbol、bigInt。引用数据类型有 Object。Object 常用包含 Array,Function,Date,RegExp,Map,Set,WeakMaps,WeakSets等。

简单的概括了有什么数据类型之后,下面看下目前有什么办法可以检测数据类型

2-1.typeof

确切地说 typeof 操作符适合用来判断一个变量是否为 string、number、function、boolean、symbol、 bigint或 undefined 的比较适合

图片描述

但是如果用 typeof 检测 null,以及除了 function 外的引用数据类型,均返回 object

图片描述

由此得知,用 typeof 检测一个变量是否为 string、number、function、boolean、symbol、 bigint或 undefined。typeof 比较适合。如果是其他数据,typeof 作用不大。

2-2.instanceof

由于 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

因为很多时候,我们要知道的不仅仅是一个变量是否是引用类型,还要知道具体是哪一种引用类型。

2-3.constructor

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

2-4.Object.prototype.toString.call()

上面的几种方法,貌似多多少少都有一点缺陷,那么接下来使用 Object.prototype.toString.call() 的方式可以说是目前最好的一个方案了,都能返回正确的数据类型

Object.prototype.toString.call 目前发现的一个问题就是无法检测一个数字是否是 NaN,如果是 NaN,就会返回 [object Number]。要检测 NaN,可以使用 Number.isNaN 方法进行检测

3.async/await 可能会造成什么问题?

ES6+ 引入的 async 函数,使得异步操作变得更加方便。但是如果滥用确实会造成问题,比如 async/await 地狱。至于什么是 async/await 地狱。先看下面代码

(async () =&gt; {
  // 根据用户 ID 获取用户信息和好友列表 
  let userID=123
  let userInfo = await getUserInfo(userID)    // 获取用户信息
  let friendList = await getFriendList(userID)    // 获取好友列表
  
  // 开始渲染
})()

上面的代码很简单,就是根据用户的 ID 获取用户信息和好友列表。然后页面开始渲染的等待时间,就是 getUserInfo 的请求时间 加上 getFriendList 的请求时间。问题就出来了,getFriendList 和 getUserInfo 之前并没有依赖关系,getFriendList 并不需要等 getUserInfo 请求回来之后,才能执行请求。

既然 getUserInfo 和 getFriendList 没有依赖关系。 优化的方式就简单了,就是把两个顺序的请求变成并发请求。这样开始渲染的等待时间就会变短。

(async () =&gt; {
  let userID=123
  // 先同时发起请求
  let userInfo = getUserInfo(userID)
  let friendList = getFriendList(userID)
  
  // 再等请求回来
  await userInfo
  await friendList
  // 开始渲染
})()

这种场景,更建议用 Promise.all

(async () =&gt; {
  let userID=123
  
  Promise.all([getUserInfo(userID),getFriendList(userID)]).then(res=&gt;{
      // 开始渲染
  })
})()

4.函数参数默认值,两种方式是否完全一致?

先看代码

//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 个点,暂时到这里了。如果之后还发现有另外的收获会第一时间再写文章。如果大家都该文章有什么建议,看法,或者文章有什么错误,改进的地方欢迎大家评论区留。

打开App,阅读手记
1人推荐
随时随地看视频慕课网APP