JavaScript中的this详解

this到底是什么

this 是在运行时进行绑定的,并不是在编写时绑定。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

一些对this的误解

1. 把 this 理解成指向函数自身,从以下例子可看出指向函数自身的理解是错误的

function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
this.count++;  //this 并不是指向foo()函数对象,而是创建了一个全局变量count
}
foo.count = 0;  //的确向函数对象 foo 添加了一个属性 count。
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 -- WTF?

若想要使count的输出结果为4,可以使用词法作用域,但是使用词法作用域忽略了真正的问题,避开了this的用法

function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
data.count++;  //当前作用域无法找到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( data.count ); // 4

还可使用foo标识符替代this来引用函数对象解决,但是同样回避了 this 的问题,并且完全依赖于变量 foo 的词法作用域。

function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
foo.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 ); // 4

不回避this也有解决方法,那就是强制 this 指向 foo 函数对象(在下面绑定规则中的显式绑定会详细解释)

function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
// 注意,在当前的调用方式下(参见下方代码),this 确实指向 foo
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
// 使用 call(..) 可以确保 this 指向函数对象 foo 本身
foo.call( foo, i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 4

2.把this 理解为指向函数的作用域,在某种情况下它是正确的,但是在其他情况下它却是错误的。但是this 在任何情况下都不指向函数的词法作用域。如下例

function foo() {
var a = 2;
this.bar();//通过 this.bar() 来引用 bar() 函数,这是绝对不可能成功的
}
function bar() {
console.log( this.a );//试图使用 this 联通 foo() 和 bar() 的词法作用域,从而让
//bar() 可以访问 foo() 作用域里的变量 a。这是不可能实现的,你不能使用 this 来引用一
//个词法作用域内部的东西。
}
foo(); // ReferenceError: a is not defined

绑定规则

1.默认规则

可以把这条规则看作是无法应用 其他规则时的默认规则。如下例

function foo() {
console.log( this.a );//函数调用时应用了 this 的默认绑定,因此 this 指向全局对象。this.a 被解析成了全局变量 a
}
var a = 2;
foo(); // 2  foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined,此时的调用结果为TypeError: this is undefined

2.隐式绑定

调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2

无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj 对象。然而,调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥 有”或者“包含”它。

当 foo() 被调用时,它的落脚点确实指向 obj 对象。当函数引 用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。因为调 用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。

隐式丢失

一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

3.显示绑定

若想在某个对象上强制调用函数,可以使用函数的 call(..) 和 apply(..) 方法。它们的第一个参数是一个对象,它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我 们称之为显式绑定。如下所示

function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2

4.new绑定

在 JavaScript 中,构造函数只是一些 使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

1. 创建(或者说构造)一个全新的对象。

2. 这个新对象会被执行 [[ 原型 ]] 连接。

3. 这个新对象会绑定到函数调用的 this。

4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象

function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。

箭头函数无法使用这些规则,箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。

优先级

从低至高

默认绑定、隐式绑定、显式绑定、new绑定

上述内容参考《你不知道的JavaScript》

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值