
ES6声明变量的六种方法
ES5只有两种声明变量的方法:var和function命令。ES6除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6一共有六种声明变量的方法。
let命令
ES6新增了let命令,用法类似于var,但是有区别
① let命令只在所在代码块内有效
② 不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
③ 暂时性死区
在代码块内,使用let声明变量之前的区域,该变量都不可用,此被称为“暂时性死区”
(个人理解:因为没有了变量提升,才会造成‘暂时性死区’说法,但是为什么不叫永久性死区呢??)
暂时性死区有以下几个影响:
1.typeof 不再是一个安全的操作
typeof x //报错
let x;
2.作为比较,如果一个变量没有被声明,使用typeof反而不会报错。
typeof y // "undefined"
3.有些死区比较隐蔽,不太容易被发现
function bar(x = y,y = 2) {
return [x,y];
}
bar() //报错,因为参数x的默认值等于另一个参数y,而此时y还没有被声明
4.使用let生命变量,只要变量在还没有声明完成前就使用,就会保错
var x = x; //不报错
let x = x; //报错
④ 不允许重复声明
let 不允许在相同作用域内,重复生命同一个变量。
function bar() {
let a = 10;
var a = 1;
}
//报错
function brp() {
let a = 10;
let a = 1;
}
//报错
function bup(arg) {
let arg;
}
bup(); //报错
function bpg(arg){
{
let arg;
}
}
bpg(); //不报错
const命令
声明一个只读的常量。一旦声明,常量的值就不能改变
//变量的值不可更改
const PI = 3.1415;
PI = 3; //报错
//const声明变量必须立即初始化,不能留到以后赋值
const foo; //报错
//const作用域与let相同:只在声明所在的块级作用于内有效
if(true){
const MAX = 5;
}
MAX; //MAX is not defined
// const命令声明的变量也是不提升,同样存在暂时性死区,只能在声明之后的位置使用
if(true) {
console.log(MAX); //报错
const MAX = 5;
}
本质:const 实际上保证的,并不是变量的值不可改变,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(number、string、boolean),值就保存在变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
foo.prop = 123; // 为fool添加一个新属性,可以成功
foo.prop //123
foo = {}; //把 fool指向另一个地址,则报错
//如果真的想将对象冻结,应该使用Object.freeze方法;
const foo = object.freeze({});
foo.prop = 123;
...
顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。ES5之中,顶层对象的属性与全局变量是等价的。
window.a = 123;
a; // 123
a = 321;
window.a; // 321
上面代码中,顶层对象的属性赋值与全局变量的属性赋值是同一件事。
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不适合的。
ES6为了改变这一点,一方面规定,为了保证兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不再属于顶层对象的属性。也就是说,从ES6开始,全局对象将逐步与顶层对象的属性脱钩。
var a = 123;
window.a; // 123
let a = 123;
window.a; //undefined;
...
globalThis对象
JavaScript语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是顶层对象在各种实现里面是不统一的。
--在浏览器环境,顶层对象是window、self
--Web Worker里面,顶层对象是self
--Node里面,顶层对象是global,但其他环境都不支持。
同一段代码,为了能够在各种环境下都能渠道顶层对象,现在一般是使用this变量,但是有局限性:
①全局环境中,this会返回顶层对象,但是在node和ES6模块中,this返回的是当前模块。
②函数里面的this,如果函数不是作为对象的方法运行,this会指向顶层对象。但是,严格模式下, this会返回undefined。
③不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全策略),那么eval、new Function 这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的放大。
//方法1:
(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');
}
网友评论