1.什么是作用域(scope)?

简单来讲,作用域(scope)就是变量访问规则的有效范围

  • 作用域外,无法引用作用域内的变量;
  • 离开作用域后,作用域的变量的内存空间会被清除,比如执行完函数或者关闭浏览器
  • 作用域与执行上下文是完全不同的两个概念。我曾经也混淆过他们,但是一定要仔细区分。

JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

函数作用域是在函数声明的时候就已经确定了,而函数执行上下文是在函数调用时创建的。假如一个函数被调用多次,那么它就会创建多个函数执行上下文,但是函数作用域显然不会跟着函数被调用的次数而发生什么变化。

1.1 全局作用域

var foo = 'foo';
console.log(window.foo); // => 'foo'

在浏览器环境中声明变量,该变量会默认成为window对象下的属性。

function foo() {
name = "bar"
}
foo();
console.log(window.name) // bar

在函数中,如果不加 var 声明一个变量,那么这个变量会默认被声明为全局变量,如果是严格模式,则会报错。

全局变量会造成命名污染,如果在多处对同一个全局变量进行操作,那么久会覆盖全局变量的定义。同时全局变量数量过多,非常不方便管理。

这也是为什么jquery要在全局建立变量 ,其余私有方法属性挂在,其余私有方法属性挂在 下的原因。

1.2 函数作用域

假如在函数中定义一个局部变量,那么该变量只可以在该函数作用域中被访问。

function doSomething () {
var thing = '吃早餐';
}
console.log(thing); // Uncaught ReferenceError: thing is not defined

嵌套函数作用域:

function outer () {
var thing = '吃早餐';
function inner () {
console.log(thing);
}
inner();
} outer(); // 吃早餐

在外层函数中,嵌套一个内层函数,那么这个内层函数可以向上访问到外层函数中的变量。

既然内层函数可以访问到外层函数的变量,那如果把内层函数return出来会怎样?

function outer () {
var thing = '吃早餐'; function inner () {
console.log(thing);
} return inner;
} var foo = outer();
foo(); // 吃早餐

函数执行完后,函数作用域的变量就会被垃圾回收。而这段代码看出当返回了一个访问了外部函数变量的内部函数,最后外部函数的变量得以保存。

这种当变量存在的函数已经执行结束,但扔可以再次被访问到的方式就是“闭包”。后期会继续对闭包进行梳理。

1.3 块级作用域

很多书上都有一句话,javascript没有块级作用域的概念。所谓块级作用域,就是{}包裹的区域。但是在ES6出来以后,这句话并不那么正确了。因为可以用 let 或者 const 声明一个块级作用域的变量或常量。

比如:

for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // Uncaught ReferenceError: i is not defined

发现这个例子就会和函数作用域中的第一个例子一样的错误提示。因为变量i只可以在 for循环的{ }块级作用域中被访问了。

扩散思考:

究竟什么时候该用let?什么时候该用const?

默认使用 const,只有当确实需要改变变量的值的时候才使用let。因为大部分的变量的值在初始化之后不应再改变,而预料之外的变量的修改是很多bug的源头。

1.4 词法作用域

词法作用域,也可以叫做静态作用域。意思是无论函数在哪里调用,词法作用域都只在由函数被声明时所处的位置决定。
既然有静态作用域,那么也有动态作用域。
而动态作用域的作用域则是由函数被调用时执行的位置所决定。

var a = 123;
function fn1 () {
console.log(a);
}
function fn2 () {
var a = 456;
fn1();
}
fn2(); //

以上代码,最后输出结果 a 的值,来自于 fn1 声明时所在位置访问到的 a 值 123。
所以JS的作用域是静态作用域,也叫词法作用域。

上面的1.1-1.3可以看做作用域的类型。而这一小节,其实跟上面三小节还是有差别的,并不属于作用域的类型,只是关于作用域的一个补充说明吧。

2. 什么是作用域链(scope chain)

在JS引擎中,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链。

var a = 1;
function fn1 () {
var a = 2;
function fn2 () {
var a = 3;
console.log(a);
}
fn2 ();
}
fn1(); //

console.log(a) 语句中,JS在查找 a变量标识符的值的时候,会从 fn2 内部向外部函数查找变量声明,它发现fn2内部就已经有了a变量,那么它就不会继续查找了。那么最终结果也就会打印3了。

代码分析如下:

