函数
- 函数也是对象
- 每个函数都是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)); // 13console.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)); // 20let anotherSum = sum;console.log(anotherSum(10, 10)); // 20sum = null;console.log(anotherSum(10, 10)); // 20
上面代码sum=null只是切短了sum和函数的联系,不影响anothersum
函数包含一个只读的name属性,包含函数标识符(函数名称),字符串类型
function foo() {}let bar = function() {};let baz = () => {};console.log(foo.name); // fooconsole.log(bar.name); // barconsole.log(baz.name); // bazconsole.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); // 5countArguments(...values, 5); // 5countArguments(-1, ...values, 5); // 6countArguments(...values, ...[5,6,7]); // 7
收集参数
收集参数的结果会得到一个 Array 实例,区别于arguments 对象
function getSum(...values) {// 顺序累加 values 中的所有值// 初始值的总和为 0return 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(); // Henrynew 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); // 1console.log(sum.length); // 2console.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(); // redsayColor.call(this); // redsayColor.call(window); // redsayColor.call(o); // blue
闭包
单独开一章节
