creeperyang / blog

前端博客,关注基础知识和性能优化。
MIT License
2.64k stars 211 forks source link

JavaScript问题集锦(二) #40

Open creeperyang opened 6 years ago

creeperyang commented 6 years ago

不知不觉,从事前端 4 年多了,距离本篇的前作 JavaScript问题集锦 也有 2 年多了 —— 青葱岁月啊。

又忽然想起“我变秃了,也变强了!”的梗,正好程序员工作久了,容易变秃... 当然我没有秃,所以说 😂

收起半夜突然来的感概,正式解释下文章题目和目的: 本文还是会以 JS 的一个个知识点为粒度来讲,这算是对之前那篇的继承。这个秋天,静极思动,面了BA以及其它一些公司,和面试官的尬聊中,有些觉得很懂的东西并没有解释的很好,这里也提醒下自己和大家:学无止境

1. 从 IIFE 说一说 Expression 和 Statement

IIFE (Immediately Invoked Function Expression) 即立即调用/执行函数表达式。我们常看到(包括某些库中):

(function() {})()

上面的即 IIFE 的一种写法,匿名函数会立即执行。下面是一些等价写法:

(function() {}())
!function() {}()
~function() {}()

是不是觉得很熟悉,然后觉得没什么要注意的?那下面问个问题:

function(){}()

它是 IIFE 吗?为什么?Console 中输入会发生什么?单独拎出来这样问是不是有些发懵?

上面的代码运行的话会报错,并且更进一步,单独执行 function(){} 也会报错:

2017-11-10 11 39 00

下面首先简要解释下原因:

JS 应用是由(无语法错误的) statements 组成的。

当我们单独输入:function (){}时,解释器其实期待的是合法的 statement,即一个函数声明。但很抱歉,函数声明必须有 name,所以这里报错了。

同理,function (){}() 是一样的错误原因,因为当解释器首先看到关键字 function 时,它就认为要接收一个函数声明了,但我们并没有满足这个规则。

下面的图可以帮助理解:

2017-11-11 12 49 17

接下来我们更深入一点,来全面了解下 JS 中的 Statements 和 Expression。

An expression is any valid unit of code that resolves to a value.

对 expression ,一句话:任意合法的产生值的代码单元都是表达式。所以:

等等都是表达式。更详细的可参考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators

对于 statement,我们可以直接看规范 http://es5.github.io/#x12,可以看到 statement 有

等等。其中,我们重点要讲一讲 ExpressionStatement (表达式语句) ,这是我们这个问题的由来。

ExpressionStatement :
[lookahead ∉ {{, function}] Expression ;

表达式语句的定义如上,用中文解释下就是 合法的 expression 加上 ;(即 expression;)就是表达式语句了。但是,

所以你看,一切写在规范里了。

结合规范,我们就知道开头如果是 function,那么一律按函数定义来解析,不合法就报错;而我们可以通过 () 括号/group操作符来避免。

同样{a:1}.a 报错就是因为开头是{被当作 BlockStatement 解释了,想当作对象那加括号吧:({a:1}).a

cssmagic commented 6 years ago

讲得很清楚,手动点赞! 👍

creeperyang commented 6 years ago

2. 关于 String.fromCharCode 的一点讨论

规范 ,我们可以知道:

String.fromCharCode ( [ char0 [ , char1 [ , … ] ] ] )

可以接收多个参数,并返回同样多个字符。

比如:

String.fromCharCode(50, 51, 52) // 234

但是,我们可以看一个反例(浏览器未严格遵循规范的):

String.fromCharCode(55297, 56375) // '𐐷'

很有意思对不对?两个参数却只返回一个字符。

当然这是我查阅怎么从 utf16 编码读取字符串时发现的,具体原因也和编码有关,这里先记一下,之后给出详细解答。

https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae 讲得很透彻 ,关心这个知识点的可以自行阅读。下面是一张可能会吸引你去读这篇文章的截图:

2017-11-18 10 59 40

详细解读见 #4

creeperyang commented 5 years ago

3. <script> 标签与asyncdefer 属性

浏览器在解析 HTML 时碰到普通的 <script> 标签,会暂停解析,下载并执行完脚本后,再重新开始解析。我想这个大多数人应该都了解,但asyncdefer 属性有什么区别,大家可能会有疑惑。

image

如上图所示:

1、碰到async(<script async src="app.js"></script>)脚本,浏览器下载脚本的同时继续解析HTML,下载完成后,浏览器暂停解析HTML并执行脚本;

2、碰到defer(<script defer src="app.js"></script>)脚本,浏览器下载脚本的同时继续解析HTML,在HTML解析完成后按序执行脚本;更明确一点,脚本在domInteractive后,在DOMContentLoaded前执行。

3、async不保证各脚本的执行顺序而 defer 保证按序执行。

参考: https://flaviocopes.com/javascript-async-defer/

creeperyang commented 4 years ago

4. <input> 和清除按钮 X 的显示问题

假设这样一个场景,有一个输入框 <input> 与一个清除按钮 X

看起来实现没什么难的,我也不是要问清除按钮怎么用纯CSS来写,问题是,按钮的 click 和输入框的 blur 事件的发生顺序?

blur 先于 click 发生,那么问题来了,blur时清除按钮就被移除,导致 click 事件没触发,也就导致文字不会被清除。

临时方案1 :blur 的回调塞到 setTimeout(fn, 0) 里,让清除按钮的操作延后,是不是就可以让 click 触发?

不是个可靠方案,放弃。

临时方案2 :不移除按钮DOM,改为设置透明度 0。这样 click 百分百可以触发,但需要考虑透明度 0 点击的时候当作无效点击。

不是完美方案。

方案3 :利用 onmousedownevent.preventDefault() 来让 blurclick 之后发生。

按钮的 onmousedown 优先于输入框的 blur 发生,在 onmousedownpreventDefault 即可避免 blur 发生在 click 之前。

详情见 https://stackoverflow.com/questions/17769005/onclick-and-onblur-ordering-issue

完美。

creeperyang commented 4 years ago

5. 当出现垂直滚动条之后,为什么出现了水平滚动条?【css、layout、vw】

在测试UI的时候,发现个很有意思的问题:UI一直正常,直到浏览器宽度增加到某个值,出现了水平滚动条。

怀疑是某个元素布局问题,查找,并没找出原因。

用排除法,逐一删除相比线上不同的元素,删除第一个时,UI已经恢复正常,但是反复检查这个元素,并没有什么不对的地方;换第二个删除,UI也恢复正常....

最终找到原因,删除一个元素时,垂直滚动条消失!滚动条的原因!

继续查找原因才知道:宽度单位 vw 是包括滚动条宽度的,即100vw 的宽度是 document.documentElement.offsetWidth,使用 rem 布局时需要额外注意。

html {
    font-size: calc(100vw / 3.75);
}

解决办法:

::-webkit-scrollbar {
    width: 0;
    height: 0;
    display: none;
}

隐藏滚动条即可。

参考:Vertical Scrollbar leads to horizontal scrollbar