blog icon indicating copy to clipboard operation
blog copied to clipboard

Javascript里的this,call,apply,bind和arguments

Open diamont1001 opened this issue 6 years ago • 0 comments

在 Javascript 的学习中,this 的重要性不言而喻,而且容易被人误解,之前遇到过一位同学,在函数体内大量使用 this.xxx = xxx 的方式来定义变量,错误的以为 this 指代的是函数体内的命名空间,最后全部的变量都挂在 window 下而导致变量冲突。

这里就通过简单的方式来重点说明一下 this 的用法,当然,与之相关的两个也容易被忽略的方法 callapply 也一同介绍一下。

this

要说 this,必须先从「函数」说起,在 《Javascript 权威指南》里的「函数」那一节,有给 this 做了一翻解释,简单来说,可以这么理解:「this 关键字,是函数调用的上下文」

怎么去理解它呢,"一般" 来说,是酱紫的:

  • 普通函数调用时,它的上下文就是 window
  • 函数挂载在对象上,作为方法去调用时,它的上下文就是对象本身
  • 构造函数调用(以 new 方式调用),this 指向返回的这个对象
  • callapply 间接调用,可指定 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

diamont1001 avatar May 24 '18 04:05 diamont1001