JavaScript 高级 作用域&解构&箭头函数

本文深入探讨JavaScript的高级特性,包括作用域(全局与局部、作用域链、变量提升)、垃圾回收机制、闭包以及其内存影响。接着讲解函数提升、函数参数细节,如默认值、动态参数`arguments`和剩余参数`...`的区别。还介绍了箭头函数的特性和解构赋值(数组解构和对象解构)的用法。最后,阐述了遍历数组的`forEach`方法和筛选数组的`filter`方法。
摘要由CSDN通过智能技术生成

一、作用域

作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。

 局部作用域

局部作用域分为函数作用域和块作用域。

函数作用域

<script>
  // 声明 counter 函数
  function counter(x, y) {
    // 函数内部声明的变量
    const s = x + y
    console.log(s) // 18
  }
  // 设用 counter 函数
  counter(10, 8)
  // 访问变量 s
  console.log(s)// 报错
</script>
块作用域
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【 有可能 】无法被访问。
<script>
  {
    // age 只能在该代码块中被访问
    let age = 18;
    console.log(age); // 正常
  }
  
  // 超出了 age 的作用域
  console.log(age) // 报错
  
  let flag = true;
  if(flag) {
    // str 只能在该代码块中被访问
    let str = 'hello world!'
    console.log(str); // 正常
  }
  
  // 超出了 age 的作用域
  console.log(str); // 报错
  
  for(let t = 1; t <= 6; t++) {
    // t 只能在该代码块中被访问
    console.log(t); // 正常
  }
  
  // 超出了 t 的作用域
  console.log(t); // 报错
</script>

<script>
  // 必须要有值
  const version = '1.0.0';

  // 不能重新赋值
  // version = '1.0.1';

  // 常量值为对象类型
  const user = {
    name: '小明',
    age: 18
  }

  // 不能重新赋值
  user = {};

  // 属性和方法允许被修改
  user.name = '小小明';
  user.gender = '男';
</script>
总结:
1. let 声明的变量会产生块作用域,var 不会产生块作用域
2. const 声明的常量也会产生块作用域
3. 不同代码块之间的变量无法互相访问
4. 推荐使用 let 或 const
注:开发中 letconst 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。

全局作用域

<script> 标签 .js 文件 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
<script>
  // 此处是全局
  function sayHi() {
    // 此处为局部
  }
  // 此处为全局
</script>


<script>
    // 全局变量 name
    const name = '小明'
  	// 函数作用域中访问全局
    function sayHi() {
      // 此处为局部
      console.log('你好' + name)
    }
    // 全局变量 flag 和 x
    const flag = true
    let x = 10
  	// 块作用域中访问全局
    if(flag) {
      let y = 5
      console.log(x + y) // x 是全局的
    }
</script>

总结:

  1. window 对象动态添加的属性默认也是全局的,不推荐!

  2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!

  3. 尽可能少的声明全局变量,防止全局变量被污染

JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。

作用域链

1. 作用域链本质是什么?
作用域链本质上是底层的变量查找机制
2. 作用域链查找的规则是什么?
会优先查找当前函数作用域中查找变量
查找不到则会依次逐级查找父级作用域直到全局作用域

JS垃圾回收机制

内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
2. 内存使用:即读写内存,也就是使用变量、函数等
3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
4. 说明:
全局变量一般不会回收(关闭页面回收);
一般情况下局部变量的值, 不用了, 会被自动回收掉
垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉
下面介绍两种常见的浏览器垃圾回收算法: 引用计数法 标记清除法
  引用计数
IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。
算法:
1. 跟踪记录每个值被引用的次数。
2. 如果这个值的被引用了一次,那么就记录次数1
3. 多次引用会累加。
4. 如果减少一个引用就减1。
5. 如果引用次数是0 ,则释放内存。
标记清除法
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进 行回收。

闭包

闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数

闭包作用: 封闭数据,提供操作,外部也可以访问函数内部的变量

 

总结:

  1. 闭包本质仍是函数,只不是从函数内部返回的

  2. 闭包能够创建外部可访问的隔离作用域,避免全局变量污染

  3. 过度使用闭包可能造成内存泄漏

注:回调函数也能访问函数内部的局部变量。

    <script>
        function get() {
            let a = 1
            function out() {
                a++
                console.log(a)
            }
            return out
        }
        let f = get()
        f()
        f()
        f()
        //闭包的作用: 延长局部变量的生命周期,让局部变量活的久一些
        //缺点: 占用内存
    </script>
1. 怎么理解闭包?
闭包 = 内层函数 + 外层函数的变量
2. 闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3. 闭包可能引起的问题?
内存泄漏

