JavaScript中有四条调用规则,我们可以根据这四条调用规则来判断当前this的指向。
第一条:默认绑定。
function foo(){
console.log(this.a);
}
var a = 2;
foo(); // 2
代码很简单,那要怎么给默认绑定一个定义呢?答案就是:
不带任何修饰的函数引用进行调用。
这个时候this指向window.(严格模式下指向undefined)
第二条:隐式绑定
隐式绑定,需要在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接绑定到这个对象上。
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
obj.foo(); // 2
这里在调用的时候增加了一个上下文对象,隐式绑定会把函数调用中的this绑定到这个上下文对象。如果存在多层,按照最近的一层合账。a.b.c.foo(); 则实际指向的是c.
在这里呢,有一种特殊的情况,函数的赋值操作。
function foo(){
console.log(this.a);
}
function lee(func){
func();
}
var obj = {
a:2,
foo:foo
}
var a = 1;
doFoo(obj.foo); //1
看似是隐式绑定,但是函数在传递参数的时候就执行了赋值操作,这个时候再次调用的绑定规则应该为默认绑定,所以指向window.
第三条:显式绑定
这里呢,关于 call和apply以及bind的知识,之前有说过,不清楚的可以先了解一下。这些函数都可以直接指定this的指向,所以是显式绑定。call和apply 仍然可以通过调用时传递的参数去修改当前this的绑定,现在想象一个场景:在一个函数内部要调用一个函数,需要将这个函数绑定在一个obj对象上,但是会有其他因素影响函数绑定到obj对象上,这样一来就会产生丢失绑定的问题。这个时候可以使用硬绑定:
function foo(){
console.log(this.a);
}
var obj = {
a:2,
}
var bar = function(){
foo.call(obj);
}
bar(); //2
setTimeout(bar,100); //2
bar.call(window); //2
这样一来,就无法修改函数的绑定对象了。
ES5提供了内置方法,Function.prototype.bind方法,每个function都自带bind方法,
var bar = foo.bind(obj)
如此就可以使用了。
第四条:new绑定
也就是通过new去实例化一个对象。、function foo(a){
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2
一言以蔽之:
构造一个新对象,并把它绑定到foo()调用中的this上。
多说一句:这里foo()是一个普通的函数,通过new关键字调用就是对函数的构造调用。由此可得出JS中实际上并不存在所谓的构造函数,只有对函数的构造调用。
优先级:
说了四种方式,既然存在那难免就会有个比较,到底谁更优先呢?function foo(value){
this.a = value;
}
var obj1 = {
foo:foo
}
var obj2 = {}
obj1.foo(2);
console.log(obj1.a); //2
obj1.foo.call(obj2,3);
console.log(obj2.a); //3 显式 > 隐式
--------------------------------------------
var oo1 = {};
var bar = foo.bind(oo1);
bar(2); //001.a = 2
var oo2 = new bar(3);
// oo1.a = 2,oo2.a = 3; => new > 显式
之前我们看过的硬绑定的实现方式,按道理来说应该是无法修改才对的,而实际使用的时候bind()有自己的实现机制,所以new调用仍然会修改当前指向对象。
这里是传统的四种绑定方式,有没有例外呢,有。感兴趣的可以看下我总结的箭头函数对this的影响,