JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用

1. 认识JavaScript函数

1.1 程序中的foo、bar、baz

在国外的一个问答网站stackover flow中,常常会使用这几个次进行变量,函数,对象等等声明,地位如同张三,李四,王五。foo、bar这些名词最早从什么时候、地方流行起来的一直是由争论的,有兴趣可以自己了解一下。

  • “伪变量”(metasyntactic variable)==> foo、bar、baz : 它们通常被用来作为函数、变量、文件的名词,随着使用的普及,目前已经是计算机编程中术语的一部分,但实际上没有特别的用途和意义

备注:foo、bar、baz已经是编程领域非常常用的名词,习惯在写一些变量、函数名词时使用这些词汇

1.2 认识函数

在之前我们已经使用过很多函数,如 alert()浏览器弹出一个弹窗,prompt函数在浏览器弹窗中接收用户的输入,console.log函数在控制台输入内容,还有 String/Number/Boolean函数

  • 函数:函数就是对某段代码的封转,这段代码能帮助我们完成某一个功能,默认情况下JavaScript引擎或者浏览器会给我们提供一些已经实现好的函数,我们也可以自己编写函数

2. JavaScript函数使用

2.1 函数使用的步骤

函数的使用包含两个步骤

  1. 声明函数(定义函数):声明函数的过程是对某些功能的封转过程,我们可以根据自己的需求定义很多自己的函数,函数定义完后里面的代码是不会执行的,函数必须调用才会执行
  2. 调用函数(函数调用): 调用已经存在的函数,我们可以调用自己封装好的某个功能函数,也可以使用浏览器默认提供或其它三方库定义好的函数

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

// 声明一个函数
    // 制作好一个工具, 但是这个工具默认情况下是没有被使用
    function sayHello() {
      console.log("Hello!")
      console.log("My name is Coderwhy!")
      console.log("how do you do!")
    }

    // 调用一个函数
    sayHello()
    // 函数可以在任何你想要使用的时候, 进行调用
    sayHello()

2.2 函数的声明与调用

函数的声明:

  1. 函数声明的语法结构:
    JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用_第1张图片
  2. 函数名的命名规则:①与变量名命名规则相同 ②函数名见名知意 常用动词

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

    // 练习一: 定义一个函数, 打印自己的个人信息
    function printInfo() {
      console.log("my name is why")
      console.log("age is 18")
      console.log("height is 1.88")
    }

    printInfo()
    printInfo()

    // 练习二: 定义一个函数, 在内部计算10和20的和
    function sum() {
      var num1 = 10
      var num2 = 20
      var result = num1 + num2
      console.log("result:", result)
    }
    sum()

2.3 函数的参数

  • 函数定义参数的作用::在把具有独立功能的代码块封装在一起时,我们能通过函数的参数增加函数的通用性,让不同数据在相同的数据处理逻辑下同样试用
    JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用_第2张图片

因此,我们可以在函数的内部,把参数当作变量使用。在我们进行调用时,按照声明函数是所定义的参数顺序,把对应的参数传递给函数。同时参数也分为两个概念:

  1. 形参(参数 parameter)定义 函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用
  2. 实参(参数 argument)调用 函数时,小括号中的参数,是用来把数据传递到函数内部用的
// name/age/height称之为函数的参数(形参, 形式参数, parmaters)
    function printInfo(name, age, height) {
      console.log(`my name is ${name}`)
      console.log(`age is ${age}`)
      console.log(`height is ${height}`)
    }

    // why/18/1.88称之为函数的参数(实参, 实际参数, arguments)
    printInfo("why", 18, 1.88)
    printInfo("kobe", 30, 1.98)

    // 另外一个案例也做一个重构
    function sum(num1, num2) {
      var result = num1 + num2
      console.log("result:", result)
    }

    sum(20, 30)
    sum(123, 321)
    
 // 练习一: 和某人打招呼
    function sayHello(name) {
      console.log(`Hello ${name}`)
    }

    sayHello("Kobe")
    sayHello("James")
    sayHello("Curry")

    // 练习二: 给某人唱生日歌
    function singBirthdaySong(name) {
      console.log("happy birthday to you")
      console.log("happy birthday to you")
      console.log(`happy birthday to ${name}`)
      console.log("happy birthday to you")
    }

    singBirthdaySong("Kobe")
    singBirthdaySong("Why")

