「前端每日一问(36)」闭包在类库封装中的应用

392 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

本题难度:⭐ ⭐

一般这道题的问法是:闭包有什么应用场景?

答:

闭包有一个应用场景就是大量应用于类库封装中。

类库封装最重要的要求就是不能让类库中的变量污染全局。

举个例子,jQuery 内部实现是非常复杂的,但它暴露给全局的仅仅就是一个 $ 而已,内部为了实现jQuery 定义的一大堆变量不会污染全局。

它是如何做到的呢?其实就是用的闭包,我们来模拟一下,就是类似这样的代码:

(function () {
  // 实现 jQuery,内部一大堆代码,定义了一大堆变量
  let a = 'xxx'
  let b = 'xxx'
  let c = 'xxx'
  // 把 $ 挂载到 window 上,只暴露 $ 方法
  var jQuery = window.$ = function() {
    // jQuery.xxx 外部用 $,内部用 jQuery 这个变量
    // ...
  }
})()

$('#div').hide()

这种写法也可以:

var $ = (function() {
  function jQuery() {
    // ...
  }
  return jQuery
})()

$('#div').hide()

事实上,jQuery 的源码长这样子,跟我们上文分析的写法差不多,就是用到的闭包,部分代码如下:

;(function (global, factory) {...
})(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {...
    
    // ...
    jQuery = function (selector, context) {
      return new jQuery.fn.init(selector, context)
    }
    // ...
    var init = (jQuery.fn.init = function (selector, context, root) {
      // ...
    })
    // ...
    
    window.jQuery = window.$ = jQuery
    return jQuery
})

扩展:手写一个迷你 jQuery,用 Rollup 打包

虽然现在除了写官网等需求,很少使用 jQuery 了,但是 jQuery 作为前端刀耕火种时代第一个正式意义的前端框架,当年给前端带来的效率提升,具有非凡的意义。

阿林作为新生代的小前端,自工作以来,从没写过 jQuery,这次为了学习闭包去看了 jQuery 的源码,这一看就深深陷入其中,真的设计得非常巧妙,不得不佩服前人的智慧。

阿林就来手写一个究极迷你的 jQuery,实现几个小功能。

  • $ 函数,支持传入选择器和函数。
  • 实现 html、addClass、on、val、append 方法。

当然,现在是用 class 来实现的,和以前用 function 的思想是差不多的。

创建一个 test 目录,把迷你 jQuery 的逻辑写到 src 目录下的 jQuery.js 里

// src/jQuery.js
export default function (selector) {
  return new JQuery(selector)
}

class JQuery {
  constructor (selector) {
    this.selector = selector
    this.init(selector)
  }

  init (selector) {
    if (typeof selector === 'string') {
      this.elements = [...document.querySelectorAll(selector)]
    } else if (typeof selector === 'function') {
      this.elements = []
      document.addEventListener('DOMContentLoaded', selector)
    }
  }

  html (str) {
    if (str !== undefined) {
      this.elements.forEach(ele => {
        ele.innerHTML = str
      })
      return this
    } else {
      return this.elements[0].innerHTML
    }
  }

  addClass (className) {
    this.elements.forEach(ele => {
      ele.classList.add(className)
    })
    return this
  }

  on (event, callback, useCapture = false) {
    this.elements.forEach(ele => {
      ele.addEventListener(event, callback, useCapture)
    })
    return this
  }

  val (str) {
    if (str !== undefined) {
      this.elements.forEach(ele => {
        ele.value = str
      })
      return this
    } else {
      return this.elements[0].value
    }
  }

  append (child) {
    if (typeof child === 'string') {
      this.elements.forEach(ele => {
        ele.innerHTML += child
      })
    }
  }
}

终端进入这个 test 目录,执行下面的命令:

npx rollup -f iife -n $ -o ./dist/jQuery.js  ./src/jQuery.js

解释一下这些 rollup 命令

  • -f 指定iife方式输出
  • -o 指定输出文件
  • -n 指定输出变量名

这样就可以打包一个立即执行函数模式的迷你 jQuery,生成到了 dist 目录的 jQuery.js 下

image.png

打包出来的代码其实逻辑很简单,就是用一个立即执行函数把我们写到代码包起来,对外暴露一个 $ 函数:

var $ = (function () {
  'use strict';

  function jQuery (selector) {
    return new JQuery(selector)
  }

  class JQuery {... // 这里就是我们上面写的逻辑,折叠起来
  }

  return jQuery;

})();

然后就可以在业务代码中引用打包生成的dist/jQuery.js,用 jQuery 一把梭页面了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .text-red {
      color: red;
    }
  </style>
  <script src="./dist/jQuery.js"></script>
  <script>
    $(function() {
      $('#title').html('qwe').addClass('text-red')

      $('#btn').on('click', function() {
        let val = $('#input').val()
        $('#ul').append(`<li>${val}</li>`)
        $('#input').val('')
      })

    })
  </script>
</head>
<body>
  <h1 id="title">hello world</h1>
  <input id="input" type="text">
  <button id="btn">添加</button>
  <ul id="ul">

  </ul>
</body>
</html>

结尾

本文参考自这篇文章:

Day12 - 闭包应用4 - 类库封装

感谢然叔,让我对闭包的理解更进一步,阿林最近学习闭包写的文章大都参考自然叔的文章,然叔牛!

全栈然叔的主页

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

你也可以关注《前端每日一问》这个专栏,防止失联哦~

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(35)」webpack 模块化打包是如何用闭包实现的?

下一篇:

「前端每日一问(37)」call、apply 和 bind 的区别是什么?