blog
blog copied to clipboard
Javascript里的this,call,apply,bind和arguments
在 Javascript 的学习中,this 的重要性不言而喻,而且容易被人误解,之前遇到过一位同学,在函数体内大量使用 this.xxx = xxx
的方式来定义变量,错误的以为 this
指代的是函数体内的命名空间,最后全部的变量都挂在 window
下而导致变量冲突。
这里就通过简单的方式来重点说明一下 this
的用法,当然,与之相关的两个也容易被忽略的方法 call
和 apply
也一同介绍一下。
this
要说 this
,必须先从「函数」说起,在 《Javascript 权威指南》里的「函数」那一节,有给 this
做了一翻解释,简单来说,可以这么理解:「this 关键字,是函数调用的上下文」
怎么去理解它呢,"一般" 来说,是酱紫的:
- 普通函数调用时,它的上下文就是
window
- 函数挂载在对象上,作为方法去调用时,它的上下文就是对象本身
- 构造函数调用(以
new
方式调用),this
指向返回的这个对象 -
call
或apply
间接调用,可指定this
的指向
1. 普通函数调用
- 非严格模式下:
this
值为全局对象,即window
- 严格模式下:
this
值为undefined
var name = 'Job';
function getName() {
console.log(this === window); // true
console.log(this.name); // Job
return this.name;
};
getName(); // 普通函数调用
另外,补充一下「函数嵌套」的用法。
在嵌套函数里,如果想在内层函数访问最外层函数的上下文,通常的我们可以先把 this
的值保存在一个变量里(注意,this
是关键字,不能被赋值),比如:
var o = {
m: function() {
var self = this; // 将 this 值保存在一个变量中
console.log(this === o); // true,方法调用,this 指向该对象
f();
function f() {
console.log(this === o); // false,这里是普通调用,指向 window 或者是 undefined
console.log(self === o); // true
}
}
}
o.m();
2. 方法调用
当函数作为对象的方法被调用时,this
总是指向该对象。
var o = {
m: function() {
console.log(this === o); // true
}
}
o.m();
3. 构造函数调用(new
)
作为构造函数调用时,this
指向返回的这个对象。
var o = function(name) {
this.name = name;
};
var object = new o('Eric');
console.log(object.name); // Eric
但是,有时候构造函数要是显式的返回一个对象时,this
指向的即是显式返回的那个对象。
其实,可以理解为,「this
指向的是返回的那个对象」,因为,构造函数总是会返回一个对象的,如果没有显示指定,则为隐式返回,如上面的例子。
var o = function(name) {
this.name = name;
return {
name: 'Chen'
}
};
var object = new o('Eric');
console.log(object.name); // Chen
4. 间接调用(call
/ apply
)
通过 call() 和 apply() 方法,可以显式的指定 this
的指向。
var obj1 = {
name: '111',
getName: function() {
return this.name;
}
};
var obj2 = {
name: '222'
};
console.log(obj1.getName()); // 111
console.log(obj1.getName.call(obj2)); // 222
console.log(obj1.getName.apply(obj2)); // 222
call()
和 apply()
Javascript 中的函数也是对象,和其他 Javascript 对象没什么两样,函数对象也可以包含方法。
其中的两个方法 call() 和 apply() 就是每个函数都自带的,在 Function 原型上定义的两个方法,可以用来间接地调用函数,并且可以显式的指定 this
的指向。
call() 和 apply() 两者作用完全一样,差异就在于传参方式,两者第一个参数都是一样,代表所调用函数内 this
的指向,差异从第二个参数开始。
foo.call(obj, arg1, arg2) == foo.apply(obj, arguments)
- call 传入参数数量不固定,第二个参数开始,依次为所调用函数所需的参数
- apply 接收最多两个参数,第二个参数是一个「数组」,作为参数传给被调用的函数
上几个栗子:
window.name = "张三";
var student = {
name: '李四'
};
function getMessage(sex, age){
console.log(this.name + " 性别: " + sex + " age: " + age);
}
// 普通调用
getMessage('男', 18); // 张三 性别:男 age: 18
// call 调用,后面参数列举
getMessage.call(window, '男', 18); // 张三 性别:男 age: 18
getMessage.call(student, '女', 22); // 李四 性别:女 age: 22
// apply 调用,参数用「数组」传递
getMessage.apply(window, ['男', 18]); // 张三 性别:男 age: 18
getMessage.apply(student, ['女', 22]); // 李四 性别:女 age: 22
另外需要注意的是,第一个参数传的是 null
值的时候
- ES5 的严格模式下,
this
会指向null
- ES3 和非严格模式下,
this
会指向被调用函数的包装对象(wrapper object),如window
例:非严格模式,this
会被包装对象代替。
window.name = "张三";
var student = {
name: '李四'
};
function getMessage(sex, age){
console.log(this.name + " 性别: " + sex + " age: " + age);
}
getMessage.call(null, '男', 18); // 张三 性别:男 age: 18
getMessage.apply(null, ['男', 18]); // 张三 性别:男 age: 18
例:严格模式,this
会指向 null
。
'use strict';
window.name = "张三";
var student = {
name: '李四'
};
function getMessage(sex, age){
console.log(this.name + " 性别: " + sex + " age: " + age);
}
getMessage.call(null, '男', 18); // 张三 性别:男 age: 18
getMessage.apply(null, ['男', 18]); // 张三 性别:男 age: 18
这里程序会报异常,因为 this
根据传进去的 null
而设置了 null
,因此 this.name
会抛异常。
bind
方法
bind
方法是在 ES5 中新增的,是将函数绑定到某个对象,它返回的是一个新的绑定到新对象的函数,给新函数传入特定的上下文和一组指定的参数,然后调用原函数。
function f(y) {
return this.x + y;
}
var o = { x: 1 };
var g = f.bind(o); // g是一个新的函数,而且是绑定在对象o上的,可以通过 g() 来调用 o.f()
g(2); // 3
bind
方法的常见用法
var sum = function(x, y) {
return x + y;
}
// 创建一个新函数,this绑向了null,并且将第一个参数绑定到1,这个新函数就只需要传入一个实参了
var succ = sum.bind(null, 1);
succ(2); // 3
function f(y, z) {
return this.x + y + z;
}
// 将this绑向{x:1},第一个参数y绑定到2,现在新函数g只需要传入1个参数z
var g = f.bind({x: 1}, 2);
g(3); // 6
arguments
Javascript 把传入到函数里的全部参数都存储在一个叫做 arguments
的类数组里面,但是因为是类数组,它比一般的数组少了一些内置的方法,比如 push
等。
所以,就不能这样去调用:
arguments.push(1);
要酱紫:
Array.prototype.push.call(arguments, 1);
利用 arguments 实现方法的重载
下面例子,实现一个把所有参数相加的函数,无论传入多少个参数都行:
function add() {
var sum = 0;
for(var i=0; i < arguments.length; i++){
sum += arguments[i];
}
return sum;
}
console.log(add(1)); // 1
console.log(add(1, 2)); // 3
console.log(add(1, 2, 3)); // 6