• JS 函数总结


    函数

    不知道你有无听说过“函数式编程”。函数是 JS 中最有特点的部分,因为在 JS 中函数是一个对象,每一个对象都是一个 Function 类型的实例。函数名其实就是一个指向该实例的指针

    函数的 name 属性

    所有函数都会暴露一个只读的name 属性(显示赋值无效),它包含了函数的信息。一般保存的是一个字符串化的函数名:

    function test() {
      console.log(1)
    }
    test.name = "fn";
    console.log(test.name)//test
    
    • 1
    • 2
    • 3
    • 4
    • 5

    JS 中有三种定义函数的方式

    函数声明

    这是 JS 中最常用的定义函数的方式。这里函数末尾是没有分号的,

    function add(a, b) {
      return a + b;
    }
    
    • 1
    • 2
    • 3

    函数表达式

    这里函数末尾是有分号的,相当于是将函数赋值给一个变量。

    let sum = function (a, b) {
      return a + b;
    };
    
    • 1
    • 2
    • 3

    函数表达式和一般的函数声明唯一的区别就是,函数声明会有函数声明提升,但是函数表达式没有,这也就意味着函数声明的的函数可以先使用在定义,而函数表达式不行。

    Function 构造函数

    这段代码会被解释两次,第一次将它当做常规 ECMAscript 代码执行,第二次是解释传给构造函数的字符串。所以不建议使用这种方式定义函数。

    let add = new Function('a', 'b', 'return a + b');
    
    • 1

    箭头函数

    箭头函数的特点

    • 不能使用 arguments 、 super
    • 不能作为构造函数
    • 没有 prototype 属性
    • 箭头函数 this 指向声明时所在作用域下 this 的值。
    let add = () => {
      return a + b;
    }
    
    • 1
    • 2
    • 3

    如果只有一个参数可以省略小括号;如果函数体只有一句 return 语句,那么大括号也可以省略。

    let add1 = a => {
      return a + 10;
    }
    let add2 = a => return a + 10;
    
    • 1
    • 2
    • 3
    • 4

    函数参数

    JS 中函数传入的参数个数和类型(多余的参数会自动忽略),这就意味着 JS 中函数没有重载机制。

    function add(a, b) {
      console.log(a + b);
    }
    add(1, 2);//3
    add('No ', 1);//No 1
    add(1);//NaN
    add('No ', 1, 2);//No 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    虽然不能重载,但是函数可以通过类数组对象 arguments 取得传入的参数值,arguments.length 就是传入的参数个数,实现不同的功能,如下:

    function add() {
      if (arguments.length == 1) {
        console.log(arguments[0]);
      } else if (arguments.length == 2) {
        console.log(arguments[0] + arguments[1]);
      } else {
        console.log(arguments[0] + arguments[1] + arguments[2]);
      }
    }
    add(1, 2);//3
    add('No ', 1);//No 1
    add(1);//1
    add('No ', 1, 2);//No 12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    arguments 对象可以和命名参数一起使用。并且 arguments 对象的值会自动同步到对应的命名参数,但是修改命名参数的值不会影响 arguments 对象中对应的值。

    function add(a) {
      console.log(a + arguments[1]);
    }
    add(1, 2);//3
    add('No ', 1);//No 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    function add(a) {
      arguments[0] = 2;
      console.log(a + arguments[1]);
    }
    add(1, 2);//4
    
    • 1
    • 2
    • 3
    • 4
    • 5

    只传入一个参数,设置 arguments[1] 的值,这种情况下并不会同步到第二个命名参数上,arguments 对象的长度是根据实际传入的参数个数变化的,跟定义了几个命名参数没有关系

    function add(a, b) {
      arguments[1] = 2;
      console.log(a + b);
    }
    add(1);//NaN
    
    • 1
    • 2
    • 3
    • 4
    • 5

    因为箭头函数没 arguments ,所以传给箭头函数的参数只能通过命名参数访问。

    默认参数

    在定义函数的时候,给参数赋值,那么这个值就是参数的默认值。这个默认值可以使调用函数的返回值,这时只有在函数被调用时才会被求值。如下面的例子中,只有 add 函数被调用时才会调用 fn。

    function add(a, b = 3) {
      console.log(a + b);
    }
    add(1);//4
    
    function fn() {
      return 3;
    }
    function add1(a, b = fn()) {
      console.log(a + b);
    }
    add1(1);//4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    默认参数是按顺序初始化的,因此后边的参数的默认值可以使前边的参数。但是前面的参数默认值不能是后边的参数值。并且默认值不能是函数体内的变量值

    function add(a, b = a) {
      console.log(a + b);
    }
    add(1);//4
    
    • 1
    • 2
    • 3
    • 4

    函数参数可以使用扩展操作符,它将可迭代对象的每一个元素都作为一个参数传给函数。也可以使用扩展操作符将传入的多个独立参数组合为一个数组。

    const arr = [1, 2]
    function add() {
      let sum = 0;
      for (const item of arguments) {
        sum += item;
      }
      console.log(sum);
    }
    add(...arr);//3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    function add(...value) {
      let sum = 0;
      for (const item of value) {
        sum += item;
      }
      console.log(sum);
    }
    add(1, 2);//3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    this

    谁调用就指向谁,全局函数调用就是向 window,对象调用函数,this就指向该对象实例,箭头函数的this指向定义时的上下文。

    function test() {
      console.log("函数声明this" + this);
    }
    const fn = () => {
      console.log("箭头函数this" + this);
    }
    const obj = {
      test,
      fn
    }
    test();
    obj.test();
    fn();
    obj.fn();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    arguments.callee

    arguments 类数组对象还有一个 callee 属性,它指向 arguments 对象所在的函数。

    const arr = [1, 2]
    function add() {
      console.log(arguments.callee);
    }
    add(...arr);//3
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    caller

    函数对象上也会有caller属性,这个属性指向调用当前函数的函数,严格模式下使用会报错。

    function test() {
      console.log(test.caller);
    }
    const fn = () => {
      test();
    }
    fn();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    new.target

    由于 JS 中函数既可以作为普通函数被调用,也可以作为构造函数被调用,所以为了区分这两种情况,就有了 new.target 属性,如果是普通调用,那么它的值就是undefined,否则就为该构造函数。

    function fn() {
      if (new.target) {
        console.log('构造函数');
      } else {
        console.log('函数调用');
      }
    }
    fn();//函数调用
    new fn();//构造函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    函数属性

    length

    保存的是函数定的命名参数的个数,就是形参的个数。

    function fn(a) { }
    function add(a, b) { }
    console.log(fn.length)//1
    console.log(add.length)//2
    
    • 1
    • 2
    • 3
    • 4

    prototype

    属性保存引用类型所有实例方法的地方(原型对象)。

    function fn(a) { }
    console.log(fn.prototype)
    
    • 1
    • 2

    在这里插入图片描述

    尾调用优化

    ES6 新增了一项内存优化机制,来看下面的例子:

    function fn(...value) {
      add(value);
    }
    function add(value) {
      let sum = 0;
      for (const item of value) {
        sum += item;
      }
      console.log(sum);
    }
    fn(1, 2);//3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ES6 之前执行逻辑:

    1. 执行到 fn 函数体,fn 函数上下文被推到调用执行栈上。
    2. 执行 fn 函数体,计算返回值必须先得到 add 函数的结果。
    3. 执行到 add 函数体,add 函数执行上下文被推到调用执行栈上。
    4. 执行 add 函数体,计算其返回值,执行完毕 add 函数上下文弹出。
    5. 将返回值传回 fn,然后 fn 再返回值,fn 函数上下文弹出。

    在 ES6 做了尾调用优化之后执行逻辑:

    1. 执行到 fn 函数体,fn 函数上下文被推到调用执行栈上。
    2. 执行 fn 函数体,计算返回值必须先得到 add 函数的结果。
    3. 引擎发现把 fn 函数上下文弹出栈也没问题,fn 函数的返回值也是 add 函数的返回值。
    4. 弹出 fn 函数上下文,执行到 add 函数体,add 函数执行上下文被推到调用执行栈上。
    5. 执行 add 函数体,计算其返回值,执行完毕 add 函数上下文弹出。

    由例子可以看到,未做优化前每调用一次嵌套函数执行栈中就会增加一个上函数下文,做了尾调用优化后,执行栈中就减少了上下文的数量。也就减少了内存的消耗。

    尾调用优化条件

    因为尾调用优化本质上就是确定外部上下文没有必要存在了。所以需要满足如下条件:

    • 代码在严格模式下执行;
    • 外层函数一定是 return 另一个函数的调用。
    • 尾调用函数不是引用外部函数作用域中自由变量的闭包。给个反例如下,下面这种就不满足尾调用优化条件。
    function fn() { 
     let foo = 'bar'; 
     function fn1() { return foo; } 
     return fn1(); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    JS 函数部分基础知识就这么多了,我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。

  • 相关阅读:
    【Spring Boot】单元测试
    JS手写set与map
    linux下vsode超级用户运行
    Q_PROPERTY 中notify关键词使用
    [含lw+源码等]微信小程序论文管理系统+后台管理系统[包运行成功]Java毕业设计计算机毕设
    三百六十行行行出状元之短视频入门必备技巧——配音
    两种数据库MySQL 与 PostgresSQL 的 全面比较
    面向OLAP的列式存储DBMS-6-[ClickHouse]的常用DDL操作
    python分析inkscape路径数据的简单方案
    mybatis缓存-二级缓存
  • 原文地址:https://blog.csdn.net/weixin_46015333/article/details/127502843