未分类

你不知道的JavaScript——this和对象原型

一、误区

1. this指向自身
思考以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function foo(num) {
console.log("foo: "+ num);

//记录foo被调用的次数
this.count++;
}
foo.count = 0;
var i;
for (i = 0; i<10; i++) {
if (i<5) {
foo(i);
}
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(foo.count); // 0 ????

console.log()确实输出了4句,但是最终打印出来的还是0,显然字面意思理解的this是错误的。

真实原因是这段代码创建了一个全局变量count,它的值为NaN,具体解释后边会写

当一个函数对象内部引用自身,只使用this是不够的。一般需要一个指向函数对象的词法标识符(变量)来引用它。

1
2
3
4
5
6
7
function foo() {
foo.count = 4; // foo指向本身
}

setTimeout( function (){
// 匿名函数无法指向自身
}, 10)

ps:曾经有(唯一)一种方法可以让匿名函数内部引用自身——arguments.callee,目前该方法被弃用

如果想实现我们预期的效果,我们可以使用词法作用域,将count变为一个已知对象的属性然后在函数内部使用,并打印输出(如下所示),但这种方法回避了this的问题,我们不深究

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function foo(num) {
console.log("foo: "+ num)

//记录foo被调用的次数
data.count++;
}
var data = {
count: 0;
}
var i;
for (i = 0; i<10; i++) {
if (i<5) {
foo(i);
}
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(foo.count); // 4

另一种方法是使用call方法,改变this指向,我们后边会详细得说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function foo(num) {
console.log("foo: "+ num);

//记录foo被调用的次数
this.count++;
}
foo.count = 0;
var i;
for (i = 0; i<10; i++) {
if (i<5) {
foo.call(foo, i)
}
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被调用了多少次?
console.log(foo.count); // 4

2. 它的作用域
大部分人认为this指向函数作用域,确实在某种情况下它是正确的,但是在其他情况下是错误的。需要明确的是this在任何情况下都不指向函数的词法作用域。作用域“对象”([[scope]])无法通过JavaScript代码访问,它存在于JS引擎内部

this到底是什么呢?

this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。