是时候搞清楚this指向了

278 阅读3分钟

谁调用this就只想谁?

  在大多数情况下this的指向指向的是调用方,比如如下代码:

var obj = {
	say: function () {
		console.log(this)
	}
}
obj.say(); // obj
// 在全局作用域中声明的变量默认可以用window.say()进行调用
// 当然window的一些全局函数里的this也是指向window的,比如setTimeout等等
var say = obj.say;
say(); // window 

  为什么我这里要说大多数时候呢?因为这里的情况并没有包括显示绑定的情况,显示绑定有些呢?apply、bind、call。

var obj = {
	name: '张三',
	say: function () {
		console.log(this.name)
	}
}
var obj2 = {
	name: '李四'
}
obj.say.call(obj2) // 李四

  这里我们就用call强制obj.say的this指向的是obj2。

new绑定this

  new 是javascript的操作符,用来创建一个对象。当然这句话并不准确

function Foo() {
	this.name = '张三'
	this,say = function () {
		console.log(this.name)
	}
}
var foo = new Foo()
foo.say(); // ‘张三’

  那么我们使用new的时候内部发生了啥变化了呢?内部机制如下

function Foo() {
	// 首先内部创建一个临时的变量this
	this = {
		// 然后将Foo作用下所有this.xxx的属性,添加到this
		name: '张三',
		say: function () {
			console.log(this.name)	
		}
	} 
	// 在挂载属性完成之后,将这个this返回
	return this
	//
	this.name = '张三'
	this,say = function () {
		console.log(this.name)
	}
}

  以上操作就是我们用new的时候内部的操作。这也是为什么当我们使用bind返回的函数进行new操作时,bind绑定无效的原因,因为函数在new的时候会在内部创建this,使显式绑定无效

优先级问题

  这里的优先级是指的显式绑定和隐式绑定、new绑定和隐式绑定的优先级。显式绑定即bind、apply、call等,隐式绑定即obj.xxx等

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

  可以看到,显示绑定优先级更高。接下来我们来搞清楚new绑定和隐式绑定的优先级谁高谁低。

function foo (something) {
	this.a = something
}
var ob1 = {
	foo: foo
}
var obj2 = {}

obj1.foo(2);
console.log(obj1.a); //2

obj1.foo.call(obj2, 3);
console.log(obj.a); // 3

var bar = new obj1.foo(4);
console.log(obj1.a); //2
console.log(bar.a); //4

  从上面我们可以看到new绑定比隐式绑定优先级高。
  从上面的结论我们可以得出一下优先级的顺序: new绑定 > 显式绑定 > 隐式绑定

判断this

现在我们可以根据优先级来判定函数在某个调用位置应用的是那一条规则了。

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

绑定例外——箭头函数

  箭头函数是es6的具有特殊规则的函数,使用被称为胖箭头的操作符=>。箭头函数不使用this的四种标准规则,而是根据外层(函数或全局)作用域来决定this。

function foo () {
	return a => 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被调用时指向的是obj1,那么新返回的函数其this就指向obj1而保持不变,另外箭头函数的绑定是无法通过call或apply修改的(new 也不行)。 箭头函数改变this最常用在回调函数中,如事件处理程序或定时器:

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

  从上述的代码可以看出,箭头函数中的this指向规则是:箭头函数中的this指向函数执行时的this