2.4 函数的返回值

在刚开始学习js时,我们经常使用的prompt()函数,函数需要接受参数,并且会返回用户的输入,所以函数不仅仅可以有参数, 也可以有返回值

  • return关键字在函数中我们使用return关键字来返回结果, 一旦在函数中执行return操作,那么当前函数会终止
    1. 如果函数值没有 return语句,那么函数的默认返回值为undefined
    2. 如果函数有 return语句 并且后面有具体的值,那么函数返回值为return后面的值
    3. 如果函数有 return语句 但是后面不写值,那么函数返回值也是undefined
      JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用_第3张图片
    // var result = prompt("请输入一个数字:")
    // 1.理解函数的返回值
    // function sayHello(name) {
    //   console.log(`Hi ${name}`)
    // }

    // var foo = sayHello("Kobe")
    // console.log("foo:", foo)

    // 2.返回值的注意事项
    // 注意事项一: 所有的函数, 如果没有写返回值, 那么默认返回undefined
    // function foo() {
    //   console.log("foo函数被执行~")
    // }

    // var result = foo()
    // console.log("foo的返回值:", result)

    // 注意事项二: 我们也可以明确的写上return
    // 写上return关键字, 但是后面什么内容都没有的时候, 也是返回undefined
    // function bar() {
    //   console.log("bar函数被执行~")
    //   return
    // }
    // var result = bar()
    // console.log("bar的返回值:", result)

    // 注意事项三: 如果在函数执行到return关键字时, 函数会立即停止执行, 退出函数
    // function baz() {
    //   console.log("Hello Baz")
    //   return
    //   console.log("Hello World")
    //   console.log("Hello Why")
    // }

    // baz()


    // 函数的具体返回值
    function sum(num1, num2) {
      var result = num1 + num2
      return result
    }

    var total = sum(20, 30)
    console.log("total:", total)

2.5 arguments参数 (js高级继续学习)

arguments变量:arguments变量的类型是Object对象类型,它是一个类数组array-like但不是数组,它的用法和数组很相似。

arguments的应用

  1. 默认情况下,arguments对象是所有(非箭头)函数中都可以用的局部变量
  2. arguments对象中存放着所有调用者传入的参数从0开始,一依次存放
  3. 当调用者传入的参数多余函数接受的参数,我们也可以通过arguments去获取所有的参数
// 1.arguments的认识
    function foo(name, age) {
      console.log("传入的参数", name, age)

      // 在函数中都存在一个变量, 叫arguments
      console.log(arguments)
      // arguments是一个对象
      console.log(typeof arguments)
      // 对象内部包含了所有传入的参数
      // console.log(arguments[0])
      // console.log(arguments[1])
      // console.log(arguments[2])
      // console.log(arguments[3])

      // 对arguments来进行遍历
      for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i])
      }
    }

    foo("why", 18, 1.88, "广州市")


    // 2.arguments的案例
    function sum() {
      var total = 0
      for (var i = 0; i < arguments.length; i++) {
        var num = arguments[i]
        total += num
      }
      return total
    }

    console.log(sum(10, 20))
    console.log(sum(10, 20, 30))
    console.log(sum(10, 20, 30, 40))

2.6 函数的递归调用

函数的递归:在函数的内部,我们可以调用另外一个函数,而如果在函数内部调用自己,那么这样的函数调用叫做递归(Recursion)

  1. 函数内部调用函数
    function bar() {
      console.log("bar函数被执行了~")
      console.log("----------")
    }

    function foo() {
      // 浏览器默认提供给我们的其他函数
      console.log("foo函数执行")
      console.log("Hello World")
      // alert("Hello Coderwhy")

      // 调用自己定义的函数
      bar()

      // 其他代码
      console.log("other coding")
    }

    foo()

  1. 函数内部调用自己(递归)
// 递归调用
    // 默认情况下会产生无限调用的情况
    function foo() {
      console.log("foo函数被执行了")
      foo()
    }

    foo()

递归的练习: 封装一个函数, 函数可以实现x的n次方法

  1. **操作符实现(新语法)
