【你不知道的javascript上】3. 第四章 提升,var的声明提升,let 的暂时性死区,var / function / let / const 的区别

预警:【你不知道的javascript】开头的文章,是用来总结书籍《你不知道的Javascript》中我不熟悉的知识,单纯的用来巩固学到的知识,和加深记忆。

可能有些地方我理解的不透彻,所以不能够保证内容的正确性,欢迎指正。

前提为什么 js 中会有明明提升?

这是因为 js 是一门解释性语言,在执行代码之前会对代码进行扫描,进行代码解析和预处理,在这个编译的阶段会对所有 var 声明的变量 和函数声明提升到作用于的顶端,但是变量的赋值不会被提升。

重点知识

  1. var 声明的变量,会提升至全局作用域名/函数作用域 的顶部
    1. 不存在块级作用域,只有全局/函数作用域
    2. 允许重复声明
    3. 只提升声明,并且会初始化为 undefined
    4. 不提升赋值
    5. 实际步骤是
      1. 创建变量,在内存中开辟空间【会提升】
      2. 初始化变量,值为 undefined【会提升】
      3. 真正的赋值【不提升】
  2. function 函数声明会提升到全局作用域名/函数作用域的顶部
    1. 函数声明会被提升
    2. 函数名和 var 变量同名时,保留函数的声明
    3. 函数表达式不会被提升,具名函数表达式也不会被提升
      1. 函数表达式是指:var fn = function(){} 这样声明的
      2. 意思是函数表达式的声明,只提升了那个函数名的变量【也就是fn】,值会被初始化为 undefined
    4. 实际步骤
      1. 创建变量,在内存中开辟空间【会提升】
      2. 初始化函数【会提升】
      3. 真正的赋值,就是函数体【会提升】!!,但是函数表达式不会被提升
    5. 注意 return 语句并不会影响函数的提升,函数内容 return 之后,如果还有 var 声明,那么这个声明依旧会提升,只不过赋值语句不会执行,请看下面两个例子
    6. var name = 1
      function test() {
          console.log(name)
          return
      }
      test() // 打印 1
    7. var name = 1
      function test() {
          console.log(name)
          return
          var name = 2
      }
      test() // 打印 undefined
  3. 函数声明优先于 var 变量,但是函数和 var 变量重名时,函数声明有效
  4. let / const 声明的变量, 在声明之前使用,会出现暂时性死区
    1. 会存在块级作用域,就是可以使用花括号 { } 单独隔起来
    2. 提升到块级作用于的顶端
    3. 不允许重复声明
    4. const 声明的不允许改动
    5. 报错,所以暂时性死区 和 未定义的报错是不同的,所以不能说 let 没有提升,只能说它的初始化没有提升
      1. 暂时性死区:Cannot access 'a' before initialization
        1. var name = 1
          function test() {
              console.log(name)
              let name = 2
          }
          test() // Cannot access 'name' before initialization
      2. 未定义:a is not defined
        1. function test() {
              console.log(name)
          }
          test() // name is not defined
    6. 实际步骤是
      1. 创建变量,再内存中开辟空间【会提升】
      2. 初始化变量,值为 undefined【不会提升】,所以有暂时性死区
      3. 真正的赋值【不提升】

代码实践

光说不练假把式,还是需要看你个输出结果的题目来巩固一下,关于命名提升这块,常见的面试题目就是给你一段代码,让你写出他的输出,下面有几个例子:

示例一

var getName = function () { console.log(1);};
function getName() { console.log(2);}
getName()

 你猜猜这三行代码会打印啥?

答案是【打印1】,想不到吧

  1. 编译阶段
    1. 提升 var 声明的变量,并赋值为 undefined【其实这样也叫函数表达式,因为给他赋值成了一个函数】
    2. 提升 getName函数声明,并覆盖同名的变量
  2. 执行阶段
    1. 给全局的 getName 重新赋值为【function () { console.log(1);};】
    2. 打印1

示例二

var getName
function getName() { console.log(2);}
getName()

那你再看看这个打印什么?

结果是打印 2

因为这里在执行阶段没有给 getName 赋值的操作 

示例三

var getName = 1
function getName() { console.log(2);}
getName()

 再看看这个呢?

答案是报错【getName is not a function】

这是在执行阶段给 getName 重新赋值为了 1,很显然1 不是一个函数,所以报错

