前端-JS学习笔记-函数的使用

函数定义,函数参数,函数返回值,递归,变量作用域,函数表达式,函数式编程,回调函数,高阶函数,匿名函数,立即执行函数

1. 程序中的foo、bar、baz

  • 在学习编程的过程中,会经常看到foo、bar、baz这些名词

    • 它们通常被用来作为函数、变量、文件的名词
    • 目前已经编程了计算机编程的术语一部分
    • 但是它们本身并没有特别的用途和意义
    • 常被称之为 “伪变量”(metasyntactic variable)
  • 它们有什么由来吗?

    • 事实上,foo、bar这些名词最早从什么时候、地方流行起来的一直是由争论的
    • 一种说法是通过Digital(迪吉多,数字设备公司,成立于1957年的美国电脑公司)的手册说明流行起来的
    • 一种说法是说源自于电子学中的反转foo信号
    • 也有一种说法是foo因为出现在了一个漫画中,漫画中foo代表“好运”,与中文的福读音类似
  • 总之,foo、bar、baz已经是编程领域常用的名词

2. 函数

2.1. 函数的定义

  • 什么是函数呢?

  • 目前, 已经接触过几个函数了

    • alert函数:浏览器弹出一个弹窗
    • prompt函数:在浏览器弹窗中接收用户的输入
    • console.log函数:在控制台输入内容
    • String/Number/Boolean函数等
  • 当谈函数时, 到底在谈些什么?

    • 函数其实就是某段代码的封装,这段代码完成某一个功能
    • 默认情况下JavaScript引擎或者浏览器提供一些已经实现好的函数
    • 也可以编写属于自己的函数

2.2. 函数使用步骤

  • 函数的使用包含两个步骤

    • 声明函数:封装独立的功能
    • 调用函数:享受封装的成果
  • 声明函数,在JavaScript中也可以称为定义函数

    • 声明函数的过程是对某些功能的封装过程
    • 可以根据需求定义很多函数
  • 调用函数,也可以称为函数调用

    • 调用函数是让已存在的函数为己所用
    • 这些函数可以是封装好的某个功能函数
    • 也可以使用默认提供的或者其他三方库定义好的函数
  • 函数的作用

    • 在开发程序时,使用函数可以提高编写的效率以及代码的重用

2.2.1. 声明函数

  • 声明函数使用function关键字

    • 这种写法称之为函数的定义
    1
    2
    3
    function 函数名(){ 
    //函数封装的代码 ......
    }
  • 注意

    • 函数名的命名规则和变量名的命名规则是相同的
    • 函数要尽量做到见名知意(并且函数通常是一些行为(action),所以使用动词会更多一些)
    • 函数定义完后里面的代码是不会执行的,函数必须调用才会执行

2.2.2. 调用函数

  • 调用函数通过函数名()即可:比如test()

2.2.3. 函数的练习

  • 练习一:定义一个函数,打印一个人的个人信息

    1
    2
    3
    4
    5
    6
    function info(){
    var name = "why";
    var age = 18;
    console.log(`姓名:${name},年龄:${age}`);
    }
    info();
  • 练习二:定义一个函数,函数中计算10和20之间数字的和,并且打印出结果

    1
    2
    3
    4
    5
    6
    7
    8
    function sum(){
    var result = 0;
    for(var i = 10; i<=20; i++){
    result+=10;
    }
    console.log(result);
    }
    sum();

2.3. 函数的参数

  • 函数的参数

    • 函数把具有独立功能的代码块组织为一个小模块,在需要的时候调用
    • 函数的参数,增加函数的通用性,针对相同的数据处理逻辑,能够适应更多的数据
      • 在函数内部,把参数当做变量使用,进行需要的数据处理
      • 函数调用时,按照函数定义的参数顺序,把希望在函数内部处理的数据,通过参数传递
  • 形参和实参

    • 形参:定义函数时,小括号中的参数,是用来接收参数用的,在函数内部作为变量使用

      1
      2
      // a,b就是形参
      function sum(a,b){}
    • 实参:调用函数时,小括号中的参数,是用来把数据传递到函数内部用的

      1
      2
      // 10,20都是实参
      sum(10,20);
  • 有参数的函数练习

    • 练习一:传入一个名字,对这个人say Hello

      1
      2
      3
      4
      function sayHello(name){
      console.log(`Hello ${name}`);
      }
      sayHello("why");
    • 练习二:为某个朋友唱生日快乐歌

      1
      2
      3
      4
      function happySong(name){
      console.log(`happy birthday ${name}`);
      }
      happySong("why");
    • 练习三:传入两个数字,计算两个数字的和,并且打印结果

      1
      2
      3
      4
      function sum(a,b){
      console.log(`${a}+${b}=${a+b}`);
      }
      sum(10,20);

