定义方式

每个函数都是 Function 类型的实例,而 Function也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。

在 JavaScript 中,函数是一种特殊的对象,它和对象一样可以拥 有属性和值,但是函数和普通对象不同的是,函数可以被调用。
函数除了可以拥有常用类型的属性值之外,还拥有两个隐藏属性,分别是 name 属性和 code 属性。

函数声明

函数通常以函数声明的方式定义,比如:

  1. function sum (num1, num2) {
  2. return num1 + num2;
  3. }

函数表达式

另一种定义函数的语法是函数表达式。函数表达式与函数声明几乎是等价的:

  1. let sum = function(num1, num2) {
  2. return num1 + num2;
  3. };

构造函数

几乎不适用的方式是 Function 构造函数。这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。来看下面的例子:

  1. let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

箭头函数

箭头函数简洁的语法非常适合嵌入函数的场景:

  1. console.log(ints.map(function(i) { return i + 1; })); // [2, 3, 4]
  2. console.log(ints.map((i) => { return i + 1 })); // [2, 3, 4]

箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用 arguments 、 super 和 new.target ,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。

函数声明提升

  1. // 函数声明,没问题
  2. console.log(sum(10, 10));
  3. function sum(num1, num2) {
  4. return num1 + num2;
  5. }
  6. // 函数表达式,会出错
  7. console.log(sum(10, 10));
  8. let sum = function(num1, num2) {
  9. return num1 + num2;
  10. };
  • 函数声明提升(function declaration hoisting)。在执行代码时,JavaScript引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。如果把前面代码中的函数声明改为等价的函数表达式,那么执行的时候就会出错。

  • 如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。

    函数特性

    函数名

    因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。这意味着一个函数可以有多个名称,如下所示: ```javascript 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

  1. <a name="i4FLn"></a>
  2. ### 没有重载
  3. 如果在ECMAScript中定义了两个同名函数,则后定义的会覆盖先定义的。<br />把**函数名当成指针**也有助于理解为什么ECMAScript没有函数重载。
  4. <a name="haVnQ"></a>
  5. ### 函数作为值
  6. 因为函数名在ECMAScript中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。
  7. ```javascript
  8. function callSomeFunction(someFunction, someArgument) {
  9. return someFunction(someArgument);
  10. }

函数的属性和方法

  • 函数的name属性返回函数的名字
  • 函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数
  • toString()方法返回一个字符串,内容是函数的源码。

    默认参数值

    在ECMAScript5.1及以前,实现默认参数的一种常用方式就是检测某个参数是否等于 undefined ,如果是则意味着没有传这个参数,那就给它赋一个值:
    1. function makeKing(name) {
    2. name = (typeof name !== 'undefined') ? name : 'Henry';
    3. return `King ${name} VIII`;
    4. }
    5. console.log(makeKing()); // 'King Henry VIII'
    6. console.log(makeKing('Louis')); // 'King Louis VIII'

ECMAScript 6之后就不用这么麻烦了,因为它支持显式定义默认参数了。下面就是与前面代码等价的ES6写法,只要在函数定义中的参数后面用 = 就可以为参数赋一个默认值:

  1. function makeKing(name = 'Henry') {
  2. return `King ${name} VIII`;
  3. }
  4. console.log(makeKing('Louis')); // 'King Louis VIII'
  5. console.log(makeKing()); // 'King Henry VIII'

扩展操作符

调用:

  1. let values = [1,2,3,4]
  2. function countArguments() {
  3. console.log(arguments.length);
  4. }
  5. countArguments(-1, ...values); // 5

定义:

  1. function getSum(...values) {
  2. console.log(arguments.length); // 3
  3. console.log(arguments); // [1, 2, 3]
  4. console.log(values); // [1, 2, 3]
  5. }
  6. console.log(getSum(1,2,3));

函数内部