function pow1(x, n) {
      return x ** n
    }

    console.log(pow1(2, 3))
    console.log(pow1(3, 3))

    console.log(Math.pow(2, 3))
    console.log(Math.pow(3, 3))
  1. 使用for循环实现
 // 一. for循环实现方式 
    // x² = x * x
    // x³ = x * x * x
    function pow2(x, n) {
      var result = 1
      for (var i = 0; i < n; i++) {
        result *= x
      }
      return result
    }

    console.log(pow2(2, 3))
    console.log(pow2(3, 3))
  1. 使用递归方式实现
    • 使用递归实现时,必须要有一个结束条件 ,否则会产生无限调用,造成报错,导致超出最大栈空间,占用过多的栈内存。
      在这里插入图片描述
      实现思路
      JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用_第4张图片
 function pow(x, n) {
      // 结束条件
      if (n === 1) {
        return x
      }
       // 2,3==> 2* pow(2,2)=> 2*(2* pow(2,1))=>2*(2*2))
      // 递归
      return x * pow(x, n - 1)
    }

递归总结:

  1. 优点:写出来的代码非常简洁
  2. 缺点:性能是比较低(占用过多的栈内存)
  • 案例练习:实现斐波那契数列 1 1 2 3 5 8 13 21 34 55
 // 什么是斐波那契数列
    // 数列: 1 1 2 3 5 8 13 21 34 55  ... x
    // 位置: 1 2 3 4 5 6 7  8  9  10  ... n

    // 1.斐波那契的递归实现
    function fibonacci(n) {
      if (n === 1 || n === 2) return 1
      return fibonacci(n-1) + fibonacci(n-2)
    }


    // 2.斐波那契的for循环实现
    function fibonacci(n) {
      // 特殊的情况(前两个数字)
      if (n === 1 || n === 2) return 1

      // for循环的实现
      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
    }

    console.log(fibonacci(5))
    console.log(fibonacci(10))
    console.log(fibonacci(20))

3. JavaScript函数的概念

3.1 作用域的概念

作用域(Scope):表示一些标识符的作用有效范围,决定了一个变量在哪一个范围内可以被使用,根据有效范围,我们对作用域进行了分类:

  1. 全局作用域
 var message = "Hello World"
    if (true) {
      console.log(message)
    }
    function foo() {
      console.log("在foo中访问", message)
    }
    foo()
  1. 块级作用域:在相应的代码块内才能被访问到,在ES5之前是没有块级作用域的,只有函数代码块是会形成自己的块级作用域。
    • 函数的作用域在函数内部定义的变量,只有在函数内部可以被访问到
 // 2.ES5之前是没有块级作用域(var定义的变量是没有块级作用域)
    {
      var count = 100
      console.log("在代码块中访问count:", count)
    }
    console.log("在代码块外面访问count:", count)
    // for循环的代码块也是没有自己的作用域
    for (var i = 0; i < 3; i++) {
      var foo = "foo"
    }
    console.log("for循环外面访问foo:", foo)
    console.log("for循环外面访问i:", i) // 3

    // 3.ES5之前函数代码块是会形成自己的作用域
    // 意味着在函数内部定义的变量外面是访问不到的
    function test() {
      var bar = "bar"
    }

    test()
    // console.log("test函数外面访问bar:", bar)

    // 函数有自己的作用域: 函数内部定义的变量只有函数内部能访问到
    function sayHello() {
      var nickname = "kobe"
      console.log("sayHello函数的内部:", nickname)

      function hi() {
        console.log("hi function~")
        console.log("在hi函数中访问nickname:", nickname)
      }
      hi()
    }
    sayHello()
    // console.log("sayHello外面访问nickname:", nickname)

3.2 全局变量、局部变量、外部变量

  1. 全局变量(global variable):在全局(script元素中)定义一个变量, 那么这个变量是可以在定义之后的任何范围内被访问到的(全局作用域), 那么这个变量就称之为是一个全局变量.
  2. 局部变量(local variable): 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量
    • 外部变量(outer variable): 在函数内部去访问函数之外的变量, 访问的变量称之为外部变量
      其中: 外部变量是相对于局部变量的,它特指从局部作用域内访问外部声明的变量。