2.4. 函数的返回值

  • 如prompt函数会返回用户的输入

  • 所以说, 函数不仅仅可以有参数, 也可以有返回值

    • 使用return关键字来返回结果
    • 一旦在函数中执行return操作,那么当前函数会终止
    • 如果函数中没有使用 return语句 ,那么函数有默认的返回值:undefined
    • 如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined
  • 函数的练习

    • 练习一:实现一个加法计算器

      1
      2
      3
      function sum(a,b){
      return a+b;
      }
    • 练习二:定义一个函数,传入宽高,计算矩形区域的面积

      1
      2
      3
      function rectangle(width,height){
      return width*height;
      }
    • 练习三:定义一个函数,传入半径,计算圆形的面积

      1
      2
      3
      function cycle(radius){
      return (Math.PI*radius*radius).toFixed(2);
      }
    • 练习四:定义一个函数,传入n(n为正整数),计算1~n数字的和

      1
      2
      3
      4
      5
      6
      7
      function sum(n){
      var result = 0;
      for(var i=1;i<=n;i++){
      result+=i;
      }
      return result;
      }
    • 练习五:传入一个数字,可以根据数字转化成显示为亿、万文字显示的文本

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function change(num){
      if(num>=1_0000_0000){
      num=Math.floor(num/1_0000_0000);
      return num+"亿";
      }
      else if(num>=10000){
      num=Math.floor(num/10000);
      return num+"万";
      }
      return num;
      }
      // 1亿
      console.log(change(100000000));

3. 递归

3.1. 函数中调用函数

  • 在开发中,函数内部是可以调用另外一个函数的

    1
    2
    3
    4
    5
    6
    7
    8
    function foo(){
    console.log("foo函数被调用");
    }

    function bar(){
    foo();
    }
    bar();
  • 既然函数中可以调用另外一个函数,那么函数是否可以调用自己呢?

    • 当然是可以的

    • 但是函数调用自己必须有结束条件,否则会产生无限调用,造成报错

      1
      2
      3
      4
      5
      6
      7
      // Uncaught RangeError: Maximum call stack size exceeded
      var count = 0;
      function bar(){
      console.log(count++);
      bar();
      }
      bar();

3.2. 函数的递归

  • 事实上,函数调用自己还有一个专业的名词,叫做递归(Recursion)

  • 在语言学方面,也可以描述为递归

  • 递归是一种重要的编程思想

    • 将一个复杂的任务,转化成可以重复执行的相同任务

3.3. 实现幂函数pow

  • 可以先用for循环来实现

    1
    2
    3
    4
    5
    6
    7
    function pow(x,n){
    var result = 1;
    for(var i=0;i<n;i++){
    result*=x;
    }
    return result;
    }
  • 另一种实现思路是递归实现

1
2
3
4
5
6
function pow_re(x,n){
if(n===1){
return x;
}
return x*pow_re(x,n-1);
}

3.4. 实现斐波那契数列

  • 递归实现
1
2
3
4
5
6
function fibonacci(n){
if(n===1||n===2){
return 1;
}
return fibonacci(n-1)+fibonacci(n-2);
}
  • for循环实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fibonacci(n){
if(n===1||n===2){
return 1;
}
var n1 = 1;
var n2 = 1;
var result = 0;
for(var i = 3; i <= n; i++){
result = n1 + n2;
n1 = n2;
n2 = result;
}
return result;
}

4. 变量作用域

