在JavaScript中,用var
申明的变量其实是有做用域的。javascript
若是一个变量在函数体内部申明,则该变量的做用域为整个函数体,在函数体外不可引用该变量:php
'use strict'; function foo() { var x = 1; x = x + 1; } x = x + 2; // ReferenceError! 没法在函数体外引用变量x
若是两个不一样的函数各自申明了同一个变量,那么该变量只在各自的函数体内起做用。换句话说,不一样函数内部的同名变量互相独立,互不影响:java
'use strict'; function foo() { var x = 1; x = x + 1; } function bar() { var x = 'A'; x = x + 'B'; }
因为JavaScript的函数能够嵌套,此时,内部函数能够访问外部函数定义的变量,反过来则不行:数组
'use strict'; function foo() { var x = 1; function bar() { var y = x + 1; // bar能够访问foo的变量x! } var z = y + 1; // ReferenceError! foo不能够访问bar的变量y! }
若是内部函数和外部函数的变量名重名:浏览器
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。若是内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。ruby
变量提高
JavaScript的函数定义有个特色,它会先扫描整个函数体的语句,把全部申明的变量“提高”到函数顶部:app
'use strict'; function foo() { var x = 'Hello, ' + y; alert(x); var y = 'Bob'; } foo();
虽然是strict模式,但语句var x = 'Hello, ' + y;
并不报错,缘由是变量y
在稍后申明了。可是alert
显示Hello, undefined
,说明变量y
的值为undefined
。这正是由于JavaScript引擎自动提高了变量y
的声明,但不会提高变量y
的赋值。dom
对于上述foo()
函数,JavaScript引擎看到的代码至关于:函数
function foo() { var y; // 提高变量y的申明 var x = 'Hello, ' + y; alert(x); y = 'Bob'; }
因为JavaScript的这一怪异的“特性”,咱们在函数内部定义变量时,请严格遵照“在函数内部首先申明全部变量”这一规则。最多见的作法是用一个var
申明函数内部用到的全部变量:ui
function foo() { var x = 1, // x初始化为1 y = x + 1, // y初始化为2 z, i; // z和i为undefined // 其余语句: for (i=0; i<100; i++) { ... } }
全局做用域
不在任何函数内定义的变量就具备全局做用域。实际上,JavaScript默认有一个全局对象window
,全局做用域的变量实际上被绑定到window
的一个属性:
'use strict'; var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript'
所以,直接访问全局变量course
和访问window.course
是彻底同样的。
你可能猜到了,因为函数定义有两种方式,以变量方式var foo = function () {}
定义的函数实际上也是一个全局变量,所以,顶层函数的定义也被视为一个全局变量,并绑定到window
对象:
'use strict'; function foo() { alert('foo'); } foo(); // 直接调用foo() window.foo(); // 经过window.foo()调用
进一步大胆地猜想,咱们每次直接调用的alert()
函数其实也是window
的一个变量:
这说明JavaScript实际上只有一个全局做用域。任何变量(函数也视为变量),若是没有在当前函数做用域中找到,就会继续往上查找,最后若是在全局做用域中也没有找到,则报ReferenceError错误。
名字空间
全局变量会绑定到window
上,不一样的JavaScript文件若是使用了相同的全局变量,或者定义了相同名字的顶层函数,都会形成命名冲突,而且很难被发现。
减小冲突的一个方法是把本身的全部变量和函数所有绑定到一个全局变量中。例如:
// 惟一的全局变量MYAPP: var MYAPP = {}; // 其余变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其余函数: MYAPP.foo = function () { return 'foo'; };
把本身的代码所有放入惟一的名字空间MYAPP
中,会大大减小全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
局部做用域
因为JavaScript的变量做用域其实是函数内部,咱们在for
循环等语句块中是没法定义具备局部做用域的变量的:
'use strict'; function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然能够引用变量i }
为了解决块级做用域,ES6引入了新的关键字let
,用let
替代var
能够申明一个块级做用域的变量:
'use strict'; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } // SyntaxError: i += 1; }
常量
因为var
和let
申明的是变量,若是要申明一个常量,在ES6以前是不行的,咱们一般用所有大写的变量来表示“这是一个常量,不要修改它的值”:
var PI = 3.14;
ES6标准引入了新的关键字const
来定义常量,const
与let
都具备块级做用域:
'use strict'; const PI = 3.14; PI = 3; // 某些浏览器不报错,可是无效果! PI; // 3.14
解构赋值
从ES6开始,JavaScript引入了解构赋值,能够同时对一组变量进行赋值。
什么是解构赋值?咱们先看看传统的作法,如何把一个数组的元素分别赋值给几个变量:
var array = ['hello', 'JavaScript', 'ES6']; var x = array[0]; var y = array[1]; var z = array[2];
如今,在ES6中,可使用解构赋值,直接对多个变量同时赋值:
注意,对数组元素进行解构赋值时,多个变量要用[...]
括起来。
若是数组自己还有嵌套,也能够经过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; x; // 'hello' y; // 'JavaScript' z; // 'ES6'
解构赋值还能够忽略某些元素:
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素 z; // 'ES6'
若是须要从一个对象中取出若干属性,也可使用解构赋值,便于快速获取对象的指定属性:
对一个对象进行解构赋值时,一样能够直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school', address: { city: 'Beijing', street: 'No.1 Road', zipcode: '100001' } }; var {name, address: {city, zip}} = person; name; // '小明' city; // 'Beijing' zip; // undefined, 由于属性名是zipcode而不是zip // 注意: address不是变量,而是为了让city和zip得到嵌套的address对象的属性: address; // Uncaught ReferenceError: address is not defined
使用解构赋值对对象属性进行赋值时,若是对应的属性不存在,变量将被赋值为undefined
,这和引用一个不存在的属性得到undefined
是一致的。若是要使用的变量名和属性名不一致,能够用下面的语法获取:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; // 把passport属性赋值给变量id: let {name, passort:id} = person; name; // '小明' id; // 'G-12345678' // 注意: passport不是变量,而是为了让变量id得到passport属性: passport; // Uncaught ReferenceError: passport is not defined
解构赋值还可使用默认值,这样就避免了不存在的属性返回undefined
的问题:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678' }; // 若是person对象没有single属性,默认赋值为true: var {name, single=true} = person; name; // '小明' single; // true
有些时候,若是变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
// 声明变量: var x, y; // 解构赋值: {x, y} = { name: '小明', x: 100, y: 200}; // 语法错误: Uncaught SyntaxError: Unexpected token =
这是由于JavaScript引擎把{
开头的语句看成了块处理,因而=
再也不合法。解决方法是用小括号括起来:
({x, y} = { name: '小明', x: 100, y: 200});
使用场景
解构赋值在不少时候能够大大简化代码。例如,交换两个变量x
和y
的值,能够这么写,再也不须要临时变量:
var x=1, y=2; [x, y] = [y, x]
快速获取当前页面的域名和路径:
var {hostname:domain, pathname:path} = location;
若是一个函数接收一个对象做为参数,那么,可使用解构直接把对象的属性绑定到变量中。例如,下面的函数能够快速建立一个Date
对象:
function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); }
它的方便之处在于传入的对象只须要year
、month
和day
这三个属性:
buildDate({ year: 2017, month: 1, day: 1 }); // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
也能够传入hour
、minute
和second
属性:
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 }); // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)
使用解构赋值能够减小代码量,可是,须要在支持ES6解构赋值特性的现代浏览器中才能正常运行。目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等