JavaScript 高级程序设计(第 4 版)- 函数

2023-05-17 15:10:06 浏览数 (2)

函数实际上是对象。每个函数都是Function类型的实例,Function也有属性和方法。函数名就是指向函数对象的指针。

# 箭头函数

  • 只有一个参数可以不用括号,只有没有参数、或多个参数的情况下,才需要使用括号
  • 箭头函数可以不用大括号,会隐式返回箭头后面那行代码的值
  • 箭头函数不能使用arguments、super和new.target,也不能作为构造函数
  • 箭头函数没有prototype属性

# 函数名

  • 函数名就是指向函数的指针
  • 使用不带括号的函数名会访问函数指针,而不会执行函数
  • 所有函数对象都会暴露一个只读的name属性,该属性保存函数标识符即字符串化的变量名
    • 函数没有名称会显示空字符串
    • 用Function构造函数创建的会标识成“anonymous”

# 理解参数

ECMAScript函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,在使用function关键字定义函数时,可以在函数内部访问arguments。arguments可以和命名参数一起使用。

# 没有重载

ECMAScript函数不能重载。如Java中,一个可以有两个定义,只要签名(接收参数的类型和数量)不同就行。ECMAScript函数没有签名,因为参数是由零个或多个值的数组表示的。没有函数签名,也就没有重载。

# 默认参数值

  • ES6开始支持显式定义默认参数
代码语言:javascript复制
function makeKing(name = 'Henry') {
  return `King ${name} VIII`;
}

  • 使用默认参数时,arguments对象的值不反映参数的默认值,只反映传给函数的参数
  • 默认参数并限于原始值或对象类型,也可以使用调用函数返回的值
    • 函数的默认参数只有在函数被调用时才会求值,不会在函数定义时求值
    • 计算默认值的函数只有在调用函数但未传相应参数时才会被调用
  • 函数参数在某个作用域中求值,默认参数按定义顺序进行初始化,参数初始化顺序遵循暂时性死区规则,即前面定义的参数不能引用后面定义的。参数也不能引用函数体的作用域。

# 参数扩展与收集

  • 扩展参数
代码语言:javascript复制
getSum(...values);
getSum(-1, ...values);
getSum(...values, ...[5,6,7]);

收集参数

  • 在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组
  • 箭头函数也支持参数收集
代码语言:javascript复制
function ignoreFirst(firstValue, ...values) {
  console.log(values);
}
ignoreFirst(); // []
ignoreFirst(1); // []
ignoreFirst(1,2); // [2]
ignoreFirst(1,2,3); // [2,3]

# 函数声明与函数表达式

  • 函数声明会提升,函数表达式不会提升(var 和 let都不会提升)

# 函数作为值

  • 函数可以用在任何使用变量的地方

# 函数内部

# arguments

  • arguments是一个类数组对象,包含调用函数时传入的所有参数
  • 只有以function关键字定义函数时才会有该对象
  • arguments有一个callee属性,为一个指向arguments对象所在函数的指针(可以在递归时利用)

# this

  • 标准函数中,this引用的是把函数当成方法调用的上下文对象,称this值
  • 在箭头函数中,this引用的是定义箭头函数的上下文

# caller

  • ES5会给函数对象添加一个属性:caller,引用的是调用当前函数的函数,如果是在全局作用域中调用的则为null

# new.target

ECMAScript中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。ES6新增了检测函数是否用new关键字调用的new.target属性。

  • 如果函数正常调用,new.target的值是undefined
  • 如果是使用new关键字调用,new.target将引用被调用的构造函数

# 函数属性与方法

ECMAScript中的函数是对象,有属性和方法

属性

  • length:保存函数定义的命名参数的个数
  • prototype: 保存引用类型所有实例方法,在ES5中prototype属性是不可枚举的,for-in循环不会返回这个属性

方法

  • apply():接收两个参数,函数内this的值和一个参数数组(也可以是arguments对象)
  • call():第一个参数是this值,其余函数参数需逐个传递
  • bind(): ES5新增,创建一个新的函数实例,其this值会被绑定到传给bind()的对象
代码语言:javascript复制
window.color = 'red';
var o = {
  color: 'blue'
};
function sayColor() {
  console.log(this.color);
}
let objectSayColor = sayColor.bind(o);
objectSayColor(); // blue

  • toLocaleString() 和 toString(): 返回函数的代码
  • valueOf(): 返回函数本身

# 递归

递归函数通常的形式是一个函数通过名称调用自己。arguments.callee是指向正在执行函数的指针,可以用在内部递归调用优先使用。

# 尾调用优化

尾调用,即外部函数的返回值是一个内部函数的返回值

代码语言:javascript复制
function outerFunction() {
  return innerFunction(); // 尾调用
}

ECMAScript 6 规范新增了一项内存管理优化机制,让 JavaScript 引擎在满足条件时可以重用栈帧。如果函数的逻辑允许基于尾调用将其销毁,则引擎就会那么做。

# 尾调用优化的条件

尾调用优化的条件就是确定外部栈帧真的没有必要存在了。

  1. 代码在严格模式下执行;
  2. 外部函数的返回值是对尾调用函数的调用;
  3. 尾调用函数返回后不需要执行额外的逻辑;
  4. 尾调用函数不是引用外部函数作用域中自由变量的闭包。
代码语言:javascript复制
// 优化前
function fib(n) {
  if (n < 2) {
    return n;
  }
  return fib(n - 1)   fib(n - 2);
}
// 优化后
"use strict";
function fib2(n) {
  return fibImpl(0, 1, n);
}
// 执行递归
function fibImpl(a, b, n) {
  if (n === 0) {
    return a;
  }
  return fibImpl(b, a   b, n - 1);
}

# 闭包

闭包指那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。闭包会保留它们包含函数的作用域,所以比其他函数更占用内存。

  • 在闭包中使用this会让代码变复杂。
  • 每个函数在被调用时会自动创建两个特殊变量:this和arguments。内部函数永远不可能直接访问外部函数的这两个变量。
代码语言:javascript复制
window.identity = 'The Window';
let object = {
  identity: 'My Object',
  getIdentity() {
    let that = this;
    return function() {
      return {
        a: this.identity,
        b: that.identity
      }
    }
  }
}
console.log(object.getIdentity()()); // {a: "The Window", b: "My Object"}

# 立即调用的函数表达式

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

  • ES6之前用IIFE模拟块级作用域

0 人点赞