第4章 函数进阶:理解函数调用

1. 隐式函数参数

arguments

  • 所有参数集合
function foo() {
    console.log(arguments);
    console.log(arguments[0]);
    console.log(arguments.length);
}

foo(1, 2, 3);
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 1
// 3

arguments不是数组,无法调用数组方法。

大多数情况下可以使用剩余参数(...)来代替arguments。

  • arguments的值和参数的值指向同一个数据,修改后会相互影响,严格模式下不会影响
// 非严格模式下
function setInfo(name, age) {
    console.log(name + " " + age);  // Wango 24
    arguments[0] = "Lily";
    console.log(name + " " + age);  // Lily 24
}

setInfo("Wango", 24);

// 严格模式下
function setInfo(name, age) {
    "use strict";
    console.log(name + " " + age);  // Wango 24
    arguments[0] = "Lily";
    console.log(name + " " + age);  // Wango 24
}

setInfo("Wango", 24);

this

  • 代表函数调用相关联的对象,通常被称为函数上下文
  • this的指向与函数的调用方式相关

2. 函数调用

2.1 作为函数直接调用

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

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

// 作为立即调用函数被调用
(function() {})();

在作为函数直接调用时,函数内部this在非严格模式下指向全局上下文(window/global对象),在严格模式下为undefined。

// 非严格模式下
(function() {
    console.log(this);
    // Window {window: Window, self: Window, document: document, name: "", location: Location, …} 
})();

// 严格模式下
(function() {
    "use strict";
    console.log(this);      // undefined
})();

2.2 作为对象方法调用

var tools = {};
// 函数被赋值给一个对象的属性
tools.sort = function() {};
tools.sort();
  • 作为方法调用时,this指向调用此方法的对象
var tools = {};
tools.sort = function() {
    console.log(this === tools); // true
};
tools.sort();

2.3 作为构造函数调用

function Student(){
    this.study = function() {
        return this;
    };
}

var stu1 = new Student();
var stu2 = new Student();

console.log(stu1.study() === stu1);     // true
console.log(stu2.study() === stu2);     // true
  • 调用构造函数时的操作:
    • 使用关键字new来调用函数,从而创建一个新的空对象
    • 新的空对象被设置为该函数的上下文(this)
    • 为该对象增加函数中定义的方法和属性
    • 新构造的对象作为new操作符的返回值返回

使用new操作符时,若这个构造函数本身有返回值,则分为两种情况:

若返回值是原始类型值(数字、字符串、布尔值等),则new操作符返回新创建的对象。

若返回值不是原始类型值(对象、函数、数组等),则new操作符返回构造函数的返回值。

function Obj1() {
    this.value = 200;
    return 1;       // 构造函数返回原始类型值
}

var o1 = new Obj1();        // new操作符返回新创建的对象
console.log(o1.value);      // 200

function Obj2() {
    this.value = 200;
    return {        // 构造函数返回一个对象
        value: 100,
    };
}

var o2 = new Obj2();        // new操作符返回构造函数返回的对象
console.log(o2.value);      // 100


2.4 通过apply或call方法调用

  • 为什么使用apply或call
function Button() {
    this.clicked = false;
    this.click = function() {
        this.clicked = true;    // this指向elem
        /** 绑定事件时,浏览器的事件处理系统把this指向了触发事件
        * 的元素,在这里就是elem,所以this.clicked = elem.clicked
        * 将属性设置到错误的对象上了
        */
        console.log(this.clicked);      // true
        console.log(btn.clicked);       // false
        console.log(elem.clicked);      // true
    }
}

var btn = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click", btn.click);
  • 使用apply或call方法显式地指明this指向
function sum(...nums) {
    var result = 0;
    for (var i = 0; i < nums.length; i++){
        result += nums[i];
    }
    this.result = result;
}

var obj01 = {};
var obj02 = {};

// 用apply或call方法调用函数,并指明this指向
// sum函数中this指向第一个参数传入的对象
sum.apply(obj01, [1, 2, 3, 4, 5]);  
sum.call(obj02, 6, 7, 8, 9, 0);

console.log(obj01.result);      // 15
console.log(obj02.result);      // 30

// 直接调用函数,this指向默认上下文,这里为window
sum(1, 2, 3, 4, 5);

console.log(window.result);     // 15

apply个call方法唯一的不同在于apply传递参数时使用数组,

call方法直接以参数的形式传递

  • 强制指定回调函数的函数上下文
function forEach(list, callback) {
    for (var i = 0; i < list.length; i++) {
        // 使用call方法指定回调函数中this的指向
        callback.call(list[i]);
    }
}
var pers = [
    {name: "Wango"},
    {name: "Lily"},
    {name: "Jack"},
];

forEach(pers, function() {
    // 在这里thisu依次指向数组中的对象
    console.log(this.name);
});
// Wango
// Lily
// Jack

3. 解决上下文问题

3.1 用箭头函数绕过上下文

  • 箭头函数作为回调函数没有单独的this值,箭头函数的this与声明所在的上下文相同,即在函数创建时确定
function Button() {
    this.clicked = false;
    // 这里使用箭头函数创建函数
    this.click = () => {
        this.clicked = true;            // this指向btn
        console.log(this.clicked);      // true
        console.log(btn.clicked);       // true
        console.log(elem.clicked);      // undefined
    }
}

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

/**
* 当对象使用new操作符构建时(如上),this指向new返回的对象
* 但当对象是以字面量的形式定义时,当前的上下文为外部对象(window),
* this也就指向的window
*/
// 用字面量方式定义对象
var btn = {
    clicked: false,
    // 同样以箭头函数创建函数
    click: () => {
        this.clicked = true;            // this指向btn的上下文(window)
        console.log(this.clicked);      // true
        console.log(btn.clicked);       // false
        console.log(elem.clicked);      // undefined
        console.log(window.clicked);    // true
    }
}

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

3.2 使用bind方法

  • 使用bind方法可以将函数绑定到指定对象上
function Button() {
    this.clicked = false;
    this.click = function() {
        this.clicked = true;        // this指向btn
        console.log(this.clicked);  // true
        console.log(btn.clicked);   // true
        console.log(elem.clicked);  // undefined
    }
}

var btn = new Button();
var elem = document.getElementById("test");
// 使用bind方法指定函数绑定的对象
elem.addEventListener("click", btn.click.bind(btn));

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

这个函数与原始函数行为一致,函数体一致

原文地址:https://www.cnblogs.com/hycstar/p/14002540.html