本篇文章在于详细解读JavaScript的作用域,从底层原理来解释一些常见的问题,例如变量提升、隐式创建变量等问题,在和大家一起交流进步的同时,也算对自己知识掌握的记录,方便以后复习

  首先,直接捡干的来,JS作用域大致分为三部分:词法作用域、函数作用域/块作用域、闭包。

  在传统的编译语言中,程序的源代码编译由三个步骤组成:词法分析、语法分析、代码生成。而JS属于动态语言,它的编译过程不发生在构建之前,而是在代码执行前(一般只有几微妙,甚至更短),简单说,任何JS代码执行前都要编译,编译完通常马上就要执行。

  例如: var a = 2; 将其分解为以下步骤:

  1.遇到   var a  编译器会询问作用域是否已经存在同名变量于同一个作用域的集合中,若存在,则忽略该声明。若不存在,编译器在当前作用域声明一个新变量a。

  2.接下来编译器会为引擎生成运行时的代码,这些代码用于处理  a = 2的赋值操作。引擎运行时会询问作用域,当前作用域是否存在变量a,若存在,引擎直接使用该变量。否则引擎继续向上查找,直到顶层全局作用域还未找到,则会抛出ReferenceError,如果找到,就会将2赋值给它。

  在上面的例子中,引擎有两种查询方式:LHS、RHS。

  其中L、R代表“左”、“右”,是相对于赋值操作的左右,当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询,也可以这么理解,RHS查询是找到某个变量的值,而LHS是找到变量的容器本身!!!即作用域中开辟的变量存放空间。举个例子:如下代码   console.log(a); 引擎对a的查询就是RHS,这里没有赋值操作,需要查找a的值,并把它传给console.log(..);函数。在逐级向上查找中,直到全局也没找到,则抛出ReferenceError。但LHS若没找到是不会抛出错误的。具体原因继续看。

  举个例子:  a= 2; 这里对a的引用则是LHS引用,我们并不关心但前值是多少,只是想要为 =2 这个赋值操作找到合法目标,可能有童鞋疑问,=2不就是赋给a的嘛?对啊,但是a到底存不存在呢?在当前作用域中,我们是不知道是否创建了a的存储空间的,如果作用域中存在 var a ,那么该a的存储空间存在,LHS能成功,但是没有a的存储空间呢?也就是a并未创建呢?此时,LHS也不会抛出错误,而是隐式的在当前作用域(全局作用域、即最高层作用域,一层一层找上去的)为我们创建变量a的存储空间,然后把 =2 赋值给a。这也就是为啥 var a =2; 创建的是局部变量,而没有 var 申明的变量是全局变量的原因。

  作用域的嵌套

  

  作用域就是一套如何存储和查找变量的规则。在嵌套作用域中,如上图,在foo()中无法找到变量 b,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或者到达最顶层(全局作用域)。上图想要执行console.log()函数,就要对b进行RHS查找,得到其值。才foo中无法完成b的RHS,但在外层中却可以完成。即:引擎从当前的执行作用域中开始查找变量,找不到,则逐级向上查找,直到最顶层作用域。

  区分LHS和RHS

  区分两种查询方式很重要,因为上文简单提到RHS找不到会抛出ReferenceError,而LHS则不会,它会隐式创建所需的变量。如下               

  对b的RHS查找失败,因为没有声明(创建)变量b,未声明的变量,在任何作用域中都无法找到!那么,在上图中,只要把 b= 2放在console.log()之前,函数就成功执行了,因为第一步执行 b= 2;赋值操作进行LHS,找不到,则在全局中隐式创建变量b,此时使用 window.b 是可以得到2的。

词法作用域

  词法作用域就是定义此法阶段的作用域,即你写代码时将变量和作用域写在哪里而决定其作用范围。作用域在查找到第一个标识符时即停止查找,多层嵌套的作用域中同名的标识符,内部遮蔽外部(遮蔽效应),全局遮蔽可用window.得到其值,而局部遮蔽的则无论如何都无法被访问到。无论函数在哪里被调用,以何种方式调用,其词法作用域都只由被声明时所处的位置决定,即你写下哪他就在哪发挥作用。

  

上图中,全局作用域中只有一个标识符,即foo,函数foo作用域中有三个标识符,即b,bar ,a 。函数bar里面只有一个标识符 c 。其每个标识符处于不同的作用域中,而代码运行时会以他们不同的位置而访问权限不同。这些位置在书写时已经被我们写死了,他们的作用被我们写好了,这就是词法作用域!代码的位置真的被我们“写死了嘛”?接着看

  词法欺骗

  词法作用域由写代码时声明的位置决定,也可以由两种机制来动态改变词法作用域。

  1.  eval()函数。可接受一个字符串为参数,将其中内容视为好像在书写时就在这个地方的代码。可以理解为我在梦中就是高富帅,真实的就连后面的剧情(梦中剧情,哈哈)都是以高富帅为基础开展的,不知道这个比喻贴切不?即就是eval()可以让里面的参数代码段达到书写时就在这个地方的效果。如下:

  

  输出结果为a:5,b:8,而不是a= 2,根据词法作用域中。foo中找不到a,则到上一层作用域中寻找,上一层中找到了 a = 2 ;。可是eval()函数却欺骗了词法作用域,直接将a放在了foo内部,而导致引擎不需要到外层作用域去查找,直接使用 a = 5 ,从而达到此法欺骗。

