JavaScript 常见陷阱
JavaScript中的一些特性和通常我们想象的不太一样。这里我总结了一些有悖直觉的语言特性。
1 数组
1.1 数组的遍历
在直接支持for a in b的语言中,比方Python/Ruby里的a的值都是容器内保存的值。可是在JavaScript中。a仅仅代表属性。假设b是一个数组,则a就是索引(0~n),所以正确的使用for in 遍历数组的写法例如以下:
var friends = ["Tom", "Jick", "Brandon"]; for(i in friends) { console.log(friends[i]); } // ==> Tom Jick Brandon1.2 数组的长度
JavaScript是能够有稀疏数组的。所以数组对象的length属性并不一定是当中元素的个数,length仅仅是数组里最大索引值+1。
var a = []; a[100] = "aa"; console.log(a.length); // ==> 101假设想获得数组元素的个数。就仅仅能自己动手丰衣足食了。通过遍历其属性(也就是索引)来实现:
var n = 0; for(i in a) { n++; } console.log(n); // ==> 1
不像常见的语言使用块级作用域,JavaScript没有块级作用域。而使用函数作用域,在函数内声明的全部变量在函数体内始终是可见的,这意味着变量在声明之前已经能够使用!
!參看以下的代码,函数体内部的局部变量遮盖了同名全局变量,可是仅仅有在程序运行到var语句的时候,局部变量才会被真正赋值,JavaScript的这个特性被非正式的称为“声明提前”。
var scope = "global"; function f() { console.log(scope); var scope = "local"; console.log(scope); } // ==> "undefined" // ==> "local"上面的代码等效于以下的更好理解的代码:
var scope = "global"; function f() { var scope; console.log(scope); var scope = "local"; console.log(scope); } // ==> "undefined" // ==> "local"
所以把函数声明尽量放在函数体顶部,而不是将声明放在使用变量的地方,这样能够更清晰的反应变量的生命周期,降低误用。
3 this变量
JavaScript 中的this是动态绑定,或称为执行期绑定的。这就导致thiskeyword有能力具备多重含义。一句话总结就是:“this在函数内被调用时,this被绑定到调用函数的那个对象”。
当this位于全局函数和对象的函数中时,这很好理解。
陷阱出如今以下两种情况中:
3.1 this引用的对象发生了改变:
以下的代码本来打算在回调中改动Point对象里的坐标x和y。结果由于move函数已经被传递给button.oncilck导致this变成了button,move操作没能改动point对象却在button里新建了两个变量x和y 。
function Point(x, y) { this.x = x; this.y = y; this.name = "Point"; this.move = function(){ this.x = 3; this.y = 4; console.log(this); } } var point = new Point(1,2); var button = new Object(); button.name = "Button"; button.onclick = point.move; button.onclick(); console.log(point); // ==> { name: 'Button', onclick: [Function], x: 3, y: 4 } // ==> { x: 1, y: 2, name: 'Point', move: [Function] }
解决方式就是"that法":
不要在回调函数里调用this,我们用一个变量that事先存储好this。在函数里用that。因为that变量是一个普通变量。在对象Point构造之初就已经确定了,所以它不会产生"跳变"
function Point(x, y) { this.x = x; this.y = y; this.name = "Point"; var that = this; this.move = function(){ that.x = 3; that.y = 4; console.log(that); } } var point = new Point(1,2); var button = new Object(); button.name = "Button"; button.onclick = point.move; button.onclick(); console.log(point); // ==> { x: 3, y: 4, name: 'Point', move: [Function] } // ==> { x: 3, y: 4, name: 'Point', move: [Function] }
3.2 嵌套函数中使用this
在一个函数体内定义的函数里使用this。this会引用全局对象,所以以下的代码没有改动掉point的x和y属性。而是创建了两个全局变量x和y !这属于JavaScript的设计缺陷,应对方法也是that法,參考上面"陷阱1"的解法就可以。
var point = { x : 0, y : 0, moveTo : function(x, y) { var moveX = function(x) { this.x = x; }; var moveY = function(y) { this.y = y; }; moveX(x); moveY(y); } }; point.moveTo(1, 1); console.log(point); // ==> { x: 0, y: 0, moveTo: [Function] } console.log(x); // ==> 1 console.log(y); // ==> 1
4 对象直接量和JSON
JSON("JavaScript Object Notation")JavaScript对象表示法,它的语法和JavaScript对象直接量的语法很相近。JSON是某一种格式的字符串。他是把JavaScript对象序列化的结果。
当然我们也能够反序列化这个字符串来还原对象。
对象直接量是用来初始化对象的一种方法。对象直接量由JavaScript语法支持。
注意:JSON语法是JavaScript语法的子集。它并不能表示JavaScript里的全部值,比方undefined就不能序列化和还原。obj = {x:1, y:[1,2,3], z:undefined}; var str = JSON.stringify(obj); //JavaScript对象 ==> JSON字符串 console.log(str); // ==> '{"x":1,"y":[1,2,3]}' obj2 = JSON.parse(str); //JSON字符串 ==> JavaScript对象
关于JSON,稍后我会发一篇很不错的翻译小文。
5 undefined 和 null
差别:
null是JavaScriptkeyword,而undefined是一个提前定义的全局变量(ECMACScript 5已修订为不可写)他们在使用上差点儿没有不论什么差异。依据犀牛书的介绍。能够依照这种意义区别来理解他们:"undefined是表示系统级的,出乎意料的或类似错误的空缺,而null是表示程序级的,正常的或在医疗之中的空缺。 假设你想将他们赋值给变量或者属性,或将它们作为參数传入函数。最佳选择是null。"
个人觉得在实践中。主动使用空值的时候要远远少于判空,非常多时候我们不会主动将一个空值设置给变量,而很多其它的时候事实上是要推断一个值是不是为空,在大多数场景下,假设你尝试打印这种空值一般会得到undefined。所以undefined的使用场景是远远大过null的。所以我更倾向于使用undefined。
6 replace
在Java/C#/Python中,Replace(a, b)函数都是运行的全局替换,将字符串中出现的所有a所有替换成b,可是在JavaScript中。仅仅能替换掉第一个字符,假设要全局替换,必须写成以下的第二行的样子,使用正则表示法进行全局替换:
var s = "abacad"; s.replace("a", "1"); // ==> '1bacad' s.replace(/a/g, "1"); // ==> '1b1c1d'
7 全局变量
给一个未声明的变量赋值,JavaScript实际上会给全局对象创建一个同名属性。他工作起来就像一个全局变量。
这可能会造成非常多bug。以下的代码并不会输出undefined,而是输出abc
function f() { x = "abc"; } f(); console.log(x); // ==> abc注意这种全局变量和正常定义的全局变量有一点区别,它是可配置(所以可delete)的,而正常定义的全局变量是不可配置(delete失败)的:
var y = "xyz"; function f() { x = "abc"; } f(); Object.getOwnPropertyDescriptor(this, "x"); // ==> { value: 'abc', writable: true, enumerable: true, configurable: true } Object.getOwnPropertyDescriptor(this, "y"); // ==> { value: 'xyz', writable: true, enumerable: true, configurable: false } delete x; // ==> true delete y; // ==> false
放在最后。前有古人Jonathan Cardy在codeproject写了一篇A Collection of JavaScript Gotchas(JavaScript陷阱合集),中文翻译版本号也能够从网上轻易搜到,比方这里
本篇blog有相当的条目与它重合。可是内容有些许差异。