大名鼎鼎的做用域和闭包,面试常常会问到。闭包(closure)是Javascript语言的一个难点,也是它的特点。面试
理解闭包,先理解函数的执行过程。闭包
代码在执行的过程当中会有一个预解析的过程,也就是在代码的执行过程当中,会先将代码读取到内存中,检查其是否有错误,而后将全部声明在此进行标记,让js解析器知道有这样的一个名字,后面使用时便不会出现未定义的错误,这个标记的过程就是提高。dom
代码演示:函数
var num = 10; function foo(){ console.log(num); } foo();
预解析的过程(变量提高,函数提高):性能
var num; function foo(){ console.log(num) } num = 10; foo();
代码执行时,首先会执行 num = 10; 而后执行foo(),进行函数体,打印出num的值。此时的num访问到的是全局中定义的num,因此num的值为10.代理
以上代码涉及到做用域的问题,所谓的域,表示的是范围,因此做用域表示的是做用范围,也就是一个名字在什么地方可使用,在什么地方不可以使用。code
在js中,采用的是词法做用域,词法做用域是指在编写代码的过程当中体现出来的做用范围,一旦代码写好了,不用执行,做用范围就肯定好了。对象
Javascript的做用域无非就是两种:全局变量和局部变量。ip
只有函数能够构成做用域结构。只要存在代码,就至少有一个做用域,即全局做用域。凡是代码有函数,那么这个函数就构成一个做用域,若是函数中还有函数,那么在这个做用域中就又诞生一个做用域,那么将这样的全部做用域列出来,就能够有一个:函数内指向函数外的链式结构。内存
做用域链变量访问规则:看变量在当前做用域中,是否有变量的定义与赋值,若是有,则直接使用;若是没有,则到外面的做用域中查看,若是有,则中止查找,使用外面一层做用域中定义的变量或值,若是没有,则继续往外查找,直到最外层的全局,若是全局也没有定义,则会报错: xx is not defined。
闭包,是一个具备封闭功能与包裹功能的一个结构或空间。在js中,函数能够构成闭包。由于函数在当前的做用域中是一个封闭的结构,具备封闭性;同时根据做用域规则,只容许函数内部访问外部的数据,而外部没法访问函数内部的数据,即函数具备封闭的对外不公开的特性,就像把一个东西包裹起来同样,所以函数能够构成闭包。
有点难理解,简单来讲,就是可以读取其余函数内部变量的函数,再简洁一点就是:定义在一个函数内部的函数。
由于闭包不容许外界直接访问,因此只能间接访问函数内部的数据,得到函数内部数据的使用权。
function foo(){ var num = 123; function func(){ return num; } return func; } var f = foo(); var res1 = f(); var res2 = f(); // 此时,foo只调用了一次,不会再内存中从新建立一个函数,而经过f,能够访问并获取num的值,那么调用f,便可得到同样的num值 改良: function foo(){ var num = 123; return function(){ return num; } } var f = foo(); var res1 = f(); var res2 = f(); 再改良: var f = (function foo(){ var num = 123; return function (){ return num; } })(); var res1 = f(); var res2 = f();
function func(){ var num1 = Math.random(); var num2 = Math.random(); return { num1: function(){ return num1; }, num2: function(){ return num2; } } } var p = func(); console.log(p.num1()); console.log(p.num1());// 这两个访问到的是同一个随机数
如上面代码演示的那样,闭包能够经过返回函数来间接访问到函数内的数据,这样,闭包能够实现具备私有访问空间的函数,保护私有的数据。另外一方面,能够帮助其余对象读取到函数内部的变量
函数定义的变量会在函数执行结束后自动回收,可是由于闭包结构引出的数据常常会被外界所引用,这些数据将不会被回收,所以过多的闭包会消耗内存资源,影响性能。因此要谨慎使用闭包,能够在使用闭包时,若是再也不使用某些变量了,必定要赋值一个null。
在ES6中,提出来对象代理概念,在代理层操做数据而不是直接操做原数据。