JavaScript 变量作用域与解构赋值
变量作用域与解构赋值
在JavaScript中,用var申明的变量实际上是有作用域的。
如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:
![](https://img.haomeiwen.com/i16375643/0be8ad1c215b4897.png)
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。(不同函数内部的同名变量互相独立,互不影响)
function foo() {
var x = 1;
x = x + 1;
}
function bar() {
var x = 'A';
x = x + 'B';
}
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
![](https://img.haomeiwen.com/i16375643/5185528370a03f03.png)
变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
![](https://img.haomeiwen.com/i16375643/c8a3138e5de69f21.png)
console.log显示Hello, undefined,说明变量y的值为undefined。这是因为JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。
上述foo()函数,JavaScript引擎看到的代码相当于:
![](https://img.haomeiwen.com/i16375643/5f5eb93e84530395.png)
由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量:
![](https://img.haomeiwen.com/i16375643/e49b6bdbc1b3e5d7.png)
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:
![](https://img.haomeiwen.com/i16375643/18054f5586310125.png)
因此,直接访问全局变量course和访问window.course是完全一样的。
由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:
![](https://img.haomeiwen.com/i16375643/12d30ad3c7334935.png)
名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
![](https://img.haomeiwen.com/i16375643/e52a1adc3b5454bc.png)
把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
局部作用域
由于JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的:
![](https://img.haomeiwen.com/i16375643/3ff2bdf3647cf40d.png)
为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:
![](https://img.haomeiwen.com/i16375643/8e80af8a1a2ed30e.png)
常量
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
varPI =3.14;
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
![](https://img.haomeiwen.com/i16375643/c679f71996ececff.png)
解构赋值
什么是解构赋值?
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
传统的做法,把一个数组的元素分别赋值给几个变量:
![](https://img.haomeiwen.com/i16375643/783009e9dfc15484.png)
使用解构赋值,直接对多个变量同时赋值:
![](https://img.haomeiwen.com/i16375643/191ad0c500eeebf0.png)
对数组元素进行解构赋值时,多个变量要用[...]括起来。
![](https://img.haomeiwen.com/i16375643/9322acaa4cf8327b.png)
数组本身还有嵌套,变量也要进行嵌套,注意嵌套层次和位置要保持一致:
![](https://img.haomeiwen.com/i16375643/15d9d1e14699b5d9.png)
解构赋值还可以忽略某些元素:
![](https://img.haomeiwen.com/i16375643/ae04a63052b4d583.png)
要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:
![](https://img.haomeiwen.com/i16375643/d6885408bcea8ada.png)
对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的。
使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为undefined,这和引用一个不存在的属性获得undefined是一致的。
如果要使用的变量名和属性名不一致,可以使用赋值的方法:
![](https://img.haomeiwen.com/i16375643/21498d717991f0c3.png)
解构赋值还可以使用默认值,避免了不存在的属性返回undefined
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
![](https://img.haomeiwen.com/i16375643/5444a64975b70f30.png)
这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:
![](https://img.haomeiwen.com/i16375643/bcdf28feae6b53b7.png)
使用场景
解构赋值在很多时候可以大大简化代码.
交换两个变量x和y的值,不需要临时变量
![](https://img.haomeiwen.com/i16375643/142c1c0e48300e97.png)
快速获取当前页面的域名和路径:
![](https://img.haomeiwen.com/i16375643/175e948ea1f8f7f1.png)
如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date对象:
它的方便之处在于传入的对象只需要year、month和day这三个属性
![](https://img.haomeiwen.com/i16375643/2ca75cddb1b7a913.png)
也可以传入hour、minute和second属性.
使用解构赋值可以减少代码量,但是,需要在支持ES6解构赋值特性的现代浏览器中才能正常运行。目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等。