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] );
}
});
};
总而言之:
一般情况下,使用const来定义值的存储容器(变量)
只有在值容器明确地被确定将会被改变时才使用let来定义变量
不在使用var
参考引用:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。