javaScript 详解作用域

本文章使用代码

文章的讲解跟着这段代码走的。大概先瞄一眼,不用记,要看的时候滑上来看就好了。

//作用域A
var a = 1;
function funB() {
    //作用域B
    var b = 2;
    function funC() {
        //作用域C
        var c = 3;
        console.log(a, b, c);// 1 2 3
    }
    function funD() {
        //作用域D
        console.log(a, b);// 1 2
        console.log(c);//c is not defined
    }
    funC();
    funD();
}
funB();

作用域

概念:作用域管理着变量和函数的可访问范围,哪个位置可以访问哪些函数或变量都由它把关。

正文开始了

代码是自上而下运行的,会跟着你的书写顺序运行,那它在运行的时候,也需要像人脑一样记录运行到该处时候,有什么变量,有几个函数,叫啥,值是多少。我们用来记录的叫脑子,它们用来记录的叫 执行环境,在 javaScript 中也叫 执行上下文

既然使用脚本语言,我们就先不去纠结它在二进制的世界里是什么样的了。我们就用简单的形式来说明

举例说明。我们把执行环境当成一台电脑。当我们有一堆的文件的时候,我们会创建文件夹,对这些文件进行分类存放(没这么做的同学,怕是没有遇到文件找不到,搜索要半天的绝望)。作用域就是相当于文件夹,而变量和函数名就是文件夹中的文件。

  1. 执行环境 == 电脑
  2. 作用域 == 文件夹
  3. 变量和函数 == 文件夹中的文件

我们在看小说之前,会先瞄一眼,“容老夫先看看,这个小说更新到第几话了”。执行环境也是一样,代码在运行之前会有一个查看代码的过程,把出现的变量和函数先进行预声明。

我们把执行过程分成 声明阶段执行阶段,也就是我们的“瞄一眼”和"认真看"。

声明阶段,关于赋值的操作啊什么的,都不管,就记住一个变量和函数名。执行阶段才会去细看对这个变量做了什么,这个函数干嘛用的。


开始运行

第一步:作用域A在 声明阶段 时,执行环境的心理:

var a = 1;  //可以,你要创建一个变量叫a,干嘛用的我先不管
function funB(){...};  //要创建一个函数叫funB是吧,好,我先记着

类似于
在这里插入图片描述
是吧,干嘛用的我先不管,先创好放着。

注意,函数被执行的时候会在当前作用域下,创建出一个新的作用域,也就是本文例子中的作用域B。

第二步:作用域A在 执行阶段 时,执行环境的心理:

a = 1;  // 给变量a赋值1。妥
funB(); // 然后执行funB函数,创建作用域B

类似于
在这里插入图片描述
接下去进入到了作用域B中,重复第一步跟第二步。最终形成如下图
在这里插入图片描述
上诉内容就是作用域的生成过程,而 作用域链 呢,就是整条路径
在这里插入图片描述
当我们在某个作用域中,需要获取某个变量或者函数的时候。

首先会在当前的作用域中查找是否存在该变量,如果没有,它根据它的 原型链 向上一级作用域查找,依次类推,直到找到该变量后返回或者找不到该变量后抛出异常。


分析一下

问题1:在作用域C中访问变量a,b,c是怎样的过程?

先看看 在作用域C中访问变量c:

  1. 先查找当前作用域中有没有变量c。
  2. 哇,找到了。找到就用它了。

再来看看 在作用域C中访问变量b:

  1. 先查找当前作用域中有没有变量b。
  2. 没有啊,那我看看我的原型链,根据原型链向上查找,所以我要去作用域B里看看。
    在这里插入图片描述
  3. 我来到作用域B了,可以,找到变量b了。就用它了。

最后看看 在作用域C中访问变量a:

  1. 上面说的够明白了,找到就用,找不到看原型链换个地方找,我不写了。打字手疼

问题2:为什么在作用域C和在作用域D中访问变量c,结果会不一样?

作用域C的原型链:
在这里插入图片描述
作用域D的原型链:
在这里插入图片描述
根据问题1,动手试一下,如果作用域D中可以访问到c,请扣我。

问题3:经典问题之面试必考题—变量提升

不管你信不信,你把代码改成如下,还可以正常运行

//作用域A
var a = 1;
funB();
function funB() {...}