2.  with语句。with 语句通常用作重复引用一个对象的多个属性的快捷方式。代码如下:

  

  with语句也可以达到欺骗词法的作用,但是副作用也很明显,造成了变量泄露。原因是调用obj2的时候,其没有变量a,进行LHS查询,最后隐式创建全局变量属性a ,导致变量泄露。

  以上两种词法欺骗方式,第一严重影响性能,第二在严格模式下有诸多限制,所以不建议使用。函数作用域和闭包近期在整理,过几天推出

  作者:方红亮  

  博客:https://www.cnblogs.com/fanghl/p/9369414.html

  今天只介绍了作用域和词法作用域,希望对小伙伴理解有所帮助,分享转载的朋友请注明出处,码字不易,谢谢理解!

JS中的作用域(一)-详谈的更多相关文章

  1. 聊一下JS中的作用域scope和闭包closure

    聊一下JS中的作用域scope和闭包closure scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了.我就被这个概念困扰了很久 ...

  2. JS中的作用域以及全局变量的问题

    一. JS中的作用域 1.全局变量:函数外声明的变量,称为全部变量 局部变量:函数内部使用var声明的变量,称为局部变量在JS中,只有函数作用域,没有块级作用域!!!也就是说,if/for等有{}的结 ...

  3. 【详解】JS中的作用域、闭包和回收机制

    在讲解主要内容之前,我们先来看看JS的解析顺序,我们惯性地觉得JS是从上往下执行的,所以我们要用一个变量来首先声明它,来看下面这段代码: alert(a); var a = 1; 大家觉得这段代码有什 ...

  4. JS中的作用域及闭包

    1.JS中的作用域 在 es6 出现之前JS中只有全局作用域和函数作用域,没有块级作用域,即 JS 在函数体内有自己的作用域,但是如果不是在函数体的话就全部都是全局作用域.比如在 if.for 等有 ...

  5. 【授课录屏】JavaScript高级(IIFE、js中的作用域、闭包、回调函数和递归等)、MySQL入门(单表查询和多表联查)、React(hooks、json-server等) 【可以收藏】

    一.JavaScript授课视频(适合有JS基础的) 1.IIFE 2.js中的作用域 3.闭包 4.表达式形式函数 5.回调函数和递归 资源地址:链接:https://pan.baidu.com/s ...

  6. angular.js 中的作用域 数据模型 控制器

    1.angular.js 作为后起之秀的前端mvc框架,他于传统的前端框架都不同,我们再也不需要在html中嵌入脚本来操作对象了.它抽象出了数据模型,控制器及视图. 成功解耦了应用逻辑,数据模型,视图 ...

  7. JS中的作用域和作用域链

    本文原链接:https://cloud.tencent.com/developer/article/1403589 前言 作用域(Scope) 1. 什么是作用域 2. 全局作用域和函数作用域 3. ...

  8. 浅析 JS 中的作用域链

    作用域链的形成 在 JS 中每个函数都有自己的执行环境,而每个执行环境都有一个与之对应的变量对象.例如: var a = 2 function fn () { var a = 1 console.lo ...

  9. JS中的作用域链

    在js中数据的声明方式有两种: 1.用var声明,例如:var num = 10: 2.直接声明,例如:num = 10: 两种声明方式在某些情况下是有区别的: var data = 10; func ...

随机推荐

  1. GoldenGate for bigdata 12.3.2.1版本新特性

    在Oracle Tenical Network(OTN)上已经可以下载到最新的ogg4bd版本,地址如下:http://www.oracle.com/technetwork/middleware/go ...

  2. node.js浅见

    看过很多朋友node.js代码敲得很好,但是对于概念还是很生疏.个人认为,代码是树叶,树干搭起来才是王道. 1.简述node.js的适用场景: IIO密集而非计算密集的情景:高并发微数据(比如账号系统 ...

  3. Java锁与CAS

    一.加锁与无锁CAS 在谈论无锁概念时,总会关联起乐观派与悲观派,对于乐观派而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事,但对于悲观派而已,他们总会认为发 ...

  4. 自制操作系统Antz(11)——实现shell(下)命令响应

    我已经规范了系统代码风格,类似于按照linux分包,把各部分功能区分开了 Antz系统更新地址 Linux内核源码分析地址 Github项目地址 在之前的任务中,我们已经通过直接操作显卡驱动完成了简单 ...

  5. mac pycharm快捷键

    Mac键盘符号和修饰键说明 ⌘ Command ⇧ Shift ⌥ Option ⌃ Control ↩︎ Return/Enter ⌫ Delete ⌦ 向前删除键(Fn+Delete) ↑ 上箭头 ...

  6. python面试问题集锦

    GIL(全局解释器锁) 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因. 1.python语 ...

  7. 阿里云centos怎么用xshell5登陆

    第一种是用ssh,安装Xshell5   打开XShell   新建会话输入ip   选择新建的会话,点击连接,选择接受并保护,输入root,点击确定   输入密码   已经连接成功了,用Xshell ...

  8. 彻底清除 Windows 服务

    如果服务已经停止, 或从注册表中删除, 但是在任务管理器中仍能看到服务躺在列表里面. 只需要找到服务的PID, 然后运行命令: taskkill /PID 服务的PID /f 即可.

  9. thinkphp应用目录不可写,目录无法自动生成! 请手动生成项目目录~

    一是 要把 html下的相关目录设置成 777 chmod -R 777 /var/www/html/..... 二是 要设置 selinux , 或者是 临时 关闭 selinux: setenfo ...

  10. 阿里云盾SSL证书即将到期怎么办?

    如果你也像我一样用了一个有效期限1年的DV证书,颁发厂商:Symantec.那当这个证书快到期的时候要怎么更换证书呢?1.证书安装环境是IIS82.下载重新购买的免费证书,对新证书进行重新先发.3.证 ...