JavaScript中的一些特性和通常我们想象的不太一样。这里我总结了一些有悖直觉的语言特性。

1 数组

1.1 数组的遍历

在直接支持for a in b的语言中,比方Python/Ruby里的a的值都是容器内保存的值。可是在JavaScript中。a仅仅代表属性。假设b是一个数组,则a就是索引(0~n),所以正确的使用for in 遍历数组的写法例如以下:

  1. var friends = ["Tom", "Jick", "Brandon"];
  2. for(i in friends) {
  3. console.log(friends[i]);
  4. }
  5. // ==> Tom Jick Brandon

1.2 数组的长度

JavaScript是能够有稀疏数组的。所以数组对象的length属性并不一定是当中元素的个数,length仅仅是数组里最大索引值+1。

  1. var a = [];
  2. a[100] = "aa";
  3. console.log(a.length);
  4. // ==> 101

假设想获得数组元素的个数。就仅仅能自己动手丰衣足食了。通过遍历其属性(也就是索引)来实现:

  1. var n = 0;
  2. for(i in a) {
  3. n++;
  4. }
  5. console.log(n);
  6. // ==> 1

2 函数级作用域

不像常见的语言使用块级作用域,JavaScript没有块级作用域。而使用函数作用域,在函数内声明的全部变量在函数体内始终是可见的,这意味着变量在声明之前已经能够使用!

!參看以下的代码,函数体内部的局部变量遮盖了同名全局变量,可是仅仅有在程序运行到var语句的时候,局部变量才会被真正赋值,JavaScript的这个特性被非正式的称为“声明提前”。

  1. var scope = "global";
  2. function f() {
  3. console.log(scope);
  4. var scope = "local";
  5. console.log(scope);
  6. }
  7. // ==> "undefined"
  8. // ==> "local"

上面的代码等效于以下的更好理解的代码:

  1. var scope = "global";
  2. function f() {
  3. var scope;
  4. console.log(scope);
  5. var scope = "local";
  6. console.log(scope);
  7. }
  8. // ==> "undefined"
  9. // ==> "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 。

  1. function Point(x, y) {
  2. this.x = x;
  3. this.y = y;
  4. this.name = "Point";
  5. this.move = function(){
  6. this.x = 3;
  7. this.y = 4;
  8. console.log(this);
  9. }
  10. }
  11. var point = new Point(1,2);
  12. var button = new Object();
  13. button.name = "Button";
  14. button.onclick = point.move;
  15. button.onclick();
  16. console.log(point);
  17. // ==> { name: 'Button', onclick: [Function], x: 3, y: 4 }
  18. // ==> { x: 1, y: 2, name: 'Point', move: [Function] }

解决方式就是"that法":

不要在回调函数里调用this,我们用一个变量that事先存储好this。在函数里用that。因为that变量是一个普通变量。在对象Point构造之初就已经确定了,所以它不会产生"跳变"

  1. function Point(x, y) {
  2. this.x = x;
  3. this.y = y;
  4. this.name = "Point";
  5. var that = this;
  6. this.move = function(){
  7. that.x = 3;
  8. that.y = 4;
  9. console.log(that);
  10. }
  11. }
  12. var point = new Point(1,2);
  13. var button = new Object();
  14. button.name = "Button";
  15. button.onclick = point.move;
  16. button.onclick();
  17. console.log(point);
  18. // ==> { x: 3, y: 4, name: 'Point', move: [Function] }
  19. // ==> { x: 3, y: 4, name: 'Point', move: [Function] }

3.2 嵌套函数中使用this

在一个函数体内定义的函数里使用this。this会引用全局对象,所以以下的代码没有改动掉point的x和y属性。而是创建了两个全局变量x和y !这属于JavaScript的设计缺陷,应对方法也是that法,參考上面"陷阱1"的解法就可以。

  1. var point = {
  2. x : 0,
  3. y : 0,
  4. moveTo : function(x, y) {
  5. var moveX = function(x) {
  6. this.x = x;
  7. };
  8. var moveY = function(y) {
  9. this.y = y;
  10. };
  11. moveX(x);
  12. moveY(y);
  13. }
  14. };
  15. point.moveTo(1, 1);
  16. console.log(point); // ==> { x: 0, y: 0, moveTo: [Function] }
  17. console.log(x); // ==> 1
  18. console.log(y); // ==> 1

4 对象直接量和JSON

JSON("JavaScript Object Notation")JavaScript对象表示法,它的语法和JavaScript对象直接量的语法很相近。JSON是某一种格式的字符串。他是把JavaScript对象序列化的结果。

当然我们也能够反序列化这个字符串来还原对象。

对象直接量是用来初始化对象的一种方法。对象直接量由JavaScript语法支持。

注意:JSON语法是JavaScript语法的子集。它并不能表示JavaScript里的全部值,比方undefined就不能序列化和还原。

  1. obj = {x:1, y:[1,2,3], z:undefined};
  2. var str = JSON.stringify(obj); //JavaScript对象 ==> JSON字符串
  3. console.log(str); // ==> '{"x":1,"y":[1,2,3]}'
  4. 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中。仅仅能替换掉第一个字符,假设要全局替换,必须写成以下的第二行的样子,使用正则表示法进行全局替换:

  1. var s = "abacad";
  2. s.replace("a", "1"); // ==> '1bacad'
  3. s.replace(/a/g, "1"); // ==> '1b1c1d'

