-
Notifications
You must be signed in to change notification settings - Fork 840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JavaScript 优雅的实现方式包含你可能不知道的知识点 #30
Comments
圣诞快乐,1024邀请码,有缘人得! c7a5a23f4f0e5cc7 | 2017-12-23 17:50:04 | | | 未使用 | 邀请 |
这是啥 |
完全是 js 的糟粕吧。。。。各种隐式的代码背后的逻辑说不定那个版本就改了。。老老实实用官方 api 不好吗? |
不可能改的,JavaScript 设计者都承认 null 是个对象都是一种错误的决定,也没看改过来,原因你懂得。 还有就是糟粕问题,完全不敢苟同,我觉得是玄学。@huyansheng3 |
数组去重那部分小标题权重错了。。 |
感谢指正,哈哈,这些细节都被看出来了 @axuebin |
大佬真的很会玩。。。最容易懂的才是好方法呀 |
1.2 Promise 版本运行报错是为何 const t1 = +new Date(); |
有报错截图吗?请在 Chrome 浏览器运行或者在 node 高版本环境运行@LingZhenhua |
获取时间戳还可以 Date.now() |
哈哈,把这个竟然忘了,不错,谢谢补充~ @xanke |
貌似 +new Date 也一样可以获取的 |
不带参数就是当前时间的时间戳,确实可以 @limoning |
这些玄学,写在代码里,懂的人,会心一笑,不懂得人,能懵一脸血! |
|
@jawil 还得感谢GayHub啊... 一眼就看出来了... |
数字格式化那里可以用reduceRight,就不用reverse了。 |
return obj.hasOwnProperty(typeof item + item) ? |
@IMLaoJI |
4.4 API版的例子:
|
3.1 第二个循环的长度变量手误了,23333 |
@WittBulter 好棒 谢谢你! |
想请教下,在讲述Array.slice那节里面,“因为 ie 下的 dom 对象是以 com 对象的形式实现的,js 对象与com对象不能进行转换 ”,com对象是指什么? |
两种不同的标准,早期浏览器战争时代,各自都执行各自的标准,那时候标准都不统一。 |
�toLocalString 居然不能转成大写的中文,一点都不好玩 |
@jawil 感谢回复,谢谢! |
后端的人也很少写位操作运算吧,好像只有搞过算法竞赛的人才喜欢写 |
无聊翻着看,看到说的 xx 邀请码,直接公司大屏幕搜了下....之前真不知道1024是个啥 |
谢谢分享 |
对于数字字符串转千分位的分析有些异议
|
很多都很有用的,不过对于+new Date()这块一时还不是很明白。 |
github还能这样用...... |
交换顺序那个,我还看到过 |
@hua1995116 前几天好像还看到小胡子哥在知乎上写过这个? |
真的是大神 |
@xiyuyizhi 其实可以再深入一些,可以考虑带小数的情况
|
看到没人提,就提一下 |
reduce 有风险, 如果数组是空数组 [], 会爆错老哥 |
总结挺到位。 |
@yanche 那是因为扩展运算符会默认调用 Iterator 接口,而 arguments 原生具备 Iterator 接口 |
空数组导致reduce报错那个,看文档是因为
所以initialValue不要空着,加上0就行了 let arr = []
function sum(arr) {
return arr.reduce((a, b) => a + b, 0)
}
sum(arr) //0 这样就不会报错了 |
亲嘴的那个动图咋弄出来的撒。给个调用方法。 |
有些东西很好用,但是你未必知道;有些东西你可能用过,但是你未必知道原理。
实现一个目的有多种途径,俗话说,条条大路通罗马。很多内容来自平时的一些收集以及过往博客文章底下的精彩评论,收集整理拓展一波,发散一下大家的思维以及拓展一下知识面。
茴字有四种写法,233333..., 文末有彩蛋有惊喜。
1、简短优雅地实现 sleep 函数
很多语言都有
sleep
函数,显然js
没有,那么如何能简短优雅地实现这个方法?1.1 普通版
优点:简单粗暴,通俗易懂。
缺点:这是最简单粗暴的实现,确实 sleep 了,也确实卡死了,CPU 会飙升,无论你的服务器 CPU 有多么 Niubility。
1.2 Promise 版本
优点:这种方式实际上是用了 setTimeout,没有形成进程阻塞,不会造成性能和负载问题。
缺点:虽然不像 callback 套那么多层,但仍不怎么美观,而且当我们需要在某过程中需要停止执行(或者在中途返回了错误的值),还必须得层层判断后跳出,非常麻烦,而且这种异步并不是那么彻底,还是看起来别扭。
1.3 Generator 版本
优点:同 Promise 优点,另外代码就变得非常简单干净,没有 then 那么生硬和恶心。
缺点:但不足也很明显,就是每次都要执行 next() 显得很麻烦,虽然有 co(第三方包)可以解决,但就多包了一层,不好看,错误也必须按 co 的逻辑来处理,不爽。
co 之所以这么火并不是没有原因的,当然不是仅仅实现
sleep
这么无聊的事情,而是它活生生的借着generator/yield
实现了很类似async/await
的效果!这一点真是让我三观尽毁刮目相看。1.4 Async/Await 版本
优点:同 Promise 和 Generator 优点。 Async/Await 可以看做是 Generator 的语法糖,Async 和 Await 相较于 * 和 yield 更加语义,另外各个函数都是扁平的,不会产生多余的嵌套,代码更加清爽易读。
缺点: ES7 语法存在兼容性问题,有 babel 一切兼容性都不是问题
至于
Async/Await
比Promise
和Generator
的好处可以参考这两篇文章:Async/Await 比 Generator 的四个改进点
关于Async/Await替代Promise的6个理由
1.5 不要忘了开源的力量
在 javascript 优雅的写 sleep 等于如何优雅的不优雅,2333
优点:能够实现更加精细的时间精确度,而且看起来就是真的 sleep 函数,清晰直白。
缺点:缺点需要安装这个模块,
^_^
,这也许算不上什么缺点。从一个间简简单单的 sleep 函数我们就就可以管中窥豹,看见 JavaScript 近几年来不断快速的发展,不单单是异步编程这一块,还有各种各样的新技术和新框架,见证了 JavaScript 的繁荣。
你可能不知道的前端知识点:Async/Await是目前前端异步书写最优雅的一种方式
2、获取时间戳
上面第一个用多种方式实现
sleep
函数,我们可以发现代码有+new Date()
获取时间戳的用法,这只是其中的一种,下面就说一下其他两种以及+new Date()
的原理。2.1 普通版
优点:具有普遍性,大家都用这个
缺点:目前没有发现
2.2 进阶版
优点:说明开发者原始值有一个具体的认知,让人眼前一亮。
缺点: 目前没有发现
2.3 终极版
优点:对 JavaScript 隐式转换掌握的比较牢固的一个表现
缺点:目前没有发现
现在就简单分析一下为什么
+new Date()
拿到的是时间戳。一言以蔽之,这是隐式转换的玄学,实质还是调用了 valueOf() 的方法。
我们先看看
ECMAScript
规范对一元运算符的规范:一元+ 运算符
一元
+
运算符将其操作数转换为Number
类型并反转其正负。注意负的+0
产生-0
,负的-0
产生+0
。产生式UnaryExpression : - UnaryExpression
按照下面的过程执行。+new Date() 相当于 ToNumber(new Date())
我们再来看看
ECMAScript
规范对ToNumber
的定义:我们知道
new Date()
是个对象,满足上面的ToPrimitive()
,所以进而成了ToPrimitive(new Date())
。接着我们再来看看
ECMAScript
规范对ToPrimitive
的定义,一层一层来,抽丝剥茧。这个
ToPrimitive
刚开始可能不太好懂,我来大致解释一下吧:ToPrimitive(obj,preferredType)
JavaScript
引擎内部转换为原始值ToPrimitive(obj,preferredType)
函数接受两个参数,第一个obj
为被转换的对象,第二个preferredType
为希望转换成的类型(默认为空,接受的值为Number
或String
)在执行
ToPrimitive(obj,preferredType)
时如果第二个参数为空并且obj
为Date
的实例时,此时preferredType
会被设置为String
,其他情况下preferredType
都会被设置为Number
如果preferredType
为Number
,ToPrimitive
执行过程如下:如果
preferredType
为String
,将上面的第2步和第3步调换,即:首先我们要明白
obj.valueOf()
和obj.toString()
还有原始值分别是什么意思,这是弄懂上面描述的前提之一:toString
用来返回对象的字符串表示。valueOf
方法返回对象的原始值,可能是字符串、数值或bool
值等,看具体的对象。原始值指的是
'Null','Undefined','String','Boolean','Number','Symbol'
6种基本数据类型之一,上面已经提到过这个概念,这里再次申明一下。最后分解一下其中的过程:
+new Date():
你可能不知道的前端知识点:隐式转换的妙用
3、数组去重
注:暂不考虑
对象字面
量,函数
等引用类型的去重,也不考虑NaN
,undefined
,null
等特殊类型情况。3.1 普通版
无需思考,我们可以得到 O(n^2) 复杂度的解法。定义一个变量数组 res 保存结果,遍历需要去重的数组,如果该元素已经存在在 res 中了,则说明是重复的元素,如果没有,则放入 res 中。
优点: 没有任何兼容性问题,通俗易懂,没有任何理解成本
缺点: 看起来比较臃肿比较繁琐,时间复杂度比较高
O(n^2)
3.2 进阶版
优点:很简洁,思维也比较巧妙,直观易懂。
缺点:不支持
IE9
以下的浏览器,时间复杂度还是O(n^2)
3.3 时间复杂度为O(n)
优点:
hasOwnProperty
是对象的属性(名称)存在性检查方法。对象的属性可以基于Hash
表实现,因此对属性进行访问的时间复杂度可以达到O(1)
;filter
是数组迭代的方法,内部还是一个for
循环,所以时间复杂度是O(n)
。缺点:不兼容
IE9
以下浏览器,其实也好解决,把filter
方法用for
循环代替或者自己模拟一个 filter 方法。3.4 终极版
优点:
ES6
语法,简洁高效,我们可以看到,去重方法从原始的14
行代码到ES6
的1
行代码,其实也说明了JavaScript
这门语言在不停的进步,相信以后的开发也会越来越高效。缺点:兼容性问题,现代浏览器才支持,有
babel
这些都不是问题。你可能不知道的前端知识点:ES6 新的数据结构 Set 去重
4、数字格式化 1234567890 --> 1,234,567,890
4.1 普通版
优点:自我感觉比网上写的一堆 for循环 还有 if-else 判断的逻辑更加清晰直白。
缺点:太普通
4.2 进阶版
优点:把 JS 的 API 玩的了如指掌
缺点:可能没那么好懂,不过读懂之后就会发出我怎么没想到的感觉
4.3 正则版
下面简单分析下正则
/\B(?=(\d{3})+(?!\d))/g
:最终把匹配到的所有边界换成
,
即可达成目标。优点:代码少,浓缩的就是精华
缺点:需要对正则表达式的位置匹配有一个较深的认识,门槛大一点
4.4 API版
如图,你可能还不知道
JavaScript
的toLocaleString
还可以这么玩。还可以使用 Intl对象 - MDN
优点:简单粗暴,直接调用 API
缺点:Intl兼容性不太好,不过 toLocaleString的话 IE6 都支持
你可能不知道的前端知识点:Intl对象 和 toLocaleString的方法。
5、交换两个整数
let a = 3,b = 4 变成 a = 4, b = 3
5.1 普通版
首先最常规的办法,引入一个 temp 中间变量
优点:一眼能看懂就是最好的优点
缺点:硬说缺点就是引入了一个多余的变量
5.2 进阶版
在不引入中间变量的情况下也能交互两个变量
优点:比起楼上那种没有引入多余的变量,比上面那一种稍微巧妙一点
缺点:当然缺点也很明显,整型数据溢出,比如说对于32位字符最大表示有符号数字是2147483647,也就是Math.pow(2,31)-1,如果是2147483645和2147483646交换就失败了。
5.3 终极版
利用一个数异或本身等于0和异或运算符合交换率。
下面用竖式进行简单说明:(10进制化为二进制)
从上面的竖式可以清楚的看到利用异或运算实现两个值交换的基本过程。
下面从深层次剖析一下:
注:
优点:不存在引入中间变量,不存在整数溢出
缺点:前端对位操作这一块可能了解不深,不容易理解
5.4 究极版
熟悉
ES6
语法的人当然不会对解构陌生其中的解构的原理,我暂时还没读过 ES6的规范,不知道具体的细则,不过我们可以看看
babel
是自己编译的,我们可以看出点门路。哈哈,简单粗暴,不知道有没有按照 ES 的规范,其实可以扒一扒 v8的源码,chrome 已经实现这种解构用法。
这个例子和前面的例子编写风格有何不同,你如果细心的话就会发现这两行代码多了一个分号,对于我这种编码不写分号的洁癖者,为什么加一个分号在这里,其实是有原因的,这里就简单普及一下,建议大家还是写代码写上分号。
5.4 ECMAScript 自动分号;插入(作为补充,防止大家以后踩坑)
尽管 JavaScript 有 C 的代码风格,但是它不强制要求在代码中使用分号,实际上可以省略它们。
JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。 因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。
5.4.1例子
自动插入分号,解析器重新解析。
5.4.2工作原理
下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。
下面是解析器"猜测"的结果。
解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理。
5.4.3 ECMAScript对自动分号插入的规则
我们翻到7.9章节,看看其中插入分号的机制和原理,清楚只写以后就可以尽量以后少踩坑
**必须用分号终止某些 ECMAScript 语句 ( 空语句 , 变量声明语句 , 表达式语句 , do-while 语句 , continue 语句 , break 语句 , return 语句 ,throw 语句 )。这些分号总是明确的显示在源文本里。然而,为了方便起见,某些情况下这些分号可以在源文本里省略。描述这种情况会说:这种情况下给源代码的 token 流自动插入分号。**
还是比较抽象,看不太懂是不是,不要紧,我们看看实际例子,总结出几个规律就行,我们先不看抽象的,看着头晕,看看具体的总结说明, 化抽象为具体 。
首先这些规则是基于两点:
5.4.3.1 ASI的规则
1. 新行并入当前行将构成非法语句,自动插入分号。
2. 在continue,return,break,throw后自动插入分号
3. ++、--后缀表达式作为新行的开始,在行首自动插入分号
4. 代码块的最后一个语句会自动插入分号
5.4.3.2 No ASI的规则
1. 新行以 ( 开始
2. 新行以 [ 开始
3. 新行以 / 开始
4. 新行以 + 、 - 、 % 和 * 开始
5. 新行以 , 或 . 开始
到这里我们已经对ASI的规则有一定的了解了,另外还有一样有趣的事情,就是“空语句”。
5.4.4 结论
建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行, 对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。 这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。
关于JavaScript 语句后应该加分号么?(点我查看)我们可以看看知乎上大牛们对着个问题的看法。
你可能不知道的前端知识点:原来 JavaScript 还有位操作以及分号的使用细则
6、将 argruments 对象(类数组)转换成数组
{0:1,1:2,2:3,length:3}
这种形式的就属于类数组,就是按照数组下标排序的对象,还有一个length
属性,有时候我们需要这种对象能调用数组下的一个方法,这时候就需要把把类数组转化成真正的数组。6.1 普通版
优点:通用版本,没有任何兼容性问题
缺点:太普通
6.2 进阶版
这种应该是大家用过最常用的方法,至于为什么可以这么用,很多人估计也是一知半解,反正我看见大家这么用我也这么用,要搞清为什么里面的原因,我们还是从规范和源码说起。
照着规范的流程,自己看看推演一下就明白了:
英文版15.4.4.10 Array.prototype.slice (start, end)
中文版15.4.4.10 Array.prototype.slice (start, end)
如果你想知道
JavaScript
的sort
排序的机制,到底是哪种排序好,用的哪种,也可以从规范看出端倪。在官方的解释中,如[mdn]
简单的说就是根据参数,返回数组的一部分的
copy
。所以了解其内部实现才能确定它是如何工作的。所以查看V8
源码中的Array.js
可以看到如下的代码:方法
ArraySlice
,源码地址,第660
行,直接添加到Array.prototype
上的“入口”,内部经过参数、类型等等的判断处理,分支为SparseSlice
和SimpleSlice
处理。slice.call
的作用原理就是,利用call
,将slice
的方法作用于arrayLike
,slice
的两个参数为空,slice
内部解析使得arguments.lengt
等于0的时候 相当于处理slice(0)
: 即选择整个数组,slice
方法内部没有强制判断必须是Array
类型,slice
返回的是新建的数组(使用循环取值)”,所以这样就实现了类数组到数组的转化,call
这个神奇的方法、slice
的处理缺一不可。直接看
slice
怎么实现的吧。其实就是将array-like
对象通过下标操作放进了新的Array
里面:优点:最常用的版本,兼容性较强
缺点:ie 低版本,无法处理 dom 集合的 slice call 转数组。(虽然具有数值键值、length 符合ArrayLike 的定义,却报错)搜索资料得到 :因为 ie 下的 dom 对象是以 com 对象的形式实现的,js 对象与com对象不能进行转换 。
6.3 ES6 版本
使用
Array.from
, 值需要对象有length
属性, 就可以转换成数组扩展运算符
ES6
中的扩展运算符...也能将某些数据结构转换成数组,这种数据结构必须有便利器接口。优点:直接使用内置 API,简单易维护
缺点:兼容性,使用 babel 的 profill 转化可能使代码变多,文件包变大
你可能不知道的前端知识点:slice 方法的具体原理
7、数字取整 2.33333 => 2
7.1 普通版
parseInt()
函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。这个估计是直接取整最常用的方法了。更多关于
parseInt()
函数可以查看 MDN 文档7.2 进阶版
Math.trunc()
方法会将数字的小数部分去掉,只保留整数部分。特别要注意的是:
Internet Explorer
不支持这个方法,不过写个Polyfill
也很简单:数学的事情还是用数学方法来处理比较好。
7.3 黑科技版
7.3.1 ~~number
双波浪线 ~~ 操作符也被称为“双按位非”操作符。你通常可以使用它作为代替 Math.trunc() 的更快的方法。
失败时返回0,这可能在解决 Math.trunc() 转换错误返回 NaN 时是一个很好的替代。
但是当数字范围超出 ±2^31−1 即:2147483647 时,异常就出现了:
7.3.2 number | 0
| (按位或) 对每一对比特位执行或(OR)操作。
7.3.3 number ^ 0
^ (按位异或),对每一对比特位执行异或(XOR)操作。
7.3.4 number << 0
<< (左移) 操作符会将第一个操作数向左移动指定的位数。向左被移出的位被丢弃,右侧用 0 补充。
上面这些按位运算符方法执行很快,当你执行数百万这样的操作非常适用,速度明显优于其他方法。但是代码的可读性比较差。还有一个特别要注意的地方,处理比较大的数字时(当数字范围超出 ±2^31−1 即:2147483647),会有一些异常情况。使用的时候明确的检查输入数值的范围。
8、数组求和
8.1 普通版
优点:通俗易懂,简单粗暴
缺点:没有亮点,太通俗
8.2 优雅版
优点:简单明了,数组迭代器方式清晰直观
缺点:不兼容 IE 9以下浏览器
8.3 终极版
优点:让人一时看不懂的就是"好方法"。
缺点:
更多关于
eval
的探讨可以关注这篇文章: JavaScript 为什么不推荐使用 eval?你可能不知道的前端知识点:eval的使用细则
最后
祝大家圣诞快乐🎄,欢迎补充和交流。
The text was updated successfully, but these errors were encountered: