本篇文章在于详细解读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. 3.键盘输入10个数,放到数组中,(1)去除该数组中大于10的数 (2)将该数组中的数字写入到本地文件number.txt中

    package cn.it.text; import java.io.FileWriter; import java.io.IOException; import java.util.Scanner; ...

  2. sourceTree 代码冲突解决

    1.代码提交,有冲突 2. 拉取未同步的代码,勾选用衍合代替合并. 3. 在工作副本,未提交代码显示 4. 冲突代码解决 5. sourcetree页面

  3. MyPython

    目录 Python,那些不可不知的事儿 Python简介 Python环境搭建 从Hello World开始 Python中的数据类型 函数 模块 面向对象 More Python,那些不可不知的事儿 ...

  4. ps top 命令

    pstree :显示进程树 ps: a:查看和终端有关的进程 u:显示进程是哪个用户启动的 x:和终端无关 ps aux |head 进程的分类: 和终端有关 和终端无关 进程状态: D:不可中断睡眠 ...

  5. java窗体

    听完老师所讲的窗体,然后自己就去尝试写代码,结果是窗体出现了,但是就是不能关闭,求解!! package Swing.src.swring; import java.awt.Color;import ...

  6. CF786B Legacy

    思路 线段树优化建图 基本思想就是要把一个区间连边拆成log个节点连边, 然后一颗入线段树,一颗出线段树,出线段树都由子节点向父节点连边(可以从子区间出发),入线段树从父节点向子节点连边(可以到达子区 ...

  7. Bytom Kit开发辅助工具介绍

    Bytom Kit是一款为了帮助开发者更简单地理解Bytom的开发辅助工具,集合了校验.标注.解码.测试水龙头等功能. 该工具用python语言封装了一套比原的API和7个工具方法,如果有开发需求可以 ...

  8. dao层、service和action的运用和区别

    DAO层叫数据访问层,全称为data access object,属于一种比较底层,比较基础的操作,对于数据库的操作,具体到对于某个表的增删改查, 也就是说某个DAO一定是和数据库的某一张表一一对应的 ...

  9. 20165306 Exp5 MSF基础应用

    Exp5 MSF基础应用 一.实践概述 1. 实践内容 本实践目标是掌握metasploit的基本应用方式,重点常用的三种攻击方式的思路.实现: 1.1一个主动攻击实践 ms08-067+window ...

  10. 【python 3】 字符串方法操作汇总

    基础数据类型:str 1.1  字符串大小写转换 所有字母大写 : string.upper() 所有字母小写 : string. lower() 第一个单词的第一个字母大写,其他字母小写 :  st ...