Javascript中使用一个变量以前须要先声明,咱们可使用var、let、const来声明一个变量。若是在给声明的变量指定初始值,就是初始化。如:javascript
var a = 1; let b =1; const c = '123'
初始化后,这个变量就在内存中分配了空间,后面的程序就能够调用了。var、let、const均可以声明变量,可是各自又不相同。在ES6中,const 定义的变量是必需要初始化赋值,并且之后不能变动, 是一个固定值。而像var,let是能够只声明,可是不进行初始化。java
var a; let b; console.log(a) // => undefined console.log(b) // => undefined
而在咱们熟知java代码中,这样只声明,可是未赋值是会报错的面试
int a; System.out.print(a); // The local variable a may not have been initialized
这是由于javascript的执行引擎(例如V8)会在运行这段代码以前检查会这段代码, 也被称为预编译。发现有var和let声明的变量,会给这个变量提供一个默认的初始化值undefined。也正是由于要检查代码,因此像const所声明的变量必须初始化、let,const所声明的变量不能重复声明。都是在检查代码时候,抛出错误。浏览器
const 必须初始化函数
console.log('hello world') const a // a 必须初始化,若是不初始化将会报 Missing initializer in const declaration
这段代码在执行的时候,没有先打印"hello world", 而是直接报错。我这里理解是代码检查的时候,就已经把错误抛出来,程序没有运行.lua
let声明后的变量不能重复声明spa
let b = 1; console.log('hello world'); let b =2;
这段代码在执行的时候,没有先打印"hello world", 而是直接报错。我这里理解是代码检查的时候,就已经把错误抛出来,程序没有运行.code
一个变量的做用域是源程序代码中定义这个变量的区域。blog
全局做用域:在最顶层声明的变量成为全局变量,全局变量拥有全局做用域,它们在程序的任何地方都是可以被访问到。ip
函数做用域:在函数中声明的变量只能在函数中被访问到,函数外面是访问不到的。
块级做用域:任何一对花括号({和})中的语句集都属于一个块,在这之中定义的全部变量在代码块外都是不可见的,咱们称之为块级做用域。
做用域的主要做用是可以控制变量是使用范围。
在Javascript中,var声明的变量使用的是函数做用域,而let声明的变量使用的块级做用域。ES5中已经存在var来声明变量,那为啥到ES6还要使用let来声明变量?我我的以为是可以更好的规范咱们程序代码,避免出现出现一些咱们所没法预料的错误。
经典例子以下:
var arr = []; for(var i = 0; i < 10; i++) { var j = i; arr[i] = function() { console.log(j) } } arr[0](); // =>9 而咱们预想的是0
而使用let就能避免出现这样的问题
var arr = []; for(var i = 0; i < 10; i++) { let j = i; arr[i] = function() { console.log(j) } } arr[0]() // =>0
这是由于let声明的变量属于块级做用域,上面let所声明的变量j是只能在for循环每个{}中能被访问。大体流程以下
当 i = 0,大体生成这样一个块
{ let j = 0; arr[i] = function() { console.log(j) } }
当i = 1
{ let j = 1; arr[i] = function() { console.log(j) } }
. . . . . .
当i = 9
{ let j = 10; arr[i] = function() { console.log(j) } }
当后面去调用arr[0]()这个方法, 这是第一个块定义的方法, 由于let声明的变量j是块{}做用域, 这个方法就只能访问第一个块{}中j的值,也就是0;
而当咱们使用var的时候
当 i = 0
{ var j = 0; arr[i] = function() { console.log(j) } }
当 i = 1
{ var j = 1; arr[i] = function() { console.log(j) } }
当咱们调用arr[0]()这个方法, 是第一个块定义的方法,由于var声明的变量是函数做用域,
因此上面的程序,能够理解为
var j; // var 声明的变量具备声明提早的做用 var arr = []; { j = 0; arr[i] = function() { console.log(j) } } { j = 1; arr[i] = function() { console.log(j) } } **. . . . . .** { j = 9; arr[i] = function() { console.log(j) } } arr[0](); // =>9
arr[0]()运行的时候,因为没有做用域的限制,j已经被赋值为9。因此输出结果就是9
有人会疑问,为啥不能直接使用i,而是使用中间变量j去替换?
这里是为了让你们更好的理解,下面直接使用i
var arr = []; for(var i = 0 ; i < 10; i++) { arr[i] = function() { console.log(i); } } arr[0](); // => 10
var arr = []; for(let i = 0 ; i < 10; i++) { arr[i] = function() { console.log(i); } } arr[0](); // => 0 这是正常的
这样也是正常的符合预期,可是当我把代码作了修改后
var arr = []; let i; for(i = 0 ; i < 10; i++) { arr[i] = function() { console.log(i); } } arr[0](); // => 10
这里也是let声明的变量i,可是输出的结果是10,这里我看完后很不理解。因而去阅读了ES的规范 http://www.ecma-international...
这里是被认为第一种状况。let声明的i和var声明i在上面的代码执行的逻辑是同样的,就致使上面的情形。这里小伙伴们须要特别注意。
先从一个简单的示例开始吧
执行代码:
console.log(a);
浏览器运行结果以下:
执行代码:
if (false) {var b = 1}; console.log(b);
浏览器运行结果以下:
执行代码:
if (false) {let c = 1}; console.log(c);
浏览器运行结果以下:
第一种情形下: 当咱们直接在浏览器输入未声明的变量a, 有报 a is not defined的错误。
第二种情形下: b => undefined
第三种情形下: c is not defined
第二种情形下,变量在声明以前就已经可以被使用,这种特性被称非官方的称为声明提早。变量可以提早到哪里呢?全部使用var声明的变量被提早至函数顶部。
因而面试中经常使用这样的问题出现
var global = 'global' function test () { console.log(global); var global = 'local'; console.log(global); } test() // undefined, local
这是由于在test函数中,var声明的global被提早到test函数顶部,注意声明提早是在预编译时候执行的,而且给global初始化赋值undeifined。因此实际执行的逻辑,应该是这样
var global = 'global'; function test() { var global = undefined; console.log(global); global = 'local'; console.log(global); } test(); //undefined, local
变量声明提早好很差,很明显,这样很很差。一个变量尚未声明就可以被使用,太奇怪了,也太危险了。尤为是那些习惯了java、c的人看起来,这就是一个异类的特性。
因此,在ES6中,就推荐使用了let来声明变量。
做用域内 let 声明的变量不能在声明以前被使用,若是发如今let声明的变量在声明以前被使用,将会抛出 is not defined的错误。程序将会被中断,这个特性被称为 暂存死区。
在 let 变量声明的做用域内,javaScript引擎会把let声明的变量名收集起来。当程序开始执行的时候,代码一行行的往下执行,引擎获取变量以前,首先会判断此变量的变量名是不是在let声明的变量集合中。不存在,程序就继续往下执行。若是存在,就判断变量名的上一个字符串是否是let。若是是let,就把此变量名从let声明的集合中去掉,若是不是就抛出错误,程序被中断。
(纯属于我的猜测,方便理解,你们不做为参考)
console.log('hello world'); c = 1; let c;
这里是先打印"hello world",而后再进行报错,说明是程序在执行到 c = 1的时候报错。
是运行的错误。而你们能够对比前面的let声明同一个变量屡次,是程序尚未执行就报错。因此这里是运行中的错误。能够推断出变量暂存死区是发生程序运行中产生的,let声明的变量不能在声明以前使用,发现有声明以前使用的,就会抛出错误,中断程序。
另一个程序
let x = x; // x is not defined x // x is not defined let x // Uncaught SyntaxError: Identifier 'x' has already been declared
let x = x, 赋值从右往左执行,因此先获取变量 x, 因而x陷入暂存死区。抛出x is not defined的错误,程序中断。后面再获取x,仍是抛出x is not defined,x仍是陷入暂存死区。后面的let x,抛出Uncaught SyntaxError: Identifier 'x' has already been declared 错误是由于检查这段代码的时候,发现let x 已经被声明,因而抛出错误。不能被重复声明了。