1、函数作为对象
函数是ECMAScript中最有意思的部分之一,这主要是因为函数实际上是对象。每个函数都是Function类型的实例,而Function也有属性和方法,跟其他引用类型一样。
1.1 函数名
因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。
因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。
- 一个函数可以有多个名称(多个指向这个函数指针的变量)
- ECMAScript 6 中,只写函数名会指向这个函数指针,函数名加()会直接执行这个函数,
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum; //anotherSum指向的是sum函数的指针
console.log(anotherSum(10, 10)); // 20
console.log(sum.name()) // sum
1.2 函数的属性和方法
ECMAScript中的函数是对象,因此有属性和方法。
1.2.1 属性
每个函数都有两个属性:length和prototype。
- length属性保存函数定义的命名参数的个数
- prototype是保存引用类型所有实例方法的地方,
- ECMAScript 6的所有函数对象都会暴露一个只读的name属性
1.2.2 方法
函数还有两个方法:apply()和call()。这两个方法的作用是可以改变调用函数时函数体内this对象的值。
这两个方法的区别在于传参的形式不同:
- apply()方法接收两个参数:函数内this的值和一个参数数组。apply(this, [params])
- call()向函数传参时,必须将参数一个一个地列出来。call(this, param1, param2.....)
2、理解函数参数
ECMAScript函数的参数只是为了方便才写出来的,并不是必须写出来的。
与其他语言不同,在ECMAScript中的命名参数不会创建让之后的调用必须匹配的函数签名。这是因为根本不存在验证命名参数的机制。
ECMAScript函数的参数在内部表现为一个数组,函数被调用时总会接收一个数组,但函数并不关心这个数组中包含什么。
在使用function关键字定义**(非箭头)函数时,可以在函数内部访问arguments**对象,从中取得传进来的每个参数值。
// 这两种函数声明参数都可以调用
function sayHi(name, message) {
console.log("Hello " + name + ", " + message);
}
function sayHi() {
console.log("Hello " + arguments[0] + ", " + arguments[1]);
}
2.1 arguments 对象
arguments对象是一个类数组对象,包含调用函数时传入的所有参数。(这个对象只有以function关键字定义函数(相对于使用箭头语法创建函数)时才会有)
arguments对象虽然主要用于包含函数参数,但arguments对象其实还有一个callee属性,是一个指向arguments对象所在函数的指针。
来看下面这个经典的阶乘函数:
// 这个函数要正确执行就必须保证函数名是factorial,从而导致了紧密耦合。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
// 使用arguments.callee就可以让函数逻辑与函数名解耦:
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
// 来看一种体现函数实际上是一个对象的情况(ps:JavaScript是值传递的,引用类型传递的是作为值得引用)
let trueFactorial = factorial; // 函数factorial的指针保存给了trueFactorial变量
factorial = function() {return 0;}; //factorial函数又被重写为一个返回0的函数
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0
2.2 默认参数与参数拓展
PS: 这部分内容,个人目前工作中使用到的比较少,这里简单总结下
默认参数
ECMAScript 6支持显式定义默认参数。
在使用默认参数时,arguments对象的值不反映参数的默认值,只反映传给函数的参数。
function makeKing(name = 'Henry') {
name = 'Louis';
return `King ${arguments[0]}`;
}
console.log(makeKing()); // 'King undefined'
console.log(makeKing('Louis')); // 'King Louis'
参数拓展
ECMAScript 6新增了扩展操作符,使用它可以非常简洁地操作和组合集合数据。
**对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中**
3、函数声明与函数表达式
定义函数有两种方式:函数声明和函数表达式。两者的区别在于提升:
JavaScript引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义**(函数声明提升)。而函数表达式**必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。
// 函数声明
console.log(sum(10, 10));//会进行函数声明提升,这样不会报错
function sum(num1, num2) {
return num1 + num2;
}
// 函数表达式
console.log(sum(10, 10)); // 报错
let sum = function(num1, num2) {
return num1 + num2;
};
4、this
this在标准函数和箭头函数中有不同的行为。
- 在标准函数中,this引用的是把函数当成方法调用的上下文对象
- 在箭头函数中,this引用的是定义箭头函数的上下文
5、其他一些概念
- ECMAScript没有重载。如果在ECMAScript中定义了两个同名函数,则后定义的会覆盖先定义的。
- 闭包指的是那些引用了另一个函数作用域中变量的函数。