博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
6.彻底搞懂javascript-作用域链
阅读量:7169 次
发布时间:2019-06-29

本文共 4191 字,大约阅读时间需要 13 分钟。

在弄明白什么是词法环境(Lexical Environments)、什么是运行上下文(Execution Context)、函数是被创建的过程以后,我们要来理解javascript的作用域就非常的容易。

我们知道在JavaScript语言中,有两种类型的作用域:

  • 全局作用域
  • 函数作用域

那这两种作用域是如何连接起来的呢?

其实我们前面已经提到很多次,复习一下我们的运行环境模型:

Runtime = {    executionContextStack: [];};//获取当前的运行上下文,也就是运行栈,栈顶的运行上下文Runtime.getRunningExecutionContext = function() {    return this.executionContextStack[this.executionContextStack.length - 1];}//把运行栈站定的运行上下文弹出Runtime.pop = function() {    this.executionContextStack.pop();}//把一个运行上下文放到运行栈的栈顶Runtime.push = function(newContext) {    this.executionContextStack.push(newContext);}//在当前运行上下文登记一个变量信息Runtime.register = function(name) {     var env = this.getRunningExecutionContext().VariableEnvironment;     env.EnvironmentRecord.register(name);}//在当前运行上下文初始化一个变量信息Runtime.initialize = function(name,value) {     var env = this.getRunningExecutionContext().VariableEnvironment;     env.EnvironmentRecord.initialize(name,value);}//在当前运行上下文上,解析一个标识符Runtime.getIdentifierVaule = function (name) {    var env = this.getRunningExecutionContext().LexicalEnvironment;    while(env){        var envRec = env.EnvironmentRecord;        var exists = envRec.isExist(name);        if(exists) return envRec.getValue(name);        env = env.outer;    }}复制代码

我们解析一个变量,就是在当前运行运行上下文(Execution Context)上查找是否存在这个变量。那运行上下文由什么构成?

由词法环境(Lexical Environments)环境构成:

function ExecutionContext() {    this.LexicalEnvironment = undefined;    this.VariableEnvironment =  undefined;    this.ThisBinding = undefined;}function LexicalEnvironment() {    this.EnvironmentRecord = undefined;    this.outer = undefined; //outer Environment Reference}复制代码

词法环境(Lexical Environments)又是负责登记本环境(function内的)变量的。函数内的变量登记在当前词法环境(Lexical Environments)的EnvironmentRecord里面,函数如何访问到全局的变量的,这些变量并没登记在函数词法环境上?

通过LexicalEnvironment的outer。

函数运行时的过LexicalEnvironment的指向的是函数对象的[[scope]],而函数对象的[[scope]]保存着函数创建时的词法环境。

因此函数中,可访问词法环境范围就是:函数自己的词法环境+函数创建时的词法环境。如果函数创建时的词法环境也是一个函数的运行环境(内嵌函数),函数的可访问的词法环境就变成:

函数自己的词法环境+函数创建时的词法环境 = 函数自己的词法环境+外面函数的词法环境+外面函数创建时的词法环境。。。复制代码

就这样一个个的函数词法环境链接在一起,就是就是函数的作用域链。

我们分析一段代码更清晰:

var bestAvenger = "global";function a() {  var bestActor = "in a";  console.log(bestAvenger); // "global";      function c() {    var bestC = "in c";    console.log(bestActor); // "in a";    b();  }    c();}function b() {  console.log(bestC); //**not defined error**}a();复制代码
  1. 全局运行上下文初始化:

    //创建全局运行上下文var globalExecutionContext = new ExecutionContext();globalExecutionContext.LexicalEnvironment = creatGlobalEnvironment(globalobject);globalExecutionContext.VariableEnvironment = creatGlobalEnvironment(globalobject);globalExecutionContext.ThisBinding = globalobject;//入栈Runtime.push(globalExecutionContext);//这时的Runtime = {//    executionContextStack: [globalExecutionContext]//}复制代码

    看起来是这样的:

  2. 开始登记各个声明

  3. 执行代码

    • bestAvenger = "global";
    • 遇到a的调用,a()。以a的函数对象的[[scope]]创建a的运行上下文,

    模型伪代码如下:

    //创建新的运行上下文    var barExecutionContext = new ExecutionContext();        //创建一个新的词法环境(Lexical Enviroment)    var localEnviroment = new LexicalEnvironment();        //创建新的EnvironmentRecord    var barEnvironmentRecord = new EnvironmentRecord();        localEnviroment.EnvironmentRecord = barEnvironmentRecord    localEnviroment.outer = [[scope]] of bar function object        barExecutionContext.LexicalEnvironment = localEnviroment;    barExecutionContext.VariableEnvironment = localEnviroment;    barExecutionContext.ThisBinding = globalobject;//此例子中thisArg是undefined,且不是strict,所以设置为 globalobject        //把函数的运行上下文入栈:        Runtime.push(barExecutionContext);    复制代码

    这时候运行栈:

    • 开始在函数a的运行上下文上登记a函数内的声明:

      • 变量bestActor
      • 函数声明c

    • 开始执行a函数里面的语句:

      • bestActor = "in a";
      • console.log(bestAvenger); //子当当前运行上下文查找bestAvenger,途径就是图中绿虚线的途径:

      • 执行函数c调用,同样的创建运行上下文,入栈登记变量,执行语句
        • bestC = "in c";
        • console.log(bestActor); // "in a"; bestActor的查找入境如图中蓝色虚线所示

      • 接着运行函数b调用,同一样,创建运行上下文入栈,

大家会发现在当前运行上下文上,找不到变量bestC。

所以,作用域并不是按照运行上下文,上一个链接下一个,这样链接起来的。而是按照代码的词法结构,文本结构,链接起来的。

在上面的例子中:a,c是在全局运行山下文上创建的,所以a、c的作用域链是,本身函数作用域链接全局作用域。

所以尽管在函数c里运行b函数,b函数作用域链上并未不能找到在c中定义的变量。

思考

看一段代码片段:

var data = [];for (var i = 0; i < 3; i++) {  data[i] = function () {    console.log(i);  };}data[0](); //3data[1](); //3 data[2](); //3 复制代码

你能用我们提到过的运行模型分析这段代码,为什么三个函数调用都是打印3吗?

转载于:https://juejin.im/post/5c09f8afe51d451da068c303

你可能感兴趣的文章
销傲中国式销售过程管理系统功能概述
查看>>
IDEA 学习笔记之 Java项目开发深入学习(1)
查看>>
重建二叉树 (剑指offer第六题)
查看>>
爬虫基础 pyquery 详解
查看>>
QT creator+OpenCV2.4.2+MinGW 在windows下开发环境配置
查看>>
Allegro PCB Design GXL (legacy) 设置十字大光标
查看>>
数据结构--图的定义和存储结构
查看>>
[C#参考]委托机制
查看>>
linux常用命令
查看>>
自然杂志上的影评
查看>>
MATLAB制作符合IEEE标准的图插入Latex
查看>>
#HTTP协议学习# (三)摘要认证
查看>>
SQL Server 存储过程
查看>>
Appium自动化测试1 - 安装部署
查看>>
广州.NET微软技术俱乐部微信群各位技术大牛的blog
查看>>
CCLayerColor 用法
查看>>
js访问iframe中的DOM.html
查看>>
redis使用
查看>>
端口复用和端口重映射
查看>>
iOS.AddFont
查看>>