什么是作用域

为什么要了解作用域

  作用域首先是变量或者函数的存储规则,很重要,重要性无异于孩子他妈得知道孩子他爸在哪。

什么是作用域

  不是存储区域的名称,是根据变量名称存储和查找变量的一套规则,可以说是一个画圈的算法,把相同作用域得变量画一个圈,父子级的画个内圈。

相关概念

  • 编译器工作的三个阶段:词法分析(间隔成一个个词法单元)、语法分析(根据嵌套规则生成语法树)、代码生成(将代码转换成机器指令并为变量分配内存)。
  • 作用域的作用:一个是编译期间与编译器交互,将声明的变量创建到对应作用域下;另一个是代码执行期间与引擎交互,在对应的作用域查找变量值和对相应作用域下的变量进行赋值。
  • 查找规则:LHS(left-hand-side),LHS可以理解为被赋值的变量应用的查询规则,RHS可以被理解为取值的变量应用的查询规则。两者在查询不到结果时表现不一样,非严格模式下,LHS会将查询不到的变量声明会全局变量,RHS则会抛出异常。

作用域在JavaScript 中和其它部分交互的

   举例 var a = 2 涉及过程:

   1)词法分析阶段:编译器碰到var a这类的声明会向作用域询问是否已存在该变量,然后生 成执行代码。

   2)代码执行过程:引擎运行代码,进行赋值操作,赋值前会从当前作用域开始执行LHS查询(见查找规则)是否又a这个变量,如果没有抛出异常 Uncaught ReferenceError: a is not defined,如果有则进行赋值操作。

问题:JavaScript为什么被设计成解释型语言?

   自己想到原因是,解释型语言方便别人查看和复用。

什么是词法作用域

上面说到了作用域的概念以及涉及的过程,那么词法作用域是什么?

  表面上的意思是词法分析阶段定义得作用域,具体过程是词法分析阶段进行的是有状态的解析过程,给单词赋予语义,此时根据语义和作用域规则得到了词法作用域。

那么问题来了为什么没有语法作用域?

  百度了下,好像真有语法作用域,不过和词法作用域是一个东西,他大舅他二舅都是他就,我们这里暂且称为词法作用域吧。

知道了什么是作用域,也知道了作用域如何和其它部分交互,那么它内部是如何工作的,也就是如何画圈的

  暂且忽略块级作用域,可以认为作用域是根据函数来画圈的,切记这是词法作用域,不是你调用时候画圈的,而是根据函数代码位置来画圈的,千万别掉坑里。

上面的解释准确吗

  除了词法作用域还有动态作用域,大部分情况下是词法作用域,那么什么是动态作用域,动态作用域是相对于词法作用域这种静态作用域的,就是在代码执行阶段改变作用域。


// eval, 特性:动态修改所处作用域

function foo(str, a){

​    eval(str);

​    console.log(a, b); //1 3 

}

var b = 2;

foo("var b = 3;", 1)

: 根据词法作用域,词法分析时并没有b = 3的赋值,它只是一个字符串,那么打印的3肯定是执行代码时赋值的,那么做很有趣,不用听词法分析的指挥了,我们可以随时修改作用域,但是这样做性能损失大,并且在严格模式下并不这样。


// with, 特性:根据参数对象创建一个特殊作用域,该作用域包含参数对象的属性一样的变量标识符,但是该作用域中var声明的变量不限制在当前作用域,而是添加到with所处作用域。

function foo(obj) {

​    console.log(age); // undefined !!!被认为当前作用域的变量提升

​    with(obj){

​        name = "world";

​        sex= "男"

​        var age = '18';

​    }

}

var per = {

​    name: "hello"

};

foo(per);

console.log(per); // {name: "world"}

console.log(sex); // 男 !!!,和普通变量一样。未声明直接赋值被添加到全局变量中。

这么有趣的东西为什么使用频率那么少呢,缺点:

​ 1、在严格模式下表现不一。

​ 2、导致词法分析阶段的优化失效或者编译时碰了欺骗语法干脆不进行优化了。

