JavaScript函数及闭包
前面一片文章讲到过一点函数,了解到每声明一个函数就会产生一个作用域。而外面的作用域访问不了里面的作用域(把里面的变量和函数隐藏起来),而里面的可以访问到外面的。对于隐藏变量和函数是一个非常有用的技术。
基于作用域隐藏的方法叫做最小授权或最小暴露原则。
这个原则是指在软件设计中,应该最小限度的暴露必要内容,而将其内容都隐藏起来,比如某个模块或对象得API设计。隐藏变量和函数可以解决同名标识符的之间的冲突,冲突会导致变量的意外覆盖。
例如:
var a = 2;
function foo(){
var a = 3;
console.log(a);
}
foo();
console.log(a);
虽然这种技术可以解决一些问题,但是他并不理想,会导致一些额外的问题,首先必须声明一个具名函数foo(),意味着foo这个名称本身“污染”了所在的作用域,其次必须显式的通过函数名foo()调用这个函数才能运行其中的代码。
如果函数不需要函数名,并且能够自动运行,这会更加理想。幸好js提供了同时解决这两个问题的方案 -- (IIFE) Immediately Invoked Function Expression -- 立即执行函数
var a = 2;
(function foo(){
var a = 3;
console.log(a);
})()
console.log(a);
首先立即执行函数不会当做函数声明处理而是当做函数表达式处理。
区分函数声明还是函数表达式:看function在声明中是不是第一个词,如果是第一个词就是函数声明否则就是函数表达式。而立即执行函数" (function ",不是" function ",所以是函数表达式。
函数声明和函数表达式之间最重要的区别是他们的名称标识符将会绑定在何处
函数声明的函名称数会绑定在当前作用域内。假如在全局作用域创建一个函数声明,就可以在全局作用域访问这个函数名称并执行。而函数表达式的函数名称会绑定在自身的函数中,而不是当前说在作用域中。例如你全局创建一个函数表达式,如果你直接执行这个你创建的函数表达式的函数名就会报错,因为当前作用域下没有这个标识符,而你在函数表达式里面的作用域里访问这个函数名就会返回这个函数的引用。
作用域闭包,嗯,闭包这儿两个字就有点让人难以理解,(可以想象成一个包是关上的,里面隐藏了一些神秘的东西)而对于闭包的定义是这样说的:当函数可以记住并访问所在的作用域时,就产生了闭包,即使函数是在当前作用域之外执行。
for instance(拽个英文,哈哈)。
function foo() {
var a = 2;
function bar() {
console.log(a);
}
bar();
}
foo();
上面的 代码bar()可以访问外部作用域中的变量。根据上面的定义这是闭包吗?从技术来讲也许是,但我们理解的是作用域在当前作用域查找变量如果没找到会继续向上面查找,找到返回,找不到继续找,直到全局作用域。-- 而这些正是闭包的一部分。函数bar()具有一个涵盖foo()作用域的闭包。
function foo(){
var a = 2;
function bar (){
console.log(a);
}
return bar;
}
var baz = foo();
baz();
在上面的代码更好的展示了闭包。
bar()函数在定义时作用域以外的地方执行(此时在全局作用域执行)。在foo()函数执行后,通常会期待foo()整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不在使用的内存空间,由于foo()已经执行完,看上去内容不会再被使用,所以很自然的会考虑对齐进行回收,回收后意味着里面的函数和变量访问不到了。foo()执行完,baz变量存着bar函数的引用。当执行baz也就是bar函数时。console.log(a)。不理解闭包的人可能认为会报错,事实上,打印的是2;???what?
foo()函数作用域不是执行完销毁了吗?怎么还能访问到a变量?-- 这就是闭包。
当foo()执行后,bar函数被返回全局作用域下,但是bar函数还保留着当时的词法作用域(当时写代码是的顺序就已经定义了作用域,这个作用域叫词法作用域--外面函数套着里面的函数的那种)甚至直到全局作用域。所以bar还留有foo()函数的引用。使得foo()函数没有被回收。
闭包可以说不出不在,只是你没有发现认出他。在定时器,事件监听器,ajax请求,跨窗口通信或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是使用闭包。
for instance
function wait(message) {
setTimeout(function timer() {
console.log(message);
}, 1000);
}
wait("hello");
在上面的代码中将一个内部函数(名为timer)传递给setTimerout(...).timer具有涵盖wait(...)的作用域的闭包。因此还保有对变量message的引用。wait()执行1000毫秒后,它的内部作用域不会消失,timer函数依然保有wait()作用域的闭包。
而闭包和立即执行函数息息相关。
循环和闭包
for(var i = 1; i <= 5; i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}
上面代码我们以为输出的会是1-5,可事实上输出的是5个6,这是为啥啊 -- 闭包啊。
延迟函数的回调会在循环结束时执行。事实上,当定时器运行时即使每个迭代的是setTimerout(...,0),所有的回调函数依然是循环结束后才会执行。我猜是跟js执行机制有关系吧。至于为什么都是6. 因为即使5个函数是在各个迭代中分别定义的,但是他们又被封闭在一个共享的全局作用域中因此实际上只有一个i.而怎么解决呢,立即执行函数来了!!!
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i) }
打印出来1,2,3,4,5了欧,这回是你想要的数了。解释一下,5次循环创建了5个立即执行函数,这5个函数的作用域都不相同,立即函数接收的参数是当前循环的i.所以当timer执行时访问的就是自己立即执行函数对应的作用域。也就是说5个timer函数分别对应5个作用域,每个作用域保存的变量i都不同,解决啦!!!
你懂闭包了吗?
js执行机制
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。
哪些语句会放入异步任务队列及放入时机一般来说,有以下四种会放入异步任务队列:setTimeout 和 setlnterval ,DOM事件,ES6中的Promise,Ajax异步请求
JavaScript函数及闭包的更多相关文章
- JavaScript函数、闭包、原型、面向对象
JavaScript函数.闭包.原型.面向对象 断言 单元测试框架的核心是断言方法,通常叫assert(). 该方法通常接收一个值--需要断言的值,以及一个表示该断言目的的描述. 如果该值执行的结果为 ...
- javaScript函数与闭包
js中函数也是对象,具有一切对象的特征,可以作为表达式给变量赋值,可以作为函数的形参,或者函数的返回值,函数内可以嵌套函数等等,函数内以声明方式定义的函数是局部函数,用表达式声明的函数则由赋值变量的性 ...
- JavaScript 函数之 ------------------ 闭包
谈到闭包,人们常常会把匿名函数和闭包混淆在一起.闭包是指由权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数内部创建另一个函数,仍以前面的 createComparisonFun ...
- javascript函数闭包(closure)
一,首先感受下javascript函数的闭包 二,闭包 1,定义:闭包就是能够读取其他函数内部变量的函数,由于在javascript语言中,只有在函数内部的子函数才能够读取局部变量,因此可以把闭包简单 ...
- JavaScript函数之作用域 / 作用链域 / 预解析
关于作用域和作用链域的问题,很多文章讲的都很详细,本文属于摘录自己觉得对自己有价值的部分,留由后用,仅供参考,需要查看详细信息请点击我给出的原文链接查看原文件 做一个有爱的搬运工~~ -------- ...
- JavaScript权威设计--命名空间,函数,闭包(简要学习笔记十二)
1.作为命名空间的函数 有时候我们需要声明很多变量.这样的变量会污染全局变量并且可能与别人声明的变量产生冲突. 这时.解决办法是将代码放入一个函数中,然后调用这个函数.这样全局变量就变成了 局部变量. ...
- 深入理解javascript函数参数与闭包(一)
在看此文章,希望先阅读关于函数基础内容 函数定义与函数作用域 的章节,因为这篇文章或多或少会涉及函数基础的内容,而基础内容,我放在函数定义函数作用域 章节. 本文直接赘述函数参数与闭包,若涉及相关知识 ...
- 深入理解javascript原型和闭包(2)——函数和对象的关系
上文(理解javascript原型和作用域系列(1)——一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; co ...
- JavaScript中的闭包和匿名函数
JavaScript中的匿名函数及函数的闭包 1.匿名函数 2.闭包 3.举例 4.注意 1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没 ...
随机推荐
- sqlite查询问题,由字母大小写敏感引起
目前做的项目之前是用mysql,这是个错误的选择,因为本身并不是服务器-客户端模式的项目,而是一个CS架构项目,mysql这种需要较繁复配置的数据库并不合适.需要的应该是sqlite这类,内嵌的数据库 ...
- 仿win7窗体自动顶部最大化左侧右侧半屏效果(改写nativeEvent,使用AdjustWindowRectEx)
#include "HMainWindow.h" #include <QApplication> #ifdef Q_OS_WIN #include <qt_win ...
- Qt 使用 Google Breakpad 捕获程序崩溃报告(dump文件) good
http://blog.csdn.net/GoForwardToStep/article/details/56685810
- C++ Builder 控件的卸载
控件卸载: 1.选择 BCB 菜单 File→Close All (关闭所有文件) 选择BCB 菜单: Project→Options→Packages 在 ...
- kafka笔记6
我们讨论可靠性时,一般使用保证这个词,它是确保系统在各种不同的环境下能够发生一致的行为.Kafka可以在哪些方面作出保证呢? 1.Kafka可以保证分区消息的顺序 2.只有消息被写入分区的所有同步副本 ...
- Python自学day-9
一.paramiko模块 (第三方模块,需安装依赖包) paramiko模块基于SSH来连接远程主机并进行相关操作. SSHClient:用于连接远程主机并执行基本命令. import paramik ...
- 【简易bat脚本】启动java程序
前置条件:path中添加了JAVAHOME配置了java环境变量 1.新建txt文本文件 2.粘贴以下内容 @echo off set path=%path%;.;java -classpath &q ...
- tar命令压缩和解压
https://www.cnblogs.com/jyaray/archive/2011/04/30/2033362.html tar命令详解 -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向 ...
- Python 爬虫从入门到进阶之路(十二)
之前的文章我们介绍了 re 模块和 lxml 模块来做爬虫,本章我们再来看一个 bs4 模块来做爬虫. 和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也 ...
- Java上机题(封装)(编写student类)
今天帮大一的童鞋写Java上机题 题目虽然很简单,但是刚拿到题目的时候愣了一下,然后就疯狂get set QuQ 其实这是一个特别基本的封装的题目(之前实验室面试大二的时候竟然还有蛮多人不知道封装的概 ...