ESNext专题

ES6 块级绑定

Table of Content

1. 块级绑定

基本上在类C语言中,变量的创建总是在它被声明的地方.但javascript并非如此.变量实际的位置是取决于开发着如何声明它..基于这样的一个存在,ES6闪亮登场.提供了一个let,可以让你更容易更直观的控制变量的作用域.

1.1. 所谓的变量提升

如果我们使用var 声明变量.如果在函数内.不管我写在什么地方,都会被视为写在函数顶部.如果不在函数内.则视为全局作用域的顶部.就是所谓的变量提升

function getVal(cond){
    if(cond){
        var color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return null
    }
}

以上代码在js解释器中会为被修改为

function getVal(cond){
    var color
    if(cond){
        color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return null
    }
}

为了解决这个BUG..引入了块级作用域

块级声明也就是让所声明的变量在指定的块的作用域外无法被访问.块级作用域(词法作用域)

  1. 在一个函数内部
  2. 在一个代码块内部 {}

块级作用域是类C语言的工作机制.

1.2. let 声明

使用方法和var语法一致,基本上你可以用let来代替var进行变量声明,但是会把变量的作用域限制在当前的代码块当中.如果想要提升,需要手动操作
以下示例:

function resu(val){
    if(val){
        let color = 'blue';
        return color;
    }else{
        // color 在此处  color is not defined
        return color
    }
}

function resu(val){
    let color 
    if(val){
        color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return color
    }
}

console.log(resu(0))

1.3. 禁止重复声明

如果一个标识符已经被定义,就不能在此代码块中使用同一个标识符进行 let 声明,否则会报错

var number = 0;
let number = 1;   // Uncaught SyntaxError: Identifier 'number' has already been declared

在同一作用域下不能得复声明
但是在嵌套的作用域内使用同名的新变量.就不会所错

var number = 10;
if(true){
    let number = 40;
}

以上案例中.let 并没有报错,因为在if条件语句中创建新变量number.而不是在同一级别再次创建.在if代码块中会屏蔽全局的number变量.从而在局部组止对于后者的访问.

1.4. 常量声明

声明一个常量可以使用const语法进行声明.常量意味声明之后的值不可变.所以所有的常量都要在声明的时候初始化.

// 有效的常量
const MAXNUMBER = 300// 语法错误的常量
const NAME;

以上MAXNUMBER这个常量初始化并且赋值了.因此是正常而有作用的.而NAME常量没有被赋值.会导致语法误法;
const和let声明一样都是块级声明.有声明也不会有提升的情况发生

if(true){
    const NUMBER = 40;
}
// NUMBER 在此处无法被访问

已经被声明过的变量.不可以用常量再次声明

var msg = 0;
let height = 190;

// 以下两者都会报错
const msg = '1';
const height = 170;

常量不可被改变

const height = 170;
height = 180;  //Uncaught TypeError: Assignment to constant variable.

常量确定之后.不可以再次被赋值

如果使用const 声明对象.
const声明是会阻止对于常量绑定与常量自身值的修改,也就是说并不会去阻止对于常量的成员的修改;

const obj = {
    name:"张三"
}  //
obj.name = '李四';
// 报错  Uncaught TypeError: Assignment to constant variable.
obj = {
    name:"赵五"
}
// 数组也是一样的
const arr = [];
arr.push(1)
arr.push(1)
console.log(arr)

此处对象初始化的时候有一个属性,修改属性是可以,但是不能修改对象本身.只需要记住const阻止的是对于常量绑定的修改.而不是对于成员值的修改

1.4. 暂时性死区

使用let , const 声明的变量,在达到声明之前.都是无法访问的.就算是相对安全的操作(比如typeof),也是一样的

console.log(typeof abc)
let abc;

以上abc 变量使用了let 进行定义,但是这条语句.永远不会被执行.因为在声明之前就已经报错了.出现这个问题是因为暂时性死区.但是暂时性死区是 js 社区(比如网上论坛)提出的,这个名称从来没有在ECMAscript规范中被明确命名.但是经常用于描述let或const声明 的变量为何在声明处之前无法被访问

1.5. 循环中的块级绑定

我们最需要使用的变量的块级场景是,也许是for,也就是想让一次性的循环计数只能在内部使用
以下示例应该是最常见的一种行为

for (var index = 0; index < 10; index++) {}
console.log(index)   //10

但是在大多数语言中.以上index.只能在循环内工作访问.这里的index在外部可以被访问.是因为使用var声明导致了变量提升,如果换成let,就不会发生

