函数
- 函数也是对象
- 每个函数都是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;
};
1. 函数内部
函数内部存在两个特殊的对象:arguments 和 this。ES6新增了new.target 属性。
1.1 arguments
一个类数组对象,包含调用函数时传入的所有参数。
之前已经多次提到,arguments对象还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
上面代码中的arguments.callee可以代替函数名factorial实现递归。
1.2 this
1.2.1 在标准函数中,this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值。
window.color = 'red';
let o = {
color: 'blue'
};
function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'
1.2.2 在箭头函数中,this引用的是定义箭头函数的上下文。
window.color = 'red';
let o = {
color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'
将回调函数写成箭头函数可以避免一些问题。
避免this指向的不是想要的对象。
function King() {
this.royaltyName = 'Henry';
// this 引用 King 的实例
setTimeout(() => console.log(this.royaltyName), 1000);
}
function Queen() {
this.royaltyName = 'Elizabeth';
// this 引用 window 对象
setTimeout(function() { console.log(this.royaltyName); }, 1000);
}
new King(); // Henry
new Queen(); // undefined
1.3 caller
这个属性引用的是调用当前函数的函数
如果是在全局作用域中调用的则为 null。
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer();
以上代码会显示 outer()函数的源代码。
降低耦合度可以用arguments.callee.caller
function outer() {
inner();
}
function inner() {
console.log(arguments.callee.caller);
}
outer();
在严格模式下访问 arguments.callee 会报错
new.target
ES中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。
new.target是为了区分到底使用哪种方式调用的
function King() {
if (!new.target) {
throw 'King must be instantiated using "new"'
}
console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"
2.函数属性与方法
两个属性:length和 prototype
两个方法:apply()和 call()。
2.1 函数的length属性
length 属性保存函数定义的命名参数的个数
function sayName(name) {
console.log(name);
}
function sum(num1, num2) {
return num1 + num2;
}
function sayHi() {
console.log("hi");
}
console.log(sayName.length); // 1
console.log(sum.length); // 2
console.log(sayHi.length); // 0
2.2 函数的prototype属性
prototype 是保存引用类型所有实例方法的地方,这意味着 toString()、valueOf()等方法实际上都保存在 prototype 上。
相关内容已经在第 8 章详细介绍。
2.3 apply()方法和 call()方法
主要作用是传入函数体内 this值的能力。
apply()方法接收两个参数:
- 函数内 this 的值。
- 第二个参数可以是 Array 的实例,但也可以是 arguments 对象。
call()方法和apply()方法差不多,只是把数组的接收变成了一个个分散开的。
function sum(num1, num2) {
return num1 + num2;
}
function callSum(num1, num2) {
return sum.call(this, num1, num2);
}
console.log(callSum(10, 10)); // 20
下面这个例子可以看出这两个方法的用法
window.color = 'red';
let o = {
color: 'blue'
};
function sayColor() {
console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue
闭包
单独开一章节