this全面解析(this的前世今生)

总结起来就是四句话:4步走规则+3个例外+2个误解+1个箭头函数 【稳】

一、关于this

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

1.1、为什么要使用this

Talk is cheap,show me the code.先来看下比较两段代码你就明白了:

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

这段代码可以在不同的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(),不用针对每个对象编写不同版本的函数。

如果不使用 this,那就需要给 identify() 和 speak() 显式传入一个上下文对象。

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

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

1.2、对this的误解

1.2.1、指向自身

来看一段代码:

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 -- WTF?

执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count。但是函数内部代码this.count 中的 this 并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相同。
实际上,这段代码在无意中创建了一个全局变量 count(原理见***),它的值为 NaN。(**undefined++**为NaN)

那如何解决让对象内部引用它自身呢?
如果要从函数对象内部引用它自身,那只使用 this 是不够的。一般来说你需要通过一个指向函数对象的词法标识符(变量)来引用它
思考如下两个函数:

//具名函数,在它内部可以使用foo引用自身
function foo() {
	foo.count = 4; // foo 指向它自身
}
setTimeout( function(){
	// 匿名(没有名字的)函数无法指向自身,也就无法从函数内部引用自身
}, 10 );

唯一一种可以从匿名函数对象内部引用自身的方法:arguments.callee 来引用当前正在运行的函数对象。该方法已弃用

最佳实践:更好的方式是避免使用匿名函数,至少在需要自引用时使用具名函数(表达式)

由此引出如下解决办法:

  1. 用 foo 标识符替代 this(回避了this的问题)
  2. 强制 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
1.2.2、它的作用域

需要明确的是,this 在任何情况下都不指向函数的词法作用域。
在JavaScript 内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript代码访问,它存在于 JavaScript 引擎内部。

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

首先,这段代码试图通过this.bar() 来引用 bar() 函数。这是绝对不可能成功的,调用成功纯属意外。调用 bar() 最自然的方法是省略前面的 this,直接使用词法引用标识符。

此外,编写这段代码的开发者还试图使用 this 联通 foo() 和 bar() 的词法作用域,从而让bar() 可以访问 foo() 作用域里的变量 a。这是不可能实现的,你不能使用 this 来引用一个词法作用域内部的东西。

谨记:每当你想要把 this 和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。

1.3、this到底是什么?(好好理解,记住)

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

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

如何寻找函数的【调用】位置,从而判断函数在执行过程中会如何绑定this?

1.4、小结

  • 学习 this 的第一步是明白 this 既不指向函数自身也不指向函数的词法作用域,你也许被这样的解释误导过,但其实它们都是错误的。
  • this 实际上是在函数被【调用】时发生的绑定,它指向什么完全取决于 【函数在哪里被调用】

二、this全面解析

根据上一章所讲,每个函数的 this 是在 【调用时】 被绑定的,完全取决于函数的 【调用位置】(也就是函数的调用方法)。

2.1 调用位置

定义:

调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

寻找调用位置就是寻找“函数被调用的位置”,但是做起来并没有这么简单,因为某些编程模式可能会隐藏真正的调用位置。

【最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。】

【注意理解下面这段代码】:注意我们是如何(从调用栈中)分析出真正的调用位置的,因为它决定了 this 的绑定。

function baz() {
	// 当前调用栈是:baz
	// 因此,当前调用位置是全局作用域
	console.log( "baz" );
	bar(); // <-- bar 的调用位置
}
function bar() {
	// 当前调用栈是 baz -> bar
	// 因此,当前调用位置在 baz 中
	console.log( "bar" );
	foo(); // <-- foo 的调用位置
}
function foo() {
	// 当前调用栈是 baz -> bar -> foo
	// 因此,当前调用位置在 bar 中
	console.log( "foo" );
}
baz(); // <-- baz 的调用位置

你可以把调用栈想象成一个函数调用链,就像我们在前面代码段的注释中所写的一样。但是这种方法非常麻烦并且容易出错。如果你想要分析 this 的绑定,使用开发者工具得到调用栈,然后找到栈中第二个元素,这就是真正的调用位置。

2.2 绑定规则(四条规则)

必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。若多条规则都可用时它们的优先级如何排列。

2.2.1 默认绑定

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

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

函数调用时应用了 this 的默认绑定,因此 this 指向全局对象
【分析】:foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

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

