js中const,var,let区别

今天第一次遇到const定义的变量,查阅了相关资料整理了这篇文章。主要内容是:js中三种定义变量的方式constvarlet的区别。[1]

1.const定义的变量不可以修改,而且必须初始化。

const b = 2;//正确
// const b;//错误,必须初始化 
console.log('函数外const定义b:' + b);//有输出值
// b = 5;
// console.log('函数外修改const定义b:' + b);//无法输出 

本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

下面是另一个例子。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

 

 

2.var定义的变量可以修改,如果不初始化会输出undefined,不会报错。

var a = 1;
// var a;//不会报错
console.log('函数外var定义a:' + a);//可以输出a=1
function change(){
a = 4;
console.log('函数内var定义a:' + a);//可以输出a=4
} 
change();
console.log('函数调用后var定义a为函数内部修改值:' + a);//可以输出a=4

同时,var声明的变量允许子级作用域访问,但不允许父级作用于访问:

var a = 1;
function test() {
    console.log(a);//输出:1
    var b = 2;
}
test();//调用函数,输出1
console.log(b);//错误

================================

//如果想获取b的值,可通过返回值获取
function test() {
    var b = 2;
    return b;
}
var c = test();
console.log(c);//输出2

3.let是块级作用域,函数内部使用let定义后,对函数外部无影响。

let c = 3;
console.log('函数外let定义c:' + c);//输出c=3
function change(){
let c = 6;
console.log('函数内let定义c:' + c);//输出c=6
} 
change();
console.log('函数调用后let定义c不受函数内部定义影响:' + c);//输出c=3

 

Let的详细解析

众所周知,在ES6之前,声明变量的关键字就只有var。var 声明变量要么是全局的,要么是函数级的,而无法是块级的 [2]

var a=1;
console.log(a);  //1
console.log(window.a);  //1


function test(){
  var b=2;
  function print(){
    console.log(a,b);
  }
   print();
}
test();  //1  2
console.log(b);  //Uncaught ReferenceError: b is not defined


for(var i=0;i<=10;i++){ 
    var sum=0; 
    sum+=i; 
} 
console.log(i);  //11
console.log(sum);  //10  声明在for循环内部的i和sum,跳出for循环一样可以使用。

再来看看下面这个栗子:

HTML:
<ul>
    <li>0</li>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
JS:
window.onload = function(){
     var aLi = document.getElementsByTagName('li');
     for (var i=0;i<aLi.length;i++){
          aLi[i].onclick = function(){
          alert(i);
     };     
}

这是一道很经典的笔试题,也是很多初学者经常犯错而且找不到原因的一段代码。想要实现的效果是点击不同的<li>标签,alert出其对应的索引值,但是实际上代码运行之后,我们会发现不管点击哪一个<li>标签,alert出的i都为4。因为在执行for循环之后,i的值已经变成了4,等到点击<li>标签时,alert的i值是4。在ES6之前,大部分人会选择使用闭包来解决这个问题,今天我们使用ES6提供的let来解决这个问题。接下来就看看let的神奇吧。

window.onload = function(){
    var aLi = document.getElementsByTagName('li');
    for (let i=0;i<aLi.length;i++){
        aLi[i].onclick = function(){
            alert(i);
        }
    };     
}

有看出什么区别吗?奥秘就在for循环中var i=0变成了let i=0,我们仅仅只改了一个关键字就解决了这个问题,还避免了使用闭包可能造成的内存泄漏等问题。

上述代码中的for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中, 事实上它将其重新绑定到了循环的每一个迭代中, 确保使用上一个循环迭代结束时的值重新进行赋值。

后面就让我们好好来了解一下let这个神奇的关键字吧。

let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。  ----《你所不知道的JavaScript(上)》P32

上述代码,可以通过另一种方式来说明每次迭代时进行重新绑定的行为: 

window.onload = function(){
    var aLi = document.getElementsByTagName('li');
    for (let i=0;i<aLi.length;i++){
        let j = i;
        aLi[j].onclick = function(){
            alert(j);
        }
    };     
}

在这里还有个点要说明的,就是 for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。 

这就很好理解上面这段代码的意思了。每次循环体执行的时候,let声明的变量 j 会从父作用域(循环语句块)取值保存到自己的块级作用域内,由于块级作用域内的变量不受外部干扰,所以每次循环体生成的块级作用域相互独立,各自保存着各自的 j 值。


来看一下 let 和 var 的一些异同吧。

1、函数作用域 vs 块级作用域

function varTest() {
  var x = 31;
  if (true) {
    var x = 71;  // same variable!
    console.log(x);  // 71
  }
  console.log(x);  // 71
}

function letTest() {
  let x = 31;
  if (true) {
    let x = 71;  // different variable
    console.log(x);  // 71
  }
  console.log(x);  // 31
}

可以看出在letTest函数的 if 判断中重新声明的x并不会影响到 if 代码块之外的代码,而varTest函数中用var声明的却会。这是因为let声明的变量只在代码块(通常是{ }所形成的代码块)中有效。

2、变量提升 vs 暂时性死区

我们都知道,var声明的变量会有变量提升的作用,如下

console.log(a);  //1
var a=1;

console.log(b);  //undefined
var b;

可以看出,虽然代码中console调用a在前,声明a在后,但是由于在js中,函数及变量的声明都将被提升到函数的最顶部,也就是说(var声明的)变量可以先使用再声明。

然后,使用let,const(后面会提及)声明的变量却不存在变量提升。

console.log(foo); // Uncaught ReferenceError: foo is not defined
let foo = 2;

console.log(foo1); // Uncaught ReferenceError: foo1 is not defined
let foo1;

ES6明确规定,如果区块中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。所以在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

注:“暂时性死区”也意味着typeof不再是一个百分之百安全的操作,因为会使typeof报错。

3、let不允许重复声明

 

 

if (true) {
  let aa;
  let aa; // Uncaught SyntaxError: Identifier 'aa' has already been declared
}

if (true) {
  var _aa;
  let _aa; // Uncaught SyntaxError: Identifier '_aa' has already been declared
}

if (true) {
  let aa_;
  var aa_; // Uncaught SyntaxError: Identifier 'aa_' has already been declared
}

let不允许在相同作用域内,重复声明同一个变量。

 

4、全局变量 vs 全局对象的属性

ES5中全局对象的属性与全局变量基本是等价的,但是也有区别,比如通过var声明的全局变量不能使用delete从 window/global ( global是针对与node环境)上删除,不过在变量的访问上基本等价。

ES6 中做了严格的区分,使用 var 和 function 声明的全局变量依旧作为全局对象的属性,使用 letconst 命令声明的全局变量不属于全局对象的属性。

 

let let_test = 'test';
console.log(window.let_test);   // undefined
console.log(this.let_test);   // undefined

var var_test = 'test';
console.log(window.var_test);  // test
console.log(this.var_test);  // test

 

 

原文出处:

[1] 奔跑的铃铛js中const,var,let区别https://www.cnblogs.com/ksl666/p/5944718.html

[2] 李某龙let和const----你所不知道的JavaScript系列(2)http://www.cnblogs.com/slly/p/9234797.html

 

JS 使用const声明常量的本质(很多人都有误解)https://www.cnblogs.com/minigrasshopper/p/9144223.html

posted @ 2018-12-10 10:56  ryelqy  阅读(718)  评论(0编辑  收藏  举报