for (let index = 0; index < 10; index++) {}
console.log(index)   //app.js:8 Uncaught ReferenceError: index is not defined

在以上案例中.index仅在for内部使用. 循环结束..这个变量的任意位置不可被访问

1.6. 循环内的函数

在let没有出现之前,因为var的特点.会使循环变量作用域之外还可以被访问,于是在循环内创建函数会有问题.

var arr = [];
for(var i=0;i<10;i++){
    arr.push(function(){
        console.log(i)
    })
}

arr.forEach(function(fn){
    fn()
})

我们本身的预期是希望输出0-9的数字,但是实际结果是数字10被输出了10次.

我们在修正这个问题的时候可以使用IIFE(立即调用函数表达式),就可以在每次迭代中强制性的创建变量的一个新的副本.

var arr = [];
for(var i=0;i<10;i++){
    arr.push((function(value){
        return function(){
            console.log(value)
        }
    })(i))
}

arr.forEach(function(fn){
    fn()
})

上面在循环内使用IIFE,变量 i 被传递给了IIFE, 去创建了value变量为自身的副本,并在自己当中

1.7. 循环内的let

使用let声明.可以简化IIFE.在每交迭代中.都创建一个新的同名变量,对其进行初始化.

var arr = [];
for(let i=0;i<10;i++){
    arr.push(function(){
        console.log(i)
    })
}

arr.forEach(function(fn){
    fn()
})

以上代码完全实现了相同的效果.但是更加简洁.效果更好,同样的方式在for-in 和 for-of中同样适用.

var arr = [];
var obj = {
    name:"张三",
    sex:"",
    age:22
}

for (let key in obj){
    arr.push(function(){
        console.log(key)
    })
}

// console.log(arr)

arr.forEach(function(fn){
    fn()
})

在以上for-in循环中体现出for相同的行为.

let声明在循环内部的行为是在规范中特别被定义过的.而与不提升变量的特征没有必然的联系.事实上在let刚刚出来的时候是没有这种行为的...在后面不停变更过程中加进来的.

1.8. 循环内的常量

ES6规范没有明确禁止在循环中使用常量.然而它会根据循环方式的不同有不同的行为体现.在常规的for当中.可以使用const初始化.但是如果你去循环了.就等于是想要改变它.就会报错

for (const index = 0; index < 10; index++) {
    console.log(index)
}

代码会执行一次.但是第二循环的时候,试图改变index.这个时候就报错了.

但是如果是在for-in或是for-of.效果和let完全相同.

var arr = [];
var obj = {
    name:"张三",
    sex:"",
    age:22
}

//不会报错
for (const key in obj){
    arr.push(function(){
        console.log(key)
    })
}

arr.forEach(function(fn){
    fn()
})

因为每次迭代创建一个新的变量绑定.而不是试图去修改已经绑定的变量的值.所以就不会报错

1.9. 全局的块级绑定

let和const不同于var的另一个区别就是在全局作用域上的表现.在全局作用域上使用var,会创建一个新的全局变量.并且成为全局对象(window)的一个属性.这也就是竟味使用var可能会无意中覆盖一个已经有的全局属性,

var RegExp = 'h';
console.log(window.RegExp)  //h

RegExp定义在window.但还是被重写了.但是如果使用了let 或 const,虽然在全局中会创建新的绑定,但是不会有任何属性被添加到了全局对象上去.也就意味着不用使用let或const来覆盖一个全局变量.只能把他屏蔽.

let RegExp = 'h';
console.log(RegExp// h
console.log(window.RegExp//ƒ RegExp() { [native code] }
console.log(window.RegExp === RegExp)   //false

如果希望代码能从全局对象中访问.依然需要使用var.在浏览器跨窗口去访问代码时适用.

在拥有了let和const之后..默认情况下应该使用let而不是var 对于90%以上开发者来说let的行为才是正确的行为.是声明变量本来就应该有形式.所以直接使用let更合逻辑.在需要被保护的变量时使用const.

1.10. 总结

  1. let或const有块级作用域
  2. var 只有全局和函数作用域
  3. let 不能重复声明,const也不能重复声明
  4. 如果使let 或 const 必须在使用声明.如果在使用后声明,会产生暂时性死区
  5. 可以解决循环中的闭包的问题
  6. var在全局下声明的变量,会挂载到window下面
  7. let或const会挂载到window下面.
yyx219
我还没有学会写个人说明!
查看“yyx219”的所有文章 →

Leave a Reply

Your email address will not be published.

相关推荐