目录
文章目录
let 命令
基本用法
let 命令是用来声明变量,和 var 用法类似,但是let 所声明的变量作用域只在所在的代码块内有效。
在for循环中,很适合使用let命令,可以有效的避免闭包的问题。
- 使用var
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
在这里的
i
是var
声明的,在全局范围内有效,每一次循环,变量i
都会发生改变,并且循环内a
的函数内部console.log(i);
也会根据全局变量发生改变而改变,导致运行输出的是最后一次循环的i的值。
- 使用let
var a = [];
for (let i = 0; i < 10;i++){
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
在这里的
i
是let
声明的,当前的i
只对当前的循环有效,所以每一次的循环的i都是一个新的变量。JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算。
变量提升
通过 var
声明的变量会发生一些“变量提升”的现象,即变量可以在声明之前使用,值为undefined
,按照正常的逻辑,变量应该在声明语句之后才可以使用。而let
则修复了这个问题,let
声明的变量,一定要在声明后才可以使用,否则会报错。
暂时性死区
只要块级作用域有let
命令,它所声明的变量就绑定了这个区域,不在受外部的影响。ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。在这段代码块中,使用let
命令声明变量之前,这个变量都是不可用的,这不可用的区间就是“暂时性死区”。
在“暂时性死区”中,
typeof
运行时也会抛出异常。
不允许重复声明
let
不允许在相同的作用域内,重复声明同一个变量。
function fun(){
let a = 10;
let a = 1;
}
上面的代码在同一个作用域内声明了两个相同的a
;所以会报错。
但是下面代码不会报错:
function fun(){
let a = 10;
var a = 1;
}
还有let
声明的变量与当前函数的参数不能一样
function fun(param){
let param;
}
fun();
此时的函数fun()
就会因为函数参数名称和let
声明的变量名称一致,导致报错。
为什么需要块级作用域?
- 内层变量可能会覆盖外层变量
var tmp = new Date();
function fun(){
console.log(tmp);
if(false){
var tmp = 'hello world';
}
}
fun();
此时在fun()
函数执行以后,内层的tmp
变量提升,覆盖了外层的tmp
变量,所以此时为undefined
.
- 用来计数的循环变量泄露为全局变量
var str = 'hello';
for (var i = 0; i < s.length; i++){
console.log(s[i])
}
console.log(i) // 5
这里的i
只是用来控制循环的,但是循环结束以后,并没有消失,进而成为了全局变量。
块级作用域与函数声明
在ES5中,函数只能在顶层作用域和函数作用域中声明,不能在块级作用中声明。
在ES6中,允许在块级作用域中声明函数。但是在块级作用域中声明的函数在作用域之外不可调用。
块级作用域怎么来?
ES6中的块级作用域必须要有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
// 不是块级作用域
if(true) let x = 1; // 报错
// 块级作用域
if(true){
let x = 1;
}
const命令
基本用法
const
声明的变量是一个只读的常量,只要声明了,常量的值就不能改变了,并且在声明变量的同时初始化,不能在声明以后再进行赋值操作。
const demo; // 非法的
const
和let
的作用域是一样的,只在声明的块级作用域内有效。const
也存在暂时性死区,只能在声明变量的位置之后使用。
本质
const
实际上保证的并不是变量的值,而是变量指向的内存地址所保存的数据不能改动。对于简单的数据类型(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但是对于复合型的数据(只要是对象和数组),变量指向的内存地址保存的是一个指向实际数据的指针,此时的const
只能保证这个指针是固定的,至于指针指向的数据是不是可变的就不能控制了。
// 对于对象来说
const foo = {};
// 给 foo 添加一个属性,是可以的
foo.prop = 123;
// 将 foo 指向另一个对象就会报错
foo = {};
// 对于数组来说
const arr = [];
// 给数组中push元素是没有问题的
arr.push(1);
// 但是将 arr 赋值到另一个数组就会报错
arr = ['demo'];
冻结对象
如果想要冻结一个对象,可以使用Object.freeze()
方法
const foo = Object.freeze({});
foo.prop = 123;
此时在常规模式下,foo
的prop
属性完全没作用,在严格模式中,这种写法会报错。
除了将对象本身冻结,对象的属性也应该冻结:
var constantize = (obj) =>{
Object.freeze(obj);
Object.keys(obj).forEach((key,i) => {
if(typeof obj[key] === 'object'){
constantize(obj[key]);
}
})
}
ES6 声明变量的六种方法
var
命令function
命令let
命令const
命令import
命令class
命令
顶层对象的属性
顶层对象在浏览器中指的是window
对象,在Node中指的是global
对象。早在ES5中,顶层对象的属性与全局变量是等价的。
window.a = 1;
console.log(a); // 1
a = 2;
console.log(window.a); // 2
在这里,顶层对象的属性赋值与全局变量的赋值是同一件事。
在ES6中, var
和function
命令声明的变量是全局变量,依旧是顶层对象的属性;let
、const
和class
声明的变量是不属于顶层对象的属性。
var a = 1;
console.log(window.a) // 1;
let b = 1;
console.log(window.b) // undefined
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');
}
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。