前几日在网上看到一篇文章:JavaScript绝句,看了以后觉得里面的代码颇为有趣,不过文章里面只是简单的说了这样写的目的和结果,却没有令读者起到既知其然,又知其所以然的效果。这里简单写一篇小文章剖析一下这篇“绝句”背后的原理吧。

1. 取整同时转成数值型

'10.567890'|0
//结果: 10
'10.567890'^0
//结果: 10
-2.23456789|0
//结果: -2
~~-2.23456789
//结果: -2

第一条绝句短短几句话,看起来十分的简洁,实际上背后的道理确是多了去了。这个东西分三大块:

首先字符型转成数值型本身没有什么可称道的,因为这就是JavaScript内置的类型转换功能,当字符型变量参与运算时,JS会自动将其转换为数值型(如果无法转化,变为NaN)。

至于取整的原因,在蝴蝶书里有提到,道爷的原文如下:

InJava, the bitwise operators work with integers.JavaScript doesn't have integers. It only has double precision floating-point numbers. So, the bitwise operators convert their number operands into integers, do their business, and then convert them back. In most languages, these operators are very close to the hardware and very fast. In JavaScript, they are very far from the hardware and very slow. JavaScript is rarely used for doing bit manipulation.

翻译过来就是:位运算这个东西在Java里就是只能对整型进行操作的。JS压根没有整型这么个东西,JS里面的所有数值型都是双精度浮点数。因此,JS在进行位运算时,会首先将这些数字运算数转换为整数,然后再执行运算。在许多语言里,因为强类型的原因,位运算这种东西是接近于硬件处理速度的;而在JavaScript里,由于鸭子类型的存在,JavaScript根本就不知道进行运算的这货到底是个啥,所以它都尝试把它转化为整数(甚至于NaN,undefined都可以进行位运算),所以它非常非常的慢。我们基本不用JS进行位操作。

所以转化为整型这里,实际上是用到了JavaScript强大的包容性。至于运算结果为什么不变呢?因为他所取的这些操作, | 是二进制或, x|0 永远等于x;^为异或,同0异1,所以 x^0 还是永远等于x;至于~是按位取反,搞了两次以后值当然是一样的。

结论:可用。利用了Javascript本身位运算自动取整的原理,至于位运算本身的效率比硬件处理速度低下……这个倒是无妨,因为我相信咱们自己写一个取整函数的效率应该也不会比Javascript自动取整高到哪儿去,多了个位运算这一点就忍了吧。

2. 日期转数值

var d = +new Date(); //

这一段就写的不明不白的了,什么叫日期转数值?这应该叫日期转时间戳。查看MDN上的Date()对象,里面有这么一段话:

The JavaScript date is measured in milliseconds since midnight 01 January, 1970 UTC. A day holds 86,400,000 milliseconds. The JavaScript Date object range is -100,000,000 days to 100,000,000 days relative to 01 January, 1970 UTC.

意思就是说,JS本身时间的内部表示形式就是Unix时间戳,以毫秒为单位记录着当前距离1970年1月1日0点的时间单位。这里不过是用一元运算符 + 给他转换成本来的表示形式而已。至于一元运算符+ 的功能,就是把一个变量转化为数值型,并且不对其进行任何操作。MDN里对本操作符评价极高:

unary plus is the fastest and preferred way of converting something into a number, because it does not perform any other operations on the number.

结论:可用。是JS转化时间戳的一个好方法。

3. 类数组对象转数组

var arr =[].slice.call(arguments)

这里又是一个比较有趣的写法,所谓的“类数组”,这里指的是JS里面每个函数自带的内置对象arguments ,其可以获得函数的参数,并以一种类似数组的方式来保存(实际上这个对象只有callee, caller, length的方法)。如果你要对数组进行诸如切片,连接等操作怎么办?你就可以用上面的这个方法,当然也是MDN给出的解决方案。

写到这里我恍然大悟啊,怪不得前几日写由JavaScript反柯里化所想到的时,大牛在操作arguments时,统统都是Array.prototype.xxx.call(arguments, xxx, ...) ,原来原因很简单:arguments不是数组,木有这些方法;如果要用,请 call 或 apply 之。

