第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方法并不会修改原始函数,而是创建了一个全新的函数
这个函数与原始函数行为一致,函数体一致