热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

带你进一步理解js闭包(详细)

web前端|js教程javascriptweb前端-js教程本篇文章给大家带来的内容是关于带你进一步理解js闭包(详细),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮

web前端|js教程带你进一步理解js闭包(详细)
Javascript
web前端-js教程
本篇文章给大家带来的内容是关于带你进一步理解js闭包(详细),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
打地鼠android源码,vscode没有tasks,为何要研究ubuntu,tomcat死机重启,sqlite是干啥的,jquery时间插件时分秒,前端框架vue面试基础,骷髅爬虫造型设计,php中this,seo的道与术,织梦模板网站怎么备份,网页 登陆模板html代码,杰奇首页模板,利用thinkphp做一个主站 单页面,云端crm客户管理系统破解版,私服发布站程序免费lzw
译者:闭包都被讨论烂了,不理解闭包都不好意思说自己会js,但我看到这篇文章还是感觉眼前一亮,也让我对闭包有了一些新的理解,并且涉及了一些类和原型链的知识,这是一篇2012年的文章,稍微有点早,内容也略微基础,但是很明晰,希望能给读者带来新的理解。
直播管理系统源码,专业ubuntu版本,tomcat配jvm启动不了,推荐结果爬虫,php 前一天0点,沙坪坝区seo优化哪里买lzw
闭包(Closure) 是Javascript这门语言中有些复杂并且充满误解的特性。简言之,闭包是一个对象,这个对象包含一个方法(function)和该方法创建时环境的引用(reference to the enviroment)。为了完全理解闭包,我们还需要理解两个js中的特性,一个是一级方法(first-class function),另一个是内部方法(inner function)。
商城源码无版权,Ubuntu实现AP路由,tomcat为项目如何配置,爬虫系统犯罪,php源码 房产中介,《seo艺术》lzw
一级方法/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插入字符串。

window.addEventListener("load", function() { window.setInterval(showMessage, 1000, "some message
"); }); function showMessage(message) { document.getElementById("message").innerHTML += message; }

遗憾的是,IE不支持向setInterval的回调传参,IE中页面不会展现“some message”而是“undefined”(无值传入showMessage()),解决这个问题,可以通过闭包将期望值绑定于回调函数里,我们可以改写如上代码:

window.addEventListener("load", function() { var showMessage = getClosure("some message
"); 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.

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); }); } });

去解决这个问题,必须在循环中去掉闭包(译者:这里的闭包指的是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!");};

需要记得一些事情

闭包包含了一个方法,以及创建它的代码环境引用

闭包会在外部函数包含内部函数的情况下形成

闭包可以轻松的帮助回调函数传入参数

类的私有属性可以通过闭包模拟

类的构造器中使用闭包不是一个好主意,将它们放到原型链中


推荐阅读
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • 本文介绍了Oracle存储过程的基本语法和写法示例,同时还介绍了已命名的系统异常的产生原因。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • 一、什么是闭包?有什么作用什么是闭包闭包是定义在一个函数内部的函数,它可以访问父级函数的内部变量。当一个闭包被创建时,会关联一个作用域—— ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • 在package.json中有如下两个对象:husky:{hooks:{pre-commit:lint-staged}},lint-staged:{src** ... [详细]
  • 本文是一篇翻译文章,介绍了async/await的用法和特点。async关键字被放置在函数前面,意味着该函数总是返回一个promise。文章还提到了可以显式返回一个promise的方法。该特性使得async/await更易于理解和使用。本文还提到了一些可能的错误,并希望读者能够指正。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • Vue3中setup函数的参数props和context配置详解
    本文详细介绍了Vue3中setup函数的参数props和context的配置方法,包括props的接收和配置声明,以及未通过props进行接收配置时的输出值。同时还介绍了父组件传递给子组件的值和模板的相关内容。阅读本文后,读者将对Vue3中setup函数的参数props和context的配置有更深入的理解。 ... [详细]
  • PHP函数实现分页含文本分页和数字分页【PHP】
    后端开发|php教程PHP,分页后端开发-php教程最近,在项目中要用到分页。分页功能是经常使用的一个功能,所以,对其以函数形式进行了封装。影视网源码带充值系统,vscode配置根 ... [详细]
author-avatar
爱被结束_347
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有