这里还有一个奇技淫巧:当你需要把 arguments 合并入一个数组时,你当然可以先用上面的方法转换然后 concat之,你也可以利用 push 的原理直接用 push.apply,方法对比如下:

function test() {
var res = ['item1', 'item2']
res = res.concat(Array.prototype.slice.call(arguments)) //方法1
Array.prototype.push.apply(res, arguments) //方法2
}

我们可以清楚的看到,方法二比方法一短那么一点(喂!)。嗯,就是这样。

结论:可用。当然直接写[]会为内存增加垃圾,如果不怕绝句写的太长,还是可以写成上文Array.prototype.push.apply 这种形式的。

4. 漂亮的随机码

Math.random().toString(16).substring(2);
Math.random().toString(36).substring(2);

这个十分好理解,生成一个随机数,转化为n进制,然后截取其中几位而已。其中 toString() 函数的参数为基底,范围为2~36。

结论:可用,但是位数是不确定的,为保险起见建议 toString(36).substring(2, 10) ,可以妥妥的截出八位来。

5. 合并数组:

var a = [1,2,3];
var b = [4,5,6];
Array.prototype.push.apply(a, b);
uneval(a); //[1,2,3,4,5,6]

好,这个东西其实非常的不错。在上文的奇技淫巧中我们也提到了,当b是类数组时,可以用 push方法来进行数组合并。但这里的问题是……这个b根本就是数组啊喂!有什么必要啊,难道你觉得JS的concat 还不够好用么?再次比较一下代码:

var a = [1,2,3]
var b = [4,5,6]
Array.prototype.push.apply(a, b) //方法1
a = a.concat(b) //方法2

作者的方法长好多啊!然后那个自定义的函数uneval是个什么东西啊!JS木有这种函数啊!

结论:其实它正确的使用点在于3里面的奇技淫巧,对于单纯的数组……建议还是用concat吧。

6. 用0补全位数

function prefixInteger(num, length) {
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}
prefixInteger(2, 3) //

这里作者给我们展示了一个新的函数: toFixed(n) ,赶紧滚去查了一下MDN中的函数说明,这个函数的意思是对一个浮点数进行四舍五入,保留小数点后n位;默认为0,也即直接取整。

而作者这个函数的意思是把你给的一个数值先四舍五入取整,然后在前面补上各种0使最终获得一个等长的字符串。不过,由于他的算法是让原整数除以十的幂然后截取,这样当num的位数本身就多于length的时候就会出现bug,如下面这个输入:

prefixInteger(1234567, 3)     //34.567

最终输出的长度是5,不符合要求,所以函数应该进行错误处理之类的,比如加上下面这个 trycatch 语句?

function prefixInteger(num, length) {
try{
if (num.toFixed().toString().length > length)
throw 'illegal number!'
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}catch(err){
console.log(err)
}
}

结论:有点小bug,修改可用,不过改了以后蛮长的不像绝句像八股文呵呵其实我觉得还是可以再改进一点的。在某些场合的用处还是蛮强大的。

7. 交换值

a=[b, b=a][0];

本绝句中最帅的一句终于出场。这句话甚至有了pythonic的风格,虽然python的写法更简单:

a, b = b, a        #还是python最帅啊!

不过有豆瓣的网友对这一方法提出了质疑:交换值时声明的一个数组[b, b=a]产生了内存,只能等待JS自己进行内存回收。确实,如果要严格的节约内存,提高JS内存回收的效率,那么 new 、 [] 、{} 和 function 声明都应该少用。不过至于交换变量,如果用传统的方式只能再声明一个变量做中介,这样实际上依旧会占用内存,不过这样内存是在函数完成时自动释放的罢了。

结论:可用,不过如果要批量使用,还是建议写个函数用函数内部变量交换。

8. 将一个数组插入另一个数组的指定位置

var a = [1,2,3,7,8,9];
var b = [4,5,6];
var insertIndex = 3;
a.splice.apply(a, Array.prototype.concat(insertIndex, 0, b));
// a: 1,2,3,4,5,6,7,8,9

