JavaScript中的陷阱大集合(1)(2)
类型和构造函数
使用“new”关键字构造内置类型
Javascript中有Object, Array, Boolean, Number, String, 和Function这些类型,他们各自都有各自的文字语法,所以就不需要显式构造函数了。
| 显式构造(不建议) | 文字语法(推荐) |
| var a = new Object(); a.greet = "hello"; |
var a = { greet: "hello" }; |
| var b = new Boolean(true); | var b = true; |
| var c = new Array("one", "two"); | var c = ["one", "two"]; |
| var d = new String("hello"); | var d = "hello" |
| var e = new Function("greeting", "alert(greeting);"); | var e = function(greeting) { alert(greeting); }; |
然而,如果你使用new关键字来构造上面其中的一种类型,你实际上将会得到一个类型为Object并且继承自你要构造的类型的原型的对象(Function类型除外)。所以尽管你用new关键字构造了一个Number类型,它也将是一个Object类型,如下代码:
- typeof new Number(123); // "object"
- typeof Number(123); // "number"
- typeof 123; // "number"
上面的第三项是文本语法,为了避免冲突,我们应该使用这种方法来构造上面的这些类型。
使用“new”关键字来构造任何东西
如果你自写构造函数并且忘记了new关键字,那么悲剧就发生了:
- var Car = function(colour) {
- this.colour = colour;
- };
- var aCar = new Car("blue");
- console.log(aCar.colour); // "blue"
- var bCar = Car("blue");
- console.log(bCar.colour); // error
- console.log(window.colour); //"blue"
使用new关键字调用函数会创建一个新的对象,然后调用新对象上下文中的函数,最后再返回该对象。相反的,如果不使用new关键在调用函数,那它将会变成一个全局对象。
偶然忘记使用new关键字意味着很多可选择的对象构造模式已经出现可以完全删除使用这个关键字的需求的情况,尽管这超出了本文的范围,但我还是建议你去进一步阅读。
没有Integer类型
数值计算是相对缓慢的,因为没有Integer类型。只有Number类型 - Number是IEEE标准中双精度浮点运算(64位)类型。这就意味着Number会引起下面的精度舍入错误:
- 0.1 + 0.2 === 0.3 //false
因为integers和floats没有区别,不像C#和JAVA下面代码是true:
- 0.0 === 0; //true
最后是一个关于Number的疑问,我们该如何实现下面的问题:
- a === b; //true
- 1/a === 1/b; //false
答案是按照Number的规范是允许出现+0和-0的,+0等于-0,但是正无穷大不等于负无穷大,代码如下:
- var a = 0 * 1; // 这个结果为0
- var b = 0 * -1; // 这个结果为-0 (你也可以直接"b=-0",但是你为何要这样做?)
- a === b; //true: 0等于-0
- 1/a === 1/b; //false: 正无穷大不等于负无穷大
作用域
没有块作用域
因为你可能已经注意到上一个观点,javascript中没有块作用域的概念,只有函数作用域。可以试试下面的代码:
- for(var i=0; i<10; i++) {
- console.log(i);
- }
- var i;
- console.log(i); // 10
当i被定义在for循环中,退出循环后它人被保留在这个作用域内,所以最后调用console.log输出了10。这里有一个JSLint警告来让你避免这个问题:强制将所有的变量定义在函数的开头。 我们有可能通过写一个立即执行的function来创建一个作用域:
- (function (){
- for(var i=0; i<10; i++) {
- console.log(i);
- }
- }());
- var i;
- console.log(i); // undefined
当你在内部函数之前声明一个变量,然后在函数里重声明这个变量,那将会出现一个奇怪的问题,示例代码如下:
- var x = 3;
- (function (){
- console.log(x + 2); // 5
- x = 0; //No var declaration
- }());
但是,如果你在内部函数中重新声明x变量,会出现一个奇怪的问题:
- var x = 3;
- (function (){
- console.log(x + 2); //NaN - x is not defined
- var x = 0; //var declaration
- }());
这是因为在函数中x变量被重新定义了,这说明了翻译程序将var表达式移动到了函数顶部了,最终就变成这样执行了:
- var x = 3;
- (function (){
- var x;
- console.log(x + 2); //NaN - x is not defined
- x = 0;
- }());
这个实在是太有意义了!
全局变量
Javascript 有一个全局作用域,在为你的代码创建命名空间时一定要小心谨慎。全局变量会给你的应用增加一些性能问题,因为当你访问它们时,运行时不得不通过每一个作用 域来建立知道找到它们为止。他们会因你的有意或者无意而被访问或者修改,这将导致另外一个更加严重的问题 - 跨站点脚本攻击。如果一个不怀好意的家伙在你的页面上找出了如何执行那些代码的方法,那么他们就可以通过修改全局变量非常容易地扰乱你的应用。缺乏经验的 开发者在无意中会不断的将变量添加到全局作用域中,通过本文,将会告诉大家这样会发生什么意外的事情。
我曾经看到过下面的代码,它将尝试声明两个值相等的局部变量:
- var a = b = 3;
这样非常正确的得到了a=3和b=3,但是a在局部作用域中而b在全局作用域中,”b=3“将会被先执行,全局操作的结果,3,再被分配给局部变量a。
下面的代码声明了两个值为3的变量,这样能达到预期的效果:
- var a = 3,
- b = a;
“this”和内部函数
“this“关键字通常指当前正在执行的函数所在的对象,然而,如果函数并没有在对象上被调用,比如在内部函数中,”this“就被设置为全局对象(window),如下代码:
- var obj = {
- doSomething: function () {
- var a = "bob";
- console.log(this); // 当前执行的对象
- (function () {
- console.log(this); // window - "this" is reset
- console.log(a); // "bob" - still in scope
- }());
- }
- };
- obj.doSomething();