变量提升

变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问

 注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let 可以轻松规避变量的提升

二、函数

函数提升

函数提升与变量提升比较类似,是指函数在声明之前即可被调用。

<script>
  // 调用函数
  foo()
  // 声明函数
  function foo() {
    console.log('声明之前即被调用...')
  }

  // 不存在提升现象
  bar()  // 错误
  var bar = function () {
    console.log('函数表达式不存在提升现象...')
  }
</script>

总结:

  1. 函数提升能够使函数的声明调用更灵活

  2. 函数表达式不存在提升的现象

  3. 函数提升出现在相同作用域当中

参数

函数参数的使用细节,能够提升函数应用的灵活度

默认值

<script>
  // 设置参数默认值
  function sayHi(name="小明", age=18) {
    document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
  }
  // 调用函数
  sayHi();
  sayHi('小红');
  sayHi('小刚', 21);
</script>

总结:

  1. 声明函数时为形参赋值即为参数的默认值

  2. 如果参数未自定义默认值时,参数的默认值为 undefined

  3. 调用函数时没有传入对应实参时,参数的默认值被当做实参传入

动态参数

arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。

<script>
  // 求生函数,计算所有参数的和
  function sum() {
    // console.log(arguments)
    let s = 0
    for(let i = 0; i < arguments.length; i++) {
      s += arguments[i]
    }
    console.log(s)
  }
  // 调用求和函数
  sum(5, 10)// 两个参数和
  sum(1, 2, 4) // 三个参数和
</script>

总结:

  1. arguments 是一个伪数组

  2. arguments 的作用是动态获取函数的实参

剩余参数

    <script>
        // ... 可以获取所有剩余参数 是一个真数组
        // 定义函数,不知道写几个参数,...变量名
        function get(a,...args) {
            let sum = 0
            for (let i = 0; i < args.length; i++) {
                sum += args[i]
            }
            console.log(sum)
        }
        get(1, 2)
        get(1, 2, 3, 4)
        get(1, 2, 3, 4, 5)
    </script>

总结:

  1. ... 是语法符号,置于最末函数形参之前,用于获取多余的实参

  2. 借助 ... 获取的剩余实参,是个真数组

arguments和...的区别

    <script>
        function out(a,b) {
            console.log(arguments)
        }
        out(1,2,3)
     //1,2,3
        function get(a,...args) {
            console.log(args)
        }
        get(1,2,3)
     //2,3
    </script>

1. 剩余参数主要的使用场景是?
  用于获取多余的实参
2. 剩余参数和动态参数区别是什么?开发中提倡使用哪一个?
  动态参数是伪数组
  剩余参数是真数组
  开发中使用剩余参数想必也是极好的

展开运算符

展开运算符(…),将一个数组进行展开

 

    <script>
        //展开运算符 可用于合并数组 求最大值和最小值 ...
        const arr = [1,2,3,4,5]
        const out = Math.max(...arr)
        console.log(out)
    </script>


    <script>
        const obj1 = {
            uname: '张三',
            age: 18
        }
        const obj2 = {
            ...obj1,
            age: 11
        }
        obj2.uname = '王五'
        console.log(obj2)
        console.log(obj1)
    </script>

 

箭头函数

箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
    <script>
        //箭头函数: 优化函数的写法
        const out = function () {
            console.log(14)
        }
        out()

        const fn = () => {
            console.log(14)
        }
        fn()

        const go = a => {
            console.log(14)
        }
        go()
        //只有一个形参,括号可以省略不写

        const to = (a, b) => {
            console.log(14)
        }
        to()

        // const get = (x, y) => {
        //     return x + y
        // }
        // const res = get(1, 2)
        // console.log(res) 

        const get = (x, y) => x + y
        const res = get(1, 2)
        console.log(res)
        //如果函数体只有一条代码,那么大括号和return可以省略不写

        const ge = () => ({ uname: '张三', age: 14 })
        const ar = ge()
        console.log(ar)
        //如果函数体只有一条代码,并且返回值是一个对象,需要在对象外使用()
    </script>

总结:

  1. 箭头函数属于表达式函数,因此不存在函数提升

  2. 箭头函数只有一个参数时可以省略圆括号 ()

  3. 箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回

  4. 箭头函数中没有 arguments,只能使用 ... 动态获取实参

箭头函数参数

 

    <script>
        const get = (...arr) => {
            let sum = 0
            for (let i = 0; i < arr.length; i++) {
                sum += arr[i]
            }
            return sum
        }
        const res = get(1, 2, 3, 4, 5)
        alert(res)
        //箭头函数里面没有arguments
    </script>