这里用到了两个函数: splice 和 concat ,我们看一下 splice 这个函数的定义arr.splice(x, y, item1, item2, ...):就是从arr数组的第x位开始,首先削掉后面的y个,之后插入item1, item2等等。其实,这里是 apply 函数的一个通用应用:当函数foo的参数仅支持(item1, item2, ..)这样的参数传入时,如果你把item1, item2, ..存在数组items里,想把数组作为参数传给foo时,就可以这样写:

xx.foo.apply(xx, items)

结论:可用。鉴于 apply 函数可以把数组作为参数依次传入的性质,这只是广大应用中的一个特例。

9. 删除数组元素

var a = [1,2,3,4,5];
a.splice(3,1); //a = [1,2,3,5]

是的,Javascript对于数组删除来说,没有什么好的方法。如果你用 delete a[3] 来删除的话,将会在数组里留下一个空洞,而且后面的下标也并没有递减。这个方法是道爷在书里提到的,原文如下:

Fortunately, JavaScript arrays have a splice method. It can do surgery on an array, deleting some number of elements and replacing them with other elements. The first argument is an ordinal in the array. The second argument is the number of elements to delete. (...) Because every property after the deleted property must be removed and reinserted with a new key, this might not go quickly for large arrays.

道爷说了这个函数的功能的同时也说了,这个函数实际上是把后面的元素先移除掉,然后作为新的键值重新插入,这样其实等于遍历了一次,和你自己写个for循环的效率差不多。而且道爷没有提到的是,这个函数是有一个返回值的,如果多次使用这样的函数操作,显然会增加内存的负担。所以或许从省内存的方式来看,使用for循环遍历然后逐个delete后面的元素会好一些。

结论:可用。既然道爷都推荐了,就不要纠结于这点可怜的内存上了吧。但是大型数组效率始终不高。

10. 快速取数组最大和最小值

Math.max.apply(Math, [1,2,3]) //
Math.min.apply(Math, [1,2,3]) //

这个就是重复绝句,详情参见绝句8。可能作者自己也不知道,apply一直是这么用的。

结论:可用,而且要学会这个技巧呀~

11. 条件判断:

var a = b && 1;
//相当于
if (b) {
a = 1
}

呵呵,这也算绝句呀……好吧。而且作者没有考虑到,如果b不为真,a的值就变成b了,也有豆瓣的网友看出了这个问题,其实这个应该相当于:

if (b) {
a = 1
} else {
a = b
}

结论:必须可用,没啥可说的。不过这是C语言里面的特性,不能算做是JavaScript的绝句吧。条件赋值如果不这么写你就out啦~

12. 判断IE:

var ie = /*@cc_on !@*/false;

好顶赞!当然不是说这个绝句好顶赞,而是我之前从来没有研究过如何判断IE,因为这个去看了一下,发现还是有很多方式的,列举如下:

// 貌似是最短的,利用IE不支持标准的ECMAscript中数组末逗号忽略的机制
var ie = !-[1,];
// 利用了IE的条件注释
var ie = /*@cc_on!@*/false;
// 还是条件注释
var ie//@cc_on=1;
// IE不支持垂直制表符
var ie = '\v'=='v';
// 原理同上
var ie = !+"\v1";

至于IE的条件注释,如果以后有精力再详细的补上吧。

结论:亲测可用,原理有待慢慢研究。

