

JS闭包和构造函数对比研究
source link: http://nakeman.cn/engineering/webprogramming/js-clouser-vs-contructor.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JS闭包和构造函数对比研究
闭包(或闭包函数)的概念在JS社区讨论的频率异常的高,有人甚至 把闭包原理看作像是外语里语法的那样重要。闭包的确是JS开发的基石(之一),然而它的基础作用为何,可能还需进一步廓清。
有一定OOP基础的人一定会发现,闭包函数和构造函数(必须使用new操作符的JS函数)的性质和意义,有点相似。本文尝试分析它们的共通和不同的地方,以期达到深刻认识闭包的意义。
最近半个月一直在为重新出山面试作准备,在完成Promise(异步编程)的研习后,我又回到了JS 的基础复习中,其中的研习主题都是围绕着闭包、高阶函数、柯里化,和函数式编程。在这个过程,我开始酝酿着一种新看法,闭包,高阶函数,柯里化这些概念或技术都是以 「函数是一等公民」为前提的,它们都是在 「把函数作值处理的 」技术(请将它和 以数据作值处理相比较)。这种现象应该是 函数式编程范式的表现,目前我还没有对这种 「把函数作值处理的 」现象总结出完整的结论(初步使用「功能编程」概括之),研习还在进行中,不过我已经将它们进行归类,并且辨识出它各自的意义:
功能编程中功能制造的种类这里有一类首先引起了我特别的注意,就是创建新的一类。
闭包函数和构造函数的相似性
闭包的常识是说,它制作(返回)一个新函数,并且是带私有数据的新函数,从「把函数作值处理的 」的角度看,就是 新建一个「 函数值」[em],或者说新建一变量,它的值是 函数。如果是这个任务,我就想,构造函数的意义不也是这样吗,只是构造函数返回的一个新对象实例(内部可this指向)。不过如果闭包返回的是带私有数据的函数,这也不是一个对象吗?
EM:是函数作值,还是计算功能 作值?实质应该是计算功能作值,以函数为载体,以call apply为值使用!
为了证明我的猜想,我实验一下将 使用闭包函数的实例改用构造函数 ,看看会怎么样。
带私有数据的事件处理函数
前端开发中,闭包函数使用常见一种类例子就是 为DOM对象的交互事件填写 事件处理函数,而这个Event Handler是带参数数据的。例如页面提供了调整字体大小的交互功能(例子来自MDN ) ,分别用大中小三个按键的点击事件提供。每个按键的事件处理逻辑是相似的,只是字体样式不同。
<body> <div id="app"> 我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。 <a href="#" id="size-s">小</a> <a href="#" id="size-m">中</a> <a href="#" id="size-x">大</a> </div> </body>
- <body>
- <div id="app">
- 我们的文本尺寸调整按钮可以修改 body 元素的 font-size
- 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。
- <a href="#" id="size-s">小</a>
- <a href="#" id="size-m">中</a>
- <a href="#" id="size-x">大</a>
- </div>
- </body>
<body> <div id="app"> 我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。 <a href="#" id="size-s">小</a> <a href="#" id="size-m">中</a> <a href="#" id="size-x">大</a> </div> </body>
由于每个按键的事件处理逻辑是相似的,所以不必为它们编写单独的事件处理函数,我们可以把这些函数实例进行抽象,归纳出一个 「事件处理函数类」,这就是闭包派上用场了。
function makeSizerHandler(size) { return function () { document.body.style.fontSize = size + "px"; }; } var sizes_hdlr = makeSizerHandler(12); var sizem_hdlr = makeSizerHandler(14); var sizex_hdlr = makeSizerHandler(16);
- function makeSizerHandler(size) {
- return function () {
- document.body.style.fontSize = size + "px";
- var sizes_hdlr = makeSizerHandler(12);
- var sizem_hdlr = makeSizerHandler(14);
- var sizex_hdlr = makeSizerHandler(16);
function makeSizerHandler(size) { return function () { document.body.style.fontSize = size + "px"; }; } var sizes_hdlr = makeSizerHandler(12); var sizem_hdlr = makeSizerHandler(14); var sizex_hdlr = makeSizerHandler(16);
这个功能类(makeSizerHandler)的编写是非常简单的,只有一个参数和一条功能语句[注]。然后,我们可绑定事件处理了:
document.getElementById("size-s").onclick = sizes_hdlr; document.getElementById("size-m").onclick = sizem_hdlr; document.getElementById("size-x").onclick = sizex_hdlr;
- document.getElementById("size-s").onclick = sizes_hdlr;
- document.getElementById("size-m").onclick = sizem_hdlr;
- document.getElementById("size-x").onclick = sizex_hdlr;
document.getElementById("size-s").onclick = sizes_hdlr; document.getElementById("size-m").onclick = sizem_hdlr; document.getElementById("size-x").onclick = sizex_hdlr;
注:分析代码重复的情况,和利用闭包制作 功能类(makeSizerHandler)就是功能编程过程;而使用makeSizerHandler实例化 处理函数,和绑定事件处理,可归为结果编程过程;
现在我们尝试使用构造函数改写这段代码。
用对象实例的方法替换闭包实例
JS构造函数是创建 对象(Object)的标准方法,虽然大部分情况大家推荐使用字面量创建。为了相区分,我们取一个名词:
function SizerHandler(size) { this.size = size; this.make = function () { document.body.style.fontSize = size + "px"; }; }
- function SizerHandler(size) {
- this.size = size;
- this.make = function () {
- document.body.style.fontSize = size + "px";
function SizerHandler(size) { this.size = size; this.make = function () { document.body.style.fontSize = size + "px"; }; }
我们如期使用new分别创建三个实例,并绑定:
var SHs = new SizerHandler(12); var SHm = new SizerHandler(14); var SHx = new SizerHandler(16); document.getElementById("size-s").onclick = SHs.make; document.getElementById("size-m").onclick = SHm.make; document.getElementById("size-x").onclick = SHx.make;
- var SHs = new SizerHandler(12);
- var SHm = new SizerHandler(14);
- var SHx = new SizerHandler(16);
- document.getElementById("size-s").onclick = SHs.make;
- document.getElementById("size-m").onclick = SHm.make;
- document.getElementById("size-x").onclick = SHx.make;
var SHs = new SizerHandler(12); var SHm = new SizerHandler(14); var SHx = new SizerHandler(16); document.getElementById("size-s").onclick = SHs.make; document.getElementById("size-m").onclick = SHm.make; document.getElementById("size-x").onclick = SHx.make;
注意到,我们并不能像闭包那样直接用实例名,而还要指定make方法。代码测试通过,功能和闭包一样。
this丢失的分析
这里我注意到一个点,当我在SizerHandler.make实现里使用实例的数据(this.size)时,测试不通过:
... this.make = function () { document.body.style.fontSize = this.size + "px"; ...
- this.make = function () {
- document.body.style.fontSize = this.size + "px";
... this.make = function () { document.body.style.fontSize = this.size + "px"; ...
简单分析后,原因其实很简单,对象的方法被传递是不带父环境(this指向)的,当把SMs.make赋值给onclick后(就是将函数作值传递),make的执行父环境是那个按键对象,而不再是SMs了。
这就是this丢失。这里也有一个有趣的思考问题:
- 第一,为什么前面创建时使用初始size参数,可以测试通过?
- 第二,闭包例子为什么又可访问到数据呢?
这里就涉及了你怎么定了 「函数的完整形式」。例如当你初始化时就使用size,那么size就是make的形式定义的一部分,它不会丢失;但如果make是使用了对象实例其他属性数据,例如通用this.size引用,那make的形式就依赖了this。如果将make“移走”而不带这个this,它的形式是不完整的,故功能会不工作。这里的make 功能比较简单,它可不依赖this,但是如果make比较复杂必须依赖父对象时,我们如果要 将make 功能移走他用,则必须考虑make的完整形式,才能正确复用make的功能。如果我们不理解这个原理,则会被this丢失困扰。
而第二个,闭包的情况比较的特殊,闭包的涵义就是封装,强调形式的完整性,它将整个功能移走,而不仅仅语法上的一个函数,所以它可以访问到数据。
闭包是返回函数的函数,与构造函数,在制作计算功能的任务上——用一个模板类派生具体功能实例——是一致的。我们可以解读,闭包语法是制作带记忆(状态或私有数据)计算功能的轻便工具。所以闭包函数和构造函数的比较概括如下:
- 相同:都是用来创建新计算功能单元的「抽象类」
- 不同:闭包语法更轻便;构造函数使用语言支持的new操作符,闭包使用 函数作值传递
当然应用场景也有不同,计算功能比较精简时使用闭包,当功能比较复杂时,可能需要使用构造函数了。
Recommend
-
45
匿名函数:顾名思义就是没有名字的函数。很多语言都有如:java,js,php等,其中js最钟情。匿名函数最大的用途是来模拟块级作用域,避免数据污染的。 今天主要讲一下Golang语言的匿名函数和闭包。 匿名函数 示...
-
13
深入Lua:函数和闭包2Lua文件的加载Lua是以函数为编译单元的,一个Lua文件加载进来后就是一个函数,定义在Lua文件中的顶层本地变量,和普通函数内的本地变量没什么区别。对全局变量的访问会改写成_ENV.var这样的形式,_ENV为函数的第1个Upval...
-
19
深入Lua:函数和闭包接下来要开始解析函数的原型和闭包对象,这部分内容相对有点多而杂,且理解上会有点难度。如果只是想了解总体的设计思路,建议看一下The Implement...
-
14
「JavaScript 中,函数是一等公民」,在各种书籍和文章中我们总能看到这句话。 既然有一等,那么当然也有次等了。 如果公民分等级,一等公民什么都可以做,次等公民这不能做那不能做。JavaScript的函数也是对象,可以有属性,可...
-
14
有很多人抱怨,把这个特性命名为“装饰器”不好。主要原因是,这个名称与 GoF 书使用的不一致。装饰器这个名称可能更适合在编译器领域使用,因为它会遍历并注解语法书。 —“PEP 318 — Decorators for Functions and Methods”
-
24
js闭包外层函数变量为什么只执行一次?代码如图,只是在加载页面的时候alert(x)执行了一次,之后再点击 Click 按钮,alert(x) 就不执行了,这是为什么呢?请帮忙看看,多谢各…
-
8
对比研究 NEAR 生态借贷项目:Burrow、Aurigami 及 BastionPANews2022-04-15热度: 29339NEAR/Aurora上的借贷项目集中上线主网,通过代币模型...
-
7
Babel Finance:美联储加息前后股市与比特币表现对比研究 • 8 小时前...
-
4
印尼电商平台Tokopedia怎么样?新老卖家收入对比研究东南亚电商资讯AMZ123旗下东南亚跨境电商新闻栏目,专注东南亚跨境电商热点资讯,为广大卖家...
-
2
Gemini Pro还不如GPT-3.5,CMU深入对比研究:保证公平透明可重复
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK