this绑定规则

过了一个新年,有些知识又有点淡忘了吧~我们一起来复习一下

1.默认绑定

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

这个例子相信大家也能够理解,this.a被解析了全局变量a。因为在函数调用是应用了this的默认绑定,因此this指向全局对象。

那我怎住的它这里就用了这种绑定呢?其实是这样的,在foo()直接使用不带任何修饰的函数引用进行调用的,就只能使用默认绑定,一会来看看其他规则对比一下可能就会清晰些。
上面这个例子有个例外,如果使用严格模式(strict mode),全局对象将无法使用默认绑定,所以this会绑定到undefined

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

2.隐式绑定

另外一个需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对着拥有或者包含

1
2
3
4
5
6
7
8
function 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
12
function 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
10
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()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
另一种更微妙的,更常见并且出乎意料的情况发生在传入回调函数时:

1
2
3
4
5
6
7
8
9
10
11
12
13
function 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
10
function 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
4
function setTimeout(fn,delay){
//等待delay毫秒
fn(); /// <---调用位置
}

所以this的改变都是意向不到的,无法控制回调函数的执行方式,因此没有办法控制会影响绑定的调用位置。

显式绑定

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

js中所有函数都有一些有用的特性,可以使用函数的call(..)和apply(..)方法。
严格来说,javascript的宿主环境有时候会提供一些非常特殊的函数,他们并没有这两种方法。但是这种函数非常罕见,绝大多数都可以使用call(..)和apply(..)方法。

这两种方法如何工作的呢?

它们的第一个参数是一个对象,它们会吧这个对象绑定到this,接着再调用函数时置顶这个this。因为你可以直接指定this的绑定对象,所以称为显示绑定
先看下面一段代码

1
2
3
4
5
6
7
function 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
13
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

我们把foo的this绑定到了obj.无论之后如何调用函数bar,他总会手动在obj上调用foo。这种绑定是一种显示的强制绑定,所以称为硬绑定。

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

1
2
3
4
5
6
7
8
9
10
11
12
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,用法如下:

1
2
3
4
5
6
7
8
9
10
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的上下文并调用原始函数。

2.API调用上下文

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

1
2
3
4
5
6
7
8
9
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

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

new绑定

  1. 创建(或者说构造)一个全新的对象
  2. 这个新对象会被执行[[原型]]连接
  3. 这个新对象会绑定到函数调用的this.
  4. 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。
1
2
3
4
5
function foo(a){
this.a = a;
}
var bar = new foo(2)
console.log(bar.a); // 2

使用new时,会构造一个新对象并把它绑定到foo(..)调用中的this上。new 是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。

# js

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×