function foo() {
	"use strict";
	console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined

【特别注意】
这里有一个微妙但是非常重要的细节,虽然 this 的绑定规则完全取决于调用位置,但是只有 foo() 【运行在非 strict mode 下时】默认绑定才能绑定到全局对象严格模式下与 foo()的调用位置无关【严格模式下调用foo()则不影响默认绑定】

function foo() {
	//非严格模式执行
	console.log( this.a );
}
var a = 2;
(function(){
	//严格模式调用不影响默认绑定
	"use strict";
	foo(); // 2
})();
2.2.2 隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,不过这种说法可能会造成一些误导。

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

首先需要注意的是 foo() 的声明方式,及其之后是如何被当作引用属性添加到 obj 中的。但是无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj 对象

然而,调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它

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

对象属性引用链中只有上一层或者说最后一层直接调用者)会影响调用位置。如下代码所示:

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

在这里插入图片描述

【隐式丢失问题】
一个最常见的 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() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

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

function foo() {
	console.log( this.a );
}
function doFoo(fn) {
	// fn 其实引用的是 foo   **fn=obj.foo**
	//参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。
	fn(); // <-- 调用位置!
}
var obj = {
	a: 2,
	foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"

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

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

JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码类似

function setTimeout(fn,delay) {
	// 等待 delay 毫秒
	fn(); // <-- 调用位置!
}

就像我们看到的那样,回调函数丢失 this 绑定是非常常见的。除此之外,还有一种情况 this 的行为会出乎我们意料:调用回调函数的函数可能会修改 this。在一些流行的JavaScript 库中事件处理器常会把回调函数的 this 强制绑定到触发事件的 DOM 元素上。

下一节将介绍如何通过固定this来修复这个问题。

2.2.3 显式绑定

在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式) 绑定到这个对象上。
那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?

具体点说,可以使用函数的 call(…) 和apply(…) 方法。
从 this 绑定的角度来说,call(…) 和 apply(…) 是一样的,它们的区别体现在其他的参数上
它们的第一个参数是一个对象,是给this准备的,它们会把这个对象绑定到this接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我们称之为显式绑定

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

如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(…)、new Boolean(…) 或者new Number(…))。这通常被称为“装箱”。

但是,显式绑定仍然无法解决我们之前提出的丢失绑定问题。
【硬绑定】
但是显式绑定的一个变种可以解决这个问题。

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。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定

硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:

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

另一种使用方法是创建一个 可以重复使用的辅助函数:

function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
	return function() {
		return fn.apply( obj, arguments );
	};
}
var obj = {
	a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5

由于硬绑定是一种非常常用的模式,所以在 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 的上下文】 并调用原始函数。

【API调用的“上下文”】
第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为 “上下文”(context)其作用和 bind(…) 一样,确保你的回调函数使用指定的 this。

这些函数实际上就是通过 call(…) 或者 apply(…) 实现了显式绑定,这样你可以少些一些代码。

function foo(el) {
	console.log( el, this.id );
}
var obj = {
	id: "awesome"
};
// 调用 foo(..) 时把 this 绑定到 obj
[1, 2, 3].forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome
2.2.4 new绑定

JavaScript 中 new 的机制实际上和面向类的语言完全不同

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

举例:Number 构造函数
当 Number 在 new 表达式中被调用时,它是一个构造函数:它会初始化新创建的对象

所以,包括内置对象函数(比如 Number(…),)在内的 【所有函数】可以用 new 来调用,这种函数调用被称为构造函数调用。这里有一个重要但是非常细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象
  2. 这个新对象会被执行 [[ 原型 Prototype]] 连接。
  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 绑定

2.3、优先级

毫无疑问,默认绑定的优先级是四条规则中最低的;隐式绑定和显式绑定中,显式绑定优先级更高;new 绑定和隐式绑定中,new 绑定比隐式绑定优先级高。

最后需要比较的是,new 绑定和显式绑定谁的优先级更高。
new 和 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接进行测试。但是我们可以使用硬绑定来测试它俩的优先级。

先回忆一下硬绑定是如何工作的。Function.prototype.bind(…) 会创建一个新的包装函数,这个函数会忽略它当前的 this 绑定(无论绑定的对象是什么),并把我们提供的对象绑定到 this 上。

硬绑定函数如果是被 new 调用,就会使用新创建的 this 替换硬绑定的 this。

为什么要在 new 中使用硬绑定函数呢?直接使用普通函数不是更简单吗?
之所以要在 new 中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new 进行初始化时就可以只传入其余的参数。bind(…) 的功能之一就是可以把除了第一个参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)。举例来说:

function foo(p1,p2) {
	this.val = p1 + p2;
}
// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2

【判断this总结】

  1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
    var bar = new foo()
  2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。
    var bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。
    var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
    var bar = foo()

2.4 绑定例外

2.4.1 被忽略的this

如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略实际应用的是默认绑定规则

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

什么情况下,会传入 null 呢?
【情况一】
一种非常常见的做法是使用 apply(…) 来“展开”一个数组,并当作参数传入一个函数。类似地,bind(…) 可以对参数进行柯里化(预先设置一些参数),这种方法有时非常有用:

