1. let命令
基本用法
作用:用来声明变量
与var区别:let声明的变量的有效范围是let命令所在的代码块,而var声明的变量是全局范围
-
例子 :
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
一般可以使用在for循环中,如
for(let i = 0;i<10;i++)
for循环的一个特别之处,设置循环变量的部分是父作用域,而循环体内容是一个单独的子作用域
-
例子
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
不存在变量提升
let命令声明的变量不存在变量提升,而var命令声明的变量则会有变量提升,什么是变量提升呢,就是变量在声明之前就可以使用,但是值为undefined。
-
例子
// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError let bar = 2;
暂时性死区
意思就是一变量在某个区域内用let声明,那么在该区域内,任何在let声明该变量之前调用这个变量都会报
ReferenceError
错误-
例子
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
- 比较难看到错误的例子
function bar(x = y, y = 2) { return [x, y]; } bar(); // 报错
之所以报错,是因为x=y执行的时候,y还没有声明,所以把报错
暂时性死区的本质:只要一进入当前作用域,所要使用的变量就已经存在,但是不可获取,必须等到明确声明变量的一行代码出现,才可以获取和使用该变量
不允许重复声明
- let不允许在相同的作用域内,重复声明一个变量,因此不能在函数内部重新声明函数的参数,除非你在函数内部的再加一个子作用域,就可以在自作用域内声明与参数同名的变量。
块级作用域
为什么需要块级作用域
-
只有全局作用域和函数作用域会造成:①内层变量可能会覆盖外层变量 ②用来计数的循环变量泄露为全局变量
- 对于第一种 例子:
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
内层if语句的tmp覆盖了外层时间的tmp
- 对于第二种 例子
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
i变成了全局变量
ES6的块级作用域
-
let创造了块级作用域,使得外层作用域无法读取内层作用域的变量,并且内层作用域可以定义外层作用域的同名变量。
- 例子
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
这个例子,如果都是用var的话,n就会编程全局变量,结果为10
块级作用域与函数声明
- ES6规定,函数可以在块级作用域中声明,并且块级作用域外不能调用该函数。
- 浏览器实现
- 允许在块级作用域内声明函数
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
- 同时,函数声明还会提升到所在的块级作用域的头部
- 其他地方则把块级作用域的函数声明当做let处理
- 最佳实践:由于韩静导致行为差异太大,所以应该避免在块级作用域内声明函数,如果确实需要,那么携程函数表达式形式
- 其他注意事项:ES6 的块级作用域允许声明函数的规则,只在大括号的情况下成立,所以if后面一定要加括号,即使后面只有一句话。
const 命令
作用:声明一个只读的常量。一旦声明,常量的值就不能改变
-
const声明的变量一旦声明就必须马上初始化,不能以后赋值,否则会报错
const foo; // SyntaxError: Missing initializer in const declaration
const与let类似只在声明所在的块级作用域内有效,同样存在暂时性死去,只能在声明的位置之后使用,同样不能重复声明。
本质
-
const实际上保证不是说变量的值不能改动,而是变量指向的那个内存地址所保存的数据不得改动,那么对于简单的数据类型(数值,字符串,布尔值)来说,它们的值就保存在变量所指的地址,因此等同于变量,但是对于函数或数组来说,变量指向的内存地址,保存的知识一个指向实际数据的指针,const只能保证这个指针是固定的,(即总是执行另一个固定的地址),至于它指向的数据结构就可以变化了。例子
const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
简单来说,就是const只能保证变量是指向这个对象或这个数组,这点是不能变化的,但是对象或数据里面的内容则不能管理到。
Object.freeze方法
作用:将对象冻结,使之不能添加新的属性
-
例子
const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;
ES6声明变量的六种方法
六种方法分别是:var
function
let
const
import
class
顶层对象的属性
-
顶层对象,浏览器中指window对象,node中指global对象,ES5中顶层对象的属性与全局属性是等价的 例子:
window.a = 1; a // 1 a = 2; window.a // 2
顶层对象与全局变量等价造成的问题:①没法在编译时就爆出变量未声明的错误②程序员很容易不知不觉就创建了全局变量③顶层对象的属性到处可以读写,不利于模块化编程。
-
ES6将全局变量逐步与顶层对象的属性脱钩:表现在
var
和function
命令声明的全局变量依旧是顶层对象属性,但let
命令,const
命令、class
命令声明的全局变量不属于顶层属性。例子var a = 1; // 如果在 Node 的 REPL 环境,可以写成 global.a // 或者采用通用方法,写成 this.a window.a // 1 let b = 1; window.b // undefined
global对象
global是ES5的顶层对象,但在各种实现里面不统一。
浏览器里面,顶层对象是window,但Node和Web Worker没有window。
浏览器和Web Worker里面,self也指向顶层独享,但是Node没有self
node里面,顶层对象是golbal,但其他环境不支持
而为了一段代码能在各种环境都能取到顶层对象,一般用this变量,但是有它的局限性全局环境中,this会返回顶层对象,但是,node模块和es6模块中,thils返回的是当前模块
函数里面的this,如果不是作为对象的方法运行,而是单纯作为对象运行,this会指向顶层对象,但是,严格模式下,这时this会返回undefined。
-
不管是严格模式,还是普通模式,new Function(‘return’)()总是会返回全局对象。
因此很闹找到一种方法在所有情况下,都取到顶层对象,下面是两种勉强可以使用的方法。// 方法一 (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'); };