this

1. 全局范围内使用this,它会指向全局对象

1
this;

浏览器中的全局对象为Window

2. 函数调用时,this会指向全局对象

1
foo();

注意:在对象里调用函数(不是方法),this也会指向全局对象,这会导致一些奇怪的问题 ECMAScript5严格模式下不存在全局对象,因此thisundefined

3. 方法调用时,this指向test对象

1
test.foo();

4. 调用构造函数时,在函数内部,this指向新创建的对象

1
new foo();

通过new关键字方式调用的函数都被认为是构造函数

5. 显式的设置this

即使用Function.prototype中的callapply方法时,函数内部的this指向call/apply的第一个参数

1
2
3
4
5
6
7
8
9
function foo() {
  consoloe.log(this.bar);
}

var fooo = {
  bar: 'hello'
};
foo.apply(fooo);
foo.call(fooo);

常见错误

由于第二个规则,在对象方法内调用函数会使函数内部的this指向全局对象,所以下面代码的结果不是预期想要的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var Foo;

Foo = (function() {
  function Foo() {
    this.bar = 'hello';
  }
  Foo.prototype.method = function() {
    function test() {
      console.log(this.bar);
    }
    test();                 // undefined, except 'hello'
    console.log(this.bar);  // 'hello'
  };

  return Foo;
})();

var foo = new Foo;
foo.method();

解决方法一:可以先获取对this的引用

1
2
3
4
5
6
7
8
Foo.prototype.method = function() {
  var that = this;
  function test() {
    console.log(that.bar);
  }
  test();                 // 'hello'
  console.log(this.bar);  // 'hello'
};

解决方法二:CoffeeScript中的fat arrow =>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Foo.prototype.method = function() {
  var test;
  test = (function(_this) {
    return function() {
      console.log(_this.bar);
    };
  })(this);
  test();                 // 'hello'
  console.log(this.bar);  // 'hello'
};

原理相似,只不过用了IIFE而已

另外一个奇怪的地方就是当一个方法赋值给一个变量的时候:

1
2
var test = foo.method;
test();

此处的test是被当作普通函数调用的,因此函数内部的this不是指向foo对象的!

解决方法很简单,经常能够看见:

1
2
3
4
5
test.call(foo);     // or apply

// 常用
var hasProp = {}.hasProperty;
hasProp.call(foo, bar);

作用域与命名空间

JavaScript不支持块级作用域

虽然语法类似与C/C++,但是JS使用的是函数作用域,理解这一点非常重要

函数声明与表达式的不同

函数声明会被提前,所以像下面这段常识中不可正常运行的代码其实是能够正常运行的

1
2
3
4
foo();  // 'hello world'
function foo() {
  console.log("Hello world");
}

但是!如果是函数赋值表达式就不同了

1
2
3
4
5
foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {
  console.log("hello world");
};

上面这段代码其实相当于这段:

1
2
3
4
var foo;
foo;    // undefined
foo();  // TypeError
foo = function() {};

提升规则

声明变量(还有函数声明)会被提到当前作用域的最前端,在运行前解析,但是赋值却是在后面完成的,即在执行时执行 Keyword: hoisting

由于提升规则的存在,有时在不注意间会导致匪夷所思的结果:

1
2
3
4
var foo = 'bar';
(function() {
  console.log(foo);     // 'bar'
})();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var foo = 'bar';
(function() {
  console.log(foo);     // undefined
  var foo = 'inner';
})();

/* actual code */
var foo = 'bar';
(function() {
  var foo;
  console.log(foo);
  foo = 'inner';
})();