第二十节: ES6变量声明

2020-10-24  本文已影响0人  时光如剑
1. 前言: 理解ES6 与 ECMAScript 2015 的关系

2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。

ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。

因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,



ECMAScript的历代版本:
1997 ECMAScript 1.0

1998 ECMAScript 2.0

1999 ECMAScript 3.0

3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。

2000 ECMAScript 4.0

这个版本最后没有通过但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是 2000 年。

2007 年 10 月

ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本,以 ,Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动,以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。

2008 年 7 月

由于争议太多中止 ECMAScript 4.0 的开发,小幅的改进后,发布发布为 ECMAScript 3.1,激进的部分放到以后版本,会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。

2009 年 12 月,

ECMAScript 5.0 版正式发布。ES5 与 ES3 基本保持兼容,有争议的部分放在了next版本中

2011 年 6 月,ECMAScript 5.1 版发布

2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能

2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论 期,听取各方反馈。

2015 年 6 月,ECMAScript 6 正式通过,2000至今15年

ECMAScript 6于2015年6月正式发布,成为企业级开发语言,又称ES2015。

ES6细枝末节很多(因为大家知道,JS是一个千疮百孔的语言,所以ES6在定稿的时候就特别细致,细枝末节极多)

一. let 命令

1. ES5 的变量定义

定义变量(声明变量)

var a = 12;
1.1 var 的变量的特性
  1. 会发生变量提升

    var a = 12;
    function fn(){
        alert(a);  // undefined
        var a = 5;
    }
    fn()
    
  1. ES5 作用域只用全局和函数内部作用域

    for(var i = 0;i < 10; i++){
        // TODO
    }
    
    // 突然有一天我想弹出i,这个i是几
    alert(i)
    

变量提升,这个我们之前讲这就是JS语言的特性,后来就有人提议,说这种特性不好,我不知道我程序发生了什么事情,同时for循环的循环变量i会污染全局变量,比较讨厌

2. ES6 变量定义

为了解决ES5 变量声明的问题,所以在ES6 新增了两个定义变量的关键词

let, 就相当于之前的var

const 常量,定义好了以后就不能改变了

我们先来看看let;

2.1 基本用法

跟使用es5的var一样

let a = 12;
console.log(a);

3. 关于ES6 变量

3.1 变量提升的问题

之前讲var的时候有变量提升,那么let, const 是否具有变量提升呢

let a = 12;
function fn(){
    alert(a);  // 这里当然能用了,这不就是作用域的查找吗
}
fn();

// 那么如果我在函数内定义一个let a; 如下
let a = 12;
function fn(){
    alert(a);  // 报错,这个时候报引用错误
    let a = 5;
}
fn();
// Uncaught ReferenceError: a is not defined
// 报错信息是 a 没有并定义

所以let,const不会进行变量提升,必须先定义在使用

官方的称发是在变量没有let前的所有区域叫TDZ,暂时性死区

所以必须先定义在使用.用来规范大家编程行为

let会引发暂时性死区(面试常考):

在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

var m = 10;
function fun(){
    m = 20;  //报错。函数在预解析阶段会预读所有的语句,发现了let语句,所以就将这个函数变为了一个m的暂时性死区,此时m不允许在let前被赋值。
    let m;

    console.log(m);
}

fun();
3.2. 块级作用域

我们都知道 在语句中 {}叫做块,

我们都知道ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

比如:

我们刚讲过的for循环的循环变量污染全局

var s = 'hello';
for (var i = 0; i < s.length; i++) {  
  console.log(s[i]);
}
console.log(i); // 5

还有就是内层变量可能会覆盖外层变量。

var tmp = 123;

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

这也是为什么我们需要块级作用域的原因

但ES6 let const 所定义的变量具有块作用域

if(true){
    var a = 12;
}
alert(a);  // 能用  之前讲的作用域只有全局和函数作用域,什么if for 在全局就当全局用

// 如果此时换成let
if(true){
    let a = 12;
    // 接下来只能在这里使用
}
alert(a);

到了es6 里面

{
    // 块里面就具有了作用域,叫块级作用域
}
3.3 同一个变量多次定义的问题

之前咱们说过变量至少要var一次吧;但是我们多次var一个变量也没有问题

// ES5
var a = 1;

var a = 5;
alert(a);    // 弹出5

// ES6 的年代
let a = 1;
let a = 5;
alert(a);  // 报错
//Uncaught SyntaxError: Identifier 'a' has already been declared
3.4 关于for循环的循环变量问题

我们之前讲for循环的时候,是不是讲循环变量是当前作用域的

for(var i =0 ;i < 10; i++){
    
}
alert(i);   // 这个时候i指定是一个10

那么用let定义的for循环呢,会有哪些不同呢

// 如果我们使用let呢
for(let i =0 ;i < 10; i++){
    console.log(i);  // 在循环里面是可以正常使用的
}
alert(i);   // 报错: 报一个引用错误
//Uncaught ReferenceError: i is not defined
// i未定义

// 如果我们在循环体内在let i呢
for(let i =0 ;i < 10; i++){
    let i = "abc";  // 感觉应该会报错,重复定义嘛,其实不是
    // 可以理解()中的i是父级作用域,循环体内i是子级作用域,互不干扰
    console.log(i);  // 在循环里面是可以正常使用的
}
alert(i);   // 报错: 报一个引用错误
//Uncaught ReferenceError: i is not defined
// i未定义
3.5 ES6 允许块级作用域的任意嵌套。