4.1. 作用域定义

  • 在JavaScript(ES5之前)中没有块级作用域的概念,但是函数可以定义自己的作用域
    • 作用域(Scope)表示一些标识符的作用有效范围(所以也有被翻译为有效范围的)
    • 函数的作用域表示在函数内部定义的变量,只有在函数内部可以被访问到

4.2. 外部变量和局部变量的概念

  • 定义在函数内部的变量,被称之为局部变量(Local Variables)

  • 定义在函数外部的变量,被称之为外部变量(Outer Variables)

  • 全局变量

    • 在函数之外声明的变量(在script中声明的),称之为全局变量
    • 全局变量在任何函数中都是可见的
    • 通过var声明的全局变量会在window对象上添加一个属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // message在哪个范围内可以被使用,称之为message的作用域
    var message = "Hello";
    function foo(){
    console.log(message);
    }
    if(true){
    console.log(message);
    }

    // ES5之前是没有块级作用域
    // var定义的变量是没有块级作用域
    // 内外都可以访问到count
    {
    var count = 100;
    console.log(count);
    }
    console.log(count);

    // for循环的代码块也是没有自己的作用域的
    for(var i = 0; i < 10; i++){
    var foo = "foo";
    }
    // 可以访问到
    console.log(foo);
    console.log(i);

    // ES5之前函数会形成自己的作用域
    function bar(){
    var a = "a";
    }
    // ReferenceError: a is not defined
    console.log(a);

