10.1 箭头函数

ECMAScript 6 新增了使用胖箭头(=>)语法定义函数表达式的能力。
任何可以使用函数表达式的地方,都可以使用箭头函数。

  1. let arrowSum = (a, b) => {
  2. return a + b;
  3. };
  4. let functionExpressionSum = function(a, b) {
  5. return a + b;
  6. };
  7. console.log(arrowSum(5, 8)); // 13
  8. console.log(functionExpressionSum(5, 8)); // 13

箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用 arguments、super 和
new.target,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。

10.2 函数名

因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。
这意味着一个函数可以有多个名称。

  1. function sum(num1, num2) {
  2. return num1 + num2;
  3. }
  4. console.log(sum(10, 10)); // 20
  5. let anotherSum = sum;
  6. console.log(anotherSum(10, 10)); // 20
  7. sum = null;
  8. console.log(anotherSum(10, 10)); // 20

10.3 理解参数

使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中取得传进来的每个参数值。
arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是 arguments[0],第二个参数是 arguments[1])。
而要确定传进来多少个参数,可以访问 arguments.length 属性。

  1. function howManyArgs() {
  2. console.log(arguments.length);
  3. }
  4. howManyArgs("string", 45); // 2
  5. howManyArgs(); // 0
  6. howManyArgs(12); // 1

image.png

10.4 没有重载

ECMAScript 函数不能像传统编程那样重载。
在其他语言比如 Java 中,一个函数可以有两个定义,只要签名(接收参数的类型和数量)不同就行。
如前所述,ECMAScript 函数没有签名,因为参数是由包含零个或多个值的数组表示的。
没有函数签名,自然也就没有重载。

如果在 ECMAScript 中定义了两个同名函数,则后定义的会覆盖先定义的。

  1. function addSomeNumber(num) {
  2. return num + 100;
  3. }
  4. function addSomeNumber(num) {
  5. return num + 200;
  6. }
  7. let result = addSomeNumber(100); // 300

10.5 函数默认值

只要在函数定义中的参数后面用=就可以为参数赋一个默认值

  1. function makeKing(name = 'Henry') {
  2. return `King ${name} VIII`;
  3. }
  4. console.log(makeKing('Louis')); // 'King Louis VIII'
  5. console.log(makeKing()); // 'King Henry VIII'

函数的默认参数只有在函数被调用时才会求值,不会在函数定义时求值。
计算默认值的函数只有在调用函数但未传相应参数时才会被调用。

10.6 参数扩展与收集

对函数中的 arguments 对象而言,它并不知道扩展操作符的存在,而是按照调用函数时传入的参数接收每一个值

  1. function getProduct(a, b, c = 1) {
  2. return a * b * c;
  3. }
  4. let getSum = (a, b, c = 0) => {
  5. return a + b + c;
  6. }
  7. console.log(getProduct(...[1,2])); // 2
  8. console.log(getProduct(...[1,2,3])); // 6
  9. console.log(getProduct(...[1,2,3,4])); // 6
  10. console.log(getSum(...[0,1])); // 1
  11. console.log(getSum(...[0,1,2])); // 3
  12. console.log(getSum(...[0,1,2,3])); // 3

10.7 函数声明与函数表达式

JavaScript 引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。
而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

  1. // 没问题
  2. console.log(sum(10, 10));
  3. function sum(num1, num2) {
  4. return num1 + num2;
  5. }
  6. // 会出错
  7. console.log(sum(10, 10));
  8. let sum = function(num1, num2) {
  9. return num1 + num2;
  10. };

10.8 函数作为值

因为函数名在 ECMAScript 中就是变量,所以函数可以用在任何可以使用变量的地方。
这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。

  1. function callSomeFunction(someFunction, someArgument) {
  2. return someFunction(someArgument);
  3. }
  4. function add10(num) {
  5. return num + 10;
  6. }
  7. let result1 = callSomeFunction(add10, 10);
  8. console.log(result1); // 20
  9. function getGreeting(name) {
  10. return "Hello, " + name;
  11. }
  12. let result2 = callSomeFunction(getGreeting, "Nicholas");
  13. console.log(result2); // "Hello, Nicholas"

10.9 函数内部

10.9.1 arguments

它是一个类数组对象,包含调用函数时传入的所有参数。
这个对象只有以 function 关键字定义函数时才会有。
虽然主要用于包含函数参数,但 arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。

10.9.2 this