​ 3、大量的eval和with会导致代码阻塞。
函数作用域和块级作用域都是作用域,两者有相同点,也有区别,ES6中开始有块级作用域。

块级作用域和函数作用域

什么是函数作用域

包含当前函数全部变量的范围。

为什么要有函数作用域

首先要弄清为什么要有函数,函数的目的是隐藏,定义私有变量,最小限度的暴露必要内容,这样做的好处是一方面私有变量更安全,不容易被修改,另一方面是不容易出现命名冲突,这也就是函数作用域的目的。

一、函数是产生“气泡”(即作用域)的一种方式,另外还有块级作用域。

什么是块级作用域

一些关键词{}之间的内容作为了块级作用域的范围,目前来说JavaScript没有真正的块级作用域,不对?es6是有块级作用域的概念了,但不是严格意义的作用域,看下面。

// 支持es6的浏览器下运行
console.log(foo); // undefined 
if(true){
    console.log(foo); // foo ... 我是老大
    function foo(){
        console.log('我是老大')
    }
} else {
    function foo(){
        console.log('我是老二?');
    }
}
console.log(foo); // foo ... 我是老大

第一处输出undefined就很奇怪,这里的意思是有这个声明了但是没赋值,块级作用域的话不应该在这声明,不合理啊。

第二处块级作用域的代码执行完了但是变量并没有销毁。

下面看一下ES5环境下的输出

// es5环境(https://pan.baidu.com/s/1piMP-0E5c-FT24Dy8iNzcQ)
 function bar(){
     console.log(foo)
     if(true){
         function foo(){
            console.log(1)
         }
     } else {
         function foo(){
            console.log(2)
         }
     }
 }
undefined
> bar()
[Function: foo] // 总结一下:就是ES6有了作用域的概念但并不是真的作用域,得配合let 或者 const声明。
undefined
>

为什么要有块级作用域

块级作用域做为函数作用域的扩展,使变量在更近的作用域内使用,增加了代码的可读性。

提升

什么是提升

提升有变量提升和函数提升。变量提升(var 声明),是在变量声明位置的前面打印变量是undefined而不是not defined,就是该变量尚未初始化但是已经声明了;而函数提升(非函数表达式的具名函数)是在函数声明的位置之前就可以调用。

为什么会提升

因为JavaScript中的作用域是词法分析是确定的所以变量和函数执行代码之前已经声明好了,所以看上去就像是提升了。

提升的优先级

同一个作用域中变量和函数声明同时出现了提升,那么函数声明的提升优先级更高,如果有多个变量提升那么后面声明的优先级更高,函数也是如此。

代码块(if、for)中的函数声明的提升并不带函数体,同样是undefined。

这才是闭包

什么是闭包

    这个太重要了,我关注这个概念一年多,今天感觉明白了,之前百度“JavaScript 闭包”,结果给的概念大部分是"通过闭包可以在函数外部能访问到函数内部的变量”,是这个吗?很明显不是,因为这个显然说的是闭包的用途,今天我想从根上刨一下,为什么叫闭包?后来发现数学中的拓扑分支有闭包这个概念,难道。。。。拓扑中的闭包是这样定义的:集合和这个集合的导集的并集,集合都知道是啥,导集的意思是凝聚点构成的集合,凝聚点是啥呢,我理解是集合的边界点,也就是拓扑中闭包的概念是:集合和集合的边界点集合的并集。看MDN上关于闭包的定义:函数和函数所在词法环境的组合,如果把函数当成一个集合,把词法环境当成边界点,那么这不正是拓扑的闭包吗。所以说某个函数是闭包并不准确,闭包还有函数所处的词法作用域,只要创建一个函数都可以说是创建了闭包,尽管它不能被外部访问,能被外部访问的肯定是闭包,因为这个利用了闭包特性。

为什么要用闭包

从定义上分析闭包是函数和函数所处的词法作用域的组合,那么如果我们在某个地调用了函数,那么也就是在这个地访问了对应的作用域,这个地可以是返回值是函数的地方,可以是你能调用这个函数的任意地方。


walker_jiang
130 声望7 粉丝

擅长react、webpack、react-router, 喜欢分享技术,javascript基础扎实