let和块级作用域
1.1 函数作用域
在ES5中,JS的作用域分为全局作用域和局部作用域。通常是用函数区分的,函数内部属于局部作用域。
//ES6之前只有函数才构成局部作用域
//案例1
{ var a = 10; }
console.log(a);
//案例2
console.log(a);
if (true) {
var a = 10;
}
console.log(a);
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景
-
内层变量可能会覆盖外层变量。
var b = 1; fn1(); function fn1() { console.log(b);//undefined if (true) { var b = 2; } } -
用来计数的循环变量泄露为全局变量。
//变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变 for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i);//5 5 5 5 5 }) }
1.2 块级作用域
-
在ES6中新增了块级作用域的概念,使用{}扩起来的区域叫做块级作用域
-
let关键字声明变量,实际上为 JavaScript 新增了块级作用域。
-
块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
-
在块内使用let声明的变量,只会在当前的块内有效。
//全局声明一个变量b
let b = 1;
fn1();
function fn1() {
//当前的作用于没有b,沿着作用于寻找到全局的b
console.log(b);
if (true) {
//只在当前的作用于中生效
let b = 2;
}
}
1.3 let关键字
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效,也就是增加了块级作用域。
-
使用块级作用域(let定义的变量属于块级作用域) 防止全局变量污染
{ let b = 20; } console.log(b);//Uncaught ReferenceError: b is not defined -
块级作用域可以任意嵌套
//外层作用域无法读取内层作用域的变量 { { let a = 1; { console.log(a); //1 } } console.log(a);//Uncaught ReferenceError: a is not defined } -
for循环的计数器,就很合适使用let命令,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
- for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个隐藏的作用域
- for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次。
//计数器i只在for循环体内有效,在循环体外引用就会报错 for (let i = 0; i < 4; i++) { console.log(i); } console.log(i); for (let i = 0; i < 5; i++) { setTimeout(function () { console.log(i);//0 1 2 3 4 }) } -
练习
//练习1: var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 练习2: var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6]();
1.4 let关键字特点
-
let同样存在变量提升,只是let存在暂时性死区。(在赋值之前读写变量都会导致 ReferenceError 的报错。从代码块(block)起始到变量求值(包括赋值)以前的这块区域,称为该变量的暂时性死区。)
//ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错) // 这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错 let c = 1; { console.log(c);//Uncaught ReferenceError(引用错误): Cannot access 'c' before initialization let c = 2; } -
不允许重复声明
// let不允许在相同作用域内,重复声明同一个变量 // 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; } -
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了
// IIFE 写法 (function () { var tmp = "...;" }()); // 块级作用域写法 { let tmp = "...;" }
2 const关键字
常量:不会变化的数据,有些时候有的数据是不允许修改的,所以需要定义常量。
-
const声明一个只读的常量。定义常量可以写大写。const PI = "3.1415926"; console.log(PI) -
const声明的常量不得改变值const PI = "3.1415926"; PI = 22;//Assignment to constant variable.(报错:给常量赋值) -
const声明的常量如果是对象,可以修改对象的内容// const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了 //但是对象的引用不能被修改,比如用一个对象替代这个对象,那么引用就被修改了 const PATH = {}; PATH.name = "lily"; console.log(PATH);//{name:"lily"} PATH = {}//Assignment to constant variable -
const一旦声明变量,就必须立即初始化,不能留到以后赋值const time;//SyntaxError: Missing initializer in const declaration -
const只在声明所在的块级作用域内有效{ const PI = 3.14; } console.log(PI)//ReferenceError(引用错误)PI is not defined -
const命令声明的常量也拥有暂时性死区console.log(c);//Cannot access 'c' before initialization const c = "hello" -
const声明的常量,也与let一样不可重复声明const PI = "3.14"; const PI = "3.14";//SyntaxError: Identifier(标识符) 'PI' has already been declared(声明) -
let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
let a = 1; console.log(window.a);//1 var a = 2; console.log(window.a)//undefined
3 块级作用域的函数声明
函数声明一般常用的是两种,一种是function声明,一种是函数表达式。
//函数声明 及 两种声明的区别
f1()//可以使用
f2();//f2 is not a function
function f1() {}
var f2 = function () {
}
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
- ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
- ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于
let,在块级作用域之外不可引用。 - ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式
- 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
//尽量避免以下写法
fn1();
if (true) {
function fn1() {
alert(1);
}
}
fn1();
//如果真的想在声明块级作用域函数,使用函数表达式
if (true) {
let fn1 = function () {
alert(1);
}
}

浙公网安备 33010602011771号