箭头函数 this
在箭头函数出现之前,每一个新函数根据它是被 如何调用的 来定义这个函数的this值, 非常令人讨厌。
箭头函数不会创建自己的this ,它只会从自己的作用域链的上一层沿用this。
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此
DOM事件回调函数为了简便,还是不太推荐使用箭头函数

 

    <button>按钮</button>
    <script>
        // const btn = document.querySelector('button')
        // btn.addEventListener('click',function() {
        //     console.log(this)
        // })

        const btn = document.querySelector('button')
        btn.addEventListener('click',() => console.log(this))
        //事件注册 处理函数 尽量不用箭头函数
    </script>


    <button>按钮</button>
    <script>
        // const btn = document.querySelector('button')
        // btn.addEventListener('click', function () {
        //     setTimeout(() => { this.disabled = true }, 2000)
        // })
        //事件注册 处理函数 尽量不用箭头函数
        const btn = document.querySelector('button')
        btn.addEventListener('click', function () {
            setTimeout(function () {
                this.disabled = true
            }, 2000)
        })

    </script>
1. 箭头函数里面有this吗?
  箭头函数不会创建自己的this ,它只会从自己的作用域链的上一层沿用this
2. DOM事件回调函数推荐使用箭头函数吗?
  不太推荐,特别是需要用到this的时候
  事件回调函数使用箭头函数时,this 为全局的 window

解构赋值

解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。

数组解构

数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
    <script>
        const [hr, lx, mi, fz] = ['海尔', '联想', '小米', '方正']
        console.log(hr, lx, mi, fz);

        //交换两个变量的值
        let a = 1
        let b = 2
            ;[b, a] = [a, b]
        console.log(a, b)

        function get() {
            return [100, 60]
        }
        const [max, min] = get()
        document.write(max, min)
    </script>

    <script>
        const [a,[b,[c,d],e],f] = [1,[2,[3,4],5],6]
        console.log(a)
        console.log(b)
        console.log(c)
        console.log(d)
        console.log(e)
        console.log(f)
        console.log(a,[b,[c,d],e],f)

    </script>

 

 

总结:

  1. 赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量

  2. 变量的顺序对应数组单元值的位置依次进行赋值操作

  3. 变量的数量大于单元值数量时,多余的变量将被赋值为 undefined

  4. 变量的数量小于单元值数量时,可以通过 ... 获取剩余单元值,但只能置于最末位

  5. 允许初始化变量的默认值,且只有单元值为 undefined 时默认值才会生效

注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析

对象解构

 

 

<script>
  // 普通对象
  const user = {
    name: '小明',
    age: 18
  };
  // 批量声明变量 name age
  // 同时将数组单元值 小明  18 依次赋值给变量 name  age
  const {name, age} = user

  console.log(name) // 小明
  console.log(age) // 18
</script>

   

 <script>
        const obj = {
            uname: '张三',
            age: 14
        }
        const {uname: hello,age} = obj
        console.log(hello)
        //解构时 变量名需要和 对象里面的属性名保持一致
    
</script>

多级对象解构
    <script>
        const pig = {
            name: '佩奇',
            family: {
                mother: '猪妈妈',
                father: '猪爸爸',
                sister: '乔治'
            },
            age: 6
        }

        const { name, age, family: { mother, father, sister } } = pig
        console.log(name, age, mother, father, sister)
    </script>

总结:

  1. 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量

  2. 对象属性的值将被赋值给与属性名相同的变量

  3. 对象中找不到与变量名一致的属性时变量值为 undefined

  4. 允许初始化变量的默认值,属性不存在或单元值为 undefined 时默认值才会生效

注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析

遍历数组 forEach 方法

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
主要使用场景: 遍历数组的每个元素

 

    <script>
        const arr = [1, 2, 3, 4, 5]
        let sum = 0

        // arr.forEach(function(item){
        //     sum = sum + item
        // })
        // console.log(sum)

        arr.forEach(a => sum = sum + a)
        console.log(sum)
    </script>
1. forEach 主要是遍历数组
2. 参数当前数组元素是必须要写的, 索引号可选。

筛选数组 filter 方法

 

  返回值: 返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
  参数: currentValue 必须写, index 可选
  因为返回新数组,所以不会影响原数组
    <script>
        const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        // const newArr = []
        // arr.forEach(function (item) {
        //     if (item >= 5) {
        //         newArr.push(item)
        //     }
        // })
        const newArr = arr.filter(item => item >= 5)
        console.log(newArr)
    </script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值