写点什么

JS 知识点梳理之作用域、作用域链、柯里化、闭包

作者:hellocoder2029
  • 2022-11-10
    浙江
  • 本文字数:3145 字

    阅读完需:约 10 分钟

一、作用域与作用域链

作用域是指 js 变量使用时所存在的一个区域,分为全局作用域(window)和局部作用域(function、setTimeout...等都会产生局部作用域)。当局部作用域变量名与全局作用域变量名重复时,局部变量会覆盖全局变量。


在局部作用域使用变量时,如果在自己作用域找不到对应变量,则会往上一级作用域查找,直到全局作用域,如果全局作用域无此变量则会报 undefined。相反,全局作用域中无法使用局部作用域中的变量。


window.a = 1function(){  // 输出 1,虽然局部没有 a 变量,但是 全局中有。  console.log(a)  var b = 2}// 报错,全局中无法使用局部变量。console.log(b)
复制代码


上面这种一层层向外查询变量的过程叫做查询作用域链。而这种一层层局部作用域直到全局作用域的结构被称为作用域链。


// 全局作用域,声明了一个全局变量 avar a = 100
// 函数会生成局部作用域function acs(){ // 在此局部作用域中声明一个局部变量 b var b = 50 // 输出:100, 50 console.log(a, b) // 执行过程:在此作用域查找变量 a, // 找不到-->往上一级作用域找-->在全局找到,使用全局作用域中的a // 在此作用域查找变量 b,查找到了,使用此局部变量的 b}()
// 输出:b is not definedconsole.log(a, b)
复制代码

二、闭包(Closure)

1. 闭包是什么?

闭包是指在函数外部调用函数内部的局部变量,且在调用后局部变量不会被浏览器立即回收,会一直存在的一种私有变量。再简单点说就是函数返回函数。


红宝书中的描写:闭包是指有权访问另一个函数作用域中的变量的函数。


其实闭包就是返回一个函数,且这个函数对局部变量存在引用形成的包含关系就是闭包。


其实就是创建一个不会被 GC 回收的局部变量。也正因如此,闭包才会有内存泄漏的风险,需要在每次使用完后立刻清除。


闭包的形成:当前环境中存在指向父级作用域的引用。

2. 闭包的写法

// 使用自执行函数形成闭包var add = function(){    let sum = 0  return function operation(){      return sum = sum ? sum + 1 : 1  }}()
// 输出:1add()// 输出:2add()// 输出:3add()// 输出:4add()
// 清除闭包,删除私有变量add = null// 输出:nullconsole.log(add)// 输出:add is not functionadd()
复制代码

3. 闭包的作用

使用闭包的目的――隐藏变量,间接访问一个变量,在定义函数的词法作用域外,调用函数。


闭包通常在回调函数、私有属性、函数柯里化中使用。

4. 使用闭包实现多个图片点赞功能

使用闭包完成,多图点赞单独点赞功能,且每个 input 的点赞数量互不干扰。在这个例子中利用闭包声明了 5 个新的独立词法作用域。


<!-- * @Description: 闭包实现多图点赞 * @Author: CY小尘s * @Date: 2021-07-28 18:39:33 * @LastEditTime: 2021-11-08 17:19:37 * @LastEditors: Please set LastEditors--><!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>闭包实现多图点赞</title>  </head>  <style>    ul li {      list-style: none;      float: left;      margin: 0px 20px 20px 0px;    }    ul li img {      width: 200px;      height: 200px;    }  </style>  <body>    <ul>      <li>        <img          class="img"          src="https://www.keaidian.com/uploads/allimg/181206/co1Q206121F4-0-0.jpg"          alt="你好"        />        <input type="button" class="add" value="当前点赞数量(1)" />      </li>      <li>        <img          class="img"          src="https://www.keaidian.com/uploads/allimg/181206/co1Q206121F4-0-1.jpg"          alt="你好"        />        <input type="button" class="add" value="当前点赞数量(1)" />      </li>      <li>        <img          class="img"          src="https://www.keaidian.com/uploads/allimg/181206/co1Q206121F4-0-2.jpg"          alt="你好"        />        <input type="button" class="add" value="当前点赞数量(1)" />      </li>      <li>        <img          class="img"          src="https://www.keaidian.com/uploads/allimg/181206/co1Q206121F4-0-3.jpg"          alt="你好"        />        <input type="button" class="add" value="当前点赞数量(1)" />      </li>      <li>        <img          class="img"          src="https://www.keaidian.com/uploads/allimg/181206/co1Q206121F4-0-6.jpg"          alt="你好"        />        <input type="button" class="add" value="当前点赞数量(1)" />      </li>      <li>        <img          class="img"          src="https://www.keaidian.com/uploads/allimg/181206/co1Q206121F4-0-7.jpg"          alt="你好"        />        <input type="button" class="add" value="当前点赞数量(1)" />      </li>    </ul>
<script> window.onload = function () { // 获取 ul let add = document.querySelectorAll("ul li input"); // 循环取出每个 input 添加上闭包 for (let i = 0; i < add.length; i++) { // 给每个 input 添加点击事件 add[i].onclick = (function () { let sum = 2; // 返回函数,完成私有变量的创建,形成闭包 return function () { this.value = "当前点赞数量(" + sum++ + ")"; }; })(); } }; </script> </body></html>
复制代码


参考视频讲解:进入学习

5. 使用闭包保护私有属性

创建一个计数器函数,在里面定义一个私有属性,这里通过闭包保护它不会被直接修改。


可以看见在这个例子中我们并没有直接操作 privatelyCounter,而是通过 makeCounter 主动暴露的方法来操作计数器中的 privateCounter。


// 创建一个计数器const makeCounter = function(){    // 创建私有变量    var privatelyCounter = 0    // 输出私有变量    function console(){        return privatelyCounter    }    // 更改计数器方法    function change(num){        privatelyCounter += num    }    // 暴露公有方法    return {        CounterAdd(num){            change(num)        },        CounterSub(num){            change(num)        },        CounterLog(){            return console()        }    }}// 声明两计数器const counter1 = makeCounter()const counter2 = makeCounter()counter1.CounterAdd(1)counter1.CounterAdd(1)counter2.CounterSub(-1)counter2.CounterSub(-1)// 输出:2console.log(counter1.CounterLog())// 输出:-2console.log(counter2.CounterLog())
复制代码

三、使用闭包实现函数柯里化

所谓函数柯里化就是将一个多参函数转为单参函数。


// 正常求自增方法function numAdd(x, y){    return x + y}console.log(numAdd(1, 2))
// 使用闭包实现柯里化function numAddCurry(x){ return function(y){ return x + y }}// 先声明一个变量拿到自增方法const curry = numAddCurry(1)// 在调用这个变量进行自增,输出:3console.log(curry(2))// 亦或者直接调用自增方法传入两个参数,输出也是:3console.log(numAddCurry(1)(2))
复制代码


用户头像

还未添加个人签名 2022-09-08 加入

还未添加个人简介

评论

发布
暂无评论
JS知识点梳理之作用域、作用域链、柯里化、闭包_JavaScript_hellocoder2029_InfoQ写作社区