更多
更多
文章目录
  1. this
  2. call/apply/bind

Javascript之this-call/apply/bind

this

this关键字 在js中可谓随处可见,看似简单,但要做到心里有数还是需要来一次的梳理。

无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)this都指代全局对象;

函数内部this的值取决于函数被调用的方式。

所以this的值不是固定的,它的值不取决于它所在的位置,而在于他所在的function是被怎样调用的。

接下来理解一下函数的几种调用方式和它的this值吧:

1.全局函数调用

这里又有严格模式和非严格模式的区别。

非严格模式下 this的值默认指向全局对象。

1
2
3
4
5
6
7
8
9
10
11
12
function a(){
this.b=1;
alert(this.b);
}
a();//1
//----------------
function a(){
var b='chen';
console.log(this.b);//undefined
console.log(this);//window
}
a();

严格模式下 this将保持他进入执行上下文时的值,所以this将默认undefined。

2.作为方法被调用

this指向它的父级(上一级)对象:

1
2
3
4
5
6
7
var o = {
user:"chen",
fn:function(){
console.log(this.user); //chen
}
}
o.fn();

更复杂一点案例的理解:

1
2
3
4
5
6
7
8
9
10
11
12
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window.

3.作为构造函数调用

所谓构造函数,就是通过这个函数生成一个新对像(object)。这时,this就指这个新对象。

1
2
3
4
5
function a(){
this.b = "chen";
}
var a2 = new a();
console.log(a2.b); //chen

通过以上其实我们可以了解到this的作用,就是可以实现动态指向不同的作用域中,动态实现其功能的特点,比如我们写了一个方法需要作用于多个对象中,此时就可以用this在不同环境下对不同的父级赋能。

call/apply/bind

以上我们可以判定this的默认值,那当我们需要更改this指向不等于默认值,绑一个环境去执行呢?

这个时候就需要用到function的三个方法:

  • call()
  • apply()
  • bind()

这三个函数的存在意义是改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向。

理解了this关键字之后我们就更容易看懂下面的这写代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hello(name){
console.log('hello,'+name+'我是'+this.name);
}
var user={
name:'chen'
}
//hello() //这是hello.call()的快捷写法;
hello.call(user,'qiqi');
//↑第一个参数是绑定前面函数的this指向,之后的参数是传入hello函数中的参数
hello.apply(user,['chichi']);
//↑apply方法和call的使用方法一样,只是传参的方式不同,apply是以数组的方式进行传参

hello2=hello.bind(user);//绑定user环境但不执行返回一个新的function
hello2('keke');//传参使用

通过上面得例子我们可以对call、apply、bind三个方法进行总结比较了:

  • call和apply的使用方法一样,都是将函数的this指向绑定在一个指定的环境对象中,进行传参执行;
  • 二者的区别是传参方式的不同,apply是进行数组传参;
  • call和apply在绑定的同时就已经进行了函数执行;
  • bind分两步操作,第一步操作只进行环境绑定但不执行函数,之后在新的function中进行传参使用;

让我们再看一个例子去理解会更直观(感谢翻译):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
  //设立一个简单地对象作为“上下文”
var context = { foo: "bar" };

//一个在this上下文中指向foo变量的函数
function returnFoo () {
return this.foo;
}

// 变量在作用域中不存在,因此显示undefined
returnFoo(); // => undefined

// 如果我们把它绑定在context上下文中
var bound = returnFoo.bind(context);

// 现在的作用域中有这个变量了
bound(); // => "bar"

//
// 这就是Function.prototype.bind的作用.
//由于returnFoo也是函数,因此它继承了function的原型
//
// 如果你觉得享受,接着往下读,下面更精彩
//

// 有许多方法将函数绑定在一个上下文中
// Call和Apply让你能在上下文中调用函数
returnFoo.call(context); // => bar
returnFoo.apply(context); // => bar

// 将函数添加到对象中
context.returnFoo = returnFoo;
context.returnFoo(); // => bar

//
// 现在我们来玩一点诡异的东西
//

// Array.prototype 中有一个叫做slice的方法
// 对一个数组调用slice,可以返回一个从start index到end index的数组
[1,2,3].slice(0,1); // => [1]

// 因此我们把Array.slice赋值给一个本地变量slice
var slice = Array.prototype.slice;

//现在的slice是"自由的",由于Array.prototype中的slice一般指定了上下文
//或者默认为this,此时slice将不起作用
slice(0, 1); // => TypeError: can't convert undefined to object
slice([1,2,3], 0, 1); // => TypeError: ...

// 但是如果我们使用call或者apply,slice又将在一个上下文中执行
slice.call([1,2,3], 0, 1); // => [1]

// Apply和Call差不多,只是参数要放在一个数组中
slice.apply([1,2,3], [0,1]); // => [1]

// 使用call没错了,那么能不呢使用bind呢?
// 没错,我们来把"call"绑定在slice上
slice = Function.prototype.call.bind(Array.prototype.slice);

// 现在slice可以把第一个参数作为上下文了
slice([1,2,3], 0, 1); // => [1]

//
// 很酷,对吧。现在再来完成一件事
//

// 现在我们对bind本身做一件刚才对silce做的事
var bind = Function.prototype.call.bind(Function.prototype.bind);

// 在这里总结一下,好好想想
// 发生了什么事? 我们改变了call,
// 返回一个接收一个函数和一个上下文作为ic桉树的函数
//并且返回了一个完全绑定的函数

// 回到最初的例子
var context = { foo: "bar" };
function returnFoo () {
return this.foo;
}

// 现在来使用神奇的"bind"函数
var amazing = bind(returnFoo, context);
amazing(); // => bar

推荐阅读

[译] JavaScript 中至关重要的 Apply, Call 和 Bind

深入浅出妙用 Javascript 中 apply、call、bind

感谢阅读
公众号