有关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解析与序列化json数据(三)json的解析探讨
Feb 01 Javascript
jquery实现弹出层遮罩效果的简单实例
Mar 03 Javascript
AngularJS入门教程之学习环境搭建
Dec 06 Javascript
JavaScript中pop()方法的使用教程
Jun 09 Javascript
javascript实现状态栏中文字动态显示的方法
Oct 20 Javascript
jQuery实现获取绑定自定义事件元素的方法
Dec 02 Javascript
关于JS 预解释的相关理解
Jun 28 Javascript
jquery封装插件时匿名函数形参和实参的写法解释
Feb 14 Javascript
详解webpack和webpack-simple中如何引入css文件
Jun 28 Javascript
D3.js实现拓扑图的示例代码
Jun 30 Javascript
vue获取元素宽、高、距离左边距离,右,上距离等还有XY坐标轴的方法
Sep 05 Javascript
解决vue脚手架项目打包后路由视图不显示的问题
Sep 20 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
Yii结合CKEditor实现图片上传功能
2014/06/13 PHP
十个PHP高级应用技巧果断收藏
2015/09/25 PHP
Yii2.0多文件上传实例说明
2017/07/24 PHP
JavaScript 检测浏览器和操作系统的脚本
2008/12/26 Javascript
JavaScript 应用技巧集合[推荐]
2009/08/30 Javascript
使用ExtJS技术实现的拖动树结点
2010/08/05 Javascript
页面加载完毕后滚动条自动滚动一定位置
2014/02/20 Javascript
JavaScript代码判断点击第几个按钮
2015/12/13 Javascript
JSONP跨域请求实例详解
2016/07/04 Javascript
JavaScript学习笔记整理_setTimeout的应用
2016/09/19 Javascript
jQuery弹出窗口打开链接的实现代码
2016/12/24 Javascript
微信小程序实现多个按钮toggle功能的实例
2017/06/13 Javascript
bootstrap+jquery项目引入文件报错的解决方法
2018/01/22 jQuery
对angularJs中controller控制器scope父子集作用域的实例讲解
2018/10/08 Javascript
vue+Vue Router多级侧导航切换路由(页面)的实现代码
2018/12/20 Javascript
Vue按时间段查询数据组件使用详解
2020/08/21 Javascript
零基础写python爬虫之urllib2中的两个重要概念:Openers和Handlers
2014/11/05 Python
实现python版本的按任意键继续/退出
2016/09/26 Python
tensorflow 使用flags定义命令行参数的方法
2018/04/23 Python
浅谈python写入大量文件的问题
2018/11/09 Python
react+django清除浏览器缓存的几种方法小结
2019/07/17 Python
python对 MySQL 数据库进行增删改查的脚本
2020/10/22 Python
基于python模拟TCP3次握手连接及发送数据
2020/11/06 Python
OpenCV实现机器人对物体进行移动跟随的方法实例
2020/11/09 Python
Pytorch如何切换 cpu和gpu的使用详解
2021/03/01 Python
手把手教你实现一个canvas智绘画板的方法
2019/03/04 HTML / CSS
戴尔马来西亚官网:Dell Malaysia
2020/05/02 全球购物
英语系毕业生自荐信
2013/10/31 职场文书
村官工作鉴定评语
2014/01/27 职场文书
小学数学国培感言
2014/03/10 职场文书
目标责任书范本
2014/04/16 职场文书
信用卡工作证明模板
2014/09/14 职场文书
标枪加油稿
2015/07/22 职场文书
创业计划书之少年玩具店
2019/09/05 职场文书
Python趣味实战之手把手教你实现举牌小人生成器
2021/06/07 Python
Win11右下角图标点了没反应怎么办?Win11点击右下角图标无反应解决方法汇总
2022/07/07 数码科技