1.默认绑定
1 | function foo(){ |
这个例子相信大家也能够理解,this.a被解析了全局变量a。因为在函数调用是应用了this的默认绑定,因此this指向全局对象。
那我怎住的它这里就用了这种绑定呢?其实是这样的,在foo()直接使用不带任何修饰的函数引用进行调用的,就只能使用默认绑定
,一会来看看其他规则对比一下可能就会清晰些。
上面这个例子有个例外,如果使用严格模式(strict mode),全局对象将无法使用默认绑定,所以this会绑定到undefined1
2
3
4
5
6function foo(){
"use strict";
console.log(this.a);
}
var a = 2;
foo(); //TypeError:this is undefined
2.隐式绑定
另外一个需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对着拥有或者包含1
2
3
4
5
6
7
8function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
};
obj.foo(); //2
foo()的声明方式可以知道,它不属于obj对象,而是智慧被当做引用属性被添加到obj中。
当foo()被调用是时,他的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会吧函数调用中的this绑定要这个上下文对象,调用foo()时this被绑定到obj,所以this。啊和obj.a一样。
对象属性引用链中只有最顶层或者说最后一层灰影响调用位置,比如1
2
3
4
5
6
7
8
9
10
11
12function foo(){
console.log(this.a);
}
var obj2 = {
a:4,
foo:foo
};
var obj1 = {
a:2,
obj2:obj2
};
obj1.obj2.foo(); //2
隐式丢失
有个常见的this绑定问题就是被隐式绑定
的函数会丢失绑定对象,也就是它会应用默认绑定
,至于刚刚那个this会绑定要全局/undefined,取决是否是严格模式。1
2
3
4
5
6
7
8
9
10function 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()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
另一种更微妙的,更常见并且出乎意料的情况发生在传入回调函数时:1
2
3
4
5
6
7
8
9
10
11
12
13function foo(){
console.log(this.a);
}
function doFoo(fn){
//fn其实引用的是foo
fn(); // <---调用位置
}
var obj = {
a:2,
foo:foo
};
var a = "oops,global";
doFoo(obj.foo); //"oops,global"
参数传递其实就是一种隐式赋值,所以结果和上个例子一样。
如果把函数传入语言内置的函数而不是传入你自己声明的函数,会发生什么呢?结果是一样的,没有区别:1
2
3
4
5
6
7
8
9
10function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
};
var a = "oops,global";
setTimeout(obj.foo,100); //"oops,global"
js环境中内置的setTimeout()函数和下面的伪代码类似:1
2
3
4function setTimeout(fn,delay){
//等待delay毫秒
fn(); /// <---调用位置
}
所以this的改变都是意向不到的,无法控制回调函数的执行方式,因此没有办法控制会影响绑定的调用位置。
显式绑定
前面我们讲到,在隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上
如果不想再对象内部包含函数引用,而是在某个对象强调函数,改怎么做呢?
js中所有函数都有一些有用的特性,可以使用函数的call(..)和apply(..)方法。
严格来说,javascript的宿主环境有时候会提供一些非常特殊的函数,他们并没有这两种方法。但是这种函数非常罕见,绝大多数都可以使用call(..)和apply(..)方法。
这两种方法如何工作的呢?
它们的第一个参数是一个对象,它们会吧这个对象绑定到this,接着再调用函数时置顶这个this。因为你可以直接指定this的绑定对象,所以称为显示绑定
先看下面一段代码1
2
3
4
5
6
7function foo(){
console.log(this.a);
}
var obj = {
a:2
};
foo.call(obj); //2
通过foo.call(..)我们可以在调用foo时强制把它的this绑定到obj上。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当做this的绑定对象,这个原始值会被转化成它的对象形式(也就是new String(..),new Boolean(..) 或者new Number(…))。这通常会被称为装箱
可惜,显示绑定仍然无法解决我们之前的问题啊。
1.硬绑定
但是显示绑定的一个变种可以解决这个问题1
2
3
4
5
6
7
8
9
10
11
12
13function 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
我们把foo的this绑定到了obj.无论之后如何调用函数bar,他总会手动在obj上调用foo。这种绑定是一种显示的强制绑定,所以称为硬绑定。
硬绑定的典型常见就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:1
2
3
4
5
6
7
8
9
10
11
12function 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
另一种方法是创建一个i可以重复使用的辅助函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function 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
,用法如下:1
2
3
4
5
6
7
8
9
10function 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的上下文并调用原始函数。
2.API调用上下文
第三方许多函数,以及javascript语言和宿主环境中许多新的内置函数,都提供了可选参数,通常被称为“上下文”($context$),其作用和bind(..)一样,确保你的回调函数使用指定的this.1
2
3
4
5
6
7
8
9function 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
这些函数实际上就是通过call(..)或者apply(..)实现了显示绑定,这样可以少一些代码
new绑定
- 创建(或者说构造)一个全新的对象
- 这个新对象会被执行[[原型]]连接
- 这个新对象会绑定到函数调用的this.
- 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。
1 | function foo(a){ |
使用new时,会构造一个新对象并把它绑定到foo(..)调用中的this上。new 是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。