函数
- 函数也是对象
- 每个函数都是Function类型的实例,而 Function 也有属性和方法
- 函数名是指向函数对象的指针,而且不一定与函数本身紧密绑定
定义
- 函数声明的方式
function sum (num1, num2) {
return num1 + num2;
}
- 函数表达式
let sum = function(num1, num2) {
return num1 + num2;
};
- 箭头函数
let sum = (num1, num2) => {
return num1 + num2;
};
- Function 构造函数(不推荐)
接受任意多个字符串参数
最后一个参数是函数体
let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
箭头函数
箭头函数与正式的函数表达式创建的函数对象行为是相同的
let arrowSum = (a, b) => {
return a + b;
};
let functionExpressionSum = function(a, b) {
return a + b;
};
console.log(arrowSum(5, 8)); // 13
console.log(functionExpressionSum(5, 8)); // 13
- 如果只有一个参数,可以不用括号
以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
- 只有没有参数,或者多个参数的情况下,才需要使用括号
// 没有参数需要括号
let getRandom = () => { return Math.random(); };
// 多个参数需要括号
let sum = (a, b) => { return a + b; };
// 无效的写法:
let multiply = a, b => { return a * b; };
- 可以不使用大括号,但是只能写一行代码,省略大括号会隐式返回这行代码的值
// 以下两种写法都有效,而且返回相应的值
let double = (x) => { return 2 * x; };
let triple = (x) => 3 * x;
// 可以赋值
let value = {};
let setName = (x) => x.name = "Matt";
setName(value);
console.log(value.name); // "Matt"
// 无效的写法:
let multiply = (a, b) => return a * b;
箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用 arguments、super 和new.target,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。
函数名
函数名就是指向函数的指针
意味着一个函数可以有多个名称
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
sum = null;
console.log(anotherSum(10, 10)); // 20
上面代码sum=null只是切短了sum和函数的联系,不影响anothersum
函数包含一个只读的name属性,包含函数标识符(函数名称),字符串类型
function foo() {}
let bar = function() {};
let baz = () => {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //(空字符串)
console.log((new Function()).name); // anonymous
理解参数
ES不会关心传入参数的个数和数据类型,无论你定义多少个
arguments对象
- 在非箭头函数内部可以访问arguments对象
- arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素
- length属性检查传入的参数个数
- arguments 对象可以跟命名参数一起使用
虽然改变arguments的值能改变命名参数所代替的值
但是两个所对应的内存是不一样的
function doAdd(num1, num2) {
arguments[1] = 10;
console.log(arguments[0] + num2);
}
比较传入两个参数和传入一个参数的区别
箭头函数中的参数
箭头函数中不能使用arguments对象
可以在包装函数中把arguments提供给箭头函数
function foo() {
let bar = () => {
console.log(arguments[0]); // 5
};
bar();
}
foo(5);
没有重载
如果在 ECMAScript 中定义了两个同名函数,则后定义的会覆盖先定义的。
function addSomeNumber(num) {
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
let result = addSomeNumber(100); // 300
默认参数值
- ES5.1及以前,实现默认参数主要检测是否等于undefined
function makeKing(name) {
name = (typeof name !== 'undefined') ? name : 'Henry';
return `King ${name} VIII`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII'
- ES6之后,直接在定义出加一个=就可以定义默认参数。
function makeKing(name = 'Henry') {
return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'
- 而给参数传undefined相当于没传值,函数会调用默认值
function makeKing(name = 'Henry', numerals = 'VIII') {
return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing(undefined, 'VI')); // 'King Henry VI'
- arguments 对象的值不反映参数的默认值,只反应传给函数的参数
function makeKing(name = 'Henry') {
name = 'Louis';
return `King ${arguments[0]}`;
}
console.log(makeKing()); // 'King undefined'
console.log(makeKing('Louis')); // 'King Louis'
- 默认参数值并不限于原始值或对象类型,也可以使用调用函数返回的值
let romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI'];
let ordinality = 0;
function getNumerals() {
// 每次调用后递增
return romanNumerals[ordinality++];
}
function makeKing(name = 'Henry', numerals = getNumerals()) {
return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry I'
console.log(makeKing('Louis', 'XVI')); // 'King Louis XVI'
console.log(makeKing()); // 'King Henry II'
console.log(makeKing()); // 'King Henry III'
注意:
- 默认参数只有在函数被调用时才会求值
- 计算默认值的函数只有在未传相应参数时才会被调用
- 箭头函数也可以使用默认参数,在只有一个参数时,括号不能省略
let makeKing = (name = 'Henry') => `King ${name}`;
console.log(makeKing()); // King Henry
默认参数作用域与暂时性死区
function makeKing(name = 'Henry', numerals = 'VIII') {
return `King ${name} ${numerals}`;
}
console.log(makeKing()); // King Henry VIII
可以想象成下面的代码
function makeKing() {
let name = 'Henry';
let numerals = 'VIII';
return `King ${name} ${numerals}`;
}
参数初始化顺序遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的。像这样就会抛出错误:
// 调用时不传第一个参数会报错
function makeKing(name = numerals, numerals = 'VIII') {
return `King ${name} ${numerals}`;
}
参数也存在于自己的作用域中,它们不能引用函数体的作用域:
// 调用时不传第二个参数会报错
function makeKing(name = 'Henry', numerals = defaultNumeral) {
let defaultNumeral = 'VIII';
return `King ${name} ${numerals}`;
}
参数扩展与收集
扩展参数
有时候可能不需要传一个数组,而是要分别传入数组的元素
console.log(getSum(...values)); // 10
对函数中的 arguments 对象而言,它并不知道扩展操作符的存在,而是按照调用函数时传入的参数接收每一个值:
let values = [1,2,3,4]
function countArguments() {
console.log(arguments.length);
}
countArguments(-1, ...values); // 5
countArguments(...values, 5); // 5
countArguments(-1, ...values, 5); // 6
countArguments(...values, ...[5,6,7]); // 7
收集参数
收集参数的结果会得到一个 Array 实例,区别于arguments 对象
function getSum(...values) {
// 顺序累加 values 中的所有值
// 初始值的总和为 0
return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6
多余的参数要放在它前边
// 不可以
function getProduct(...values, lastValue) {}
// 可以
function ignoreFirst(firstValue, ...values) {
console.log(values);
}
ignoreFirst(); // []
ignoreFirst(1); // []
ignoreFirst(1,2); // [2]
ignoreFirst(1,2,3); // [2, 3]
箭头函数支持收集参数,虽然不支持aarguments,但能利用收集函数实现同样的功能。
函数声明与函数表达式
函数声明会得到函数声明提升
// 没问题
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;
};