理解作用域与闭包的问题是理解this指向的基本阶段。
执行上下文
概念定义:当前 JavaScript 代码被解析和执行时所在环境的抽象概念
函数指定参数(若未传入,初始化该参数值为 undefined)
函数声明 (若发生命名冲突,会覆盖之前的函数参数)
变量声明 (初始化变量值为 undefined,若发生命名冲突,会忽略)
作用域
作用域存在着嵌套关系,对某段代码来说,可能存在多个同名的标识符,其作用域都覆盖了这段代码,这时,被适用的将是作用域范围最小(即离代码最近)的那个标识符。
作用域分为词法域与动态域。
javascript采用的是词法作用域,也就是静态作用域。
-
静态作用域
标识符的作用范围是由标识符的声明位置和程序的结构决定的,也就是说某段代码所参照的标识符是在哪定义的,通过对程序代码进行词法解析即可确定,标识符的作用域是静态不变的。即静态作用域是在函数定义的时候决定的。 -
动态作用域
与静态作用域相对。
标识符的作用范围是由程序运行时函数的调用情况决定的,也就是说某段代码所参照的标识符是在哪定义的,需在程序运行时根据执行栈才能确定,标识符的作用域可能会是动态变化的。即动态作用域是在函数被调用的时候决定的。
看下例子:
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
因为js采用的是静态作用域,所以上述打印结果是1。 foo方法内部没有定义value,在作用域的限制下,会向上寻找定义好的变量,这里就是全局定义的value = 1
。
如果js采用的是动态作用域,上述打印结果就是2。foo方法内部没有定义,但是在调用的时候已经能拿到bar方法内部定义的value = 2
。
那么,动态作用域的语言一般很少。
作用域的类型有这样几种:
- 全局作用域 => 变量是全局变量,所有程序都能访问
在浏览器中,我们的全局作用域就是 window。因此在浏览器中,所有的全局变量和函数都是作为 window 对象的属性和方法创建的。 - 文件作用域 => 与全局作用域类似,但变量只能是同一个源文件模块里的程序才能访问。比如C/C++。
- 函数作用域 => 一个局部作用域,变量在函数内声明,只在函数内部可见。大部分语言都支持函数作用域。
用var声明的变量都具有函数作用域。不管在函数那个位置声明的,只要是函数内部就可以访问到。 - 闭包作用域 => 一种让函数的代码能够访问函数声明(函数对象被创建)时作用域内(上下文环境)的变量机制。闭包在函数式语言中非常普遍。
闭包是将函数定义时的局部作用域环境保存起来后生成的一个实体。
闭包实现了一个作用域,函数始终是运行在它们被定义的闭包作用域里,而不是它们被调用的作用域里。
闭包可以嵌套,全局作用域→闭包(0…n)作用域→函数作用域→代码块(0…n)作用域就整个的形成了一个代码执行时的作用域链。 - 代码块作用域 => 一个局部作用域,只在声明变量的代码块内部可见。
ECMAScript6 之后,函数内用 let 声明的变量和 const 声明的常量都属于代码块作用域。比如C/C++/Java… - 静态局部作用域 =>
局部作用域一般只在某个特定的代码片段内可访问到,JavaScript 中的局部作用域分为函数作用域和代码块作用域两类,其中代码块作用域在 ECMAScript6 之前不被支持。
如果是自由变量,取值应该去哪里取呢? 要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记!
作用域链
举例子说明:当前调用了一个之前定义的函数fn(里面了自由变量x),那么fn中的自由变量x就要到定义fn的作用域中去取,如果没有就跨到上一个上下文作用域中取,一步步跨作用域,直到跨到全局作用域中取值,如果没有找到定义就是真的没有了。这个一步步“跨”的路线,就是作用域链。
闭包
闭包就是函数套函数,即有权访问另一个作用域的函数。
创建闭包的方式就是在一个函数的内部,创建另外一个函数。那么,当外部函数被调用的时候,内部函数也就随着创建,这样就形成了闭包。
闭包容易造成的问题:
- 变量污染:
因为外部作用域链上的变量是内部定义函数的自由变量,是一个静态变量,如果内部函数改变了自由变量的值,就会影响到外部作用域链上的变量值,那么其他函数引用到这个变量就是更改后的变量值。这样就造成了变量污染的问题。
拯救方法:匿名函数传参的形式,形成一个独立的函数作用域,不影响外部作用域链上的自由变量。
- 内存泄漏:
俗称内存空间不足,变量或者方法使用的内存空间过大或得不到释放。 是内存溢出的一种原因。
借用别人的例子:
内存泄漏是申请的空间没有及时释放或者干脆丢了指针没法释放.不是泄漏,是漏不出来;只是泄漏的内存远小于可分配的内存时影响不大,多了就玩完.
内存溢出就是申请的内存超过了可用内存,内存不够用了,比如申请了10m的内存,但是一共只有5m,申请不了,就溢出了.
内存泄漏堆积形成内存溢出。