// 1.全局变量(global variable): 在全局(script元素中)定义一个变量, 那么这个变量是可以在定义之后的任何范围内被访问到的, 那么这个变量就称之为是一个全局变量.
    var message = "Hello World"
    
    // 在函数中访问message
    function sayHello() {
      // 外部变量(outer variable): 在函数内部去访问函数之外的变量, 访问的变量称之为外部变量
      console.log("sayHello中访问message:", message)

      // 2.局部变量(local variable): 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量
      var nickname = "coderwhy"

      function hi() {
        console.log("hi function~")
        // message也是一个外部变量
        console.log("hi中访问message:", message)
        // nickname也是一个外部变量
        console.log("hi中访问nickname:", nickname)
      }
      hi()
    }


    

    sayHello()

3.2.1 变量的访问顺序

从最近的局部变量开始查找,依次从外部变量到全局变量,没有找到就报错。
JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用_第5张图片

3.3 函数表达式(Function Expressions)

定义函数的两种方式

  1. 函数的声明(Function Declaration)
  function foo() {
      console.log("foo函数被执行了~")
    }
  1. 函数表达式(Function Expressions)函数表达式允许省略函数名。
 // 函数表达式允许省略函数名。
    // var baz = function baz01() {
    //   console.log("baz函数被执行了~")
    // }
    var bar = function () {
      console.log("bar函数被执行了~")
    }

备注: 无论函数是如何创建的,函数都是一个值(这个值的类型是一个对象,对象的概念后面会讲到)

3.4 函数声明与函数表达式的区别

  1. 语法不同
    • 函数声明:在主代码流中单独的函数声明语句
    • 函数表达式:在一个表达式中或另一个语法结构中创建的函数
  2. 创建函数的时机不同
    • 函数声明:由于内部算法原因,当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数,因此我们可以在在函数声明被定义之前就可以调用
    • 函数表达式: 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用

JavaScript之函数 (七):认识JavaScript函数、函数的声明和调用、函数的递归调用、局部和全局变量、函数表达式的写法、立即执行函数使用_第6张图片

3.5 JavaScript头等函数

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

函数作为头等公民(第一公民)有以下几个特点

  1. 支持函数表达式的写法,即函数可以被赋值给变量
	var foo1 = function() {
      console.log("foo1函数被执行~")
    }
    foo1()
  1. 函数可以被传递
	var foo2 = foo1
    foo2()
  1. 可以把函数作为实参传递给另外一个函数进行调用
	function bar(fn) {
      console.log("fn:", fn)
      fn()
    }
    bar(foo1)
  1. 函数可以作为另外一个函数的返回值
	function sayHello() {
      function hi() {
        console.log("hi kobe")
      }
      return hi
    }

    var fn = sayHello()
    fn()
  1. 将函数存储在另外一个数据结构中
 // 存储在对象或者数组结构中
    var obj = {
      name: "why",
      eating: function() {
        console.log("eating")
      }
    }
    obj.eating()
    function bar1() {
      console.log("bar1函数被执行~")
    }
    function bar2() {
      console.log("bar2函数被执行~")
    }
    function bar3() {
      console.log("bar3函数被执行~")
    }
    // 事件总线的封装
    var fns = [bar1, bar2, bar3]
  • 函数式编程: 指在程序设计语言中,支持函数作为头等公民进行编程的方式,那么就称这种编程方式(范式)为函数式编程
    • JavaScript就是符合函数式编程的语言,这个也是JavaScript的一大特点;

3.6 回调函数(Callback Function)

前面介绍了头等函数,我们知道函数可以作为一个值互相赋值传递,也可以传递给另外一个函数,

  • 回调函数:我们通过将函数作为实参传递给另外一个函数,我们在这个函数的内部引用并调用传递进来的函数,那么我们就称这个引用的函数为回调函数
// 1.函数回调的概念理解
    function foo(fn) {
      // 通过fn去调用bar函数的过程, 称之为函数的回调
      fn()
    }
    function bar() {
      console.log("bar函数被执行了~")
    }
    foo(bar)
  • 匿名函数:匿名函数是回调函数的一种形式,我们可以直接在调用函数的括号里去编写函数,并且可以省略名字。
/ 2.函数回调的案例
    // function request(url, callback) {
    //   console.log("根据URL向服务器发送网络请求")
    //   console.log("需要花费比较长的时间拿到对应的结果")
    //   var list = ["javascript", "javascript学习", "JavaScript高级编程"]
    //   callback(list)
    // }

    // function handleResult(res) {
    //   console.log("在handleResult中拿到结果:", res)
    // }
    // request("url", handleResult)


    // 3.函数回调的案例重构
    function request(url, callback) {
      console.log("根据URL向服务器发送网络请求")
      console.log("需要花费比较长的时间拿到对应的结果")
      var list = ["javascript", "javascript学习", "JavaScript高级编程"]
      callback(list)
    }

    // 传入的函数是没有名字, 匿名函数
    request("url", function(res) {
      console.log("在handleResult中拿到结果:", res)
    })

