有关JavaScript的10个怪癖和秘密分享


Posted in Javascript onAugust 28, 2011

原文作者:Andy Croxall
原文链接:Ten Oddities And Secrets About JavaScript
翻译编辑:张鑫旭 

数据类型和定义

1. Null是个对象

JavaScript众多类型中有个Null类型,它有个唯一的值null, 即它的字面量,定义为完全没有任何意义的值。其表现得像个对象,如下检测代码:

alert(typeof null); //弹出 'object'

如下截图:

有关JavaScript的10个怪癖和秘密分享

尽管typeof值显示是"object",但null并不认为是一个对象实例。要知道,JavaScript中的值都是对象实例,每个数值都是Number对象,每个对象都是Object对象。因为null是没有值的,所以,很明显,null不是任何东西的实例。因此,下面的值等于false。

alert(null instanceof Object); //为 false

译者注:null还有被理解为对象占位符一说

2. NaN是个数值

NaN本意是表示某个值不是数值,但是其本身却又是数值,且不等于其自身,很奇怪吧,看下面的代码:

alert(typeof NaN); //弹出 'Number' 
alert(NaN === NaN); //为 false

结果如下截图:
有关JavaScript的10个怪癖和秘密分享有关JavaScript的10个怪癖和秘密分享

实际上NaN不等于任何东西。要确认某玩意是不是NaN只能使用isNaN.
3. 无关键字的数组等同于false(关于Truthy和Falsy)

下面是JavaScript另一个极品怪癖:

alert(new Array() == false); //为 true

结果如下截图:
有关JavaScript的10个怪癖和秘密分享

想要知道这里发生了什么,你需要理解truthy和falsy这个概念。它们是一种true/flase字面量。在JavaScript中,所有的非Boolean型值都会内置一个boolean标志,当这个值被要求有boolean行为的时候,这个内置布尔值就会出现,例如当你要跟Boolean型值比对的时候。

因为苹果不能和梨做比较,所以当JavaScript两个不同类型的值要求做比较的时候,它首先会将其弱化成相同的类型。false, undefined, null, 0, "", NaN都弱化成false。这种强制转化并不是一直存在的,只有当作为表达式使用的时候。看下面这个简单的例子:

var someVar =0; 
alert(someVar == false); //显示 true

结果如下截图:
有关JavaScript的10个怪癖和秘密分享

上面测试中,我们试图将数值0和boolean值false做比较,因两者的数据类型不兼容,JavaScript自动强制转换成统一的等同的truthy和falsy,其中0等同于false(正如上面所提及的)。

你可能注意到了,上面一些等同false的值中并没有空数组。只因空数组是个怪胚子:其本身实际上属于truthy,但是当空数组与Boolean型做比较的时候,其行为表现又属于falsy。不解?这是由原因的。先举个例子验证下空数组的奇怪脾气:

var someVar = []; //空数组 
alert(someVar == false); //结果 true 
if (someVar) alert('hello'); //alert语句执行, 所以someVar当作true

结果如下截图,连续弹出两个框框:
有关JavaScript的10个怪癖和秘密分享有关JavaScript的10个怪癖和秘密分享

译者注:之所以会有这种差异,根据作者的说法,数组内置toString()方法,例如直接alert的时候,会以join(“,”)的形式弹出字符串,空数组自然就是空字符串,于是等同false。具体可参见作者另外一篇文章,《Twisted logic: understanding truthy & falsy》。不过我个人奇怪的是,像空对象,空函数,弱等于true或者false的时候都显示false,为何?真的因为数组是个怪胎,需要特殊考虑吗?

为避免强制转换在比较方面的问题,你可以使用强等于(===)代替弱等于(==)。

var someVar = 0; 
alert(someVar == false); //结果 true ? 0属于falsy 
alert(someVar === false); //结果 false ? zero是个数值, 不是布尔值

结果如下截图(win7 FF4):
有关JavaScript的10个怪癖和秘密分享有关JavaScript的10个怪癖和秘密分享

如果你想深入探究JavaScript中类型强制转换等些特有的癖好,可以参见官方相关的文档规范:
section 11.9.3 of the ECMA-262
正则表达式

4. replace()可以接受回调函数

