JavaScript 函数 作用域问题

148 阅读13分钟

JavaScript 函数 作用域问题

前端 JavaScript ES5 学习目录

1. 认识 JavaScript

2. JavaScript 基础语法

3. JavaScript 变量 数据类型 运算操作符

4. JavaScript 逻辑运算符 分支语句 循环语句

5. JavaScript 函数 作用域问题

6. JavaScript IIFE 面向对象 函数中的this

7. JavaScript 类和对象 window对象 常见内置类

8. JavaScript 数组基本使用 Date日期的使用

  • 程序中的foo, bar, baz

    • 这个术语的话,我们是可以在任何语言中使用的

    • 这些名词常用来作为我们的 函数,变量,文件的名称

    • 是我们的计算机编程术语的一部分,本身是没有任何的用途和意义的,一般是利用我们的 C++ 进行的讲解

    • 常被称之为"伪变量"metasyntactic variable

    • 看官方描述

    • 还是可以上我们前面的 stackover flow

    • 就是我们平时举例子的时候,我们是可以直接使用这个术语来实现描述

    • 我们通过下面的文字描述我们就可以知道

      • 这三个命名也只是命名而已,是没有具体的意义的,最大的作用就是对计算机科学表达出的敬意

image-20241017163132751.png

JavaScript 函数(function

  • 开始认识我们的函数(function

    • 我们为什么使用我们的函数,就是为了提高我们的代码的复用性,我们才开始实现使用我们的函数的

    • 比方说

      • alert() 这个就是我们的弹出警示框的函数
      • prompt() 这个就是用来提醒用户输入的函数
      • console,log() 这个就是用来在终端和浏览器调试者工具中实现打印出内容的函数
      • 就是因为有了这些函数,所以说,我们平时的话,就可以直接使用,就可以提高代码的复用率
    • 函数肯定是我们的具有特定功能的代码的封装

      • 函数的实现和变量的实现是可以分为两步的:

        • 声明函数 —— 封装独立的功能
        • 调用函数 —— 使用函数里面的代码块
        • 后面学习到了 IIFE 我们就可以实现直接函数本身实现调用函数了(立即执行函数)【在很多的源码中就使用了的
        • 还是先书写我们的代码块,然后使用定义函数的关键字,这样更能够理解我们的代码块
      • 声明函数,也是可以理解为是在 定义函数的

        • 声明函数的过程就是对某些功能的封装过程
        • 在实际的开发中我们是可以根据具体的需求实现定义更多的函数的
      • 调用函数

        • 让已存在的函数直接使用即可
        • 函数是可以是自己实现定义封装的具有某种特定功能的代码端,或者说是别人写好的其他第三方库的使用
        • 如果是涉及到了很好用的第三方库,建议可以看看源码,然后再使用,这样无论是面试还是后面的学习都有好处的
      • 使用函数的最大的作用:

        • 使用函数是可以提高编写代码的效率的以及我们的代码的重用
    • 声明函数 以及 调用函数

      • 首先我们前面声明一个分支语句的时候,我们使用的是我们的 if 关键字,然后后面就是对应需要执行的代码块

      • 声明循环的时候,我们使用的是我们的 for while 关键字, 后面就是对应的需要循环执行的代码块

      • 这个时候我们实现声明一个函数的时候,就需要使用我们的function 关键字

        •       /** 
                * 函数的功能描述
                * param {DateType} variable1
                * param {DateType} variable2
                */ 
                function func_name(variable1, variable2...) {
                    // 函数封装的功能代码块
                }
          
        • 注意事项

          • 然后函数是可以有参数也是可以没有的,上面的例子中的 variable1 就是参数,这个是根据实际情况决定有没有的
          • 函数名尽量做到见名知意
          • 函数本身是不会执行的,之后调用后才可以执行
          • 书写一个函数的时候,尽量把每一个函数的文档描述书写好,这样别人可以实现快速的理解,有助于协同办公
      •       // 定义一个没有参数的函数// 声明函数
              function func() {
                var num = 10
                var num02 = 20
                console.log(num + num02)
              }
              ​
              // 调用函数
              func()
        
    • 函数中的形参

      • 函数的参数的出现就是为了增加函数的 通用性,针对相同的数据结构书写特定的处理数据的功能代码
      • 在函数调用的时候,我们需要根据函数声明的时候的需要,传递需要的数据进去,这个参数就是我们的形参
      • 注意,我们对形参的修改并不会影响实际参数的值
      • 形参(parameter) 实参(argument) 回调函数(callback) 【了解这些利于对英文文档的阅读,多看文档!!!】
      •       // 开始定义一个含有参数的函数/**
               * 
               * @param {Number} num01 
               * @param {Number} num02 
               */
              function func(num01, num02) {
                console.log(num01 + num02)
              }
              ​
              // 调用函数
              func(10, 20)
        
    • 函数的返回值使用

      • 经典的具有函数返回值的函数 prompt()

      • 函数的返回值的使用就是使用 return 关键字,实现将需要返回的内容实现返回即可

        • 但是需要注意的是,只要是在处于 return 同级下面的代码,都是不会生效的
        • 我们需要注意的是,我们的函数没有 return 语句的时候,函数是具有一个默认的返回值的 undefined
        • 或者说含有我们的 return 语句,但是 没有 return 的内容,那么就是 undefined
        •       // 开始定义一个含有参数的函数/**
                 * 
                 * @param {Number} num01 
                 * @param {Number} num02 
                 */
                function func(num01, num02) {
                  return arguments
                }
                ​
                // 调用函数
                var res = func(10, 30)
                console.log(typeof res)
                console.log(res)
                ​
                ​
                /**
                 * 实现求取一个 1-n 的数字之和
                 * @param {Number} num 
                 * @returns {undefined | Number}
                 */
                function get_sum(num) {
                  if (num < 0) {
                    console.log("你输入的是无效数字...")
                    return
                  }
                  var total = 0
                  for (let i = 0; i < num; i++){
                    total += i;
                  }
                  return total
                }
                ​
                var res = get_sum(-1)
                console.log(res)
          
        • 需要注意的是,我们通过 arguments 我们是可以获取得到函数传入的实参,以键值对的形式出现
    • 数字格式化的函数封装

      •       // 首先我们需要注意的是我们的数据是从我们的服务器中实现请求回来的,不是我们的平时这样进行自定义的
              // 这个就涉及到了我们的前后端交互的知识了,后面会讲的,慢慢来/**
               * 实现的是对我们的数据的格式化处理
               * @param {Number} data 
               * @returns 
               */
              function formatData(data) {
                if (data > 10_0000_0000) {
                  data = `${Math.floor(data / (10_0000_0000 / 10))}亿播放量`
                }
                else if (data > 10_0000) {
                  data = `${Math.floor(data/(10_0000 / 10))}万播放量`
                }
                else {
                  data = `${data}播放量`
                }
                return data
              }
              ​
              console.log(formatData(123_4224_5340))  // 123亿播放量// 注意我们前端对数字的格式化的效果还不止这样的,还有很多种的数据格式化的形式
        
    • 函数的 arguments

      • 我们可以通过这个参数来实现获取我们传入函数的实参

      • 这个是我们在一个函数中都可以实现使用的一个局部变量(非箭头函数)——> arguments 对象

      • 首先这个是一个 object 类型, 类数组对象(array-like

        • 为什么这么说耶,我们是可以发现一点,访问数组中得分某个元素的时候,我们是通过下标 index 来实现访问的
        • 然后这个类数组的 arguments对象 就是使用的是我们的用: 0 开始的键的键值对对实参实现的存储
        •       /**
                 * 用来实现演示我们的 arguments 的具体形式的使用的函数
                 * @param {any} variable1 
                 * @param {any} variable2 
                 */
                function get_args(variable1, variable2) {
                  for (let i = 0; i < arguments.length; i++) {
                    console.log(arguments[i])
                  }
                }
                ​
                get_args(10, 20)
          
        • 所以说我们就可以实现对我们的求和函数实现优化,以前的是只可以传入特定的形参数,现在可以改为传入多个了
        • 后面的话还有一种解决方案: 使用我们的剩余参数 ...args
    • 函数中的递归思想的使用

      • 函数中调用函数,就可以实现理解为我们的函数的递归

        • 这个的话还是我们对代码块的理解代码块中实现书写的是我们的一坨一起执行的代码,所以说,我们是可以
        • 在某一个代码块中实现使用我们的自定义函数,可以是其他的函数也可以是当前的函数
        • 然后我们的递归的话就是在函数中执行本次的本身函数,这个就是我们的递归思想
        •       function func() {
                  console.log("func 函数执行了")
                  func()
                }
                ​
                func()/* 
                 注意我们默认的情况下的话,我们的函数是实现的是无限的执行
                 这个时候就会导致我们的调用超过最大的栈空间额度,导致最终的程序报错
                 拓展: 栈: stack
                 当然我们的递归肯定不能像我们上面那么书写,只是一个简单的用例
                */ 
          
      • 函数调用自己的话,这个就是我们的 递归(recursion)

        • 可以实现的是将一个相同的复杂任务(就是我们的代码段), 实现可以重复调用,但只不过是会设置一个递归深度
        • 递归深度就是实现停止递归的继续执行
        •       // 开始实现自己封装一个函数实现求取幂次方/**
                 * 使用for循环的方式来实现我们的求取一个数的幂次方
                 * @param {Number} x 
                 * @param {Number} n 
                 * @returns 
                 */
                function pow01(x, n) {
                  var result = 1
                  for (var i = 1; i <= n; i ++) {
                    result *= x
                  }
                  return result
                }
                console.log(pow01(3, 3))
                ​
                // ==================================================================================/**
                 * 开始实现使用我们的递归来实现我们的求取幂次方
                 * @param {Number} x 
                 * @param {Number} n 
                 * @returns 
                 */
                function pow02(x, n) {
                  if (n === 1) return x
                  return x * pow02(x, n-1)
                }
                ​
                console.log(pow02(3, 3))
                ​
                ​
                /*
                  注意事项: 我们任何可以使用我们的递归来实现的功能的代码,
                  我们都是可以使用 for 循环来实现的,只是思想不同而已 
                  但是我们还需要注意的就是: 递归的实现使用的时候,我们是需要有一个终止的地方
                  否则就会因为栈区被占用导致报错
                  但是递归的性能是十分低的,因为过多的调用函数,站内存是会被占用的
                */
          
      • 使用递归实现斐波那契数列

        • 什么是斐波那契数列耶: 就是我们的一个数字是前面的两个数字之和
        • 1 1 2 3 5 8 13 21 ...
        •       /**
                 * 实现的是求取处于在这个 传入的 index 下的斐波那契数列的值
                 * @param {Number} n 
                 */
                function achieveFeiBoNaQie(n) {
                  if (n === 1 || n === 2) return 1
                  return achieveFeiBoNaQie(n - 1) + achieveFeiBoNaQie(n - 2)
                }
                ​
                console.log(achieveFeiBoNaQie(10))
                ​
                // ================================================================================/**
                 * 使用我们的 for 循环来实现我们的 斐波那契数列
                 * @param {Number} n 
                 */
                function getFeiBoNaQie(n) {
                  // 由于前面的两个数字是固定的,直接二指定即可
                  if (n === 1 || n === 2) return 1
                  
                  // 后面的数字就直接从我们的 第三个数字通过循环一个一个的全球求出来即可
                  // 但是需要注意的就是我们的数据交换的那几步  
                  var n1 = 1, n2 = 1, result = 0
                  for(var i = 3; i <= n; i++) {
                    result = n1 + n2
                    n1 = n2
                    n2 = result
                  }
                  return result
                }
                console.log(getFeiBoNaQie(10))
          

JavaScript 作用域(Scope)问题

  • 首先在我们的 javascript 的 es5 的版本的标准之下,我们是没有块级作用域的问题的

  • 但是我们的函数是可以定义属于自己的作用域的

    • 函数作用域就是表示的是在函数内部实现定义的变量只有在函数内部才可以访问
  • 首先如果说我们定义了一个全局的变量,那么这个时候,在我们的任何地方都是可以实现访问这个值的

    • 这个就是我们的全局变量,处于我们的全局作用域中
  • 块级作用域就是实现的是在我们的某一个代码块中实现使用一个定义一个变量

    • 但是我们需要注意的是: 通过的我们的 关键字 var 来实现定义的变量是没有块级作用域的
    • 默认是添加到全局的, window 或者 global
  • 在我们的 es5 之前,只有我们的函数可以生成一个作用域 (函数作用域)

    • 但是我们的 for / while / if代码块是不会生成一个作用域的
    • 如果生成了一个独特的作用域,那么就可以实现我们的变量不会被随意的访问
    • 函数作用域中,函数内部的变量只有子啊函数中才可以实现被访问到
    • 如果想要函数作用域外面的变量可以实现访问到函数内部的变量,那么这个时候就可以使用 return 来把变量暴露出去
    •       function func() {
              var num = 10
              var str = "hello world"
              var array = [1, 2, 3, 4]
            ​
              // 直接把我们子啊函数内部想要暴露给外面使用的变量通过 return 实现暴露出去即可
              return {
                num, 
                str, 
                array
              }
            }
            ​
            console.log(func())  // { num: 10, str: 'hello world', array: [ 1, 2, 3, 4 ] }
      
    •       function getFunc() {
              
              // 开始实现定义我们的第一个函数作用域中的函数
              function func01() {
                console.log("我是函数 1")
                // 注意我们的函数默认的返回值是我们的 undefined 这里没有返回值,所以说终端中就会多一个 undefined
              }
            ​
              function func02(num01, num02) {
                return num01 + num02
              }
              
              function func03 () {
                return undefined
              }
            ​
              return {
                func01,
                func02,
                func03
              }
            ​
            }
            ​
            console.log(getFunc().func01())
            console.log(getFunc().func02(10, 20))
      
  • 变量的查询顺序(全局 局部 外部

    • 全局变量: 我们代码中随便一个地方都是可以进行访问到的,任何范围都是可以实现访问的

      • 通过 var 定义的变量,这个变量会被添加到我们的 window对象中 或者 global对象中
    • 局部变量: 在函数内部定义的变量,只有在函数内部才可以实现访问,这个就是我们的局部变量(local variables

    • 外部变量: 在函数的内部我们访问函数外部的变量,这个变量就是外部变量(Outer variables

    • 优先实现访问我们的最近的作用域中的变量,但是这个作用域的话,是网上一层的作用域中实现查找(作用域链

  • 函数的定义(在 javascript 中,我们函数本身也就是一个值而已)

    • 函数的声明(function decalaration

      •       function 函数名() {
                  // 代码块代码    
              }  
              // 这个就是我们的函数的声明
        
    • 函数表达式(function expressions

      • 定义变量的关键字(var / let / const) 函数名 = function() {
            // 代码块代码    
        }   
        // 函数表达式的实现书写 
        
    • 后面的话我们还是会拓展一个箭头函数的,后面补充

    • 实际上的话,我们的函数实际上就是一个值,属于我们的 object 对象,是一种数据类型

  • 回调函数(匿名函数)【callback function

    • 就是实现的是我们直接通过在一个函数实现传入一个函数即可,实现的就是将我们的一个函数作为我们的一个形参即可

      • function func(fn) {
            fn()
        }
        ​
        function func01() {
            console.log("hello world")
        }
        ​
        func(func01)  // hello world
        

建议阅读:

阮一峰es6的讲解

stackover flow