arguments

  • arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是arguments[0] ,第二个参数是 arguments[1] )
  • 要确定传进来多少个参数,可以访问arguments.length 属性。
  • arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。在严格模式下运行的代码是不能访问arguments.callee 的,因为访问会出错。
    1. function factorial(num) {
    2. if (num <= 1) { return 1;}
    3. else { return num * arguments.callee(num - 1);} //arguments.callee=factorial
    4. }
    如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。 ```javascript var args = Array.prototype.slice.call(arguments);

var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }

  1. <a name="Bs098"></a>
  2. ### this
  3. - 标准函数中, this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值(在网页的全局上下文中调用函数时, this 指向 windows )
  4. ```javascript
  5. •window.color = 'red';
  6. let o = {
  7. color: 'blue'
  8. };
  9. function sayColor() {
  10. console.log(this.color);
  11. }
  12. sayColor(); // 'red'
  13. o.sayColor = sayColor;
  14. o.sayColor(); // 'blue'
  • 箭头函数中的 this 会保留定义该函数时的上下文。
    1. function King() {
    2. this.royaltyName = 'Henry';
    3. // this引用King的实例
    4. setTimeout(() =>
    5. console.log(this.royaltyName), 1000);
    6. }
    7. function Queen() {
    8. this.royaltyName = 'Elizabeth';
    9. // this引用window对象
    10. setTimeout(function() {
    11. console.log(this.royaltyName); }, 1000);
    12. }
    13. new King(); // Henry
    14. new Queen(); // undefined

    caller

    ES5,这个属性引用的是调用当前函数的函数。
    1. function outer() {
    2. inner();
    3. }
    4. function inner() {
    5. console.log(inner.caller); //=arguments.callee.caller
    6. }
    7. outer();//显示 outer() 函数的源代码

    new.target

    ECMAScript 6新增了检测函数是否使用new 关键字调用的 new.target 属性。如果函数是正常调用的,则new.target 的值是 undefined ;如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。
    1. function King() {
    2. if (!new.target) {
    3. throw 'King must be instantiated using
    4. "new"'
    5. }
    6. console.log('King instantiated using "new"');
    7. }
    8. new King(); // King instantiated using "new"
    9. King(); // Error: King must be instantiated using "new"

立即调用函数表达式

立即调用的匿名函数又被称作立即调用的函数表达式(IIFE,Immediately Invoked Function Expression)。它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。

  1. // IIFE
  2. (function () {
  3. for (var i = 0; i < count; i++) {
  4. console.log(i);
  5. }
  6. })();
  7. console.log(i); // 抛出错误
  • ECMAScript 5.1及以前,为了防止变量定义外泄,IIFE是个非常有效的方式。这样也不会导致闭包相关的内存问题,因为不存在对这个匿名函数的引用。为此,只要函数执行完毕,其作用域链就可以被销毁。
  • 在ECMAScript 6以后,IIFE就没有那么必要了,因为块级作用域中的变量无须IIFE就可以实现同样的隔离。
  1. // JavaScript 中有一个圆括号运算符,圆括号里面可以放一个表达式,比如下面的代码:
  2. (a=3) // 括号里面是一个表达式,整个语句也是一个表达式,最终输出 3。
  3. // 如果在小括号里面放上一段函数的定义,V8 就会把这个函数看成是函数表达式
  4. (function () {
  5. //statements
  6. })
  7. // 存放在括号里面的函数便是一个函数表达式,它会返回一个函数,如果加上调用的括号
  8. (function () {
  9. //statements
  10. })()
  11. //也可以这也写
  12. ;(function () {
  13. //statements
  14. })()
  15. // 分号是为了断句
  16. //只要把函数声明变成表达式就行

和普通函数传参一样,立即执行函数也可以传递参数

  1. //IIFE
  2. (function(name){
  3. var greeting = 'Hello';
  4. console.log(greeting+ ' ' +name);
  5. })("world");
  6. //Hello world