理解javascript函数调用

232 阅读4分钟

隐式参数arguments与this

当调用函数时,除了传入在函数定义中显式声明的参数外,还传入了两个隐式参数:arguments与this。

arguments参数
  • arguments参数表示传入函数的所有参数的集合。
  • 使用arguments.length属性来获取传递给函数的实际参数个数。
  • 通过数组下标的方式访问到arguments参数中的每个参数值,如arguments[2]将获取第三个参数。
  • arguments是一个类数组对象,不可以对arguments对象使用数组的方法。
  • 在非严格模式下,arguments对象是函数参数的别名,修改arguments对象会影响对应的函数实参,反之亦然。
  • arguments作为参数别名使用时,会影响代码可读性,应该避免使用参数别名修改参数。在严格模式下通过arguments修改参数是不起作用的。
this参数
  • this表示函数上下文,即与函数调用相关联的对象。
  • 在java面向对象语言中,this通常指向定义当前方法的类的实例。
  • 但是,在javascript中,将一个函数作为方法调用仅仅是函数的一种调用方式。this参数是由函数的定义方式调用方式决定

函数的调用方式

函数的调用方式有4种

  1. 作为函数直接被调用;myfunc()
  2. 作为方法关联在一个对象上,实现面向对象编程;obj.myfunc()
  3. 作为构造函数调用,实例化一个对象;new Myfunc()
  4. 通过函数的apply和call方法
作为函数调用

作为函数直接被调用,在非严格模式下,函数上下文(this关键字的值)是全局上下文(window对象)。在严格模式下,this值为undefined。

/*函数定义作为函数被调用*/
function aa(){console.log(this)}
aa();
/*函数表达式作为函数被调用*/
let bb = function(){console.log(this)}
bb();
/*立即调用函数表达式作为函数被调用*/
(function(){console.log(this)})();

image.png image.png

作为对象方法调用

当一个函数被赋值一个对象的属性,并且通过对象属性引用的方式调用函数时,函数会作为对象的方法被调用。 作为对象方法调用的函数this值与对象关联,通过this可以访问所关联对象的其他方法和属性。

function aa(){return this}
console.log(aa()==window);

var obj1 = {}
obj1.aa = aa;
console.log(obj1.aa()==obj1);

var obj2 = {}
obj2.bb = aa;
console.log(obj2.bb()==obj2);

image.png

作为构造函数调用
  • 在函数调用之前加上关键字new,即为构造函数调用。
  • 构造函数目的是用来创建和初始化一个新对象,然后将这个对象作为构造函数的返回值。
  • 使用关键字new调用函数会触发以下几个动作:
    1. 创建一个新的空对象;
    2. 该对象作为this参数传递给构造函数,成为构造函数的上下文;
    3. 新构造的对象作为new运算符的返回值。
    4. 如果构造函数返回一个对象,则该对象将作为整个表达式的返回值,而传入构造函数的this将被丢弃。
    5. 如果构建函数返回的是非对象类型,则忽略返回值,返回新创建的对象。
function Ninja(){
  //这里的this表示Ninja函数的上下文
    this.skulk = function(){
      //这里的this表示该匿名函数的上下文
        return this;
    }
}
//skulk以对象的方式调用是,返回值是其关联的对象
var ninja1 = new Ninja();
var ninja2 = new Ninja();
console.log(ninja1.skulk() == ninja1);
console.log(ninja2.skulk() == ninja2);
// skulk复制给一个变量后,直接调用函数时,非严格模式下skulk返回的值是window
var skulk = ninja2.skulk;
console.log(skulk() == window);
通过apply与call方法调用

javascript提供了可以显示指定任何对象作为函数的上下文的函数调用方式。每个函数都存在apply和call方法。通过apply与call方法来设置函数的上下文。

function juggle(){
    var result = 0;
    for(var n=0; n<arguments.length; n++){
        result+=arguments[n]
    }
    this.result = result;
}

var ninja1 = {};
var ninja2 = {};

juggle.apply(ninja1, [1,2,3,4,5])
juggle.call(ninja2, 1,2,3,4,5, 6)

console.log(ninja1.result == 15)
console.log(ninja2.result == 21) 

apply和call功能类似,唯一的不同在于如何传递参数。apply和call第一个参数作为函数的上下文,apply第二个参数是一个包含参数值的数组。call可以传入任意数量参数,作为函数的参数。

总结四种函数的调用方式对this取值的影响
  • 如果作为函数调用,在非严格模式下,this指向全局window对象;在严格模式下,this指向undefined。
  • 作为方法调用,this通常指向调用的对象
  • 作为构造函数调用,this指向新创建的对象。
  • 通过call或apply调用,this指向call或apply的第一个参数。

解决函数上下文问题

在回调函数中,函数上下文与预期不符。如事件回调函数的上下文是触发事件的对象。除了使用call和apply显示的设置函数上下文,还可以使用箭头函数和bind方法解决函数上下文问题。

箭头函数的this值确定
  • 箭头函数没有单独的this值,this在箭头函数创建时确定。
  • 箭头函数的this与声明所在的上下文的相同。
  • 调用箭头函数时,不会隐式传入this参数,而是从定义时的函数继承上下文。
<button id="test">click me</button>
function Button(){
  this.clicked =false;
  this.click = function(){
    this.clicked = true;
  }
}
var button = new Button();
var elem = document.getElementById('test');
elem.addEventListener('click', button.click);//将事件触发的目标元素即<button id="test">click me</button>作为button.click的上下文。
<button id="test">click me</button>
function Button(){
  this.clicked =false;
  this.click = ()=>{
    this.clicked = true;
  }
}
var button = new Button();
var elem = document.getElementById('test');
elem.addEventListener('click', button.click);//button.click的上下文为定义时的上下文即为new 返回的对象button。
bind方法
  • 所有的函数均可使用bind方法,创建新函数,并绑定到bind方法传入的参数上。被绑定的函数与原始函数具有一致的行为。
<button id="test">click me</button>
function Button(){
  this.clicked =false;
  this.click = ()=>{
    this.clicked = true;
  }
}
var button = new Button();
var elem = document.getElementById('test');
elem.addEventListener('click', button.click.bind(button));//无论button.click如何定义,其上下文均指向button。