javascript 变量的声明与提升

ゞ 浴缸里的玫瑰 2022-11-21 01:25 343阅读 0赞

变量声明

  • 不使用关键字(作用域链机制)
  • 使用 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对象上的属性。(这种查找机制称为作用域链机制
  2. 全局作用域下本质是操作 window 的属性

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

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

  1. /* 栗子1 全局作用域中 */
  2. console.log('a' in window); // false 其本质是属性,不是变量,无变量提升(区分与 `var`, `function` 声明的变量)
  3. a = 0;
  4. window.a; // 0
  5. /* 栗子2 全局函数中 */
  6. function func(){
  7. b = 1;
  8. }
  9. func();
  10. console.log(window.b); // 1
  11. /* 栗子3 嵌套函数 */
  12. function func1(){
  13. function func2(){
  14. c = 2;
  15. }
  16. func2();
  17. }
  18. func1();
  19. console.log(window.c); // 2
  20. /* 栗子4 若查找到 window 的属性都未找到该变量,直接使用会报错 */
  21. console.log(d); // Uncaught ReferenceError: d is not defined
  22. function func3(){
  23. console.log(d);
  24. }
  25. 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. /* 栗子1 */
    2. console.log(value);
    3. var value = 8;
    4. console.log(value);
    5. // js进行变量提升后实际的执行顺序是
    6. var value;
    7. console.log(value);
    8. value = 8;
    9. console.log(value);
    10. // undefined 8
    11. /* 栗子2 */
    12. console.log(v1);
    13. var v1 = 100;
    14. function foo() {
    15. console.log(v1);
    16. var v1 = 200;
    17. console.log(v1);
    18. }
    19. foo();
    20. console.log(v1);
    21. // js进行变量提升后实际的执行顺序是
    22. var v1;
    23. console.log(v1);
    24. v1 = 200;
    25. function foo(){
    26. var v1; // var 声明的变量不能跨函数,且函数内的变量会覆盖掉函数外层的同名变量
    27. console.log(v1);
    28. v1 = 200;
    29. console.log(v1);
    30. }
    31. foo();
    32. console.log(v1);
    33. // 打印结果:undefined undefined 200 100
  • 全局作用域下声明的变量会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)

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

    1. /* 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; } */
    2. function func(){
    3. var a1 = 10, b1 = 10;
    4. var a2 = b2 = 10;
    5. }
    6. func();
    7. console.log(window.a1); // undefined
    8. console.log(window.b1); // undefined
    9. console.log(window.a2); // undefined
    10. console.log(window.b2); // 10

三、function

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

    函数声明有两种形式

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

      var 的变量提升结果相同

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

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

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

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

    1. /* 栗子1 */
    2. func();
    3. if(true){
    4. function func(){
    5. console.log(1)
    6. }
    7. }
    8. else{
    9. function func(){
    10. console.log(2)
    11. }
    12. }
    13. // IE11及以后: error: foo is not a function
    14. // IE10及之前: 2
    15. /* 栗子2 进阶 */
    16. foo = function() { return true;}
    17. bar = function() { return false;}
    18. ~function(){
    19. if(bar() && [] == ![]){
    20. foo = function() { return false;}
    21. function bar(){ return true};
    22. }
    23. }()
    24. console.log(foo);
    25. console.log(bar);
    26. // IE11及以后: Uncaught TypeError: bar is not a function
    27. // IE10及以前: f foo() {return false;}
    28. // f bar() {return false;}
    29. /* 栗子3 块级作用域顶部 */
    30. console.log('块级作用域外:', func2);
    31. {
    32. console.log('块级作用域内顶部', func2); //
    33. function func2() { return true;}
    34. }
    35. // IE11及以后
    36. // 块级作用域外:undefined
    37. // 块级作用域内顶部:f func2() {return true;}
    38. // IE10及以前
    39. // 块级作用域外:f func2() {return true;}
    40. // 块级作用域内顶部: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. /* 栗子1 */
    2. a = 1;
    3. let a;
    4. // Uncaught ReferenceError: Cannot access 'a' before initialization
    5. /* 栗子2 */
    6. function func(){
    7. a = 1;
    8. let a;
    9. }
    10. func(); // Uncaught ReferenceError: Cannot access 'a' before initialization
  • 不可重复定义(重复定义会报错(在所有代码执行前报错))

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

    1. /* 栗子1 */
    2. let a = 10;
    3. console.log(a);
    4. let a; // Uncaught SyntaxError: Identifier 'a' has already been declared
    5. /* 栗子2 */
    6. let b = 10;
    7. console.log(b);
    8. {
    9. let b;
    10. let b; // Uncaught SyntaxError: Identifier 'b' has already been declared
    11. }
    12. /* 栗子3 函数声明时也会报错,而不是延迟到函数执行时才报错 */
    13. let c = 10;
    14. console.log(c);
    15. function f(){
    16. let c = 10;
    17. let c; // Uncaught SyntaxError: Identifier 'c' has already been declared
    18. }
    19. /* 栗子4 */
    20. let can = 1;
    21. console.log(can); // 1
    22. console.log(d); // Uncaught SyntaxError: Identifier 'd' has already been declared
    23. let d = 10;

五、const

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

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

    1. /* 栗子1 */
    2. console.warn(a);
    3. const a = 1;
    4. // Uncaught ReferenceError: Cannot access 'a' before initialization
    5. /* 栗子2 */
    6. function func(){
    7. console.warn(a);
    8. const a = 1;
    9. }
    10. func(); // Uncaught ReferenceError: Cannot access 'a' before initialization

发表评论

表情:
评论列表 (有 0 条评论,343人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JavaScript变量声明提升

    变量提升 JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这