7 全局变量

给一个未声明的变量赋值,JavaScript实际上会给全局对象创建一个同名属性。他工作起来就像一个全局变量。

这可能会造成非常多bug。以下的代码并不会输出undefined,而是输出abc

  1. function f() {
  2. x = "abc";
  3. }
  4. f();
  5. console.log(x);
  6. // ==> abc

注意这种全局变量和正常定义的全局变量有一点区别,它是可配置(所以可delete)的,而正常定义的全局变量是不可配置(delete失败)的:

  1. var y = "xyz";
  2. function f() {
  3. x = "abc";
  4. }
  5. f();
  6. Object.getOwnPropertyDescriptor(this, "x");
  7. // ==> { value: 'abc', writable: true, enumerable: true, configurable: true }
  8. Object.getOwnPropertyDescriptor(this, "y");
  9. // ==> { value: 'xyz', writable: true, enumerable: true, configurable: false }
  10. delete x; // ==> true
  11. delete y; // ==> false

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

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

JavaScript 常见陷阱的更多相关文章

  1. JavaScript的陷阱

    这本来是翻译Estelle Weyl的<15 JavaScript Gotchas>,里面介绍的都是在JavaScript编程实践中平时容易出错或需要注意的地方,并提供避开这些陷阱的方法, ...

  2. Java多线程编程的常见陷阱(转)

    Java多线程编程的常见陷阱 2009-06-16 13:48 killme2008 blogjava 字号:T | T 本文介绍了Java多线程编程中的常见陷阱,如在构造函数中启动线程,不完全的同步 ...

  3. JavaScript 常见使用误区

    JavaScript 常见使用误区,都是平时开发过程中填过的一些坑,整理记录下. 比较运算符常见错误 //在常规的==比较中,数据类型是被忽略的,以下 if 条件语句返回 true: var x = ...

  4. JavaScript常见集合操作

    JavaScript常见集合操作 集合的遍历 FOR循环(效率最高) 优点:JavaScript最普遍的for循环,执行效率最高 缺点:无法遍历对象 for(let i=0;i<array.le ...

  5. 关于JavaScript 常见的面试题

    关于JavaScript常见的面试题总结 一.JavaScript基本数据类型 null:空.无.表示不存在,当为对象的属性赋值为null,表示删除该属性 undefined:未定义.当声明变量却没有 ...

  6. javascript常见的20个问题与解决方法

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. JavaScript 常见的六种继承方式

    JavaScript 常见的六种继承方式 前言 面向对象编程很重要的一个方面,就是对象的继承.A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 大部分 ...

  8. JavaScript —— 常见用途

    javaScript 简介 第一个JavaScript 程序: 点击按钮显示日期   <!DOCTYPE html> <html> <head> <meta ...

  9. JavaScript常见的六种继承方式

    前言 面向对象编程很重要的一个方面,就是对象的继承.A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 大部分面向对象的编程语言,都是通过"类 ...

随机推荐

  1. Swift - 控制流/控制结构说明(if,switch,for,while)

    1,if语句 1 2 3 4 5 if count >=3 {     println("yes") }else{     println("no") } ...

  2. linux命名管道通信过程

    前一个道,这节学习命名管道. 二命名管道 无名管道仅仅能用来在父子进程或兄弟进程之间进行通信,这就给没有亲缘关系的进程之间数据的交换带来了麻烦.解决问题就是本节要学习的还有一种管道通信:命名管道. 命 ...

  3. setjmp和longjmp函数使用详解

    源地址:http://blog.csdn.net/zhuanshenweiliu/article/details/41961975 非局部跳转语句---setjmp和longjmp函数.非局部指的是, ...

  4. Delphi面向对象设计的经验原则(61条)

    (1)所有数据都应该隐藏在所在的类的内部. (2)类的使用者必须依赖类的共有接口,但类不能依赖它的使用者. (3)尽量减少类的协议中的消息. (4)实现所有类都理解的最基本公有接口[例如,拷贝操作(深 ...

  5. Referer反反盗链

    0x00 前言 最近用Python非常多,确实感受到了Python的强大与便利.但同时我并没有相见恨晚的感觉,相反我很庆幸自己没有太早接触到Python,而是基本按着C→C++→Java→Python ...

  6. HTML5文件上传还有进度条

    以下是自学it网--中级班上课笔记 网址:www.zixue.it 需要在chrome,ff,IE10下运行 html页面 <!DOCTYPE html> <html lang=&q ...

  7. Jquery事件的连接

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. 解决xShell4某些情况下按删除键会输出^H的问题

    当我们用Xshell登录进入linux后,在普通模式下,对输入进行删除等操作没有问题. 而在执行中,按delete,backspace键时会产生^H等乱码问题. 这是由于编码不匹配的问题. 解决方法: ...

  9. 第m个全排列

    #include<stdio.h> #include<string.h> int flag,n,m; ],sum,vis[]; void dfs(int k) { ) retu ...

  10. 关于使用commons-email包测试发送邮件遇到的问题

    项目中有个需求是这样的:客户办理某一项业务,当用户成功提交业务办理信息后,系统生成一个业务随机码给用户,以此作为以后的业务办理结果查询依据.鉴于随机码较长,方便用户记录,在生成随机码的同时,提供用户发 ...