【JavaScript】深入理解JavaScript中的this

1, 为什么要用this?

来看一个例子:

function identify() {
	return this.name.toUpperCase();
}
function speak() {
	var greeting = "Hello, I'm " + identify.call( this );
	console.log( greeting );
}
var me = {
	name: "Kyle"
};
var you = {
	name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READER

如果不用this,代码会变成这样:

function identify(context) {
	return context.name.toUpperCase();
}
function speak(context) {
	var greeting = "Hello, I'm " + identify( context );
	console.log( greeting );
}
var me = {
	name: "Kyle"
};
var you = {
	name: "Reader"
};
identify( you ); // READER
speak( me ); //hello, 我是 KYLE

每次调用identify() 和 speak()都需要显式传入一个上下文对象给函数

然而, this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计
得更加简洁并且易于复用。

随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this
则不会这样。

2,对this的误解

第一种常见的错误:吧this 理解成指向函数自身

来看一个例子:

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  !!!

显然this并没有指向foo函数

也许你会回避这个问题并使用其他方法来达到目的:

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( data.count ); // 4

从某种角度来说这个方法确实“解决”了问题,但可惜它忽略了真正的问题——无法理解
this 的含义和工作原理

第二种常见的误解是, this 指向函数的作用域。在某种情况下它是正确的,但是在其他情况下它却是错误的。

来看一个经典的例子:

function foo() {
	var a = 2;
	this.bar();
}
function bar() {
	console.log( this.a );
}
foo(); // ReferenceError: a is not defined

你可能以为this.a就是foo()函数中定义的a,然而并不是你想的那样

理解this最重要的一点:
this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.

3,理解this的四种绑定规则

第一种:默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用
其他规则时的默认规则。
来看一个例子:

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

应该注意到的第一件事是,声明在全局作用域中的变量(比如 var a = 2 )就是全局对
象(Window)的一个同名属性。

接下来我们可以看到当调用 foo() 时, this.a 被解析成了全局变量 a 。为什么?因为在本
例中,函数调用时应用了 this 的默认绑定,因此 this 指向全局对象。

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

function foo() {
	"use strict";
	console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined
第二种:隐式绑定

这一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包
含。
看个例子:

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

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

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。举例来说:

function foo() {
	console.log( this.a );
}
var obj2 = {
	a: 42,
	foo: foo
};
var obj1 = {
	a: 43,
	obj2: obj2
};
obj1.obj2.foo(); // 42
隐式绑定出现的问题:隐式丢失

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

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

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

2,另一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:

function foo() {
	console.log( this.a );
}
function doFoo(fn) {
	fn();
}
var obj = {
	a: 2,
	foo: foo
};
var a = "global"; // a 是全局对象的属性
doFoo( obj.foo ); // "global"

如果把函数传入语言内置的函数而不是传入你自己声明的函数,会发生什么呢?结果是一
样的,没有区别:

function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
var a = "global"; // a 是全局对象的属性
setTimeout( obj.foo, 100 ); // "global"
第三种:显示绑定

显示绑定的主要用法就是使用fn.call(), fn.apply(), fn.bind()
call()和apply()的第一个参数都是你要绑定的对象,apply的第2个参数是一个数组,在使用
call() 方法时,传递给函数的参数必须逐个列举出来
注意:call()和apply()都是立即调用函数
看一张图片:函数并没有等待2.5s再去调用
在这里插入图片描述

接着往下看:

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

通过 foo.call(…) ,我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。

不过,这种绑定仍然无法解决丢失绑定的问题
看一个例子:

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

你试图用call来显示将this绑定到obj上,不过失败了

不过显式绑定的一个变种可以解决这个问题。称之为:硬绑定

function foo() {
	console.log( this.a );
}
var obj = {
	a:2
};
var bar = function() {
	foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2

我们创建了函数 bar() ,并在它的内部手动调用了 foo.call(obj) ,因此强制把 foo 的 this 绑定到了 obj 。无论之后如何调用函数 bar ,它总会手动在 obj 上调用 foo 。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype.
bind ,它的用法如下:

function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}
var obj = {
	a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5

注意:bind(…) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。

第四种: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
上,也就是说this就是bar

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值