ES6的深入认识(1)
let&const
首先列出我对于let和cons起初粗浅的认识
- let ≈ var,const只是定义一个不可变常量
- 二者均不支持提升
学习总结后的一些认识:
(1) var在全局创建的变量,全局各处均可以访问到,let则不是,如果在代码块中创建,则只能在代码块中访问。
验证:
第一点认识起初不是很明朗,于是进行了如下的验证:
var arr01 = [],
arr02 = [];
for(let i = 0; i < 5; i++) {
arr01.push(function() {
console.log('i in arr01', i);
});
}
for(var i = 0; i < 5; i++) {
arr02.push(function() {
console.log('i in arr02', i);
});
}
arr01.forEach(item => item());
arr02.forEach(item => item());
结果
结论:很明显,var创建的i一轮又一轮被重新赋值,到最后我们执行打印函数的时候,全局的i只等于最后循环结束时被赋予的值了,前面的值均已经被覆盖掉,let创建的i则健全的保留了每一次创建时被赋予的新值。
(2) var创建的变量,在循环中始终保持只有一个变量被循环覆盖式赋值,而let在循环中每一次都会创建一个新的变量,赋予新值(js的内部引擎会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。)
(3)暂时性死区
在代码块中,如果我们在没有用let或者const去声明一个变量之前就使用了该变量,那么就会报引用错误。换一句话说,也就是在进入当前作用域时,需要使用的变量就已经存在,但是不可获取,只有在遇到声明该变量的这一行代码出现之后,才可以正常的使用。
(4)块级作用域存在的意义
内层变量可能会覆盖外层变量
先解读一段代码
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
起初并不明白为什么会返回Undefined,我最开始认为应当返回new Date(),但是后来发现var是存在变量提升的,所以上述代码应当解读为:
var tmp = new Date();
function f() {
var tmp = undefined;
console.log(tmp);
if (false) {
tmp = 'hello world';
}
}
f(); // undefined
用来计数的循环变量泄露为全局变量
同样先读一段代码
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
在假设不考虑性能的前提下,如果我有两个以上的循环,
for() {
//...
}
for() {
//...
}
for() {
//...
}
//...
那么我一个页面要设置多个计数的循环变量。但是如果换做是let,那么情况就发生变化了。
var s = 'hello';
for (let i = 0; i < s.length; i++) {
console.log(s[i]);
}
for(let i = 5; i < 10; i++) {
console.log('第二个循环', i);
}
for(let i = 10; i < 15; i++) {
console.log('第三个循环', i);
}
console.log(i); // 5
结果
我不用再考虑计数变量会泄露到全局了。
(5)ES6的块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
起初看这一段代码的时候,会有一些疑问,前面的学习告诉我let是不能重复对一个变量进行声明的,这一段代码必然会报错,但是运行之后没有报错甚至打印了5,这就说明函数中不仅有函数自己的局部作用域,还有if代码块中的块级作用域。
此时,就不得不联想到es5中常年为了躲避变量泄露二使用到的IIFE(Immediately-invoked-function-expression),哇,很方便了。
// before
(function(){
var temp = 'hello';
})();
// after
{
let temp = ...;
...
}
(6)块级作用域与函数声明
在学习过程中,碰到这么一段代码,说在es5环境下运行和es6环境下运行是不一样的,前者环境下会执行内部函数,后者则会视执行环境的表现而定。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
果然,在Chrome中执行结果如下:
这么一看,与理论上es6的块级作用域表现不符,我们期待的是会打印出外层函数的outside,但是这里的表现说明,我们的函数没有做到es5的提升,即
function f() { console.log('I am outside!'); }
(function () {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
if (false) {
f();
}());
那具体是怎么回事呢,在查阅资料之后发现,es6的附录中规定了浏览器的表现可以不遵守理论上的规定,它可以有自己的表现方式,如下:
- 允许在块级作用域内声明函数。
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
- 同时,函数声明还会提升到所在的块级作用域的头部。
那么上述代码的解读就变成了:
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
(7)const保证不变的并非是值
以前的学习只会粗浅的认为const会保证一个值不变,但是直到我遇到const设置对象为常量。
const foo = {};
foo.name = 'foo';
console.log('foo', foo);
foo.name = 'boo';
console.log('foo', foo);
foo = {};
结果
原来const只能保证设置的值指向的内存指针是不变的,而这个内存地址缩储存的值为多少const并不关心,所以上述代码中name属性如何变又或是foo的对象属性增或减与const无关,因为foo所在的内存地址始终没有变过。
结论:使用const去声明一个不变的对象时需要格外的谨慎
那么,我们真的想要一个始终都不会变的对象,又该怎么办呢?Object有一个方法叫做freeze
const foo = Object.freeze({});
foo.name = 'foo';
console.log('foo', foo);
foo.name = 'boo';
console.log('foo', foo);
foo = {};
结果
严格模式下:
可是,这就结束了吗?我们以前复制对象的时候回去考虑深浅拷贝的问题,那么这里修改的时候,会不会也考虑到层级深浅的问题呢?于是就出现了以下的代码(函数声明或是表达式都可以)
var freezeComplete = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
freezeComplete( obj[key] );
}
});
return obj;
};
const foo = freezeComplete({
person: {},
otherKeys: 'otherKeys'
});
foo.person.name = 'foo';
foo.person.height = '1.8';
console.log('foo', foo);