let & var 区别

201 阅读6分钟

变量提升:当浏览器开辟出供代码执行的栈内存后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var/function关键字的进行提前的声明和定义,即变量提升机制。

  • 带var的只是提前声明:如果只声明不赋值,默认值为undefined;
  • 带function的不仅声明还定义:准备来说就是创建函数并且让函数与开辟的堆内存地址关联;
  1. let和const不存在变量提升机制,但是在词法解析阶段也可以得知变量是否为私有变量;

    6种创建变量的方式中,var和function存在变量提升,而let/const/class/import不存在这个机制。

    console.log(a)   //输出:undefined
    var a = 1;
    //因为变量提升阶段声明了a只是未赋值,赋值前输出a则为undefined
    
    console.log(a)  //报错 Uncaught ReferenceError: Cannot access 'a' before initialization
    let a = 1;
    //let不存在变量提升,在初始化前不能使用
    
    console.log(a); //报错  Uncaught ReferenceError: a is not defined
    
  2. var允许重复声明,而let不允许;

    相同作用域(执行上下文)中:

    • 如果使用var/function声明变量,且重复声明,是不会有影响的(再声明第一次之后就不再重复声明了)
    • 如果使用let/const就会报错,因为浏览器会校验当前作用域中是否已经存在该变量,如果已经存在,再次声明就会报错
    console.log(a)
    let a = 1;
    console.log(a)
    let a = 2;     //报错 Uncaught SyntaxError: Identifier 'a' has already been declared
    console.log(a)
    /*
    *  在浏览器开辟占内存供代码自上而下执行前,不仅有变量提升,还有很多别的操作:形参赋值,此法解析等
    *  词法解析(词法检测):检测当前将要执行的代码是否会出现‘语法错误(SyntaxError)’,如果出现错误,代码将不会执行,第一行也不会执行
    */
    
    //重复声明:不管之前通过什么方法,只要当前栈内存中存在该变量,使用let/const重再复声明该变量就是语法错误
    var a = 1;
    let a = 2;   //报错 Uncaught SyntaxError: Identifier 'a' has already been declared
    console.log(a);
    
  3. let能解决typeof检测时出现的暂时性死区问题(let比var更严谨);

    console.log(typeof a);   //undefined
    //浏览器bug,本应该报错因为没有a(暂时性死区)
    
    console.log(typeof a); //报错 Uncaught ReferenceError: Cannot access 'a' before initializatio
    let a;
    
  4. let创建的全局变量没有给window设置对应的属性;

    //全局作用域不带var:相当于给全局对象window设置了一个属性
    a = 10;    // 相当于 window.a = 10;
    console.log(a);  //10
    console.log(a==window.a)  //true
    console.log("a" in window)  //true
    
    //带var:在全局作用域下声明一个变量(全局变量),同时相当于给window增加了一个对应的属性(只有全局作用域具备这个特点)
    var b = 5;
    console.log(b);   //5
    console.log(window.b);  //5
    
    let c = 3;
    console.log(c);  //3
    console.log(window.c); //undefined
    
  5. let会产生块级作用域;

console.log(a) //undefined
var a = 12;
function fn(){
	console.log(a)   //undefined
	var a = 13;
}
fn()
console.log(a)   //12
//输出结果:undefined  undefined  12
console.log(a)  // undefined
var a = 12;
function fn(){
	console.log(a) //12
	a = 13
}
fn()
console.log(a)  //13
//输出结果: undefined   12   13
console.log(a)  //报错 Uncaught ReferenceError(引用错误): a is not defined
a = 12; 
function fn(){
	console.log(a)
	a = 13
}
fn()
console.log(a)

/* 去掉第一行后 */
a = 12;   //在全局作用域下,带var/function声明的全局变量相当于给window设置了对应的属性(既是全局变量也是属性),不带var声明的只是给window设置了对应的属性,不是全局变量(eg:a=12)。如果使用的是let/const声明的,只是全局变量,没有给window设置对应的属性(eg:let a=12;)。
function fn(){
	console.log(a)   //12
	a = 13
}
fn()
console.log(a)   //13
// 如果去掉第一行console后输出结果: 12  13
console.log(a)  //报错 Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 12;
function fn(){
	console.log(a)
	let a = 13;
}
fn()
console.log(a)

