# 1、变量声明 -- let 、 const
# 1、变量声明--let、coust
var和let用法类似,let相对于var函数作用域引入块级作用域
{ var a = 10; let b = 20; } a // 10 b // b is not defined.
改进var变量提升带来的问题,引入暂时性死区概念,绑定所在区域,不会受外界影响
// var console.log(foo); // 输出undifined var foo = 2; // let console.log(bar); // 报错 ReferenceError let bar = 2;
var tmp = 123; if(true){ console.log(tmp) // ReferenceError typeof tmp; // ReferenceError tmp = 'abc'; // ReferenceError let tmp; // 绑定此区域,先于声明之前的使用和赋值会报错 }
不允许重复声明变量,不能在函数内部重新声明参数
// 报错 function func(){ let a = 10; var a = 10; } // 报错 function func(arg) { let arg; } func() // 不报错 function func(arg) { { let arg; } } func()
ES6的块级作用域, 是得变量不存在提升
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
上面代码中,变量
i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。块级作用域代替了以前常用的匿名立即执行函数表达式(匿名 IIFE )
// IIFE写法 (function(){ Var tmp = ...; })(); // 块级作用域写法 { let tmp = ...; }
const 命令
const声明一个只读的常量。一旦声明,常量的值就不能改变。所以只声明不赋值会报错。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const和let一样都不存在变量提升,都不允许重复声明,都不可以在声明前使用。
const 的本质: const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址保存的数据不得改动,因为简单的数据类型,值就保存在指向的那个内存地址,所以相当于常量,但是对于复合类型的数据(对象和数组),变量指向的内存中地址,保存的只是指向实际数据的指针,const只能保证这个指针式固定的,但是它指向的数据结构是不是可变的,就完全不能控制了,因此,将一个对象声明为敞亮必须小心了。
const foo = {};
// foo添加属性
foo.prop = 123;
foo.prop // 123
// 将foo指向另一个对象,就会报错
foo = {}; // TypeError: 'foo' is read-only
const arr = [];
arr.push('Hello');
a.length = 0;
a = ['Dave'] // 报错,可以操作不能再赋值
如果想冻结对象本身,需要Object.freeze方法
const foo = Object.freeze({});
// 常规模式下,下面一行代码不起作用,
// 严格模式下,会报错 'use strict'
foo.prop = 123;
对象和对象的属性都冻结的函数,递归
var cosntantize = (obj)=>{
Object.freeze(onj);
Object.keys(obj).forEach((key,i)=>{
if(typeof(obj[key]==='object'){
cosntantize(obj[key]);
})
})
}
# 2、顶层对象的属性
浏览器中指window对象,在Node中指global对象,ES5之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a //1
a = 2
window.a // 2
顶层对象的属性与全局变量挂钩,被认为是JacaScript语言设计的最大败笔,有几大弊端
1、没法在编译时就报出变量未声明的错误,只有在运行时才知道(全局变量可能是又顶层对象的属性创造,而属性创造是动态的)
2、程序会因为手写失误,不自觉创造一个全局变量
3、顶层对象的属性到处可以读写,不利于模块化编程
4、window是一个有实体概念的对象指的就是浏览器窗口对象,但是说顶层对象是有实体的对象是不合适的
ES6为了改变这一点,但是又兼顾兼容性,规定var和function声明的全局变量,依然是顶层对象的属性,另一方面规定let、const 、class命令声明的全局变量,不属于顶层对象的属性,从ES6开始,全局变量将逐渐与顶层对象属性脱钩。
var a = 1;
// 如果Node的REPL环境,可以写成global.a
// 或者this.a
window.a // 1
this.a // 1
let b =1;
window.b // undifined
this.b // undifined
# 3、globalThis对象
Javascript语言存在一个顶层对象,它提供全局环境(即全局作用域), 所有代码都是在这个谎精中运行,但是顶层对象在各种实现里面是不统一的
比如
- 浏览器里面,顶层对象是window
- 浏览器和Web Worker里面,self也指向self
- Node里面,顶层对象是global,其他环境不支持
为了同样的代码都能取到同样的顶层度喜庆,现在一般用this变量,但是有局限。
全局环境中,this会返回顶层对象,但是Node模块和ES6模块中,this返回的是当前的模块。
函数里面的this,如果函数不是做为对象的方法运行,而是单纯做为函数运行,this会执行顶层对象,但是,严格模式下,会返回undifined。
不管是严格模式,还是普通模式,
new Function('return this')()
,总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval
、new Function
这些方法都可能无法使用。综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一 (typeof window!=='undifined' ? 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
-- 2020 - 2 - 23 10:58pm