也就是扩展父子级的块作用域

{
    let a = 12;
    console.log(a);  //12
}
console.log(a); // Uncaught ReferenceError: d is not defined

// 如果我开心我是不是可以在块作用域中在加一个块作用域啊
{
    let a = 12;
    {
        let b = 10;
        console.log(b);  // 10
    }
    console.log(a);  //12
}
console.log(a);  // 报错

// 这不叫重复定义,重复定义是不能在同一个作用域中重复定义
// 如果你愿意包含多少层作用域都可以.但是没什么意义,无论你包多少层,外边又访问不到,只有里面能访问到

let 特点:

  1. 没有预解析,不存在变量提升

    官方的称发是在变量没有let前的所有区域叫TDZ,暂时性死区

    所以必须先定义在使用.用来规范大家编程行为

  2. 不能重复定义变量

  3. for循环里比较特殊,可以将()中的循环变量理解为父级作用域中的变量,循环体重的变量理解为子作用域中的变量,所以他们let同一个变量不会报错

还记得我们讲for循环里面函数的问题吗

var arr = [];
for(var i = 0; i < 10; i++){
    arr[i] = function(){
        console.log(i);   // 这里我们是不是希望,数组第几项函数执行,打印几啊
    }
}
arr[6]();  //10  结果你发现所有的打印都是10

// 有些小伙伴说我代码没有问题,是的,代码真没有什么问题,这是这么语言特性造成的

// 这是不是就是我们之前讲的函数闭包造成的问题啊

// 这个时候我们换成let去定义
var arr = [];
for(let i = 0; i < 10; i++){
    arr[i] = function(){
        console.log(i);   // 你发现这个时候就符合我们的预期了
    }
}
arr[6]();  // 6

示例:

<input type="button" value="aaa"/>
<input type="button" value="bbb"/>
<input type="button" value="ccc"/>

<script>
    var aInput = document.querySelectorAll("input");
    
    for(var i = 0 ;i < aInput.lenght; i++){
        aInput[i].onclick = function(){
            console.log(i)
        }
    }
</script>

4. const 声明常量

4.1 const 基本使用

const声明一个只读的常量。

const: 特性和let一样

只是const定义的变量不能修改,因为人家叫常量,所以不能修改.

let a  = 12;
a = 20;
console.log(a);  // 20

有些人可能会说.老师修改怎么了

但是实际开发中有些我是不希望它修改的,比如一些配置文件

const root = 12;
console.log(root);  // 12 你发现能正常使用

// 一旦更改
root = 20;  // 直接报出 错误, TypeError 类型错误
//Uncaught TypeError: Assignment to constant variable.
// 你把一个常量转换到变量的错误
4.2 声明变量赋初值问题

const还有一个特点平时我们喜欢先定义变量,在赋初值

var a;
a = 10;
console.log(a);   // 10  没什么问题

let a;
a = 20
console.log(a);   // 20  let 也没什么问题

// 但是const有问题
const a;
a = 10;
console.log(a);  // 报错  语法错误
//Uncaught SyntaxError: Missing initializer in const declaration 
//缺少初始值在常量定义时

也就是说cons定义时必须赋值,不能后赋值,后赋值也是修改,不能修改

如果只是定义不赋值也会报错.

const a;
console.log(a);  // 报错  语法错误
//Uncaught SyntaxError: Missing initializer in const declaration
//缺少初始值在常量定义时
4.2 const 无提升
const a = 12;
function fn(){
    alert(a);  // 报错,这个时候报引用错误
    const a = 5;
}
fn();
// Uncaught ReferenceError: a is not defined
// a未定义

有人说不能修改真的假的,看个例子

const arr = ["apple","banana"];
//arr = [];
//console.log(arr);  // 报错
// Uncaught TypeError: Assignment to constant variable.

// 哎 我们学过push修改数组
arr.push("wuwei");
console.log(arr);  //["apple", "banana", "wuwei"]
// 发现可以为什么呢

因为数组本来就是引用类型,他们是引用关系,

如果真想定义一个动都不能然它动的,那么对象身上有个方法Object.freeze(),冻结的意思

const arr = Object.freeze(["apple","banana"]);
arr.push("orange");
console.log(arr);  // 这个时候报类型错误
// Uncaught TypeError: Cannot add property 2, object is not extensible
// 不能添加属性,对象是不可扩展的

这不是const的问题,这是对象的特性问题

总结: const的特点
  1. const`一旦声明变量,就必须立即初始化,不能留到以后赋值。
  2. 一旦声明,常量的值就不能改变。

5.变量的其他问题

5.1自执行函数

IIFE

(function(){
    // TODO
})();

// 因为之前只有函数作用域

// 现在有块作用域了
// 可以使用
{
   // TODO 
}
5.2. 关于顶层对象属性与全局变量

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。

为了解决这个问题,es6引入的letconstclass声明的全局变量不再属于顶层对象的属性。

而同时为了向下兼容,var和function声明的变量依然属于全局对象的属性

var a = 1;
window.a // 1

let b = 1;
window.b // undefined

hhhhhhhhhhh

上一篇下一篇

猜你喜欢

热点阅读