<script type="text/javascript">
var a = 100;
function fun(){
var b = 200
function fun2(){
var c = 300
}
function fun3(){
var d = 400
}
fun2()
fun3()
}
fun()
</script>

首先预编译,一开始生成一个GO{

  a:underfined

  fun:function fun(){//fun的函数体

      var b = 200
      function fun2(){
        var c = 300
      }
      function fun3(){
      var d = 400
      }
      fun2()
      fun3()
    }

}

逐行执行代码,GO{

  a:100

  fun:function fun(){//fun的函数体

      var b = 200
      function fun2(){
        var c = 300
      }
      function fun3(){
      var d = 400
      }
      fun2()
      fun3()
    }

}

当fun函数执行时,首先预编译会产生一个AO{

  b:underfined

  fun2:function fun2(){
       var c = 300
     }

  fun3:function fun3(){
      var d = 400
     }

}

这里注意的是fun函数是在全局的环境下产生的,所以自己身上挂载这一个GO,由于作用域链是栈式结构,先产生的先进去,最后出来,

在这个例子的情况下,AO是后于GO产生的,所以对于fun函数本身来说,执行代码的时候,会先去自己本身的AO里找找看,如果没有找到要用的东西,就去父级查找,此题的父级是GO

此刻fun的作用域链是  第0位    fun的AO{}

          第1位    GO{}

fun函数开始逐行执行AO{

  b:200

  fun2:function fun2(){
       var c = 300
     }

  fun3:function fun3(){
      var d = 400
     }

}

注意:函数每次调用才会产生AO,每次产生的AO还都是不一样的

然后遇到fun2函数的执行,预编译产生自己的AO{

  c:underfined

}

此刻fun2的作用域链是第0位    fun2的AO{}

          第1位    fun的AO{}

          第2位    GO{}

然后遇到fun3函数的执行,预编译产生自己的AO{

  d:underfined

}

此刻fun3的作用域链是第0位    fun3的AO{}

          第1位    fun的AO{}

          第2位    GO{}

fun2和fun3的作用域链没有什么联系。

当函数fun2和fun3执行完毕,自己将砍掉自己和自己的AO的联系,

最后就是fun函数执行完毕,它也是砍掉自己和自己AO的联系。

这就是一个我们平时看到不是闭包的函数。

闭包

1.闭包在红宝书中的解释就是:有权访问另一个函数作用域中的变量的函数。

2.写法:

 1 <script type="text/javascript">
2 function fun1(){
3 var a = 100;
4 function fun2(){
5 a++;
6 console.log(a);
7 }
8 return fun2;
9 }
10
11 var fun = fun1();
12 fun()
13 fun()
14 </script>

3.效果如下:

4.分析:

执行代码

GO{

fun:underfined

fun1:function fun1()

   {

     var a = 100;

     function fun2()

    {

        a++;

        console.log(a);

     }

     return fun2;

     }

}

然后第十一行开始这里,就是fun1函数执行,然后把fun1的return返回值赋给fun,这里比较复杂,我们分开来看,

这里fun1函数执行,产生AO{

a:100

fun2:function fun2(){

    a++;
    console.log(a);
    }

}

此刻fun1的作用域链为 第0位   AO

           第1位   GO

此刻fun2的作用域链为 第0位   fun1的AO

           第1位   GO

解释一下,fun2只是声明了,并没有产生调用,所以没有产生自己的AO,

正常的,我们到第7行代码我们就结束了,但是这个时候来了一个return fun2,把fun2这个函数体抛给了全局变量fun,好了,fun1函数执行完毕,消除自己的AO,

此刻fun2的作用域链为 第0位   fun1的AO

           第1位   GO

第十二行就是fun执行,然后,它本身是没有a的,但是它可以用fun1的AO,然后加,然后打印,

因为fun中的fun1的AO本来是应该在fun1销毁时,去掉,但是被抛给fun,所以现在fun1的AO没办法销毁,所以现在a变量相当于一个只能被fun访问的全局变量。

所以第十三行再调用一次fun函数,a被打印的值为102。

