文章目录
前言
写本《JavaScript简餐》系列文章的目的是记录在阅读学习《JavaScript高级程序设计(第4版)》一书时出现的各个知识点。虽是对读书的笔记和总结,但是希望它轻量、简洁、犀利,不会引起阅读疲劳,可以在碎片化时间和闲暇之余轻巧地沐浴一下知识点。每篇文章只针对一个小部分进行讲解式的梳理,来达到个人复习总结和分享知识的目的。
一、arguments对象的callee属性
关于arguments对象,前面已经详细介绍过了,它是一个类数组对象,包含调用函数时传入的所有参数。其中有一个比较有意思的属性叫callee,这是指向arguments对象所在函数的指针。直接来看一下具体例子来理解一下:function person() {
console.log(arguments.callee);
}
person();
//输出结果就是这个函数:
// ƒ person() {
// console.log(arguments.callee);
// }
这个属性一个实用的地方是在递归函数中将函数逻辑与函数名解耦。来看一个用递归计算阶乘的例子:
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
这个例子中,这个函数要正确执行就必须保证函数名是factorial,从而导致了紧密耦合。那么,我们就可以使用arguments.callee来让函数逻辑与函数名解耦:
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1); // 这里我们将factorial换成arguments.callee来代替函数名
}
}
这样就意味着无论函数叫什么名称,都可以引用正确的函数。再来考虑下面这种函数被中途修改的情况:
let anotherFactorial = factorial;
factorial = function () {
return 0;
};
console.log(anotherFactorial(5)); // 120
console.log(factorial(5)); // 0
在这里,anotherFactorial被赋值为刚刚定义的factorial函数,而后面factorial函数被重写为一个返回0的函数。如果像我们第一次的那个版本,在递归函数内使用factorial函数进行递归而不是使用arguments.callee,那么这两个函数的运行结果就都是0。不过将函数逻辑与函数名称解耦后,anotherFactorial函数就依旧可以返回正确的阶乘运算结果。
二、函数的caller属性
caller这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null:function world() {
person();
}
function person() {
console.log(person.caller);
}
world();
//输出结果就是调用person函数的earth函数:
// ƒ world() {
// person();
// }
当然,这里如果想像前面那样降低耦合性的话我们还是可以用arguments.callee来代替person:
function world() {
person();
}
function person() {
console.log(arguments.callee.caller);
}
world();
//输出结果就是调用person函数的earth函数:
// ƒ world() {
// person();
// }
结果是一样的。
三、new.target
ECMAScript中的函数既可以当作普通函数来调用,也可以当作构造函数来实例化一个对象。在ECMAScript6中新增了检测函数是否使用了new关键字调用的new.target属性。如果函数是正常调用的,那么在函数中调用new.target则会返回undefined,如果函数是使用new关键字来调用的,那么new.target将会引用被调用的构造函数。来看一个具体例子:function Person(name, age) {
if (new.target === Person) {
console.log("当前函数被当作构造函数使用来初始化一个实例!");
} else {
console.log("当前函数被当作普通函数调用。");
}
}
new Person(); // 当前函数被当作构造函数使用来初始化一个实例!
Person(); // 当前函数被当作普通函数调用。
四、call、apply和bind方法
call、apply、bind方法都是用来改变函数体内this对象的指向的,同时还具备传参的功能,我们一个个看过来:1.call方法
还是直接通过代码实例来理解一下吧://实例1
const person = {
name: "Lucy",
age: 20,
};
function sayInformation() {
console.log(this.name);
console.log(this.age);
}
sayInformation.call(person);
// Lucy
// 20
//实例2
function doAdd(x, y) {
return x + y;
}
function add(x, y) {
return doAdd.call(this, x, y);
}
console.log(add(1, 2)); // 3
在实例1中person对象中没有定义任何方法,打印信息的函数我们在全局中定义了一个sayInformation函数,可是这个函数打印的是this.name和this.age而不是person.name和person.age。那如果我们想打印person对象中的name和age属性要怎么办呢?那当然是要在函数和对象之间搭个桥了。这个时候call方法就派上用场了,就用它来搭桥。原理就是让sayInformation函数的this对象指向person。所以在最后我们调用的是sayInformation.call(person)。这样就可以打印出person中的属性了。
2.apply方法
apply在功能上与call方法是等价的,只不过在传参的方法上不同。call方法需要一个一个传参,而apply方法需要传入参数数组。将上面的例子改成利用apply方法的版本如下://实例1
const person = {
name: "Lucy",
age: 20,
};
function sayInformation() {
console.log(this.name);
console.log(this.age);
}
sayInformation.apply(person);
// Lucy
// 20
//实例2
function doAdd(x, y) {
return x + y;
}
function add(x, y) {
return doAdd.apply(this, [x, y]); // 在这里不是逐个传参了,而是传一个参数数组
}
console.log(add(1, 2)); // 3
到底是使用apply还是call,完全取决于怎么给要调用的函数传参更方便。如果想直接传arguments对象或者一个数组,那就用apply();否则就用call()。如果不用给被调用的函数传参,则用哪种方法都一样。
3.bind方法
ECMAScript5出于同样的目的定义了一个新方法:bind()。bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind的对象。其实换汤不换药,来看以下例子:const numbers = {
x: 1,
y: 2,
};
function add(num1, num2) {
return this.x * num1 + this.y * num2;
}
let getResult = add.bind(numbers, 2, 3);
console.log(getResult()); // 8
在这个例子中我们定义了numbers对象,其中有属性x和y。之后我们又定义了add函数,函数的功能是将x和y分别乘以一个数字并返回。主要看下面的部分,我们先利用bind方法将add函数的this指向numbers对象,顺便传了两个参数2和3(注意,这里的参数是逐个传的,和call方法相同)。这会创建一个新的函数实例,之后我们将这个新产生的函数实例赋值给getResult变量,在最后我们通过getResult()来调用这个函数。当然最后一部分也可以将两句简写成一句let getResult = add.bind(numbers, 2, 3) ()来直接调用。
//实例1
const person = {
name: "Lucy",
age: 20,
};
function sayInformation() {
console.log(this.name);
console.log(this.age);
}
sayInformation.bind(person)();
// Lucy
// 20
//实例2
function doAdd(x, y) {
return x + y;
}
function add(x, y) {
return doAdd.bind(this, x, y)(); // 在这里利用bind,并且直接调用函数。参数逐个传
}
console.log(add(1, 2)); // 3