javascript忍者秘籍-第四章 理解函数调用

1,217 阅读7分钟

调用函数时,隐式的函数参数 this 和 arguments 会被静默的传递给函数

this 表示调用函数的上下文对象

arguments 表示函数调用过程中传递的所有参数。通过 arguments 参数可以访问 函数调用过程中传递的实际参数。

函数调用的方式 对 函数的隐式参数有很大的影响

4.1 隐式的函数参数

arguments 和 this

arguments 参数

arguments 参数是 传递给函数的所有参数的集合

//arguments 是一个类数组对象 
//length 属性 表示实参的确切个数
arguments[2]  

function whatever(a,b,c){
    arguments.length  //实际传入的参数个数
}

//arguments 对象是函数参数的别名 所以改变了 arguments 对象的值,同时也会影响到对应的函数参数

在严格模式下,不能改变 arguments[index] 的值。

"use strict"

function infiltrate(person){
    arguments[0] = 'ninja';  //严格模式下 报错
}

this 参数:函数上下文

调用函数时,除了显示提供的参数外,this 参数也会默认的传递给函数。 => 函数上下文

this 参数的指向 不仅是由 定义函数的方式 和 位置决定的,还受到 函数调用方式的影响。

4.2 函数调用

函数调用的4种方式

//1.作为一个函数直接调用
function skulk(name){}
shulk('Hattori');

//2.作为一个方法,关联到对象上调用
var ninja = {
    shulk: function(){}
};
ninja.shulk('Hattori');

//3.作为构造函数调用
function Ninja(name){}
ninja = new Ninja('Hattori');

//4.通过apply 和 call调用
skulk.apply(ninja,['Hattori']);
skulk.call(ninja,'Hattori');

作为函数直接调用

函数上下文 this 有两种可能性:

1.在非严格模式下,this是全局上下文(window对象)

2.在严格模式下,this是undefined

//函数定义 作为函数调用
function ninja(){};
ninja();

//函数表达式 作为函数调用
var samurai = function(){};
samurai();

(function(){})();  //立即调用的函数表达式,作为函数被调用
//非严格模式下的函数调用
function ninja(){
    return this;  //window
}

//严格模式下的函数调用
function ninja(){
    "use strict";
    return this;  //undefined
}

作为方法被调用

当一个函数被赋值给一个对象的属性,并且通过对象属性引用的方式调用函数,函数会被作为 对象的方法 调用

当函数作为某个对象的方法被调用时,该对象会成为函数的上下文,并且在函数内部可以通过参数(this)访问到

var ninja = {};
ninja.skulk = function(){};
ninja.skulk();
function whatsMyContext(){
    return this;
}

whatsMyContext();  //当作函数调用 this == window

var getMyThis = whatsMyContext();
getMyThis();  //创建原函数的引用 当作函数调用 this == window

var ninja1 = {
    getMyThis: whatsMyContext
}
ninja1.getMyThis == ninja1  //this 返回函数上下文 == 该方法所在的对象

作为构造函数调用

//通过构造函数的方式调用,需要在函数调用之前使用关键字new
new MyObject();

function Ninja(){
    this.skulk = function(){
        return this;
    }
}

var ninja1 = new Ninja();
ninja1.skulk() === ninjia1  //true

当用 new 调用构造函数时,触发以下几个动作

  1. 创建一个新的空对象
  2. 该对象作为 this 参数传递给构造函数,从而成为构造函数的函数上下文
  3. 新构造的对象作为 new 运算符的返回值

构造函数的目的:创建一个新对象,并进行初始化设置,然后将其作为构造函数的返回值。

构造函数的返回值

function Ninja(){
    this.skulk = function(){
        return true;
    };
    return 1;  //返回一个基本数据类型
}

Ninja()   //返回值为1

//返回值1被忽略了,一个新的初始化对象返回
var ninja = new Ninja();
typeof ninja === "object"  //true
typeof ninja.skulk === "function"  //true

var puppet = {rules:false};
function Ninja(){
    this.skulk = function(){
        return true;
    }
    return puppet;  //返回一个新对象
}
  • 如果构造函数返回了一个对象,那么该对象作为整个表达式的值返回,传入构造函数的 this 被丢弃
  • 如果构造函数返回了 非对象类型,则忽略返回值,返回新创建的对象

