1. let
- 声明的变量只在
let
块内有效
{
var a = 2;
let b =3;
}
console.log(a);//2
console.log(b);// b is not defined
适用于for
循环计数器
for(let i = 0; i < 10; i++){
}
console.log(i);//i is not defined
var a = [];
for(let i = 0; i < 10; i++){
a[i] = function(){
console.log(i);
};
}
a[6]();//6
for(var j = 0; j < 10; j++){
a[j] = function(){
console.log(j);
};
}
a[6]();//10
上述命令中 let
声明的i
变量,仅在本轮循环中有效,每次循环的 i
都是一个新变量,最终结果即为6
而var
声明的 j
变量,是一个全局变量,所有数组a
的成员里面的i
均是指向同一个i
,即为10
注:for
循环设置循环变量的部分是一个父作用域,而循环体内部则是一个单独的子作用域
for(let i = 0; i < 3; i++){
let i = 'abc';
console.log(i);
}
//abc
//abc
//abc
上述代码的运行结果即可说明。函数内部的变量i
与循环变量i
不在同一作用域,有各自单独的作用域
-
不存在变量提升
console.log(a);//undefined var a = 100; console.log(b);//error let b = 50;
var
变量声明会出现变量提升,只是没有值let
变量不存在变量提升,声明之前使用会抛出错误 -
暂时性死区
本质: 只要进入当前作用域,所要使用的变量已存在,但是无法获取,只有等到变量声明的那一行代码出现,才可以获取和使用该变量
var tmp = 123; if(true){ tmp = 'abc'; let tmp; }
在
let
、const
声明变量前,对tmp
赋值会报错,let
声明变量tmp
之前,均属于tmp
的死区两种隐蔽性死区
function bar(x = y, y = 2){ return [x,y]; } bar();//error //参数x默认值等于另一个参数y时,此时y还没有声明,属于死区 //修改 function bar(x = 2, y = x){ return [x,y]; } bar();//[2,2]
//unerror var x = x; //error let x = x
-
不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量//error function func(){ let a = 10; var a = 20; } //error function func(){ let a = 10; let a = 20; }
function func(arg){ let arg; } func() //error function func(arg){ { let arg; } } func() //unerror
-
块级作用域
- 外层代码块不受内层代码块影响
- 允许块级作用域任意嵌套
- 内层作用域可定义外层作用域的同名变量
浏览器的es6环境中,块级作用域内声明的函数,行为类似
var
声明的变量,其他环境中,类似let
声明的变量注:
- es6的块级作用域必须有大括号
- 避免在块级作用域内声明函数,确实需要时,应写为函数表达式,而非函数声明语句
- 严格模式,函数只能声明在当前作用域顶层
2. const
命令
声明只读的常量,一旦声明,常量值就不能改变
const
一旦声明变量,立即初始化,不可留到以后赋值const
的作用域与let
命令相同,只在声明所在的块级作用域有效const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用- 不可重复声明
本质
const
实际保证的是,变量指向的内存地址的值不可改变
-
简单数据类型(number,string,boolean)
值保存于变量指向的内存地址,等同于常量
-
复合类型数据(object,array)
变量指向的内存地址,保存的只是一个指向实际数据的指针,
const
仅能保证此指针固定,而它指向的数据结构是否固定,就不可控了//对象{} const foo = {} foo.prop = 123; foo.prop //123 foo = {};//error
常量
foo
存储的是一个地址,这个地址指向一个对象,这个地址不可变,但对象本身是可变的,不可把foo
指向另一个地址const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错
常量a本身是一个可写的数组,但如果将另一个数组赋给a,则
error
冻结对象
Object.freeze()
方法//将对象及其属性彻底冻结的函数 var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach((key,i) => { if(typeof obj[key] === 'object'){ constantize(obj[key]); } }) }
3.顶层对象属性
顶层对象,浏览器window
;Node global
window
对象的实体含义:指浏览器的窗口对象
-
为保持兼容性,
var
命令和function
命令声明的全局变量,依旧是顶层对象的属性 -
let
、const
、class
命令声明的全局变量,不属于顶层对象的属性var a = 1; window.a //1 let b = 1; window.b //undefined
4. globalThis
对象
JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。 - 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。 - Node 里面,顶层对象是
global
,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
变量,但是有局限性。
- 全局环境中,
this
会返回顶层对象。但是,Node 模块和 ES6 模块中,this
返回的是当前模块。 - 函数里面的
this
,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this
会指向顶层对象。但是,严格模式下,这时this
会返回undefined
。 - 不管是严格模式,还是普通模式,
new Function('return this')()
,总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval
、new Function
这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
ES2020 在语言标准的层面,引入globalThis
作为顶层对象。也就是说,任何环境下,globalThis
都是存在的,都可以从它拿到顶层对象,指向全局环境下的this
。
垫片库global-this
模拟了这个提案,可以在所有环境拿到globalThis