JavaScript 常见陷阱

来源:互联网 发布:淘宝视频制作 编辑:程序博客网 时间:2024/06/05 06:26

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 Brandon
1.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


2 函数级作用域

不像常见的语言使用块级作用域,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是动态绑定,或称为运行期绑定的,这就导致this关键字有能力具备多重含义,一句话总结就是:“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); // ==> 1console.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是JavaScript关键字,而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; // ==> truedelete y; // ==> false


放在最后,前有古人Jonathan Cardy在codeproject写了一篇A Collection of JavaScript Gotchas(JavaScript陷阱合集),中文翻译版本也可以从网上轻易搜到,比如这里

本篇blog有相当的条目与它重合。但是内容有些许差异。

1 0
原创粉丝点击