# 函数
# 1.定义方式
每个函数都是 Function 类型的实例,而 Function也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。
在 JavaScript 中,函数是一种特殊的对象,它和对象一样可以拥 有属性和值,但是函数和普通对象不同的是,函数可以被调用。
函数除了可以拥有常用类型的属性值之外,还拥有两个隐藏属性,分别是 name 属性和 code 属性。
# 函数声明
函数通常以函数声明的方式定义,比如:
function sum (num1, num2) {
return num1 + num2;
}
2
3
# 函数表达式
另一种定义函数的语法是函数表达式。函数表达式与函数声明几乎是等价的:
let sum = function(num1, num2) {
return num1 + num2;
};
2
3
# 构造函数
几乎不适用的方式是 Function 构造函数。这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。来看下面的例子:
let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
# 箭头函数
箭头函数简洁的语法非常适合嵌入函数的场景:
console.log(ints.map(function(i) { return i + 1; })); // [2, 3, 4]
console.log(ints.map((i) => { return i + 1 })); // [2, 3, 4]
2
3
箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用 arguments 、 super 和 new.target ,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。
# 函数声明提升
// 函数声明,没问题
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;
};
2
3
4
5
6
7
8
9
10
11
函数声明提升(function declaration hoisting)。在执行代码时,JavaScript引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。如果把前面代码中的函数声明改为等价的函数表达式,那么执行的时候就会出错。
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。
# 2.函数特性
# 函数名
因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。这意味着一个函数可以有多个名称,如下所示:
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
2
3
4
5
6
7
8
9
10
# 没有重载
如果在ECMAScript中定义了两个同名函数,则后定义的会覆盖先定义的。
把函数名当成指针也有助于理解为什么ECMAScript没有函数重载。
# 函数作为值
因为函数名在ECMAScript中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。
function callSomeFunction(someFunction, someArgument) {
return someFunction(someArgument);
}
2
3
# 函数的属性和方法
- 函数的name属性返回函数的名字
- 函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数
- toString()方法返回一个字符串,内容是函数的源码。
# 默认参数值
在ECMAScript5.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'
2
3
4
5
6
ECMAScript 6之后就不用这么麻烦了,因为它支持显式定义默认参数了。下面就是与前面代码等价的ES6写法,只要在函数定义中的参数后面用 = 就可以为参数赋一个默认值:
function makeKing(name = 'Henry') {
return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'
2
3
4
5
# 扩展操作符
调用:
let values = [1,2,3,4]
function countArguments() {
console.log(arguments.length);
}
countArguments(-1, ...values); // 5
2
3
4
5
定义:
function getSum(...values) {
console.log(arguments.length); // 3
console.log(arguments); // [1, 2, 3]
console.log(values); // [1, 2, 3]
}
console.log(getSum(1,2,3));
2
3
4
5
6
# 3.函数内部
# arguments
- arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是arguments[0] ,第二个参数是 arguments[1] )
- 要确定传进来多少个参数,可以访问arguments.length 属性。
- arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。在严格模式下运行的代码是不能访问arguments.callee 的,因为访问会出错。
function factorial(num) {
if (num <= 1) { return 1;}
else { return num * arguments.callee(num - 1);} //arguments.callee=factorial
}
2
3
4
如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。
var args = Array.prototype.slice.call(arguments);
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
2
3
4
5
6
# this
- 标准函数中, this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值(在网页的全局上下文中调用函数时, this 指向 windows )
•window.color = 'red';
let o = {
color: 'blue'
};
function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'
2
3
4
5
6
7
8
9
10
- 箭头函数中的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
# caller
ES5,这个属性引用的是调用当前函数的函数。
function outer() {
inner();
}
function inner() {
console.log(inner.caller); //=arguments.callee.caller
}
outer();//显示 outer() 函数的源代码
2
3
4
5
6
7
# new.target
ECMAScript 6新增了检测函数是否使用new 关键字调用的 new.target 属性。如果函数是正常调用的,则new.target 的值是 undefined ;如果是使用 new 关键字调用的,则 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
3
4
5
6
7
8
9
# 4.立即调用函数表达式
立即调用的匿名函数又被称作立即调用的函数表达式(IIFE,Immediately Invoked Function Expression)。它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。
// IIFE
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误
2
3
4
5
6
7
- ECMAScript 5.1及以前,为了防止变量定义外泄,IIFE是个非常有效的方式。这样也不会导致闭包相关的内存问题,因为不存在对这个匿名函数的引用。为此,只要函数执行完毕,其作用域链就可以被销毁。
- 在ECMAScript 6以后,IIFE就没有那么必要了,因为块级作用域中的变量无须IIFE就可以实现同样的隔离。
// JavaScript 中有一个圆括号运算符,圆括号里面可以放一个表达式,比如下面的代码:
(a=3) // 括号里面是一个表达式,整个语句也是一个表达式,最终输出 3。
// 如果在小括号里面放上一段函数的定义,V8 就会把这个函数看成是函数表达式
(function () {
//statements
})
// 存放在括号里面的函数便是一个函数表达式,它会返回一个函数,如果加上调用的括号
(function () {
//statements
})()
//也可以这也写
;(function () {
//statements
})()
// 分号是为了断句
//只要把函数声明变成表达式就行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
和普通函数传参一样,立即执行函数也可以传递参数。
//IIFE
(function(name){
var greeting = 'Hello';
console.log(greeting+ ' ' +name);
})("world");
//Hello world
2
3
4
5
6
7