陌上寒

陌上寒个人博客

javascript作用域之块级作用域

昨天对函数作用域进行了简单介绍

简单回顾

看一个栗子

let d = 4;

(function(c) {
  let a = 1,
    b = 2
  foo2()

  function foo2() {
    let a = 11
    foo3()

    function foo3() {
      console.log(`a:${a}`); //=>11
      console.log(`b:${b}`); //=>2
      console.log(`c:${c}`); //=>3
      console.log(`d:${d}`); //=>4
      console.log(`e:${e}`); //Uncaught ReferenceError: e is not defined
    }
  }
})(3)

上面函数就是下面的简写形式

let d = 4;

function foo1(c) {
    //相同部分省略
}
foo1(3)

当然也可以这么写

  let d = 4;
  (function (c) {
    let a = 1,
      b = 2;
    (function () {
      let a = 11;
      (function () {
        console.log(`a:${a}`); //=>11
        console.log(`b:${b}`); //=>2
        console.log(`c:${c}`); //=>3
        console.log(`d:${d}`); //=>4
        console.log(`e:${e}`); //Uncaught ReferenceError: e is not defined
      })()
    })()
  })(3)

理论上是可行的,但代码可读性太差
函数创建后立即执行,并传入一个参数=>3
《javascript作用域之块级作用域》
变量的查找是从内向外逐层查找,如果到最外层依然找不到就会抛一个not defined的错
这样就形成了一个作用域链,内层同名变量会覆盖外层的,(有没有想到原型链?)

块级作用域

先普及一个概念:
ECMAScript和JavaScript关系:
ECMAScript是一个国际通过的标准化脚本语言。可以简单理解为:ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展。

什么是块级作用域?

任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。

在es6标准出来之前,javascript是不存在块级作用域的
一个栗子

{
  var name = "郭靖";
  console.log(name);//=>郭靖
}
console.log(name);//=>郭靖

再看一个不那么hello world的栗子

function foo() {
  var hisName = "郭靖"
  for (var i = 0; i < 10; i++) {
    // 啥也不干
  }
  console.log(i);//=>10
  console.log(hisName);//=>"郭靖"
}
foo();
//console.log(i);//=>Uncaught ReferenceError: i is not defined
console.log(hisName); //=>Uncaught ReferenceError: hisName is not defined

通过上面的栗子我们可以发现
在foo内部,在for循环外部,成功的打印出i,这说明使用var声明的变量是不存在块级作用域的
在foo外部,我们打印i或者hisName,都会报Uncaught ReferenceError的错误,i和hisName是foo内部的私有变量,这说明使用var声明的变量存在函数作用域

我们上面介绍了js块级作用域的概念,两个花括号内部定义的内容,外部无法访问的,而javascript又存在函数作用域,
好了,我们可以通过javascript的函数作用域来模拟实现块级作用域
还是上面的栗子

function foo() {
  (function () {
    var hisName = "郭靖"
    for (var i = 0; i < 10; i++) {
      // 啥也不干
    }
  })()
  console.log(i);//Uncaught ReferenceError: i is not defined
  console.log(hisName);//上面报错,这里就不会执行,这里就算执行,也会报错
}
foo();
// console.log(i);//=>Uncaught ReferenceError: i is not defined
// console.log(hisName); //=>Uncaught ReferenceError: hisName is not defined

这不正是我们昨天讨论的内容吗?立即执行函数,函数执行后,内部变量会被销毁(闭包情况暂不考虑,后面的文章会详细介绍),所以外部就无法访问啦
所以又说到昨天的内容了,为了防止命名冲突我们一般这么写

(function () {
  var herName = "黄蓉"
})()
console.log(herName);//Uncaught ReferenceError: herName is not defined

一个简易块级作用域诞生了

ES6中块级作用域

我们现在写代码,如果项目允许使用ES6,那就很少会有人通过函数作用域来实现块级作用域了,因为ES6支持块级作用域

let

说起let,就会情不自禁的将let和var进行比较,关于let和var的介绍的文章多如牛毛,不再赘述
说几个明显的区别

var变量声明提升

看几个栗子,有助于理解

hisName = "郭靖"
var hisName
console.log(hisName);

undefined?
最后输出“郭靖”
上面的代码经过js引擎编译处理,变成了这样

var hisName
hisName = "郭靖"
console.log(hisName);

继续看栗子

console.log(hisName);
var hisName = "郭靖"

最后输出“undefined”
上面的代码经过js引擎编译处理,变成了这样

var hisName
console.log(hisName);
hisName = "郭靖"

javascript代码并不是一行一行往下执行的,分为2个步骤:

  • 编译(词法解释/预解释)
  • 执行

理解了没?如果理解了,继续看一个栗子

var hisName = "郭靖";

function hero() {
    console.log(hisName);
    var hisName = "洪七公";
}
hero();
console.log(hisName);

先不关心输出什么,我排一下顺序

var hisName = "郭靖";

function hero() {
  var hisName
  console.log(hisName);
  hisName = "洪七公";
}
hero();
console.log(hisName);

如果顺序排队了,基本就没什么问题
执行hero函数,打印hisName,声明了,但是没有复制,所以是undefined
到这里,你可能会有一个疑问,js存作用域链,hero内部的hisName是undefined,它不会继续像外层作用域继续查找吗?
当然不会啦,只要声明过,如果没赋值,就存在一个默认值=>undefined
hero()后面的console.log(hisName);//=>郭靖,为什么?
因为函数作用域的作用,hero内部的变量,外部无法访问(暂不考虑闭包),变量首先查找的是同级作用域,同级作用域不存在就会继续向外层查找,但不会向内层查找
通过这三个栗子,我相信,你已经搞懂了javascript的变量提升

let块级作用域

前面说在ES6标准出来之前,是不存在块级作用域的,需要函数作用域进行配合模拟实现块级作用域
let 存在块级作用域,let 不存在变量提升(必须先声明,声明前不能进行赋值等相关操作)

{
  hisName = "郭靖";//在这里级报错了Uncaught ReferenceError: hisName is not defined
  let hisName
  console.log(hisName);
}

再来一个栗子

let hisName = "黄药师";
let herName = "黄蓉"; {
  let hisName = "洪七公"; {
    let hisName = "郭靖";
    console.log(hisName);//=>郭靖
    console.log(herName);//=>黄蓉
  }
  console.log(hisName);//=>洪七公
}
console.log(hisName);//=>黄药师

输出也没什么悬念

//=>郭靖
//=>黄蓉
//=>洪七公
//=>黄药师
let 暂时性死区

暂时性死区,听起来好迷糊,其实没什么,
因为let不存变量提升,使用let命令声明变量之前,该变量都是不可用的
其实还是关于变量提升的问题

const?

关于const在块级作用域上,和let保持一致,不在此赘述

END

发表评论

电子邮件地址不会被公开。 必填项已用*标注