这是JavaScript最鲜为人知的秘密之一,v1.3中首次引入。大部分情况下,replace()的使用类似下面:

alert('10 13 21 48 52'.replace(/\d+/g, '*')); //用 * 替换所有的数字

这是一个简单的替换,一个字符串,一个星号。但是,如果我们希望在替换发生的时候有更多的控制,该怎么办呢?我们只希望替换30以下的数值,该怎么办呢?此时如果仅仅依靠正则表达式是鞭长莫及的。我们需要借助回调函数的东风对每个匹配进行处理。

alert('10 13 21 48 52'.replace(/\d+/g, function(match) { 
return parseInt(match) <30?'*' : match; 
}));

当每个匹配完成的时候,JavaScript应用回调函数,传递匹配内容给match参数。然后,根据回调函数里面的过滤规则,要么返回星号,要么返回匹配本身(无替换发生)。

如下截图:
有关JavaScript的10个怪癖和秘密分享
5. 正则表达式:不只是match和replace

不少javascript工程师都是只通过match和replace和正则表达式打交道。但JavaScript所定义的正则表达式相关方法远不止这两个。

其中值得一提的是test(),其工作方式类似match(),但是返回值却不一样:test()返回的是布尔型,用来验证是否匹配,执行速度高于match()。

alert(/\w{3,}/.test('Hello')); //弹出 'true'

上面行代码用来验证字符串是否有三个以上普通字符,显然"hello"是符合要求的,所以弹出true。
结果如下截图:
有关JavaScript的10个怪癖和秘密分享

我们还应注意RegExp对象,你可以用此创建动态正则表达式对象,例如:

function findWord(word, string) { 
var instancesOfWord = string.match(new RegExp('\\b'+word+'\\b', 'ig')); 
alert(instancesOfWord); 
} 
findWord('car', 'Carl went to buy a car but had forgotten his credit card.');

这儿,我们基于参数word动态创建了匹配验证。这段测试代码作用是不区分大小选的情况下选择car这个单词。眼睛一扫而过,测试英文句子中只有一个单词是car,因此这里的演出仅一个单词。\b是用来表示单词边界的。

结果如下截图:
有关JavaScript的10个怪癖和秘密分享

函数和作用域

6. 你可以冒充作用域

作用域这玩意是用来决定什么变量是可用的,独立的JavaScript(如JavaScript不是运行中函数中)在window对象的全局作用域下操作,window对象在任何情况下都可以访问。然而函数中声明的局部变量只能在该函数中使用。

var animal ='dog'; 
function getAnimal(adjective) { alert(adjective+''+this.animal); } 
getAnimal('lovely'); //弹出 'lovely dog'

这儿我们的变量和函数都声明在全局作用域中。因为this指向当前作用域,在这个例子中就是window。因此,该函数寻找window.animal,也就是'dog'了。到目前为止,一切正常。然而,实际上,我们可以让函数运行在不同的作用域下,而忽视其本身的作用域。我们可以用一个内置的称为call()的方法来实现作用域的冒充。

var animal ='dog'; 
function getAnimal(adjective) { alert(adjective+''+this.animal); }; 
var myObj = {animal: 'camel'}; 
getAnimal.call(myObj, 'lovely'); //弹出 'lovely camel'

call()方法中的第一个参数可以冒充函数中的this,因此,这里的this.animal实际上就是myObj.animal,也就是'camel'了。后面的参数就作为普通参数传给函数体。

另外一个与之相关的是apply()方法,其作用于call()一样,不同之处在于,传递给函数的参数是以数组形式表示的,而不是独立的变量们。所以,上面的测试代码如果用apply()表示就是:

getAnimal.apply(myObj, ['lovely']); //函数参数以数组形式发送

demo页面中,点击第一个按钮的结果如下截图:
有关JavaScript的10个怪癖和秘密分享

点击第二个和第三个按钮的结果如下:
有关JavaScript的10个怪癖和秘密分享7. 函数可以执行其本身

下面这个是很OK的:

(function() { alert('hello'); })(); //弹出 'hello'

这里的解析足够简单:声明一个函数,然后因为()解析立即执行它。你可能会奇怪为何要这么做(指直接屁股后面()调用),这看上去是有点自相矛盾的:函数包含的通常是我们想稍后执行的代码,而不是当下解析即执行的,否则,我们就没有必要把代码放在函数中。