另一个特殊的对象是 this,它在标准函数和箭头函数中有不同的行为。
在标准函数中,this 引用的是把函数当成方法调用的上下文对象,在网页的全局上下文中调用函数时,this 指向 windows)。

  1. window.color = 'red';
  2. let o = {
  3. color: 'blue'
  4. };
  5. function sayColor() {
  6. console.log(this.color);
  7. }
  8. sayColor(); // 'red'
  9. o.sayColor = sayColor;
  10. o.sayColor(); // 'blue'

在箭头函数中,this引用的是定义箭头函数的上下文。

  1. window.color = 'red';
  2. let o = {
  3. color: 'blue'
  4. };
  5. let sayColor = () => console.log(this.color);
  6. sayColor(); // 'red'
  7. o.sayColor = sayColor;
  8. o.sayColor(); // 'red'

10.9.3 caller

这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为 null。

  1. function outer() {
  2. inner();
  3. }
  4. function inner() {
  5. console.log(inner.caller); //outer() 内容
  6. }
  7. outer();

10.9.4 new.target

ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性。
如果函数是正常调用的,则 new.target 的值是 undefined,
如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。

  1. function King() {
  2. if (!new.target) {
  3. throw 'King must be instantiated using "new"'
  4. }
  5. console.log('King instantiated using "new"');
  6. }
  7. new King(); // King instantiated using "new"
  8. King(); // Error: King must be instantiated using "new"

10.10 函数属性与方法

每个函数都有两个属性:length和 prototype。length 属性保存函数定义的命名参数的个数

函数还有两个方法:apply()和 call()。
这两个方法都会以指定的 this 值来调用函数,即会设置调用函数时函数体内 this 对象的值。
apply()方法接收两个参数:函数内 this 的值和一个参数数组。第二个参数可以是 Array 的实例,但也可以是 arguments 对象。

  1. function sum(num1, num2) {
  2. return num1 + num2;
  3. }
  4. function callSum1(num1, num2) {
  5. return sum.apply(this, arguments); // 传入 arguments 对象
  6. }
  7. function callSum2(num1, num2) {
  8. return sum.apply(this, [num1, num2]); // 传入数组
  9. }
  10. console.log(callSum1(10, 10)); // 20
  11. console.log(callSum2(10, 10)); // 20

call()方法与 apply()的作用一样,只是传参的形式不同。
第一个参数跟 apply()一样,也是 this值,而剩下的要传给被调用函数的参数则是逐个传递的。

  1. function sum(num1, num2) {
  2. return num1 + num2;
  3. }
  4. function callSum(num1, num2) {
  5. return sum.call(this, num1, num2);
  6. }
  7. console.log(callSum(10, 10)); // 20

ECMAScript 5 出于同样的目的定义了一个新方法:bind()。
bind()方法会创建一个新的函数实例,其 this 值会被绑定到传给 bind()的对象。

  1. window.color = 'red';
  2. var o = {
  3. color: 'blue'
  4. };
  5. function sayColor() {
  6. console.log(this.color);
  7. }
  8. let objectSayColor = sayColor.bind(o);
  9. objectSayColor(); // blue

10.11 函数表达式

函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义。这意味着函数声明可以出现在调用它的代码之后

  1. sayHi();
  2. function sayHi() {
  3. console.log("Hi!");
  4. }

这个例子不会抛出错误,因为 JavaScript 引擎会先读取函数声明,然后再执行代码。

第二种创建函数的方式就是函数表达式。
函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量functionName。
函数表达式跟 JavaScript 中的其他表达式一样,需要先赋值再使用。

  1. sayHi(); // Error! function doesn't exist yet
  2. let sayHi = function() {
  3. console.log("Hi!");
  4. };

10.12 递归

10.13 尾调用优化

10.14 闭包

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

  1. function createComparisonFunction(propertyName) {
  2. return function(object1, object2) {
  3. let value1 = object1[propertyName];
  4. let value2 = object2[propertyName];
  5. if (value1 < value2) {
  6. return -1;
  7. } else if (value1 > value2) {
  8. return 1;
  9. } else {
  10. return 0;
  11. }
  12. };
  13. }

image.png

函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。
函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。

10.15 立即调用的函数表达式

立即调用的匿名函数又被称作立即调用的函数表达式。
它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。
紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。

使用 IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。

  1. // IIFE
  2. (function () {
  3. for (var i = 0; i < count; i++) {
  4. console.log(i);
  5. }
  6. })();
  7. console.log(i); // 抛出错误

10.16 私有变量

任何定义在函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量。
私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。