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 下
打包出来的代码其实逻辑很简单,就是用一个立即执行函数把我们写到代码包起来,对外暴露一个 $ 函数:
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>
结尾
本文参考自这篇文章:
感谢然叔,让我对闭包的理解更进一步,阿林最近学习闭包写的文章大都参考自然叔的文章,然叔牛!
如果我的文章对你有帮助,你的👍就是对我的最大支持^_^
你也可以关注《前端每日一问》这个专栏,防止失联哦~
我是阿林,输出洞见技术,再会!
上一篇:
「前端每日一问(35)」webpack 模块化打包是如何用闭包实现的?
下一篇: