Skip to content

1、函数作为对象

函数是ECMAScript中最有意思的部分之一,这主要是因为函数实际上是对象。每个函数都是Function类型的实例,而Function也有属性和方法,跟其他引用类型一样。

1.1  函数名

因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。

因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。

  • 一个函数可以有多个名称(多个指向这个函数指针的变量)
  • ECMAScript 6 中,只写函数名会指向这个函数指针,函数名加()会直接执行这个函数
javascript
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**对象,从中取得传进来的每个参数值。

javascript
// 这两种函数声明参数都可以调用
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对象所在函数的指针

来看下面这个经典的阶乘函数:

javascript
// 这个函数要正确执行就必须保证函数名是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对象的值不反映参数的默认值,只反映传给函数的参数。

javascript
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引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义**(函数声明提升)。而函数表达式**必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

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中定义了两个同名函数,则后定义的会覆盖先定义的。
  • 闭包指的是那些引用了另一个函数作用域中变量的函数。