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 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是动态绑定,或称为执行期绑定的。这就导致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有相当的条目与它重合。可是内容有些许差异。

时间: 2024-10-10 04:54:59

JavaScript 常见陷阱的相关文章

JavaScript的陷阱

这本来是翻译Estelle Weyl的<15 JavaScript Gotchas>,里面介绍的都是在JavaScript编程实践中平时容易出错或需要注意的地方,并提供避开这些陷阱的方法,总体上讲,就是在认清事物本质的基础样要坚持好的编程习惯,其实这就是Douglas Crockford很久以前提出的JavaScript风格要素问题了,有些内容直接是相同的,具体请看<Javascript风格要素(1)>和<Javascript风格要素(2)>.在翻译的过程中,我又看到了

JavaScript常见技术点

今天看到一篇博客讲解了几个JavaScript的技术点,感觉很实用. 原地址:Javascript常见技术点 1.javascript面向对象中继承实现 javascript面向对象中的继承实现一般都使用到了构造函数和Prototype原型链,简单的代码如下: <span style="font-family:Microsoft YaHei; font-size:12px"> function Animal(name) { this.name = name; } Anima

javascript常见的设计模式举例

    近日重读<javascript面型对象编程指南>这本书,最后一章介绍了常见的javascript设计模式的实现.主要讲解了四种设计模式:单例模式.工厂模式.装饰器模式和观察者模式.js作为动态语言,实现这四种模式的实例相对简单,当然既然称之为模式,那么吃透思想更重要,那么下面,由乐帝来实例讲解四种模式.    1.单例模式    顾名思义,对象构造出来的是实例,从字面上理解,单例即单实例,这意味一个类只能创建一个实例对象.当需要创建一种类型或者一个类的唯一对象时,可使用该模式.以下两个

Linux共享内存使用常见陷阱与分析 - 51CTO.COM http://os.51cto.com/art/201311/418977_all.htmIPC---共享内存

共享内存就是允许两个或多个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据时,不需要在客户进程和服务器进程之间幅值,因此是最快的一种IPC.不同进程之间共享的内存通常安排为同一段物理内存.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样.而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程. 注意:共享内存并未提供同步机制,也就是说,

js验证网址等Javascript常见验证代码合集

发一个利用js验证网址是否正确,email格式是否正确,是否为数字及数字的范围,密码或字符长度及是否相等及要求的最小字符串长度,输入是否为空等Javascript常见验证代码合集,用的上的朋友可以拿去了自行添加整理. 关键的JavaScript代码函数: 查看代码 打印 001 /** 002 * 数据验证框架.增加了对id字段检查出错时,直接在对应后面添加一< span>元素来显示错误信息. 003 * 004 * @author www.phpernote.com 005 * @versi

【javascript】javascript常见正则表达式实例

javascript常见正则表达式实例 实例来源 1 var myRegExp = { 2 // 检查字符串是否为合法QQ号码 3 isQQ: function(str) { 4 // 1 首位不能是0 ^[1-9] 5 // 2 必须是 [5, 11] 位的数字 \d{4, 9} 6 var reg = /^[1-9][0-9]{4,9}$/gim; 7 if (reg.test(str)) { 8 console.log('QQ号码格式输入正确'); 9 return true; 10 }

javascript常见编程模式举例

最近买到手了一本<javascript框架设计>,详细介绍开发js框架所用到的知识.初读一点,乐帝脆弱的理论修养就暴露无遗了,所以专门加强理论修养,重看javascript编程模式的举例.下面来介绍下js中,常见的编程模式.    1.命名空间    同其他高级语言一样,js中的命名空间概念,也是为了减少命名冲突,但js没有命名空间关键字.js实现命名空间的思路是定义一个全局变量,将此命名空间的变量和方法,定义为这个全局变量的属性. var MYAPP=MYAPP||{};//全局变量 MYA

JavaScript 常见安全漏洞及自动化检测技术

序言 随着 Web2.0 的发展以及 Ajax 框架的普及,富客户端 Web 应用(Rich Internet Applications,RIA)日益增多,越来越多的逻辑已经开始从服务器端转移至客户端,这些逻辑通常都是使用 JavaScript 语言所编写.但遗憾的是,目前开发人员普遍不太关注 JavaScript 代码的安全性.据 IBM X-Force 2011 年中期趋势报告揭示,世界五百强的网站及常见知名网站中有 40% 存在 JavaScript 安全漏洞.本文将结合代码向读者展示常见

JavaScript —— 常见用途

javaScript 简介 第一个JavaScript 程序: 点击按钮显示日期   <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>菜鸟教程(runoob.com)</title> <script> function displayDate(){ document.getElementById("demo").