apply/call方法调用

不同类型函数调用之间的区别:最终作为 函数上下文(this) 传递给 执行函数的对象不同。

直接函数:window undefined

方法:方法所在的对象

构造函数:新创建的对象实例

==每个函数都存在这两个方法==

改变函数的上下文,显示指定函数的上下文对象

事件回调函数的上下文 是 触发事件的对象 => 调用的位置

function Button(){
    this.clicked = false;
    this.click = function(){
        this.clicked = true;
        assert(button.clicked,"The button has been clicked");
    }
}

var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
//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]);
juggle.call(ninja2,5,6,7,8);

ninja1.result == 10
ninja2.result == 26

手动设置call和apply

强制指定回调函数的函数上下文

forEach 遍历函数将每个元素传给回调函数,将当前元素作为回调函数的上下文

function forEach(list,callback){
    for(var n=0;n<list.length;n++){
        callback.call(list[n],n);  //当前元素作为函数上下文,循环索引作为回调函数的参数
    }
}

var weapons = [{type:'abc'},{type:'bcd'},{type:'efg'}];
forEach(weapons,function(index){
    this === weapons[index]  //list[n]
})

4.3 解决函数上下文的问题

上下文不一致可以考虑以下几种办法:

1.call 和 apply 显示指定 this 上下文

2.箭头函数

3.bind 方法

箭头函数绕过函数上下文

箭头函数没有单独的this值.

箭头函数的 this 与声明所在的上下文相同

//箭头函数自身不含上下文,从定义时所在的函数继承上下文
//调用箭头函数时,不会隐式传入this参数,而是从定义时的函数继承上下文
function Button(){
    this.clicked = false;
    this.click = () => {
        this.clicked = true;
        button.clicked == true;  //button == this
    }
}

var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);

箭头函数与对象字面量

只有一个按钮,不需要使用构造函数。直接使用对象字面量。单例模式

//箭头函数与对象字面量
//对象字面量在全局代码中定义,所以this与全局代码的this相同
var button = {
    clicked: false,
    click: () => {  //箭头函数在创建时确定了this的指向
        this.clicked = true;
        button.clicked;   //false
        this === window;  //true
        window.clicked;   //true
    }
}

var elem = document.getElementById("test");
elem.addEventListener("click",button.click);

bind 方法

函数可以访问 bind 方法创建新函数,创建的的新函数与 **原始函数的函数体 **相同,新函数被绑定到指定的对象上,this 被设置为对象本身

调用 bind 方法不会修改 原始函数,而是创建了一个全新的函数

var button = {
    clicked : false,
    click : function(){
        this.clicked = true;
        button.clicked;  //true
    }
}

var elem = document.getElementById("test");
elem.addEventListener("click",button.click.bind(button));

//bind方法会新创建一个全新的函数
var boundFunction = button.click.bind(button);
boundFunction != button.click   //true

4.4 总结

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

    • arguments 参数是传入函数的所有参数的集合。具有 length 属性,表示传入参数的个数,通过 arguments 参数还可获取那些与函数形参不匹配的参数。 在非严格模式下,arguments 对象是函数参数的别名,修改 arguments 对象 会修改函数实参,可以通过严格模式避免修改函数实参。

    • this 表示函数上下文,即与函数调用相关联的对象。函数的定义方式和调用 方式决定了 this 的取值。

  • 函数的调用方式有 4 种。

    • 作为函数调用:skulk()。

    • 作为方法调用:ninja.skulk()。

    • 作为构造函数调用:new Ninja()。

    • 通过 apply 与 call 方法调用:skulk.apply(ninja)或 skulk.call(ninja)。

  • 函数的调用方式影响 this 的取值。

    • 如果作为函数调用,在非严格模式下,this 指向全局 window 对象;在严格 模式下,this 指向 undefined。

    • 作为方法调用,this 通常指向调用的对像。

    • 作为构造函数调用,this 指向新创建的对象。

    • 通过 call 或 apply 调用,this 指向 call 或 apply 的第一个参数。

  • 箭头函数没有单独的 this 值,this 在箭头函数创建时确定。

  • 所有函数均可使用 bind 方法,创建新函数,并绑定到 bind 方法传入的参数上。被绑定的函数与原始函数具有一致的行为。