javascript 变量的声明与提升

变量声明

  • 不使用关键字(作用域链机制)
  • 使用 var, function 关键字声明变量
  • 使用 let, const 关键字声明变量

变量提升

  • 栈内存(作用域)形成,JS代码自上而下执行之前,JS引擎首先会将所有使用 var / function 关键字的变量进行提前声明定义
  • 浏览器中,var/function 声明的全局变量会与window的属性存在映射机制(会在变量提升阶段在window对象中创建相应的属性,在赋值的同时对window对象对应的属性进行赋值)
  • var 只声明未定义
  • function 声明 + 定义(变量赋值,值为代码字符串在堆中地址(指针))

条件判断下(或块级作用域内)的变量提升

  • 老版本(IE10及之前):
    • 全局(或函数)作用域顶部function 声明 + 定义 提升
  • 新版本(IE11及之后):
    • 全局(或函数)作用域顶部function 只有声明提升,也就是 functionvar 一样了
    • 块级作用域顶部function 声明 + 定义 提升

ES6变量重复检测机制

  • 栈内存(作用域)形成,JS代码自上而下执行之前,JS引擎会对使用ES6语法(let, const)声明的变量进行变量重复检测(函数声明时也会报错,而不是延迟到函数执行时才报错)

ES6的变量声明方法解决了旧版本JS的暂时性死区问题

一、不使用关键字“操作”变量

1. 此时不应该叫变量声明,而应该叫变量的“操作”
2. 不使用关键字来“操作”的变量不是私有变量,会向它的上级作用域查找,看是否为上级作用域的变量,一直找到全局作用域的window对象上的属性。(这种查找机制称为作用域链机制
3. 全局作用域下本质是操作 window 的属性

  • 例如 a = 10 本质是 window.a = 10 的简写
  • 例如 document.getElementById() 本质是 window.document.getElementById() 的简写
  • 其本质是属性,不是变量,无变量提升(区分与 var, function 声明的变量)
  • 注意这种情况会导致全局环境的污染(内存泄漏)

若查找到 window 的属性都未找到该变量,直接使用会报错

/* 栗子1 全局作用域中 */
console.log('a' in window);	// false 其本质是属性,不是变量,无变量提升(区分与 `var`, `function` 声明的变量)
a = 0;
window.a;					// 0

/* 栗子2 全局函数中 */
function func(){
	b = 1;
}
func();
console.log(window.b);		// 1

/* 栗子3 嵌套函数 */
function func1(){
	function func2(){
		c = 2;
	}
	func2();
}
func1();
console.log(window.c);		// 2

/* 栗子4 若查找到 window 的属性都未找到该变量,直接使用会报错 */
console.log(d);				// Uncaught ReferenceError: d is not defined
function func3(){
	console.log(d);
}
func3();					// Uncaught ReferenceError: d is not defined

二、var

  1. 没有块的概念(可以跨块访问,不能跨函数访问)
  2. 未初始化时默认为 undefined
  3. 变量提升
  4. 可重复定义(使用变量提升解释)
  5. 全局作用域下声明的变量会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升

注: 区分 var a = 10, b = 10var a = b = 10

  • 变量提升

    var 声明的变量在js中会经历了两个阶段

    • 编译阶段进行变量声明提升
    • 执行阶段进行赋值

    下面举几个栗子

    /* 栗子1 */
    console.log(value);
    var value = 8;
    console.log(value);
    
    // js进行变量提升后实际的执行顺序是
    var value;
    console.log(value);
    value = 8;
    console.log(value);
    // undefined 8
    
    /* 栗子2 */
    console.log(v1);
    var v1 = 100;
    function foo() {
        console.log(v1);
        var v1 = 200;
        console.log(v1);
    }
    foo();
    console.log(v1);
    
    // js进行变量提升后实际的执行顺序是
    var v1;
    console.log(v1);
    v1 = 200;
    function foo(){
    	var v1;	// var 声明的变量不能跨函数,且函数内的变量会覆盖掉函数外层的同名变量
    	console.log(v1);
    	v1 = 200;
    	console.log(v1);
    }
    foo();
    console.log(v1);
    // 打印结果:undefined undefined 200 100
    
  • 全局作用域下声明的变量会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)

    // window
    console.log('a' in window);	// true	挂载会提升
    var a = 10;
    console.log(window.a);		// 10
    
  • 注: 区分 var a = 10, b = 10var a = b = 10

    /*
    var a = 10, b = 10	这里的 a, b 都是使用 var 声明的变量
    var a = b = 10		这里的 a 是 var 声明的变量,b 没有使用 var 声明,本质是window上的属性
    var a = b = 10 的本质其实是 { b = 10; var a = b; }
    */
    function func(){
    	var a1 = 10, b1 = 10;
    	var a2 = b2 = 10;
    }
    func();
    console.log(window.a1);		// undefined
    console.log(window.b1);		// undefined
    console.log(window.a2);		// undefined
    console.log(window.b2);		// 10
    

