带你进一步理解js闭包(详细)
本篇文章给大家带来的内容是关于带你进一步理解js闭包(详细),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
译者:闭包都被讨论烂了,不理解闭包都不好意思说自己会js,但我看到这篇文章还是感觉眼前一亮,也让我对闭包有了一些新的理解,并且涉及了一些类和原型链的知识,这是一篇2012年的文章,稍微有点早,内容也略微基础,但是很明晰,希望能给读者带来新的理解。
闭包(Closure) 是javascript这门语言中有些复杂并且充满误解的特性。简言之,闭包是一个对象,这个对象包含一个方法(function)和该方法创建时环境的引用(reference to the enviroment)。为了完全理解闭包,我们还需要理解两个js中的特性,一个是一级方法(first-class function),另一个是内部方法(inner function)。
一级方法/First-Class Functions
在js中,方法是头等公民,因为它可以被轻易转换成其他数据类型。比如,一级方法可以实时构建并且赋值给一个变量。也可以传递给其他方法,或者通过其他方法返回。除了满足这些标准以外,方法也拥有自己的属性和方法。
通过下述例子,我们来看一下一级方法的能力。
var foo = function() { alert("Hello World!"); }; var bar = function(arg) { return arg; }; bar(foo)();
内部方法/Inner Functions
内部方法或者说嵌套方法,是指定义在其他方法内部的方法,每当外部方法被唤起,内部方法的实例就被创建。下面的例子反应内部方法的使用,add方法是外部方法,doAdd是内部方法。
function add(value1, value2) { function doAdd(operand1, operand2) { return operand1 + operand2; } return doAdd(value1, value2); } var foo = add(1, 2); // foo equals 3
这个例子中,一个重要的特性是,内部方法获取到了外部方法的作用域,这意味着内部方法能够使用外部方法的变量,参数等。例子中add()的参数value1,value2传递给doAdd()的operand1,operand2参数。然而这并没有必要,因为doAdd可以直接获取value1,value2。所以上面的例子我们还可以这么写:
function add(value1, value2) { function doAdd() { return value1 + value2; } return doAdd(); } var foo = add(1, 2); // foo equals 3
创建闭包/Creating Closures
内部方法获取外部方法的作用域,便形成了一个闭包。典型的场景是外部函数将其内部方法返回,内部方法保持了外部环境的引用,并保存了作用域下的所有变量。
一下例子展示闭包如何创建并使用。
function add(value1) { return function doAdd(value2) { return value1 + value2; }; } var increment = add(1); var foo = increment(2); // foo equals 3
说明:
add返回了内部方法doAdd,doAdd调用了add的参数,闭包创建。
value1是add方法的本地变量,对doAdd来说是非本地变量(非本地变量指变量既不在函数体本身,也不在全局),value2是doAdd的本地变量。
当add(1)被调用,一个闭包被创建并储存在increment中,在该闭包的引用环境中,value1绑定了1,被绑定的1相当于“封锁”在这个函数中,这也是“闭包”这个名字的由来。
当increment(2)被调用,进入闭包函数,这意味着携带着value1为1的doAdd被调用,因此该闭包本质上可以当做如下函数:
function increment(value2) { return 1 + value2; }
何时使用闭包?
闭包可以实现很多功能。比如将回调函数绑定指定参数。我们说两个让你的生活和开发变得更简单的场景。
配合定时器
闭包结合setTimeout和setInterval非常有用,闭包允许你向回调函数传入指定参数,比如下面的例子,每秒钟在给指定dom插入字符串。
<!DOCTYPE html> <html lang="en"> <head> <title>Closures</title> <meta charset="UTF-8" /> <script> window.addEventListener("load", function() { window.setInterval(showMessage, 1000, "some message<br />"); }); function showMessage(message) { document.getElementById("message").innerHTML += message; } </script> </head> <body> <span id="message"></span> </body> </html>
遗憾的是,IE不支持向setInterval的回调传参,IE中页面不会展现“some message”而是“undefined”(无值传入showMessage()),解决这个问题,可以通过闭包将期望值绑定于回调函数里,我们可以改写如上代码:
window.addEventListener("load", function() { var showMessage = getClosure("some message<br />"); window.setInterval(showMessage, 1000); }); function getClosure(message) { function showMessage() { document.getElementById("message").innerHTML += message; } return showMessage; }
2.模拟私有属性
绝大多数面向对象的程序语言支持对象的私有属性,然而js不是纯正的面向对象的语言,因此也没有私有属性的概念。不过,我们可以通过闭包来模拟私有属性。回想一下,闭包包含了一份其创建环境的引用,这份引用已经不在当前作用域中了,因此这份引用只能在闭包中访问,这本质上就是私有属性。
看如下例子(译者:省略对代码的文字描述):
function Person(name) { this._name = name; this.getName = function() { return this._name; }; }
这里有一个严重的问题,因为js不支持私有属性,所以我们没法阻止别人修改实例的name字段,比如我们创建一个Person实例叫Colin,然后可以将他的名字改成Tom。
var person = new Person("Colin"); person._name = "Tom"; // person.getName() now returns "Tom"
没有人愿意不经同意就被别人改名字,为了阻止这种情况的发生,通过闭包让_name字段变成私有。看如下代码,注意这里的_name是Person构造器的本地变量,而不是对象的属性,闭包形成了,因为外层方法Person对外暴露了一个内部方法getName。
function Person(name) { var _name = name;// 注:区别在这里 this.getName = function() { return _name; }; }
现在,当getName被调用,能够保证返回的是最初传入类构造器的值。我们依然可以为对象添加新的_name属性,但这并不影响闭包getName最初绑定的值,下面的代码证明,_name字段,事实私有。
var person = new Person("Colin"); person._name = "Tom"; // person._name is "Tom" but person.getName() returns "Colin"
什么时候不要用闭包?
正确理解闭包如何工作何时使用非常重要,而理解什么时候不应该用它也同样重要。过度使用闭包会导致脚本执行变慢并消耗额外内存。由于闭包太容易创建了,所以很容易发生你都不知道怎么回事,就已经创建了闭包的情况。本节我们说几种场景要注意避免闭包的产生。
1.循环中
循环中创建出闭包会导致结果异常。下例中,页面上有三个按钮,分别点击弹出不同的话术。然而实际运行,所有的按钮都弹出button4的话术,这是因为,当按钮被点击时,循环已经执行完毕,而循环中的变量i也已经变成了最终值4.
<!DOCTYPE html> <html lang="en"> <head> <title>Closures</title> <meta charset="UTF-8" /> <script> window.addEventListener("load", function() { for (var i = 1; i < 4; i++) { var button = document.getElementById("button" + i); button.addEventListener("click", function() { alert("Clicked button " + i); }); } }); </script> </head> <body> <input type="button" id="button1" value="One" /> <input type="button" id="button2" value="Two" /> <input type="button" id="button3" value="Three" /> </body> </html>
去解决这个问题,必须在循环中去掉闭包(译者:这里的闭包指的是click事件回调函数绑定了外层引用i),我们可以通过调用一个引用新环境的函数来解决。下面的代码中,循环中的变量传递给getHandler函数,getHandler返回一个闭包(译者:这个闭包指的是getHandler返回的内部方法绑定传入的i参数),独立于原来的for循环。
function getHandler(i) { return function handler() { alert("Clicked button " + i); }; } window.addEventListener("load", function() { for (var i = 1; i < 4; i++) { var button = document.getElementById("button" + i); button.addEventListener("click", getHandler(i)); } });
2.构造函数里的非必要使用
类的构造函数里,也是经常会产生闭包的错误使用。我们已经知道如何通过闭包设置类的私有属性,而如果当一个方法不需要调用私有属性,则造成的闭包是浪费的。下面的例子中,Person类增加了sayHello方法,但是它没有使用私有属性。
function Person(name) { var _name = name; this.getName = function() { return _name; }; this.sayHello = function() { alert("Hello!"); }; }
每当Person被实例化,创建sayHello都要消耗时间,想象一下有大量的Person被实例化。更好的实践是将sayHello放入Person的原型链里(prototype),原型链里的方法,会被所有的实例化对象共享,因此节省了为每个实例化对象去创建一个闭包(译者:指sayHello),所以我们有必要做如下修改:
function Person(name) { var _name = name; this.getName = function() { return _name; }; } Person.prototype.sayHello = function() { alert("Hello!"); };
需要记得一些事情
闭包包含了一个方法,以及创建它的代码环境引用
闭包会在外部函数包含内部函数的情况下形成
闭包可以轻松的帮助回调函数传入参数
类的私有属性可以通过闭包模拟
类的构造器中使用闭包不是一个好主意,将它们放到原型链中
以上是带你进一步理解js闭包(详细)的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

用法:在JavaScript中,insertBefore()方法用于在DOM树中插入一个新的节点。这个方法需要两个参数:要插入的新节点和参考节点(即新节点将要被插入的位置的节点)。

JavaScript是一种广泛应用于Web开发的编程语言,而WebSocket则是一种用于实时通信的网络协议。结合二者的强大功能,我们可以打造一个高效的实时图像处理系统。本文将介绍如何利用JavaScript和WebSocket来实现这个系统,并提供具体的代码示例。首先,我们需要明确实时图像处理系统的需求和目标。假设我们有一个摄像头设备,可以采集实时的图像数
