es6学习笔记-let,const和块级作用域_v1.0

块级作用域

  • javascript 原来是没有块级作用域的,只有全局作用域和函数作用域

例子1

因为没有块级作用域,所以每次的i都是一样

var a = []; 
for (var i = 0; i < 10; i++) {//变量i是var声明的,在全局范围内都有效
    a[i] = function () {
        console.log(i); //每次的i变化都是全局的,所以每一个i的值都会变成最终的10
    };
}
a[1](); //返回10
a[6](); //返回10

例子2

这里可以跟以前在<js高程3>里面有一个闭包的例子一起理解

function createFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function () { //这是一个闭包
                //因为闭包保存的是整个createFunctions变量对象,所以当他执行完成的时候(for循环结束),
                //i是等于10的,所以就会是10,由始至终,闭包里面引用的都是一整个变量对象,而不是一个变量
                return i;
            };
        }
        return result; //返回的是一个数组,数组里面每一个项目都是一个function
    }
    var test = createFunctions();
    for (var i = 0; i < 10; i++) {
        //需要执行这个 function 才能够获取里面的值
        console.log(test[i]());//都是10 
    }
//看看区别,这里会传入一个参数来避免使用变量对象的参数
      function createFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function (num) {//这是一个匿名函数,参数是 num
                return function () {// 这是一个闭包,不过这个闭包访问的num是我们传入的num,即使闭包保存一整个变量对象,但是我们将变量对象改成了外面这个匿名函数
                    return num;     //相当于在闭包外面包了一层活动对象,将活动对象改变成能够改变值 num的活动对象
                };
            }(i);//这个匿名函数会立即执行,并且传入了 i 作为 num
        }
        return result; //返回的是一个数组,数组里面每一个项目都是一个function
    }
    var test = createFunctions();
    for (var i = 0; i < 10; i++) {
        //需要执行这个 function 才能够获取里面的值
        console.log(test[i]());//返回0-9
    }

虽然这里是说闭包保存的是整个变量对象,导致了i的值跟着变量对象一起变化,不过也是殊途同归,这里只是替换为做全局对象来做例子

例子3

这里还原到真实的应用

<button class="button">
    a
</button>
<button class="button">
    b
</button>
<button class="button">
    c
</button>
<button class="button">
    d
</button>

<p id="output"></p>
//这里运行报错:Uncaught TypeError: Cannot read property 'innerText' of undefined
    at HTMLButtonElement.<anonymous> (
 var buttons = document.querySelectorAll('.button');
 var output = document.querySelector('#output');

 for(var i =0;i<buttons.length;++i){
 //注意到这里就是因为i的没有块级作用域,都是使用全局作用域导致问题
     buttons[i].addEventListener('click',function (i) {
     //这里的i会出现4的情况,因为buttons的长度是4,但是数组的话是0-3,没有4,所以报错
             output.innerText = buttons[i].innerText;
     },false)
 }

//需要改成这样才能正常
 var buttons = document.querySelectorAll('.button');
 var output = document.querySelector('#output');

 for(var i =0;i<buttons.length;++i){
 //需要改成这样,单独使用一个新的内部函数作用域的变量来保持顺序
     buttons[i].addEventListener('click',function (num) {
         return function () {
             output.innerText = buttons[num].innerText;
         }
     }(i),false)
 }

总的来说,那么在以前的js里面要实现块级作用域的话:

  • 要么使用函数作用域,例如在函数里面重新定义变量

  • 要么就使用类似闭包的方式,使用独立的变量

let

  • let的作用就是将变量保持在块级作用域里面,那就没有了跟其他作用域互抢的情况了.

  • var会出现变量提升的情况(变量可以在声明前使用),但是let没有

  • 不允许重复声明(var可以,let不可以)

改成用let之后


var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = function () {
        console.log(i);
    };
}
a[1](); // 返回1
a[6](); // 返回6

其他例子也是可以简单的改成let代替var就可以生效了.

块级作用域与函数声明

  • es5规定函数只能在顶层作用域和函数作用域之中声明,不能再块级作用域声明(一般模式可以使用,但是严格模式不可以使用)

  • es6规定,明确允许在块级作用域之中声明函数

但是考虑到旧代码的问题,为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 函数声明语句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函数表达式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

for循环

for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) { //i在for循环变化
  let i = 'abc'; //然后for循环内部的i也设置了一个
  console.log(i);
}
// abc
// abc
// abc

上面代码输出了3次abc,这表明函数内部的变量i和外部的变量i是分离的。

const

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

  • 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

  • 于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了

const foo = {};

// 为 foo 添加一个属性,可以成功,虽然设置了常量,但是里面的属性还是能够改变
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错,这里看到常量还是设置成功的
foo = {}; // TypeError: "foo" is read-only

如果需要将对象冻结,保证为一个真正的"常量",需要用Object.freeze,而且为了保证排除对象里面包含对象的情况,需要递归冻结

//普通冻结
const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

//递归冻结
var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, value) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

总而言之:

  1. 一般情况下,使用const来定义值的存储容器(变量)

  2. 只有在值容器明确地被确定将会被改变时才使用let来定义变量

  3. 不在使用var

参考引用:

  1. es6-函数的扩展

  2. es实战2015


线上猛如虎
2.2k 声望178 粉丝

你们都有梦想的,是吧.怀抱着梦想并且正朝着梦想努力的人,寻找着梦想的人,我想为这些人加油呐喊!