JS 函数

函数的四种定义方法及组成

首先,要明确一点就是函数本身也是一个对象。

  1. 具名函数

    function 函数名 (形式参数1,形式参数2){语句 return 返回值}

  2. 匿名函数

    • 上面的具名函数去掉函数名就是匿名函数

    • 例如 let a = function(x,y){return x+y}

    • 也叫函数表达体

我们可以把匿名函数和具名函数结合起来使用,如 let a =function fn(x,y){return x+y} ,那么需要注意的是这时只能通过 a(x,y) 来调用函数,fn(x,y)不能调用函数,因为 fn 的作用域只在等号之后存在,在外部无法访问到 fn。

  1. 箭头函数

    • let f1 = x => x*x

    • let f2 = (x,y) => x+y // 包含两个及以上形参时圆括号不能省

    • f3 = (x,y) => {console.log('hi');return x+y} // 函数体包含两句以上语句时花括号不能省

    • let f4 = (x,y) => ({name:x, age: y}) //当箭头函数返回一个对象时,为了避免对象被识别为块从而出现报错的情况,这里的解决办法是使用一个圆括号将返回对象包起来

  2. 用构造函数

    • let f = new Function('x', 'y', 'return x+y')

    • 很少使用,但是能让你知道函数是谁构造的

    • 所有函数都是 Function 构造出来的

    • 包括 Object、Array、Function 也是

每个函数都包含以下内容

  • 调用时机
  • 作用域
  • 闭包
  • 形式参数
  • 返回值
  • 调用栈
  • 函数提升
  • arguments(除了箭头函数)
  • this(除了箭头函数)

执行时机

结合例子单独开一篇来讲

作用域&闭包

  • 全局变量 VS 局部变量

    • 在顶级作用域下声明的变量就是全局变量
    • window 的属性是全局变量
    • 除此之外的全部是局部变量
  • 作用域的嵌套

    • 如果多个作用域有同名变量 a

      • 那么查找 a 的声明时,就向上取最近的作用域

      • 简称「就近原则」

      • 查找 a 的过程与函数执行无关

      • 但 a 的值与函数执行有关

    • 以上其实说的就是作用域链,只要记住就近原则这一点就能弄明白了

  • 闭包

    • 如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包详情以后再补充

参数和返回值

  • 形式参数
    • 形参的就是非实际参数
    • 形参可以认为就是变量声明
  • 返回值
    • 每一个函数都有返回值
    • 函数执行完了才有返回值
    • 只有函数才有返回值
      • 1+2 的返回值是 3
      • 1+2 的值是 3

递归、调用栈、爆栈与函数提升

什么是调用栈

  • JS 引擎在调用一个函数前

  • 需要把函数所在的环境 push 到一个数组里(压栈)

  • 这个数组叫做调用栈

  • 等函数执行完了,就会把环境弹(pop)出来(弹栈)

  • 然后 return 到之前的环境,继续执行后续代码

  • 调用栈最长有多少?,使用如下函数可以测试调用栈有多长

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function computeMaxCallStackSize() {

    try {

    return 1 + computeMaxCallStackSize();

    } catch (e) {

    // 报错说明 stack overflow 了

    return 1;

    }
    • Chrome 12578

    • Firefox 26773

    • Node 12536

  • 爆栈:如果调用栈中压入的帧过多,程序就会崩溃

变量提升

  • 什么是变量提升

    • function fn(){}
    • 不管你把具名函数声明在哪里,它都会跑到第一行
  • 什么不是函数提升

    • let fn = function(){}

    • 这是赋值,右边的匿名函数声明不会提升(let 没有变量提升)

arguments 与 this

  • arguments

    是一个伪数组,使用 Array.from 转化为数组

  • this

    • 如果没有任何指示,那么 this 就指向 window

    • 目前可以用 fn.call(xxx, 1,2,3) 传 this 和 arguments

      如果没有使用严格模式那么 xxx 会被自动转化成对象(JS 的糟粕),如果使用严格模式那么就是原本的 xxx

    • 绑定 this

      • 使用 bind() 可以让 this 不再改变

        1
        2
        3
        4
        5
        6
        function f1(p1, p2) {
        console.log(this, p1, p2);
        }
        let f2 = f1.bind({ name: 'frank' });
        // 那么 f2 就是 f1 绑定了 this 之后的新函数
        f2(); // 等价于 f1.call({name:'frank'})
      • .bind 还可以绑定其他参数

        1
        2
        let f3 = f1.bind({ name: 'frank' }, 'hi');
        f3(); // 等价于 f1.call({name:'frank'}, hi)

箭头函数

没有 arguments 和自己的 this

立即执行函数

原理

  • ES 5 时代,为了得到局部变量,必须引入一个函数

  • 但是这个函数如果有名字,就得不偿失

  • 于是这个函数必须是匿名函数

  • 声明匿名函数,然后立即加个 () 执行它

  • 但是 JS 标准认为这种语法不合法

  • 所以 JS 程序员寻求各种办法

  • 最终发现,只要在匿名函数前面加个运算符即可

  • !、~、()、+、- 都可以

  • 但是这里面有些运算符会往上走

  • 所以推荐永远用 ! 来解决


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!