this、call和apply知识点整理

JavaScript 设计模式与开发实践 学习笔记。
学习 JavaScript 设计模式之前,回顾一下 this 的特性。

this 的指向

this 的指向基本有以下四种(除 with 和 eval)

  • 对象的方法
  • 普通函数
  • 构造器
  • call、apply

作为对象的方法调用

this 直接指向该对象

1
2
3
4
5
6
7
var obj = {
a: 1,
getA: function() {
alert(this.a);
}
};
obj.getA();

作为普通函数调用

当函数不作为对象的方法调用时,即是作为普通函数调用,此时函数内的 this 永远指向全局对象(浏览器中为 window)。

1
2
3
4
5
window.name = "g";
function getName() {
return this.name;
}
getName(); // g
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
document.getElementById("div1").onclick = function() {
alert(this);
var callback = function() {
alert(this); // window
};
// 因为此时callback作为普通函数调用
callback();
};

// 解决方案:使用变量保存div1的引用
document.getElementById("div1").onclick = function() {
alert(this);
var that = this;
var callback = function() {
alert(thas); // div1
};
// 因为此时callback作为普通函数调用
callback();
};

通过构造器调用

构造器和普通函数看上去一模一样,区别在于他们的调用方式,构造器需要用 new 运算符调用,当用 new 运算符调用构造器时,该函数会返回一个对象(这个对象即是通过构造器生成的实例),而此时构造器中的 this 就指向这个对象。

1
2
3
4
5
var MyClass = function() {
this.name = "tom";
};
var obj = new MyClass();
alert(obj.name); // tom

call、apply

Function.prototype.call 或 Function.prototype.apply 可以改变函数内部的 this 指向。

1
2
3
4
5
6
7
8
var obj = {
name: "tom",
getName: function() {
return this.name;
}
};

obj.getName.call({ name: "wei" });

丢失的 this

1
2
3
4
5
6
7
8
9
10
11
// 思考这段代码
var obj = {
name: "tom",
getName: function() {
return this.name;
}
};

obj.getName(); // tom
var getName2 = obj.getName;
getName2(); // undefined

当调用 obj.getName()时,是通过对象的方法调用,所以 this 指向 obj,可以成功获取。但是将 obj.getName 赋值给 getName2 后,是通过普通函数方式调用,此时 getName2 内部的 this 指向的是全局对象,然而全局对象并没有 name 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 解决方法
var obj = {
name: "tom",
getName: function() {
return this.name;
}
};
// 通过apply把obj当做this传入getName函数,即使getName作为普通函数调用时,this也被修正为obj
obj.getName = (function(func) {
return function() {
return func.apply(obj, arguments);
};
})(obj.getName);

var getName2 = obj.getName;
getName2(); // tom

call 和 apply

call 和 apply 的作用一模一样,区别在于他们传入的参数形式的不同。
他们传入的第一个参数都是指定函数体内 this 的指向

  • apply 传入的第二个参数是一个数组或类数组,apply 把这个集合的元素作为参数传入被调用的函数。
  • call 传入的参数数量不固定,从第二个参数开始,所有的参数都被依次作为参数传入被调用的函数。
1
2
3
4
5
6
7
var func = function(a, b, c) {
alert([a, b, c]);
};

func.apply(null, [1, 2, 3]);
func.call(null, 1, 2, 3);
// 当第一个参数传入null时,函数体内的this会指向默认的宿主对象(在浏览器中是window对象)

call 和 apply 的用途

改变 this 指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
document.getElementById("div1").onclick = function() {
var func = function() {
alert(this.id);
}
// 如果不通过call去显式指定func内部的this指向的话,将会输出undefined
func.call(this); // div1
}
```i
### 模拟Function.prototype.bind方法
```js
Function.prototype.bind = function(context) {
//在这里把调用bind的函数的引用保存起来,因为下面返回了一个function
//如果这里不保存this(即函数的引用),则下面返回的function如果作为普通函数调用,那么该函数体内的this就是全局对象。
var self = this;
return function() {
return self.apply(context, arguments);
}
}

借用其他对象的方法

比如类数组实际上是一个对象,他并不具有数组的方法,由于他的特性和数组一样,他可以通过 call、apply 方法调用数组的一系统方法

1
2
3
4
5
(function() {
// 这里的arguments是一个类数组,通过call方法,调用了数组的push方法
Array.prototype.push.call(arguments, 3);
console.log(arguments); // [1,2,3]
})(1, 2);
0%