三、function

  1. 没有块的概念(可以跨块访问,不能跨函数访问)
  2. 函数提升(function 会进行 声明+定义 提升)
  3. 可重复定义(重写)
  4. 全局作用域下声明的函数会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升
  5. 条件判断下(或块级作用域内)的变量提升
  6. js 函数不可重载
  • 函数提升

    函数声明有两种形式

    • 变量字面量式:var func = function() {}
    • 函数声明式: function func() {}
    • 变量字面量式

      var 的变量提升结果相同

      /* 变量字面量式 */
      console.log(foo);
      var foo = function(){
      	console.log(1)
      }
      // undefined
      
    • 函数声明式

      会进行 声明、定义 提升,且不会被变量声明覆盖(因为已有变量不会被重复声明),但会被变量赋值覆盖

      /* 函数声明式 栗子1  */
      console.log(foo)
      function foo(){
      	console.log(1)
      }
      // ƒ foo(){console.log(1)}
      
      /* 函数声明式 栗子2 */
      // 函数声明+函数定义 提升
      console.log(foo)			// f foo(){ return 1 }
      var foo
      function foo(){ return 1 }	
      
      // 不会被变量声明覆盖(因为已有变量不会被重复声明)
      function bar(){ return 2 }	// 先声明定义
      var bar						// 后声明同名变量
      console.log(bar)			// f bar(){ return 2 } ,函数不会被变量声明覆盖
      
      // 会被变量赋值覆盖
      bar = 10
      console.log(bar)			// 10 函数会被变量赋值覆盖
      
  • 全局作用域下声明的函数会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)

    // window
    console.log('func' in window);	// true 挂载也会提升
    function func(){ return 1 }
    console.log(window.func);		// f func(){ return 1 }
    
  • 条件判断下(或块级作用域内)的变量提升

    /* 栗子1 */
    func();
    if(true){
    	function func(){
    		console.log(1)
    	}
    }
    else{
    	function func(){
    		console.log(2)
    	}
    }
    // IE11及以后: error: foo is not a function
    // IE10及之前: 2
    
    /* 栗子2 进阶 */
    foo = function() {return true;}
    bar = function() {return false;}
    ~function(){
    	if(bar() && [] == ![]){
    		foo = function() {return false;}
    		function bar(){return true};
    	}
    }()
    console.log(foo);
    console.log(bar);
    // IE11及以后: Uncaught TypeError: bar is not a function
    // IE10及以前: f foo() {return false;}
    // 			   f bar() {return false;}
    
    /* 栗子3 块级作用域顶部 */
    console.log('块级作用域外:', func2);
    {
    	console.log('块级作用域内顶部', func2);	// 
    	function func2() {return true;}
    }
    // IE11及以后
    // 块级作用域外:undefined
    // 块级作用域内顶部:f func2() {return true;}
    // IE10及以前
    // 块级作用域外:f func2() {return true;}
    // 块级作用域内顶部:f func2() {return true;}
    
  • js 函数不可重载

    重载:函数名相同,函数参数不同(Java中函数可以重载)
    重写:多用于类中的继承,重写父类的函数

      JS并没有重载,JS可以这样理解, 万物皆对象。var func = function() { alert(1) } 这里的 func 只是对 function() { alert(1) } 的引用。当你重写时,func = function() { alert(2) },这时 func 的引用指向了 function() { alert(2) },调用不到 alert(1) 了,所以JS只有重写。

      Java是强类型语言, 根据传入的参数类型,个数不同等可以找到不同的方法, 所以Java有重载;

四、let

  1. 有块的概念(不可跨块访问)
  2. 未初始化时默认为 undefined
  3. 无变量提升(所以不能在声明前使用,否则会报错)
  4. 不可重复定义(重复定义会报错)
  • 无变量提升(所以不能在声明前使用,否则会报错)

    /* 栗子1 */
    a = 1;
    let a;
    // Uncaught ReferenceError: Cannot access 'a' before initialization
    
    /* 栗子2 */
    function func(){
    	a = 1;
    	let a;
    }
    func();	// Uncaught ReferenceError: Cannot access 'a' before initialization
    
  • 不可重复定义(重复定义会报错(在所有代码执行前报错))

    栈内存(作用域)形成,JS代码自上而下执行之前,JS引擎会对使用ES6语法(let, const)声明的变量进行变量重复检测(函数声明时也会报错,而不是延迟到函数执行时才报错)

    /* 栗子1 */
    let a = 10;
    console.log(a);
    let a;				// Uncaught SyntaxError: Identifier 'a' has already been declared
    
    /* 栗子2 */
    let b = 10;
    console.log(b);
    {
    	let b;
    	let b;			// Uncaught SyntaxError: Identifier 'b' has already been declared
    }
    
    /* 栗子3 函数声明时也会报错,而不是延迟到函数执行时才报错 */
    let c = 10;
    console.log(c);
    function f(){
    	let c = 10;
    	let c;			// Uncaught SyntaxError: Identifier 'c' has already been declared
    }
    
    /* 栗子4 */
    let can = 1;
    console.log(can);	// 1
    console.log(d);		// Uncaught SyntaxError: Identifier 'd' has already been declared
    let d = 10;
    

五、const

  1. 有块的概念(不可跨块访问)
  2. 必须初始化,且初始化后其值不能修改
  3. 无变量提升(同 let,不能在声明前使用,否则会报错)
  4. 不可重复定义(重复定义会报错)
  • 必须初始化

    const a;	// Uncaught SyntaxError: Missing initializer in const declaration
    
  • 无变量提升(同 let,不能在声明前使用,否则会报错)

    /* 栗子1 */
    console.warn(a);
    const a = 1;
    // Uncaught ReferenceError: Cannot access 'a' before initialization
    
    /* 栗子2 */
    function func(){
    	console.warn(a);
    	const a = 1;
    }
    func();	// Uncaught ReferenceError: Cannot access 'a' before initialization
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值