咦,前面我们不是说好了,代码自上而下运行的吗,为何在funB函数声明之前调用它,还可以用呢?

以这段代码跟着上诉教程再走一遍,你会发现这问题背后的本质。

有些东西没必要全讲完,自己动手看看才是王道,学完不用就感觉关注一下,等着回头看吧。

这里点几个 注意事项 就可以了。(划重点,要考)

  1. 声明阶段 函数名和变量名相同时,会以函数名覆盖变量名。
  2. 以向变量赋值函数的形式,在 声明阶段 当成变量处理。
//作用域A
var a;
function a(){};
console.log(a);// 你以为我是函数?对,我就是函数。
--------------------------
function a(){};
var a;
console.log(a);// 你以为我是变量?不,我是函数。
--------------------------
var a = function(){...};// 你这样玩的话,我在声明阶段就当你是一个变量哦

静态作用域和动态作用域

在编译器阶段,作用域分为两种,静态作用域和动态作用域。

与之关联较深的是JS的 闭包 概念,学习是循环渐进的,没必要一锅端。

本章不对其做出相关的讲解,我会在讲解 闭包 时提到,请看后记。


全局作用域和局部作用域

不管是静态作用域还是动态作用域,它们都细分为全局作用域和局部作用域。

全局作用域 就是最最最外面的那层作用域。

在实例中,作用域A就是全局作用域。在javaScript中,全局作用域就是window对象。

也就是说,实例中的作用域A的真实身份就是!! window对象

顺带点一个很有意思的东西。 使用 var 声明的变量,会跑到全局作用域上,也就是说

var a == window.a

于是乎,优雅的网友们忍不住了,“这不是玷污我的作用域吗!”。

于是有了ES6的 let 关键字,由它声明的变量,会形成一个暂时性死区,它会绑定当前的作用域,而不是变到全局作用域上。

局部作用域 也被称为 块级作用域

在javascript中,只要是位于 “{ }” 符号之中,就会形成一个局部作用域。

实例中的作用域B,C,D就是局部作用域。

注意事项

根据变量查找的规则。全局作用域在最后一层,如果把局部作用域中的变量都放到全局变量中,那么就会出现执行效率低的情况,因为查找变量的时候总是跑到最后一层才找到。

好吧,有的人会说,现在的硬件这么强,提升这样一点的执行效率对于前端而言微忽其忽,那我们从代码的简易性来说。

如果把变量的使用放在一个局部作用域中,后续修改的人只需要关注这个作用域的逻辑就可以了。

而如果把变量的使用放到全局作用域中,那不得了,不管是开发的人还是后续修改的人,在使用的时候都要注意,该变量是不是在某个时候被意外的修改成其他样子,或者是在审查代码的时候,需要查看整个逻辑。

当技术在同一水平上时,比的就是那一份简洁易懂的代码。

后记一下

我看了很多大佬的文章。关于作用域的文章中都提到了 [[scope]] 、AO、VO、this指针之类的,为何我闭口不谈呢。

因为我在看《你不知道的javaScript》的时候,也没有明文提到这些东西。。回想起来自己一开始学习作用域的知识时,这些属性除了让我更难理解之外,对后续也没啥帮助(目前而言并没有用上)。。

对于作用域的基本理解,本文章已经足够了。如果要深入理解作用域,就要从编译的角度上来讲了。

后续我会再讲解 js编译原理this指针。请看后记。

人不可能懂得世间万物的,每个人掌握一条路,团队配合才是王道。

我要做的,只是让大家能够基础的学会它,然后你想往哪个方向走,或深入或背离,取决于你们。

对了。有一张画了半天又不舍得放弃的图,想想还是放出来吧。作用域链是 从内到外 的。
在这里插入图片描述
对this指向性问题详解,可以跳转至以下链接

<div v-if="作者有写相应链接">
	<a href="作者写的链接">点击跳转this指向详解</a>
</div>

对于静动态作用域和闭包的详解,可以跳转至以下连接

<div v-if="作者有写相应链接">
	<a href="作者写的链接">点击跳转深入闭包</a>
</div>

js编译原理,可以跳转至以下连接

<div v-if="作者有写相应链接">
	<a href="作者写的链接">点击跳转js编译原理</a>
</div>

跪求点评中。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值