彻底搞定javascript中的this对象

126 阅读6分钟

this对象

this关键字是javascript中最复杂的机制之一,它是一个很特别的关键字,
所有函数都具有的对象之一,另外一个就是arguments;

为什么我们特别关心this呢?

因为this给我们提供了一种更优雅的方式来隐式传递一个对象,
得益于this的机制,我们可以设计出更简洁并且易于复用的API;

那么,this到底又是什么呢?

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

总而言之:this指的是谁,得看该函数调用位置的调用形式

this在不同方式调用this指向是不一样的,那么都有哪些调用方式呢?

一、独立调用(可以把这条规则看作是无法应用其他规则时的默认规则)
1. 在非严格模式下调用,this会被绑定给全局对象window
2. 函数如果在严格模式下执行,this会绑定为undefined
3. 如果只是在严格模式下调用,this绑定给的还是window

对应以上三种情况时,上代码看看都是什么情况:

/*1. 在非严格模式下调用,this会被绑定给全局对象window*/
function test(){
	console.log(this); // window
}
test();
/*2.函数如果在严格模式下执行,this会绑定为undefined*/
function test(){
	"use strict"
	console.log(this); // undefined
}
test();
/*3. 如果只是在严格模式下调用,this绑定给的还是window*/
function test(){
	console.log(this); // window
}
(function(){
	"use strict"
	test();
})()
二、隐式调用(this使用隐式绑定规则 绑给离他最近的调用者)
隐式绑定的规则是调用位置是否有上下文对象,
或者说是否被某个对象拥有或者包含,当函数引用有上下文对象时,
隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象;

这句话用代码就是这么个意思如下:

function foo() {
	console.log( this.a ); // 这个this就是obj这个对象所以输出的是 2
}
var obj = {
    a: 2,
	foo: foo
};
obj.foo();

隐式调用函数有可能造成一个很大隐患,那就是隐式丢失;隐式丢失特别容易在变量赋值,传参,定时器 回调时发生;那么什么是隐式丢失呢?在三种情况下会造成隐式丢失

1. 变量赋值
function foo() {
	console.log( this.a ); // oops, global  
}
var a = "oops, global"; 
var obj = {
	a: 2,
	foo: foo
};

//隐式丢失
/*
    obj.foo本来是想把this绑定给obj的,
    但是在将obj中的foo方法赋值给变量bar,这时候隐式丢失就发生了
    此时bar调用形式是独立调用,this绑定给了window
*/
var bar = obj.foo; 
bar();

2. 传参数
// 参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值
function foo() {
	console.log( this.a ); // "oops, global"
}
function doFoo(fn) {
	fn(); // 独立调用,该函数的this绑定给window
}

var a = "oops, global"; 
var obj = {
	a: 2,
	foo: foo
};
	
//隐式丢失
doFoo( obj.foo ); 
3. 定时器回调
如果把函数传入语言内置的函数而不是传入你自己声明的函数,结果是一样的,没有区别
//		JavaScript环境中内置的 setTimeout() 函数实现和下面的伪代码类似:
//		function setTimeout(fn,delay) {
//			// 等待delay毫秒
//			fn(); // <-- 调用位置!
//		  }
function foo() {
	console.log( this.a ); // "oops, global"
}

var a = "oops, global"; 
var obj = {
	a: 2,
	foo: foo
};
//隐式丢失
setTimeout( obj.foo, 1000 ); 
三、显示绑定(this使用显式绑定规则 this绑给call apply bind指定的对象)
我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数
具体点说,可以使用函数的 call(..) 和 apply(..) 方法来实现显示绑定
function foo(a,b) {
	console.log( this.a,a,b );//装箱
}
var obj = {
	a:2
};
foo.call(obj,"a","b");   //  2  a b
foo.apply(obj,["c","e"])  //  2	c  e

call()和apply()之间有什么区别呢?从上面代码中可以看出,共同点第一个参数都是this指向,都是立即调用的;区别在call()中第二个参数是以列表的形式; apply()第二个参数是以数组的形式;

bind():方法中第一个参数也是表示this指向,与上面两种方法不一样的是,为函数调用该方法返回 的是一个函数,叫做“硬绑定函数”;不是立即调用的;调用时机由使用者决定;

function test(){
	console.log(this);
}
		
//fn 我们 一般称之为test的硬绑定函数 此用法还解决了上面提到的隐式丢失问题
var fn = test.bind({name:"hhl"});
fn();

没错,bind()函数可以解决上面所提及的三种隐式丢失的问题;

四、new绑定
在js中实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”;
使用 new 来调用函数,或者说发生构造函数调用时,对于我们的this来说。
这个实例化的对象会绑定到函数调用的 this
function Foo(a) {
	this.a = a; // this 指向实例化对象bar
}
var bar = new Foo(2);
console.log( bar.a ); // 2

如果四种调用形式同时存在或者说有一个以上调用形式存在时,this又会怎么绑定绑定给谁呢?所以以上四种调用形式当然有会有个优先级,结论就是new调用 > call apply bind 显示绑定 > 隐式绑定 > 独立调用

1. 显示绑定与隐式绑定哪个优先级高呢?
function foo() {
	console.log( this.a );
}
var obj1 = {
	a: 2,
	foo: foo
};
var obj2 = {
	a: 3,
	foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

如上代码所示,可得出 显示绑定 > 隐式绑定

2. new 绑定 与 隐式绑定比较
// new 绑定比隐式绑定优先级高
function foo(something) {
	this.a = something;
}
var obj1 = {
	foo: foo
};
//显>隐
//new>隐
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // undefined
console.log( bar.a ); // 4

如上代码所示,可得出 new绑定 > 隐式绑定 3. new 绑定 与 显示绑定比较

//	new绑定优先级高于显示绑定
function foo(something) {
	this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
var baz = new bar(3);
console.log( obj1.a ); // undefined
console.log( baz.a ); // 3

如上代码所示,可得出 new绑定 > 显式绑定

最后this绑定有三例特殊的情况:

1. es6中的箭头函数没有this,this指向外围函数
2. 如果你把null或者undefined作为this绑定的对象传入 call apply bind,这些值在调用时会被忽略,实际应用的是默认绑定规则
3. 柯里化:为函数预定一些参数