有关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 函数的执行环境和作用域链的深入解析
Nov 01 Javascript
模仿JQuery sortable效果 代码有错但值得看看
Nov 05 Javascript
深入理解JavaScript系列(9) 根本没有“JSON对象”这回事!
Jan 15 Javascript
原生js编写设为首页兼容ie、火狐和谷歌
Jun 05 Javascript
javascript搜索框点击文字消失失焦时文本出现
Sep 18 Javascript
JavaScript中的slice()方法使用详解
Jun 06 Javascript
jQuery实现的简单折叠菜单(折叠面板)效果代码
Sep 16 Javascript
详解JavaScript时间格式化
Dec 23 Javascript
Three.js学习之几何形状
Aug 01 Javascript
vue.js的双向数据绑定Object.defineProperty方法的神奇之处
Jan 18 Javascript
javascript 原型与原型链的理解及应用实例分析
Feb 10 Javascript
vue使用v-model进行跨组件绑定的基本实现方法
Apr 28 Vue.js
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中根据变量的类型 选择echo或dump
2012/07/05 PHP
php常用表单验证类用法实例
2015/06/18 PHP
详解使用php-cs-fixer格式化代码
2020/09/16 PHP
css3实现背景模糊的三种方式
2021/03/09 HTML / CSS
bgsound 背景音乐 的一些常用方法及特殊用法小结
2010/05/11 Javascript
javascript 闭包详解
2015/02/15 Javascript
使用C++为node.js写扩展模块
2015/04/22 Javascript
jquery实现倒计时功能
2015/12/28 Javascript
node.js微信公众平台开发教程
2016/03/04 Javascript
JavaScript中函数声明与函数表达式的区别详解
2016/08/18 Javascript
DOM 事件的深入浅出(一)
2016/12/05 Javascript
原生JS下拉加载插件分享
2016/12/26 Javascript
Ajax高级笔记 JavaScript高级程序设计笔记
2017/06/22 Javascript
JS switch判断 三目运算 while 及 属性操作代码
2017/09/03 Javascript
vue.js项目中实用的小技巧汇总
2017/11/29 Javascript
element-ui 设置菜单栏展开的方法
2018/08/22 Javascript
vue项目使用微信公众号支付总结及遇到的坑
2018/10/23 Javascript
vue-cli 3.x配置跨域代理的实现方法
2019/04/12 Javascript
Vue实现todo应用的示例
2021/02/20 Vue.js
深入讲解Python中的迭代器和生成器
2015/10/26 Python
PyQt5每天必学之弹出消息框
2018/04/19 Python
Pandas实现数据类型转换的一些小技巧汇总
2018/05/07 Python
Tensorflow 训练自己的数据集将数据直接导入到内存
2018/06/19 Python
python自动化生成IOS的图标
2018/11/13 Python
利用python实现平稳时间序列的建模方式
2020/06/03 Python
浅谈Python协程
2020/06/17 Python
python文件及目录操作代码汇总
2020/07/08 Python
Html5自定义字体解决方法
2019/10/09 HTML / CSS
世界上最好的精品店:Shoptiques
2018/02/05 全球购物
软件测试工程师笔试题带答案
2015/03/27 面试题
中级会计职业生涯规划书
2014/03/01 职场文书
新法人代表任命书
2014/06/06 职场文书
2014最新自愿离婚协议书范本
2014/11/19 职场文书
明星邀请函
2015/02/02 职场文书
2015年护士节慰问信
2015/03/23 职场文书
同意离婚答辩状
2015/05/22 职场文书