万事具备,看一下下面这个比较难的题目

示例四


function Foo() {
    getName = function () { console.log(1); };
    return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}

Foo.getName();
getName();
Foo().getName();
getName();

这个例子不算简单,因为他涵盖了关于命名提升、this 指针、原型这三方面的知识【这是我在面试美团的时候遇到的面试题,没答上来,面试也没过~】

来解释一下

  1. 编译阶段
    1. 声明了函数 Foo,把整个代码提升到全局作用域顶部
      1. 虽然本来就在顶部
      2. 注意,函数的提升,没有赋值这一说【因为没有等号 】,但是有初始化这一说,初始化就是函数的主代码,肯定是跟着函数名一块提升的
      3. 而且这个时候不需要考虑函数内部的任何代码
    2. 使用 var 声明了全局变量 getName,提升到全局作用域顶部并初始化为 undefined【注意这里赋值没有提升,会方法后面执行阶段执行,到时候千万别忘了】
    3. 声明了函数 getName,需要提升到全局作用域顶部
      1. 但是和全局变量重名了,所以保留函数的声明
      2. 这个时候只有一个全局的名为 getName 的函数了,没有值为 undefined的 getName 变量了
  2. 执行阶段
    1. 给 getName 赋值为【 function () { console.log(4);};】的函数
      1. 在编译阶段结束,全局有一个名字为 getName 的函数
      2. 但是在代码的第7行,有一个赋值操作不能遗忘了,编译阶段只提升了声明,这个赋值没有执行
      3. 所以运行到这里会把全局的 getName 函数再更新为打印4
      4. 这个实在太重要了,总是忘掉
    2. 调用Foo.getName()
      1. Foo 是已经命名提升了的全局函数
      2. 注意是调用 Foo的属性 getName,而不是调用函数 Foo
      3. Foo 有自定义的 getName属性,所以【打印2】
    3. 调用 getName
      1. 此时是调用全局的 getName 函数
      2. 所以会【打印4】
      3. 注意现在全局没有名字为 getName 的变量了,只有一个名为 getName 的函数,因为同名的提升,保留函数的
    4. 调用Foo().getName()
      1. Foo 是已经命名提升了的全局函数
      2. 这句代码是先运行 Foo,然后调用它返回值的 getName 函数
      3. Foo 里面给 getName 赋值,会在作用域链上查找名字为 getName 的变量或着函数,此时会找到全局 getName 的函数【这个时候全局getName 是打印4】
      4. 把全局名为 getName 的函数重新赋值为打印 1
      5. 返回 this,这个 this 是调用 Foo 的位置的执行上下文,这里就是全局【注意this 指针的执行取决于调用位置】
      6. 下一步相当于调用 this.getName(),也就是运行全局的getName【打印1】
    5. 调用 getName
      1. 这个时候就是单纯的调用全局的 getName方法了【打印1】

所以这个实例的打印结果是2、4、1、1

示例五


function Foo() {
    getName = function () { console.log(1); };
    var getName = function() {console.log(6)}
    getName()
    return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}

Foo.getName();
getName();
Foo().getName();
getName();

把上面的例子稍作改动,在 Foo 里面增加了 2 行代码

打印结果是【2、4、6、4、4】

在调用 Foo 函数的时候,没有修改全局的 getName,因为 Foo 函数内部有使用 var 声明的 getName 也会在编译阶段【注意是函数的编译阶段】然后后面修改和调用的都是函数内部的 getName 变量。全局的 getName 没有收到影响

示例六

var x = 1, y = 0, z = 0
var add = function (x) {
    return x = x + 1
}
y = add(x)
function add(x) {
    return x = x + 3
}
z = add(x)
console.log(x, y, z)

 打印结果是 【1, 2,2】

知识点,函数的 return 语句中的赋值表达式返回的是一个值,而且函数内部的 x 是局部变量。

示例七

(function(){
    var x = y = 1;
 })();
 var z;
 
 console.log(y);
 console.log(z); 
 console.log(x);
 

  打印结果是 【1, undefined,x is not defined】

知识点,立即执行函数中,未被 var 声明的变量,会提升到全局,所以y 是全局变量。

var 声明多个变量,是使用 逗号隔开的。

总结

关于 js 中命名提升的知识点是很重要的,感觉只要搞懂这篇文章中的几个例子,一般的问题就都能理解了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值