另外一个执行函数自身(self-executing functions (SEFs))的不错使用是为在延迟代码中使用绑定变量值,例如事件的回调(callback),超时执行(timeouts)和间隔执行(intervals)。如下例子:

var someVar ='hello'; 
setTimeout(function() { alert(someVar); }, 1000); 
var someVar ='goodbye';

Newbies在论坛里总问这里timeout的弹出为什么是goodbye而不是hello?答案就timeout中的回调函数直到其运行的时候才去赋值someVar变量的值。而那个时候,someVar已经被goodbye重写了好长时间了。

SEFs提供了一个解决此问题的方法。不是像上面一样含蓄地指定timeout回调,而是直接将someVar值以参数的形式传进去。效果显著,这意味着我们传入并孤立了someVar值,保护其无论后面是地震海啸还是女朋友发飙咆哮都不会改变。

var someVar = 'hello'; 
setTimeout((function(someVar) { 
returnfunction() { alert(someVar); } 
})(someVar), 1000); 
var someVar ='goodbye';

风水轮流转,这次,这里的弹出就是hello了。这就是函数参数和外部变量的点差别了哈。
例如,最后一个按钮点击后的弹出如下:
有关JavaScript的10个怪癖和秘密分享浏览器

8. FireFox以RGB格式读与返回颜色而非Hex

直到现在我都没有真正理解为何Mozilla会这样子。为了有个清晰的认识,看下面这个例子:

<!-- 
#somePara { color: #f90; } 
--> 
<p id="somePara">Hello, world!</p> 
<script> 
var ie = navigator.appVersion.indexOf('MSIE') !=-1; 
var p = document.getElementById('somePara'); 
alert(ie ? p.currentStyle.color : getComputedStyle(p, null).color); 
</script>

大部分浏览器弹出的结果是ff9900,而FireFox的结果却是rgb(255, 153, 0),RGB的形式。经常,处理颜色的时候,我们需要花费不少代码将RGB颜色转为Hex。
下面是上面代码在不同浏览器下的结果:
有关JavaScript的10个怪癖和秘密分享有关JavaScript的10个怪癖和秘密分享其它杂七杂八

9. 0.1 + 0.2 !== 0.3

这个古怪的问题不只会出现在JavaScript中,这是计算机科学中一个普遍存在的问题,影响了很多的语言。标题等式输出的结果是0.30000000000000004。

这是个被称为机器精度的问题。当JavaScript尝试执行(0.1 + 0.2)这行代码的时候,会把值转换成它们喜欢的二进制口味。这就是问题的起源,0.1实际上并不是0.1,而是其二进制形式。从本质上将,当你写下这些值的时候,它们注定要失去精度。你可能只是希望得到个简单的两位小数,但你得到的(根据Chris Pine的注解)是二进制浮点计算。好比你想把一段应该翻译成中文简体,结果出来的却是繁体,其中还是有差异是不一样的。

一般处理与此相关的问题有两个做法:

转换成整数再计算,计算完毕再转换成希望的小数内容
调整你的逻辑,设定允许范围为不是指定结果。

例如,我们不应该下面这样:

var num1=0.1, num2=0.2, shouldEqual=0.3; 
alert(num1 + num2 == shouldEqual); //false

而可以试试这样:

alert(num1 + num2 > shouldEqual - 0.001&& num1 + num2 < shouldEqual +0.001); //true

10. 未定义(undefined)可以被定义(defined)

我们以一个和风细雨的小古怪结束。听起来可能有点奇怪,undefined并不是JavaScript中的保留字,尽管它有特殊的意义,并且是唯一的方法确定变量是否未定义。因此:

var someVar; 
alert(someVar == undefined); //显示 true

目前为止,一切看上去风平浪静,正常无比,但剧情总是很狗血:

undefined ="I'm not undefined!"; 
var someVar; 
alert(someVar == undefined); //显示 false!

这就是为什么jQuery源码中最外部的闭包函数要有个并没有传入的undefined参数,目的就是保护undefined不要被外部的些不良乘虚而入。

有关JavaScript的10个怪癖和秘密分享

