1.let变量声明—作用与var关键字类似
let关键字的几个特点:
- 不能重复声明同一个变量;
let a = 1;
let a = 1; // 抛出错误 Identifier 'a' has already been declared
- 不存在变量提升;
console.log(b);
let b = 1; // 抛出错误,不存在变量提升 Cannot access 'b' before initialization
- 用let声明的变量仅在块级作用域下有效;
// 3.仅在块级作用域下有效
for(let i = 0 ; i < 5 ; i++){
// console.log(i);
}
console.log(i); //报错,i未定义
- 不会影响作用域链;
// 4.不影响作用域链
let a = "王宝鸡";
function test() {
console.log(a);
}
test();// 王宝鸡 ,说明作用域链不受影响,仍会从函数外部查找变量
- 暂时性死区;
什么是暂时性死区?===>只要块级作用域中存在let命令,let所声明的变量就绑定到了该区域,不再受到外部的影响;
// 不受外部影响
var test = 1;
{
let a;
console.log(a); // undefined
}
在存在let关键字的代码块内,在使用let声明变量之前,该变量都是不可用的,这在语法上称为“暂时性死区”;
简单理解:在存在let或const关键字的作用域内,变量都必须在let声明之后再使用;
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
2.块级作用域
为什么要使用块级作用域?
- ES5只有全局作用域和函数作用域,以下场景下任意出错:
- 1.内层变量覆盖外层变量
- 2.for循环中的循环变量变成全局变量;
// 场景1:内层变量覆盖外层变量
var temp = 1;
function test(){
console.log(temp);
if(true){
var temp = "hello world"
}
}
test(); // undefined 内层temp变量由于变量提升,成为外层中一个未初始化的变量
ES6中的块级作用域:
- 可以任意嵌套;
{{{{let a = 0 ;}}}}
- 外层作用域无法读取内层作用域中的变量;
// 外层作用域无法访问内层作用域中的变量
{
let a = 1;
{
let b = 2;
console.log(a); // 1 .内层可以访问外层
}
console.log(b); // b is not defined 外层无法访问内层
}
- 内层作用域可以定义外层作用域中的同名变量;
{
let aa = 1;
{
let aa = 2;
console.log(aa); // 2 内层可以修改外层的同名变量
}
}
块级作用域和函数声明:
- ES5规定函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明;
- ES6中引入了块级作用域,并规定可以在块级作用域中声明函数,在块级作用域中声明的函数,类似于let声明的变量,函数外部是无法调用的;
- 为了避免出错,在作用域中需要声明函数时,尽量使用函数表达式的形式,不要使用函数声明的方式;
// ES6的浏览器环境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());// Uncaught TypeError: f is not a function
// ES6的浏览器环境 上述代码的执行过程
function f() { console.log('I am outside!'); }
(function () {
var f = undefined; // 函数提升,但是函数声明不会随着函数提升到作用域顶部
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
do表达式
- 作用:块级作用域是一个语句,将多个表达式封装在一起,没有返回值,使用do表达式能让块级作用域成为表达式,进而拥有返回值;
- 用法:在块级作用域前加上do;
let x = do {
let t = f();
t * t + 1;
};
3.const命令
基本用法:
- const关键字用于声明一个常量,一旦声明就不能再修改;
- const关键字声明的常量被声明之后需要立即初始化,否则会报错;
- 作用域与let相同,仅在所在的块级作用域内有效;
- 同样存在暂时性死区;
- 不可以重复声明同一变量;
本质:
- 本质上来说,const保证的并不是值不允许改变,而是保证变量的内存地址不改变;
- 对于简单数据类型来说,const保证数据与变量是相等的,但对于复杂数据类型来说,const常量中保存的仅仅是一个指向该常量的一个指针,只能保证该指针不会变化,并无法保证指针指向的数据结构不发生变化;
// 本质
const obj = {};
obj.name = "peanut";
console.log(obj.name); // "peanut" 可以对const声明的对象添加属性
obj = new Object(); // Assignment to constant variable. 将obj指向新的对象会报错
- 上述代码表明,const声明的复杂类型数据中,不可变的只是那个指向对象的地址,对象本身是可以改变的;
- 解决办法:Object.freeze()方法
- 该方法用于冻结对象及其所有的属性;
// Object.freeze():冻结某个对象,使其无法被修改
var testObj = Object.freeze({
name : "peanut",
age : 18
})
testObj.gender = "male";// 无效,冻结后无法添加属性
testObj.name = "andy";// 无效,冻结后无法修改属性
console.log(testObj);
声明变量的6种方法(包含ES5):
1.var
2.function
3.let
4.const
5.import
6.class
4.顶层对象的属性
- 顶层对象在浏览器环境下指的是window对象;
- Node环境下指的是global对象;
- 在es5中,顶层对象的属性与全局变量是等价的;
- es5中的var、function声明的变量仍会成为window对象的属性;let、const、class声明的变量不会成为window对象的属性;
// 在es5中,顶层对象的属性与全局变量是等价的
// es5中的var、function声明的变量仍会成为window对象的属性
// let、const、class声明的变量不会成为window对象的属性
var a = 111;
let b = 222;
console.log(window.a); // 111
console.log(window.b); // undefined
global对象
- ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。
- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
- 浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self。
- Node 里面,顶层对象是global,但其他环境都不支持。
因此,在不同环境下拿到顶层对象很难有统一的方法;
- 解决办法:在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象。
- 使用垫片库(https://github.com/es-shims/globalThis))可以在所有环境下拿到global对象;