4.3. 访问变量的顺序

  • 优先访问自己函数中的变量,没有找到时,在外部中访问(父函数…->全局->window对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
window.message = "Hello World";

var message = "Hello global";

function foo(){
var message = "Hello foo";

return function bar(){
var message = "Hello bar";
console.log(message);
}
}
// Hello bar
foo()();
  • 如果删除 var message = "Hello bar";语句重新运行,结果是 Hello foo
  • 如果再删除 var message = "Hello foo";语句重新运行,结果是 Hello global
  • 如果再删除 var message = "Hello global";语句重新运行,结果是 Hello World
  • 如果再删除 window.message = "Hello World";语句重新运行,结果会报错 message is not defined

5. 函数声明 vs 函数表达式

5.1. 函数声明

  • 在JavaScript中,函数并不是一种神奇的语法结构,而是一种特殊的值

  • 前面定义函数的方式,称之为函数的声明(Function Declaration)

1
2
3
4
function foo(){
console.log("foo函数");
}
foo();

5.2. 函数表达式

  • 而类似变量声明的写法是函数表达式(Function Expressions)

    1
    2
    3
    4
    var foo = function(){
    console.log("foo函数");
    }
    foo();
  • 注意,function 关键字后面没有函数名

    • 函数表达式允许省略函数名

5.3. 两者不同

  • 首先,语法不同

    • 函数声明:在主代码流中声明为单独的语句的函数

    • 函数表达式:在一个表达式中或另一个语法结构中创建的函数

  • 其次,JavaScript创建函数的时机不同

    • 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用
    • 在函数声明被定义之前,它就可以被调用
      • 这是内部算法的原故
      • 当 JavaScript 准备运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数
  • 开发中如何选择呢?

    • 需要声明一个函数时,首先考虑函数声明语法
    • 它能够为组织代码提供更多的灵活性,因为可以在声明这些函数之前调用这些函数

6. 函数式编程概念

6.1. 头等函数

  • 头等函数(first-class function,第一级函数)是指在程序设计语言中,函数被当作头等公民

    • 头等函数可以被赋值给变量

      1
      2
      3
      var foo1 = function(){
      console.log("foo1");
      }
    • 头等函数可以在变量之间来回传递

      1
      2
      3
      4
      5
      function foo(){
      console.log("foo");
      }
      var bar = foo;
      bar();
    • 头等函数可以是另外函数的参数

      1
      2
      3
      4
      function baz(fn){
      fn();
      }
      barz(foo);
    • 头等函数可以作为另外函数的返回值

      1
      2
      3
      4
      5
      6
      7
      function sayHello(name){
      function hi(){
      console.log("hi " + name);
      }
      return hi;
      }
      fun("why")();
    • 头等函数可以存储在另外一个数据结构中

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      var obj = {
      eating: function(){
      console.log("eating");
      }
      }
      obj.eating();

      // 数组
      function bar1(){
      console.log("bar1");
      }
      function bar2(){
      console.log("bar2");
      }
      function bar3(){
      console.log("bar3");
      }
      var arr = [bar1,bar2,bar3];

6.2. 函数式编程

  • 通常对作为头等公民的编程方式,称之为函数式编程

    • JavaScript就是符合函数式编程的语言,这个也是JavaScript的一大特点

6.3. 回调函数(Callback Function)

  • 函数可以作为一个值相互赋值,也可以传递给另外一个函数

  • bar函数称为回调函数

    • 通过函数名加括号foo()叫做函数的调用
    • 通过fn去调用bar函数的过程称之为函数的回调
    1
    2
    3
    4
    5
    6
    7
    function foo(fn){
    fn();
    }
    function bar(){
    console.log("bar");
    }
    foo(bar);

6.4. 高阶函数(Higher-order function)

  • foo函数称之为高阶函数

  • 高阶函数必须至少满足两个条件之一

    • 接受一个或多个函数作为输入
    • 输出一个函数

6.5. 匿名函数(anonymous)

  • 如果在传入一个函数时,没有指定这个函数的名词或者通过函数表达式指定函数对应的变量,那么这个函数称之为匿名函数
1
2
3
4
5
6
7
function request(url, callback){
var list = ["a","b","c"];
callback(list);
}
request("url",function(res){
console.log(res);
})

7. 立即执行函数

7.1. 什么是立即执行函数

  • Immediately-Invoked Function Expression(IIFE 立即调用函数表达式)

  • 表达的含义是一个函数定义完后被立即执行

    • 第一部分是定义了一个匿名函数,这个函数有自己独立的作用域
    • 第二部分是后面的(),表示这个函数被执行了
    1
    2
    3
    4
    var result = (function(name){
    console.log("立即执行函数",name);
    return "Hello World";
    })("why");

7.2. 作用

  • 会创建一个独立的执行上下文环境,可以避免外界访问或修改内部的变量,也避免了对内部变量的修改

  • user.js

    1
    2
    3
    4
    5
    var userModule = (function(){
    var module = {};
    module.message = "Hello World";
    return module;
    })();
  • index.js

    1
    2
    3
    (function(){
    console.log(userModule.message);
    })();
  • 监听每个按钮的点击

    1
    2
    3
    4
    5
    6
    7
    8
    var btns = document.querySelectorAll(".btn");
    for(var i = 0; i < btns.length; i++){
    (function(m){
    btns[m].onclick = function(){
    console.log(`第{m}个按钮被点击了`);
    }
    })(i);
    }

7.3. 其他写法

  • 立即执行函数必须是一个表达式,不能是函数声明

    • 下面的这种写法会报错,因为是一个函数声明,不是一个函数表达式
    • 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明
    1
    2
    3
    function foo(){
    console.log("立即执行函数");
    }();
  • 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明

    1
    2
    3
    (function foo(){
    console.log("立即执行函数");
    })();
  • 下面是一个函数表达式,所以可以执行

    1
    2
    3
    4
    5
    6
    7
    8
    // +-!都可以
    +function foo(){
    console.log("立即执行函数");
    }();

    (function foo(){
    console.log("立即执行函数");
    }());

8. 函数代码风格

  • 函数名和左括号之间没有空格 pow(
  • 形参之间加逗号+空格 x, n
  • 右括号和左花括号之间有一个空格 ) {
  • 左花括号和函数声明放在同一行 pow(x, n){
  • 内部代码缩进2个空格
  • for/if/while等关键字后有一个空格 for ()
  • 操作符左右各一个空格 i < n
  • 每一行后加分号 result *= x;
  • 代码块之间隔一行
  • 每行代码文本不宜太长
  • else和左花括号在同一行,和if的右花括号也在同一行 } else {
  • 函数调用左右各一空格 alert( pow(x, n) )
本文结束  感谢您的阅读