Javascript 相关文章推荐
js修改input的type属性及浏览器兼容问题探讨与解决
Jan 23 Javascript
JQuery 文本框回车跳到下一个文本框示例代码
Aug 30 Javascript
JS父页面与子页面相互传值方法
Mar 05 Javascript
jQuery绑定事件不执行但alert后可以正常执行
Jun 03 Javascript
实现图片预加载的三大方法及优缺点分析
Nov 19 Javascript
node.js使用require()函数加载模块
Nov 26 Javascript
JavaScript实现阿拉伯数字和中文数字互相转换
Jun 12 Javascript
jQuery模拟Marquee实现无缝滚动效果完整实例
Sep 29 Javascript
JavaScript实现简单评论功能
Aug 17 Javascript
axios携带cookie配置详解(axios+koa)
Dec 28 Javascript
ES6 Promise对象的应用实例分析
Jun 27 Javascript
Vue动态加载图片在跨域时无法显示的问题及解决方法
Mar 10 Javascript
JS面向对象编程浅析
Aug 28 #Javascript
用JS实现一个TreeMenu效果分享
Aug 28 #Javascript
JS target与currentTarget区别说明
Aug 28 #Javascript
IE6,IE7,IE8下使用Javascript记录光标选中范围(已补全)
Aug 28 #Javascript
range 标准化之获取
Aug 28 #Javascript
dojo学习第一天 Tab选项卡 实现
Aug 28 #Javascript
js中设置元素class的三种方法小结
Aug 28 #Javascript
You might like
PHP 数组遍历方法大全(foreach,list,each)
2010/06/30 PHP
Yii+upload实现AJAX上传图片的方法
2016/07/13 PHP
PHP实现上传图片到 zimg 服务器
2016/10/19 PHP
CodeIgniter框架实现的整合Smarty引擎DEMO示例
2019/03/28 PHP
php-fpm超时时间设置request_terminate_timeout资源问题分析
2019/09/27 PHP
短信提示使用 特效
2007/01/19 Javascript
Mootools 1.2教程 Tooltips
2009/09/15 Javascript
让textarea自动调整大小的js代码
2011/04/12 Javascript
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
2012/06/14 Javascript
js快速排序的实现代码
2013/12/08 Javascript
javascript ajax的5种状态介绍
2014/08/18 Javascript
ES6中如何使用Set和WeakSet
2016/03/10 Javascript
jQuery点击输入框显示验证码图片
2016/05/19 Javascript
9个让JavaScript调试更简单的Console命令
2016/11/14 Javascript
JavaScript易错知识点整理
2016/12/05 Javascript
详解百度百科目录导航树小插件
2017/01/08 Javascript
javascript 使用正则test( )第一次是 true,第二次是false
2017/02/22 Javascript
微信小程序 下拉菜单简单实例
2017/04/13 Javascript
JS自定义函数实现时间戳转换成date的方法示例
2017/08/27 Javascript
vue点击input弹出带搜索键盘并监听该元素的方法
2018/08/25 Javascript
Vue 中对图片地址进行拼接的方法
2018/09/03 Javascript
在小程序Canvas中使用measureText的方法示例
2018/10/19 Javascript
JavaScript中this的全面解析及常见实例
2019/05/14 Javascript
JS实现页面跳转与刷新的方法汇总
2019/08/30 Javascript
浅谈vue项目用到的mock数据接口的两种方式
2019/10/09 Javascript
vue计算属性无法监听到数组内部变化的解决方案
2019/11/06 Javascript
微信小程序点击view动态添加样式过程解析
2020/01/21 Javascript
vue+axios 拦截器实现统一token的案例
2020/09/11 Javascript
python 基础教程之Map使用方法
2017/01/17 Python
Python使用docx模块实现刷题功能代码
2020/02/13 Python
Python 排序最长英文单词链(列表中前一个单词末字母是下一个单词的首字母)
2020/12/14 Python
台湾最大银发乐活百货:乐龄网
2018/05/21 全球购物
档案管理员岗位职责
2013/12/01 职场文书
社团2014年植树节活动总结
2014/03/11 职场文书
初中优秀学生评语
2014/12/29 职场文书
建筑工程材料员岗位职责
2015/04/11 职场文书