3.7 立即执行函数

立即执行函数:Immediately-Invoked Function Expression,简称 IIFE 立即调用函数表达式),表示一个函数定完后立即执行

定义立即执行函数

  1. 第一部分()定义了一个匿名函数,这个函数有自己独立的作用域
  2. 第二部分(),表示这个函数被执行了
 // 1.普通函数的使用过程
    function foo() {
      console.log("foo函数被执行~")
    }
    foo()
    foo(); // ()[]{}

    // 2.定义函数, 定义完这个函数之后, 会要求这个函数立即被执行
    // {} 代码块/对象类型
    // () 控制优先级(2+3)*5/函数的调用/函数的参数
    // [] 定义数组/从数组-对象中取值/对象的计算属性
    // 立即执行函数(常用的写法)
    (function() { 
      console.log("立即执行函数被调用~")
    })()
     // 3.立即执行函数的参数和返回值
    var result = (function(name) {
      console.log("函数立刻被执行~", name)
      return "Hello World"
    })("why")
    console.log(result)

备注:在代码编写时,若有(),[],{}连续代码,却未加分号,可能会被认为是一个整体的语句

3.7.1 立即执行函数的应用

  • 应用场景1: 防止全局变量的命名冲突
    1. 在立即执行函数中定义的变量是有自己的函数作用域,能避免我们在普通代码执行时产生命名冲突
// 应用场景一: 防止全局变量的命名冲突
    
    // 立即执行函数和普通的代码有什么区别?
    // 在立即执行函数中定义的变量是有自己的作用域的
    (function() {
      var message = "Hello World"
      // console.log(message)
    })()
    // console.log(message)
    // var message = "Hello World"
    // console.log(message)
  • 应用场景2:帮助我们循环添加事件
    在通过循环给各个按钮添加事件时,我们能轻松地给所有按钮添加一个相同的点击事件,但是如果我们想让它们产生不同的效果,可能会出现问题,如下操作
// 1.获取一个按钮监听点击
    // 1.拿到html元素
    var btnEl = document.querySelector(".btn")
    console.log(btnEl)
    // 2.监听对应按钮的点击
    btnEl.onclick = function() {
      console.log("点击了按钮1")
    }

    // 2.获取所有的按钮监听点击
    // 没有使用立即执行函数
    debugger
    var btnEls = document.querySelectorAll(".btn")
    for (var i = 0; i < btnEls.length; i++) {
      var btn = btnEls[i];
      btn.onclick = function() {
        console.log(`按钮${i+1}发生了点击`)
      }
    }
    

分析:此时的i由于是全局变量,自增后值为数组长度,但是我们的点击事件的回调函数中的${i+1}是根据触发事件后i的值决定的,因此这种写法存在一定的错误,我们可以通过立即执行函数解决

var btnEls = document.querySelectorAll(".btn")
    for (var i = 0; i < btnEls.length; i++) {
      var btn = btnEls[i];
      (function(m) {
        btn.onclick = function() {
          console.log(`按钮${m+1}发生了点击`)
        }
      })(i)
    }

    console.log(i)
  • 我们将i作为参数传递给立即执行函数,这样每个按钮在触发点击事件后都是基于它自己函数作用域中的m,而不是到外部作用域中使用i变量

3.7.2 立即执行函数的其它写法

我们知道立即执行函数的写法中包含两个小括号,其中第一个括号把函数看成一个表达式,第二个括号表示调用函数,因此我们有以下几个写法:

  1. 常见的写法:
	 (function(形参){
		代码块
	})(实参)
  1. 匿名函数写法:外层括号表示为一个整体,尾部括号为调用前面声明的匿名函数
 (function(形参){
		代码块
	}(实参))
  1. 另类写法:即舍去第一个括号,用其他符号表示它为一个表达式(了解)
// +(正号)-(符号)!(取反) - 了解
    +function foo() {}()

你可能感兴趣的:(JavaScript,javascript,前端)