JavaScript:作用域与作用域链的更多相关文章

  1. 深入理解 JavaScript 变量的作用域和作用域链

    一个变量的作用域(scope)是程序源代码中定义这个变量的区域.简单的说,作用域就是变量与函数的可访问范围.全局变量拥有全局作用域,在JavaScript代码中的任何地方都有定义.局部变量是在函数体内 ...

  2. 一步步学习javascript基础篇(2):作用域和作用域链

    作用域和作用域链 js的语法用法非常的灵活,且稍不注意就踩坑.这集来分析下作用域和作用域链.我们且从几道题目入手,您可以试着在心里猜想着答案. 问题一. if (true) { var str = & ...

  3. 关于Javascript作用域及作用域链的总结

    本文是根据以下文章以及<Javascript高级程序设计(第三版)>第四章相关内容总结的. 1.Javascript作用域原理,地址:http://www.laruence.com/200 ...

  4. javascript篇-----函数作用域,函数作用域链和声明提前

    在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的(也就是我们不能在代码段外直接访问代码段内声明的变量),我们称之为块级作用域,然而,不同于 ...

  5. javascript笔记:javascript的关键所在---作用域链

    javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于 ...

  6. 浅谈JavaScript中的变量、参数、作用域和作用域链

    基本类型和引用类型 在JavaScript中有两种数据类型值.基本类型值和引用类型值.基本类型值指的是简单的数据段,而引用类型值指的是可能由多个值构成的对象.在JavaScript中有5种基本数据类型 ...

  7. javascript作用域和作用域链摘录

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...

  8. JavaScript高级之词法作用域和作用域链

    主要内容: 分析JavaScript的词法作用域的含义 解析变量的作用域链 变量名提升时什么 一.关于块级作用域         说到JavaScript的变量作用域,与咱们平时使用的类C语言不同. ...

  9. JavaScript 开发进阶:理解 JavaScript 作用域和作用域链

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...

  10. JavaScript 开发进阶:理解 JavaScript 作用域和作用域链(转载 学习中。。。)

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...

随机推荐

  1. odoo开发笔记 -- 用户配置界面如何增加模块访问权限

    在odoo设置界面,点击用户,进入用户配置界面,会看到: 访问权 | 个人资料菜单 在访问权 page菜单界面,可以看到系统预制的一些模块都会显示在这里, 那么,我们自己开发的模块如何显示在这块呢,从 ...

  2. 基于GTK+3 开发远程控制管理软件(C语言实现)系列二 Centos7下开发环境搭建

    一.安装gcc gcc-c++ make等编译工具 yum install gcc gcc-c++ kernel-devel 这一步,其实可以不用做,你在安装Centos7的时候,如果选择开发模式安装 ...

  3. 使用命令执行 sql 脚本文件

    使用命令执行 sql 脚本文件 方法: 在 Windows 下使用 cmd 命令执行(或 Unix 或 Linux 控制台下)[Mysql的bin目录]\mysql –u用户名 –p密码 –D数据库名 ...

  4. Bridge桥接模式(结构型模式)

    现有一个需求,一个游戏系统需要构建不同风格的房屋,暂不考虑其他设计模式,需要能实现在PC端.移动端....等等多个平台的构建.最简单的实现方式如下: /// <summary> /// 房 ...

  5. 全网最全的Windows下Anaconda2 / Anaconda3里Python语言实现定时发送微信消息给好友或群里(图文详解)

    不多说,直接上干货! 缘由: (1)最近看到情侣零点送祝福,感觉还是很浪漫的事情,相信有很多人熬夜为了给爱的人送上零点祝福,但是有时等着等着就睡着了或者时间并不是卡的那么准就有点强迫症了,这是也许程序 ...

  6. 《垃圾回收的算法与实现》——Python垃圾回收

    Python垃圾回收 python采用引用计数法进行垃圾回收 Python内存分配 python在分配内存空间时,在malloc之上堆放了3个独立的分层. python内存分配时主要由arena.po ...

  7. WebMagic实现分布式抓取以及断点抓取

    访问我的博客 前言 从去年到今年,笔者主要负责的是与合作方的内容对接,新增的合作商不是很多的情况下,在我自从去年引入了 WebMagic 这个爬虫框架之后,基本很少需要去关注维护爬虫,做的最多的是新接 ...

  8. linux编译找不到aprt apr-util

    Linux很多地方编译的时候都会用到apr 如果找不到apr就会报错 configure: WARNING: APR not found The Apache Portable Runtime (AP ...

  9. 第一次项目上Linux服务器(六:Nginx安装及相关命令(转))

    1.下载nginx 方法一 wget http://nginx.org/download/nginx-1.11.6.tar.gz 方法二 http://nginx.org/en/download.ht ...

  10. PHP算法------排序

    <?php/** * Created by PhpStorm. * User: 63448 * Date: 2018/5/5 * Time: 22:42 */$arr = [3,1,13,5,7 ...