前面一片文章讲到过一点函数,了解到每声明一个函数就会产生一个做用域。而外面的做用域访问不了里面的做用域(把里面的变量和函数隐藏起来),而里面的能够访问到外面的。对于隐藏变量和函数是一个很是有用的技术。html
基于做用域隐藏的方法叫作最小受权或最小暴露原则。ajax
这个原则是指在软件设计中,应该最小限度的暴露必要内容,而将其内容都隐藏起来,好比某个模块或对象得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 -- 当即执行函数
async
var a = 2; (function foo(){ var a = 3; console.log(a); })() console.log(a);
首先当即执行函数不会当作函数声明处理而是当作函数表达式处理。 函数
区分函数声明仍是函数表达式:看function在声明中是否是第一个词,若是是第一个词就是函数声明不然就是函数表达式。而当即执行函数" (function ",不是" function ",因此是函数表达式。oop
函数声明和函数表达式之间最重要的区别是他们的名称标识符将会绑定在何处spa
函数声明的函名称数会绑定在当前做用域内。假如在全局做用域建立一个函数声明,就能够在全局做用域访问这个函数名称并执行。而函数表达式的函数名称会绑定在自身的函数中,而不是当前说在做用域中。例如你全局建立一个函数表达式,若是你直接执行这个你建立的函数表达式的函数名就会报错,由于当前做用域下没有这个标识符,而你在函数表达式里面的做用域里访问这个函数名就会返回这个函数的引用。线程
做用域闭包,嗯,闭包这儿两个字就有点让人难以理解,(能够想象成一个包是关上的,里面隐藏了一些神秘的东西)而对于闭包的定义是这样说的:当函数能够记住并访问所在的做用域时,就产生了闭包,即便函数是在当前做用域以外执行。
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异步请求
原文出处:https://www.cnblogs.com/jiaobaba/p/11218624.html