JavaScript绝句的小研究的更多相关文章

  1. javascript中类的属性研究

    原文:javascript中类的属性研究 本篇文章主要针对javascript的属性进行分析,由于javascript是一种基于对象的语言,本身没有类的概念,所以对于javascript的类的定义有很 ...

  2. ( 译、持续更新 ) JavaScript 上分小技巧(四)

    后续如有内容,本篇将会照常更新并排满15个知识点,以下是其他几篇译文的地址: 第一篇地址:( 译.持续更新 ) JavaScript 上分小技巧(一) 第二篇地址:( 译.持续更新 ) JavaScr ...

  3. ( 译、持续更新 ) JavaScript 上分小技巧(三)

    最近家里杂事较多,自学时间实在少的可怜,所以都在空闲时间看看老外写的内容,学习之外顺便翻译分享~等学习的时间充足些再写写自己的一些学习内容和知识点分析(最近有在接触的:复习(C#,SQL).(学习)T ...

  4. ( 译、持续更新 ) JavaScript 上分小技巧(二)

    考虑到文章过长,不便于阅读,这里分出第二篇,如有后续,每15个知识点分为一篇... 第一篇地址:( 译.持续更新 ) JavaScript 上分小技巧(一) 第三篇地址:( 译.持续更新 ) Java ...

  5. ( 译、持续更新 ) JavaScript 上分小技巧(一)

    感谢好友破狼提供的这篇好文章,也感谢写这些知识点的作者们和将他们整理到一起的作者.这是github上的一篇文章,在这里本兽也就只做翻译,由于本兽英语水平和编程能力都不咋地,如有不好的地方也请多理解体谅 ...

  6. JavaScript apply函数小案例

    //回调函数1 function callback(a,b,c) { alert(a+b+c); } //回调函数2 function callback2(a,b) { alert(a+b); } / ...

  7. Javascript闭包的一些研究

    原文:Javascript闭包的一些研究 本文不谈闭包的概念,因为概念容易把人搞晕,本文希望通过几个鲜活的例子来探究闭包的性质,相信对理解闭包会有所帮助. 程序1 var f = (function( ...

  8. JavaScript里的小妖精

    JavaScript里的小妖精———this!! 关于this指向这个问题,活生生折磨了我一个下午,回来静下心捋顺一下,总结出来一下规律. 当然,this这个复杂的问题不是一句两句可以说清楚,作为菜鸟 ...

  9. Javascript实现让小图片一直跟着鼠标移动

    Javascript实现让小图片一直跟着鼠标移动实例 注意:图片可能加载不出来,注意更换 <!doctype html> <html> <head> <met ...

随机推荐

  1. torchvision 批量可视化图片

    1.1 简介 计算机视觉中,我们需要观察我们的神经网络输出是否合理.因此就需要进行可视化的操作. orchvision是独立于pytorch的关于图像操作的一些方便工具库. torchvision的详 ...

  2. ngnix的基本安装及配置 centos7

    1.centos7  挂载ngnix的源 rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7- ...

  3. 11.7 Daily Scrum(周末暂停两天Daily Scrum)

    由于APEC放假,有些成员离校了,他们那部分的任务会暂时拖后一些,之后会加班加点赶工. 另外,每个人的任务还是相对独立,离校成员的任务进度不会对其他成员的进度造成很大影响.   Today's tas ...

  4. 《Linux内核分析》 第五节 扒开系统调用的三层皮(下)

    <Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...

  5. c++实现计算器功能 -----初代

    由于时间问题,我就写的简单一点. 课程作业一 git链接: Operations 里面的Operations.cpp文件就是完成品. 1 我就简单的对我原来的代码进行了重构,原本的代码已经把函数都分得 ...

  6. “数学口袋精灵”App的第二个Sprint计划----开发日记

    一.现状 在第一个sprint计划中,我们已经初步完成了“数学口袋精灵”App的基本框架,现在我们要继续完善app,使其功能更加强大,界面更加有趣. 二.任务认领 完成界面后的后续任务: 冯美欣:欢迎 ...

  7. WINNER队成立(第二天)

    WINNER成立 今天我们主要寻找了两名女生加入到我们的小组当中,她们分别为:李豌湄.江丹仪.有了她们的加入,我们小组可谓如虎添翼啊,嘻嘻.之后我们建立了微信讨论群,经过一番讨论之后,我们决定把小组起 ...

  8. 对常用软件的评价(TGP腾讯游戏平台)

    1,首先说下界面,这款软件的界面有些类似于QQ的界面,登录方式和QQ的方式是一样的,可以简单的说是一款给游戏用的QQ,就是里面的用户变成了游戏 2,功能,简单的说就是将你常玩的游戏放于这游戏平台的表面 ...

  9. Final版本发布评论

    1.飞天小女警组做的礼物挑选小工具,相比较beta版本并没有太大的改动,新增添了“猜你喜欢”模块,在最后给出的礼物推荐下面会给出三个猜你喜欢的礼物.这样解决了推荐礼物的单一,也让礼物推荐变得更有趣了. ...

  10. [转帖][Bash Shell] Shell学习笔记

    [Bash Shell] Shell学习笔记 http://www.cnblogs.com/maybe2030/p/5022595.html  阅读目录 编译型语言 解释型语言 5.1 作为可执行程序 ...