ES6(2)—— let和const和var

一、var声明变量存在的问题

  • 可以重复声明同一个变量
var a = 1;
var a = 2;
console.log(a);

这时,后者的声明会把前者覆盖,没有任何的警告或者错误提示,输出a时,结果为2。

  • 无法限制修改

有的值时不变的,比如PI=3.1415926......,而var不是一个常量

  • 没有块级作用域
if(true)
{
	var a = 1;
}
console.log(a);

这时在if里面声明的变量,if外面也可以用。

  • 存在变量提升。变量可以在声明之前使用。

二、let命令:它所声明的对象只在代码块内有效。它的特点如下:

  • 不存在变量提升现象

变量在使用之前必须声明,如果未声明使用,则会报出错误:ReferenceError。

  • 暂时性死区

只要在块级作用域内存在let命令,他所声明的变量就“绑定”在这个区域,不再受外界环境的影响。这里要注意的是,在没有let之前,使用typpeof查看变量类型是百分之百安全的,但是如下操作也会报错:

typeof x;
let x;            //ReferenceError
  • 不允许重复声明

let不允许在同一个区域内重复声明同一个变量。

//会报错:SyntaxError: Identifier 'a' has already been declared.
function a(){
    let a = 1;
    let a = 10;
}
  • 只作用于块级作用域
//这时if里面的输出正常,if外面的输出会出现报错:ReferenceError: a is not defined。const和它完全一样。
if(true)
{
    let a = 1;
    console.log(a);
}
console.log(a);

三、块级作用域

1、为什么需要块级作用域

ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多场景不合适。

(1)内层变量可能会覆盖外层变量

//原因在于变量提升,内部的tmp覆盖了外部的tmp。
var tmp = new Date();
function f(){
    console.log(tmp);
    if(false){
        var tmp = 'hello world';
    }
}
f();             //undefined

(2)用来计数的循环变量泄露为全局变量

//下面代码中的i除了用来控制循环以外,循环结束后,它并没有消失,而是泄露成了全局变量
var s = 'hello';
for(var i=0; i<s.length; i++)
{
    console.log(s[i]);
}
console.log(i);         //5

2、ES6的块级作用域

let为JavaScript新增了块级作用域

(1)ES6允许作用域的任意嵌套

{{{{{let insane = 'hello world'}}}}};

(2)内层作用域可以定义与外层作用域的同名变量

{{{{
    let insane = 'hello world';
    {let insane = 'hello world';}
}}}}

3、块级作用域与函数声明

ES5规定,函数只能在全局作用域和函数作用域中声明,不能在块级作用域中声明。ES6引入了块级作用域,明确允许函数在块级作用域中声明,在块级作用域中,函数声明语句和let类似,在块级作用域之外不可引用。

function f(){ console.log('I am outside'); }
(function(){
    if(false){
        function f() { console.log('I am outside!'); }
    }
    f();
}());

//ES5环境下,实际运行的代码如下:
function f(){ console.log('I am outside'); }
(function(){
    function f() { console.log('I am outside!'); }
    if(false){
       
    }
    f();           //结果是:I am outside!
}());

但是上述代码在ES6环境下运行会出现错误,原因如下:日光改变块级作用域内声明函数的处理规则,显然会对代码产生很大的影响。为了减轻因此产生的不兼容问题,ES6中规定,浏览器可以不遵循上面的规定,而有自己的行为方式。(只对ES6浏览器实现有效,其他环境的实现不遵循,仍旧将块级作用域的函数声明当作let处理即可)具体如下:

(Ⅰ)允许在块级作用域内声明函数

(Ⅱ)函数声明类似于var,即会提升到全局作用域或函数作用域的头部

(III)同时,函数声明还会提升到所在的块级作用域的头部

考虑到环境导致的行为差异很大,应该避免在块级作用域内声明函数。如果确实需要,应该写成函数表达式的形式,而不是函数声明语句。

另外,ES6的块级作用域允许声明函数的规则只在大括号内成立,如果没有大括号,就会报错。

//报错
if(true)
    function(){}

//正确写法
if(true)
{
    function(){}
}

4、块级作用域实际上是一个语句,将多个操作封装在一起,没有返回值。现在有一个提案,使得块级作用域可以变为表达式,既可以有返回值,办法就是在块级作用域之前加上do,使他变为do表达式。

let x = do{
let f = f();
t * t + 1;
}

//这里的x会得到整个块级作用域的返回值。

5、块级作用域(以闭包为例)

看如下代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="button" value="按钮1">
    <input type="button" value="按钮2">
    <input type="button" value="按钮3">
    <script>
        var btn = document.getElementsByTagName("input");
        for(var i=0; i<btn.length; i++){
            btn[i].onclick = function(){
                alert(i);
            }
        }
    </script>
</body>
</html>

此代码运行的结果就是,点击三个按钮,弹出的结果都是3,那么我们的解决方法如下:

方法一(以前的解决方法):

var btn = document.getElementsByTagName("input");
for(var i=0; i<btn.length; i++){
    (function(i) {
          btn[i].onclick = function () {
               alert(i);
          }
     })(i);
 }

方法二(利用ES6解决):利用这种方法只需要修改for循环里面的变量i的类型,从var修改为let,因为let只作用于块级作用域。

var btn = document.getElementsByTagName("input");
     for(let i=0; i<btn.length; i++) {
         btn[i].onclick = function () {
              alert(i);
         }
     }

四、const命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。意味着,const一旦声明就必须立即赋值,不然会报错。

1、const特点如下:

(1)const与let相似,只在声明所在的作用域内有效。

(2)const声明的变量也不会提升,同样存在暂时性死区,只能在声明后使用。

(3)与let一样,声明的变量不可重复声明。

(4)const声明的变量不能修改。

let a = 1;
a = 2;
console.log(a);
const b = 3;
b = 4;
console.log(b);

//输出的时候,a就正常输出,而会对b = 4这一行进行报错:TypeError: Assignment to constant variable。

2、const不能修改变量值的本质

const实际上保证的并不是变量的值不得改动,而是变量指向的哪个内存地址不得改动。对于简单的数据类型,值就保存在变量指向的内存地址中,因此等同于常量。但对于复合数据类型(主要是对象和数组),变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,置于它指向的数据结构是不是可变的,这是不可控制的。因此将对象声明为常量时,要非常小心。

(1)修改指针所指的内容,但是不能修改变量指向内存地址中所存的指针:

const a = [];
a.push('hello'); //可执行
console.log(a);
a.length = 0;    //可执行
console.log(a);
a = ['Dave'];   //会报错,因为这是在修改变量a指向的内存地址中所存的指针

(2)如果真的想冻结对象,应该使用Object.freeze方法。冻结了对象本身和对象的属性。正常模式添加属性不起作用,严格模式下会报错。看下面例子:

//这是一个将对象彻底冻结的函数
var constantize = (obj) => {
    Object.freeze(obj);
    Object.keys(obj).forEach( (key,i) => {
        if(typeof obj[kry] === 'object'){
            constantize(obj[key]);
        }
    });
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值