/*  去掉第一行依然报错  */
let a = 12;
function fn(){
    /* 形参赋值 & 变量提升 & 词法解析
     * 报错原因:词法解析时已经知道在当前私有栈中有let a,此时出现的a都是私有的
     * 在当前作用域下(全局、私有、块作用域),如果创建变量使用的是let/const等,一定不能在创建代码的前面使用这些变量,否则会报错Uncaught ReferenceError: Cannot access 'a' before initialization
     */
	console.log(a)    //报错 Uncaught ReferenceError: Cannot access 'a' before initialization
	let a = 13;
}
fn()
console.log(a)


let a = 12;
function fn(){
	console.log(a)    //12
}
fn()
console.log(a)  //12
//let所在大括号是一个块作用域(私有作用域)
if(1===1){
	var a = 12;   //没有块作用域,全局
    let b = 13;   //有块作用域,私有
}
console.log(a)   //12
console.log(b)   //报错 Uncaught ReferenceError: b is not defined

区别这么多,我想去静静。来点经典练习题看看是否真正掌握let和var的特点以及它们的区别吧!

//练习1
var foo=1;
function bar(){
    //不管条件是否成立都要进行变量提升
    if(!foo){//!undefined->true
        var foo=10;
    }
    console.log(foo);   //10
}
bar();

//练习2
let foo=1;
function bar(){
    if(!foo){//!1->false
        let foo=10;
    }
    console.log(foo);   //1
}
bar();

//练习3
let foo=1;
function bar(){
    if(foo){//1->true
        let foo=10;
        console.log(foo);  //10
    }
    console.log(foo);   //1
}
bar();
//练习4
let n=12;
~function(){
    if(1){
        let n=13;
    }
    console.log(n);  //12
}();

//练习5
let n=12;
~function(){
    if(1){
        n=13;
    }
    console.log(n);  //13
}();
console.log(n); //13

//练习6
let n=12;
~function(){
    let n=0;
    if(1){
        n=13;
    }
    console.log(n);  //13
}();
console.log(n); //12
//练习7
/* 不管条件是否成立,都要进行变量提升
 * 第一步:var a;  //全局声明a相当于window.a  
 */
if(!("a" in window)){ //("a" in window) -> true -> !true = false -> 条件不成立
    var a = 1;
}
console.log(a);  //undefined

arguments:函数内置的实参集合,箭头函数中没有arguments,不管是否定义了形参,也不管传递了多少实参,arguments中包含所有传递的实参信息(类数组集合);

  • 在js非严格模式下,arguments和形参存在映射关系(一个改都会跟着改);
  • 严格模式下,arguments和形参的映射机制就切断了
/*	全局作用域
 *	1.变量提升  
 *      var a; function b(){}————关联堆内存AF0
 * 	2.代码执行
 *		var a = 4;   // a=> 4
 *		function b(){..}  //变量提升阶段已处理,不做处理
 *		a = b(1,2,3);   //执行b函数(传1,2,3),将函数返回值赋值给a——————————执行3
 */
var a = 4;
function b(x,y,a){
    /*	3.b函数执行形成私有作用域AF1
     *		3.1形参赋值 & 变量提升
     *			x:1  y:2  a:3
     *		3.2代码执行
     */
    //在js非严格模式下,arguments和形参存在映射关系(一个改都会跟着改)
    //console.log(arguments);   //{0:1,1:2,2:3,length:3...}
    console.log(a); //3
    arguments[2] = 10;//把传递的第三个实参修改为10,形参也跟着改为10
    console.log(a);//10
}
a = b(1,2,3);  //a = b的执行结果,无return,也就是a = undefined
console.log(a);//undefined


//严格模式下,arguments和形参的映射机制就切断了
"use strict";
function b(x,y,a){
    arguments[2] = 10;
    console.log(a);//3
}
b(1,2,3);