• JavaScript 函数 function


    1 函数 function

    • JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
    • 由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。

    1.1 函数声明

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

    1.1.1 function 命令

    function print(s) {
      console.log(s);
    }
    
    • 1
    • 2
    • 3

    1.1.2 函数表达式

    1. 将一个匿名函数赋值给变量
    var print = function (s) {
      console.log(s);
    };
    
    • 1
    • 2
    • 3
    1. 将具名匿名函数赋值给变量,该函数名只在函数体内部有效,在函数体外部无效
    • 一是可以在函数体内部调用自身,
    • 二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。
    var print = function x() {
      console.log(typeof x);
    };
    
    x; // ReferenceError: x is not defined
    
    print(); // function
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.1.3 Function 构造函数

    这种声明函数的方式非常不直观,几乎无人使用

    var add = new Function("x", "y", "return x + y");
    
    // 等同于
    function add(x, y) {
      return x + y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.1.4 箭头函数

    • 箭头函数(=>):函数简写
      • 无参数:() => {}
      • 单个参数:x => {}
      • 多个参数:(x, y) => {}
      • 解构参数:({x, y}) => {}
      • 嵌套使用:部署管道机制
      • this 指向固定化
        • 并非因为内部有绑定 this 的机制,而是根本没有自己的 this,导致内部的 this 就是外层代码块的 this
        • 因为没有 this,因此不能用作构造函数

    箭头函数误区

    • 函数体内的 this 是定义时所在的对象而不是使用时所在的对象

    • 可让 this 指向固定化,这种特性很有利于封装回调函数

    • 不可当作构造函数,因此箭头函数不可使用 new 命令

    • 不可使用 yield 命令,因此箭头函数不能用作 Generator 函数

    • 不可使用 Arguments 对象,此对象在函数体内不存在(可用 rest/spread 参数代替)

    • 返回对象时必须在对象外面加上括号

    • 箭头函数: () => {} 没有自己的 this 对象, 内部的 this 就是定义时上层作用域中的 this, this 指向是固定的

    • 普通函数: fun() {} 内部的 this 指向函数运行时所在的对象, this 指向是可变的

    1.2 函数提升

    • JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。
    f();
    
    function f() {}
    
    • 1
    • 2
    • 3
    • 表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。

    1.3 函数属性

    • 函数的name属性返回函数的名字。
    • 函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。

    1.4 函数方法

    • 函数的toString()方法返回一个字符串,内容是函数的源码。

    1.5 函数作用域

    作用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种作用域:

    • 一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;

    • 一种是函数作用域,变量只在函数内部存在。在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。

    • ES6 又新增了块级作用域

    • 函数内部定义的变量,会在该作用域内覆盖同名全局变量。

    • 对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。

    • 与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

    • 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

    • 很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

    1.6 函数参数

    • 函数参数不是必需的,JavaScript 允许省略参数,但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined

    • 函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。

    • 如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。

    • 如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值

      • 这是因为,形式参数(o)的值实际是参数obj的地址,重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响
    var obj = [1, 2, 3];
    
    function f(o) {
      o = [2, 3, 4];
    }
    f(obj);
    
    obj // [1, 2, 3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 如果有同名的参数,则取最后出现的那个值
    • 调用函数f()的时候,没有提供第二个参数,a的取值就变成了undefined。这时,如果要获得第一个a的值,可以使用arguments对象。

    1.7 函数尾调用

    函数尾调用 :某个函数的最后一步是调用另一个函数

    function f(x){
      return g(x);
    }
    
    • 1
    • 2
    • 3

    上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。

    以下三种情况,都不属于尾调用。

    // 情况一
    function f(x){
      let y = g(x);
      return y;
    }
    
    // 情况二
    function f(x){
      return g(x) + 1;
    }
    
    // 情况三
    function f(x){
      g(x);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上面代码中,情况一是调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。

    function f(x){
      g(x);
      return undefined;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 尾调用优化:只保留内层函数的调用帧
      • 尾调用
        • 定义:某个函数的最后一步是调用另一个函数
        • 形式:function f(x) { return g(x); }
      • 尾递归
        • 定义:函数尾调用自身
        • 作用:只要使用尾递归就不会发生栈溢出,相对节省内存
        • 实现:把所有用到的内部变量改写成函数的参数并使用参数默认值

    1.8 arguments 对象

    • arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
    • 虽然arguments很像数组,但它是一个对象

    1.8.1 arguments 属性

    • arguments.length属性,可以判断函数调用时到底带几个参数。
    • arguments.callee属性,返回它所对应的原函数。

    1.9 闭包

    • 把闭包简单理解成 定义在一个函数内部的函数 ,能够读取其他函数内部变量的函数
    • 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
    function createIncrementor(start) {
      return function () {
        return start++;
      };
    }
    
    var inc = createIncrementor(5);
    
    inc(); // 5
    inc(); // 6
    inc(); // 7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

    为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc)用到了外层变量(start),导致外层函数(createIncrementor)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。

    JS 中 return 一个函数与直接 return 一个函数变量的区别
    函数的节流与防抖

    function makeCounter() {
      var count = 0;
      function counter() {
        count = count + 1;
        return count;
      }
      return counter(); // 将嵌套函数返回
    }
    var doCount = makeCounter();
    console.log(doCount, "--doCount1"); // 1 '--doCount1'
    console.log(doCount, "--doCount2"); // 1 '--doCount2'
    console.log(doCount, "--doCount3"); // 1 '--doCount3'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 当 return counter()时,就自动调用了嵌套函数。
    • 那么嵌套函数返回一个经过+1 的 count,并且 count 的值为 1.
    • 所以 doCount 得到的是一个数字,并不是函数,所以无法得到闭包。
    function makeCounter() {
      var count = 0;
      function counter() {
        count = count + 1;
        return count;
      }
      return counter; // 将嵌套函数返回,但只写函数名称
    }
    var doCount = makeCounter();
    console.log(doCount(), "--doCount1"); // 1 '--doCount1'
    console.log(doCount(), "--doCount2"); // 2 '--doCount2'
    console.log(doCount(), "--doCount3"); // 3 '--doCount3'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • return counter 返回的是整一个 cunter()函数。

    • 因此执行 var doCount = makeCounter()时,doCount 将引用 counter 函数及其中的变量环境。

    • 那么 counter 函数及其中的变量环境,就是闭包了

    • 闭包的形成:内部函数引用了外部函数的数据(这里为 count),

    • 因此在 doCount=makeCounter(),则会把这个 count 保存在 doCount 中,函数执行完,count 并不会被销毁。

    • 注意: 为什么上面这段代码没有直接写的 function doCount(){…} 而是把 function 赋值给了变量 doCount 呢?

    • 我们通常会想当然的认为每次调用 doCount() 都会重走一遍 doCount()中的代码块, 但其实不然。

    • 注意 makeCounter 方法中的 return 不是 1,2,3 这样的数值, 而是一个方法,并且把这个方法赋值给了 doCount 变量。

    • 那么在这个 makeCounter 自运行一遍之后, 其实最后赋值给 doCount 的是 count = count + 1; return count; 这段代码。

    • 所以后面每次调用 doCount() 其实都是在调用 count = count + 1; return count;

    • 闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个 count 其实就是来自于第一次 makeCounter 执行时创建的变量

    1.10 立即调用的函数表达式(IIFE)

    • 根据 JavaScript 的语法,圆括号()跟在函数名之后,表示调用该函数。比如,print()就表示调用print函数。
    (function(){ /* code */ }());
    // 或者
    (function(){ /* code */ })();
    
    • 1
    • 2
    • 3

    1.11 eval 命令

    eval命令接受一个字符串作为参数,并将这个字符串当作语句执行。

    eval("var a = 1;");
    a; // 1
    
    • 1
    • 2

    使用严格模式 加一行 ‘use strict’;

  • 相关阅读:
    23.CF911G Mass Change Queries 动态开点权值线段树+线段树合并
    linux 监控CPU利用率
    短视频矩阵系统,短视频矩阵源码技术开发
    关于实际部署的一些细节
    每日三题 9.19
    信号源是什么 都有什么功能
    数据结构与算法(LeetCode) 第二节 链表结构、栈、队列、递归行为、哈希表和有序表
    python 给图片添加噪声
    【Java】封装
    HtmlUnit、Jsoup、webmagic基本介绍
  • 原文地址:https://blog.csdn.net/m0_49271518/article/details/127981579