function foo(a,b) {
	console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3

这两种方法都需要传入一个参数当作 this 的绑定对象。 如果函数并不关心 this 的话,你仍然需要传入一个占位值,这时 null 可能是一个不错的选择,就像代码所示的那样。

在 ES6 中,可以用 … 操作符代替 apply(…) 来“展开”数组,foo(…[1,2]) 和 foo(1,2) 是一样的,这样可以避免不必要的this 绑定。可惜,在 ES6 中没有柯里化的相关语法,因此还是需要使用bind(…)。

总是使用 null 来忽略 this 绑定可能产生一些副作用。 如果某个函数确实使用了this(比如第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览器中这个对象是 window),这将导致不可预计的后果(比如修改全局对象)。
【更安全的this】
一种“更安全”的做法是传入一个特殊的对象,把 this 绑定到这个对象不会对你的程序产生任何副作用。我们可以创建一个“DMZ”(demilitarized zone,非军事区)对象——它就是一个空的非委托的对象
如果我们在忽略 this 绑定时总是传入一个 DMZ 对象,那就什么都不用担心了,因为任何对于 this 的使用都会被限制在这个空对象中,不会对全局对象产生任何影响。
在 JavaScript 中创建一个空对象最简单的方法都是Object.create(null)。Object.create(null) 和 {} 很 像, 但 是 并 不 会 创 建 Object.prototype 这个委托,所以它比 {}“更空”

function foo(a,b) {
	console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var ø = Object.create( null );
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
2.4.2 间接引用

另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则
间接引用最容易在赋值时发生

function foo() {
	console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2

赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是p.foo() 或者 o.foo()。根据我们之前说过的,这里会应用默认绑定。
【注意:】 对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。 如果函数体处于严格模式,this 会被绑定到 undefined,否则this 会被绑定到全局对象。

2.4.3 软绑定

硬绑定这种方式可以把 this 强制绑定到指定的对象(除了使用 new时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this
如果可以给默认绑定指定一个【全局对象和 undefined 以外的值】,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。

if (!Function.prototype.softBind) {
	Function.prototype.softBind = function(obj) {
		var fn = this;
		// 捕获所有 curried 参数
		var curried = [].slice.call( arguments, 1 );
		var bound = function() {
			return fn.apply(
				(!this || this === (window || global)) ? obj : this,curried.concat.apply( curried, arguments ));
		};
		bound.prototype = Object.create( fn.prototype );
		return bound;
	};
}

softBind(…) 的其他原理和 ES5 内置的 bind(…) 类似。

function foo() {
	console.log("name: " + this.name);
}
var obj = { name: "obj" },
	obj2 = { name: "obj2" },
	obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 );
// name: obj <---- 应用了软绑定

软绑定版本的 foo() 可以手动将 this 绑定到 obj2 或者 obj3 上,但如果应用默认绑定,则会将 this 绑定到 obj。

2.5、this词法(特例:箭头函数 =>)

ES6 中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。
【箭头函数的词法作用域】

function foo() {
	// 返回一个箭头函数
	return (a) => {
		//this 继承自 foo()
		console.log( this.a );
	};
}
var obj1 = {
	a:2
};
var obj2 = {
	a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行!)

箭头函数最常用于回调函数中,例如事件处理器或者定时器:

function foo() {
	setTimeout(() => {
		// 这里的 this 在此法上继承自 foo()
		console.log( this.a );
	},100);
}
var obj = {
	a:2
};
foo.call( obj ); // 2

箭头函数可以像 bind(…) 一样确保函数的 this 被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的 this 机制。

实际上,在 ES6 之前我们就已经在使用一种几乎和箭头函数完全一样的模式。

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

注意区别

function foo() {
	//var self = this; // lexical capture of this
	setTimeout( function(){
		console.log( this.a );   //undefined   这里的this指向window
	}, 100 );
}
var obj = {
	a: 2
};
foo.call( obj ); // 

虽然 self = this 和箭头函数看起来都可以取代 bind(…),但是从本质上来说,它们想替代的是 this 机制。
【建议】
如果你经常编写 this 风格的代码,但是绝大部分时候都会使用 self = this 或者箭头函数来否定 this 机制,那你或许应当:

  1. 只使用词法作用域并完全抛弃错误 this 风格的代码;
  2. 完全采用 this 风格,在必要时使用 bind(…),尽量避免使用 self = this 和箭头函数。

2.6、小结

  • 如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
  1. 由 new 调用?绑定到新创建的对象。
  2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
  • 一定要注意,有些调用可能在无意中使用默认绑定规则。如果想 “更安全”地忽略 this 绑定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null),以保护全局对象。
  • ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样。
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值