函数实际上是对象。每个函数都是Function类型的实例,Function也有属性和方法。函数名就是指向函数对象的指针。
# 箭头函数
- 只有一个参数可以不用括号,只有没有参数、或多个参数的情况下,才需要使用括号
- 箭头函数可以不用大括号,会隐式返回箭头后面那行代码的值
- 箭头函数不能使用arguments、super和new.target,也不能作为构造函数
- 箭头函数没有prototype属性
# 函数名
- 函数名就是指向函数的指针
- 使用不带括号的函数名会访问函数指针,而不会执行函数
- 所有函数对象都会暴露一个只读的name属性,该属性保存函数标识符即字符串化的变量名
- 函数没有名称会显示空字符串
- 用Function构造函数创建的会标识成“anonymous”
# 理解参数
ECMAScript函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,在使用function关键字定义函数时,可以在函数内部访问arguments。arguments可以和命名参数一起使用。
# 没有重载
ECMAScript函数不能重载。如Java中,一个可以有两个定义,只要签名(接收参数的类型和数量)不同就行。ECMAScript函数没有签名,因为参数是由零个或多个值的数组表示的。没有函数签名,也就没有重载。
# 默认参数值
- ES6开始支持显式定义默认参数
function makeKing(name = 'Henry') {
return `King ${name} VIII`;
}
- 使用默认参数时,arguments对象的值不反映参数的默认值,只反映传给函数的参数
- 默认参数并限于原始值或对象类型,也可以使用调用函数返回的值
- 函数的默认参数只有在函数被调用时才会求值,不会在函数定义时求值
- 计算默认值的函数只有在调用函数但未传相应参数时才会被调用
- 函数参数在某个作用域中求值,默认参数按定义顺序进行初始化,参数初始化顺序遵循暂时性死区规则,即前面定义的参数不能引用后面定义的。参数也不能引用函数体的作用域。
# 参数扩展与收集
- 扩展参数
getSum(...values);
getSum(-1, ...values);
getSum(...values, ...[5,6,7]);
收集参数
- 在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组
- 箭头函数也支持参数收集
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()的对象
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 引擎在满足条件时可以重用栈帧。如果函数的逻辑允许基于尾调用将其销毁,则引擎就会那么做。
# 尾调用优化的条件
尾调用优化的条件就是确定外部栈帧真的没有必要存在了。
- 代码在严格模式下执行;
- 外部函数的返回值是对尾调用函数的调用;
- 尾调用函数返回后不需要执行额外的逻辑;
- 尾调用函数不是引用外部函数作用域中自由变量的闭包。
// 优化前
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。内部函数永远不可能直接访问外部函数的这两个变量。
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模拟块级作用域