1. 完全搞懂javascript-词法环境(Lexical Environments)

开始以前先来看两段代码:javascript

function foo() {
    console.log( a ); 
}

function bar() {
    var a = 3;
    foo();
}

var a = 2;
bar();
复制代码
var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    console.log(foo);
}
bar();
复制代码
  1. 你是否会疑惑在function做用域内不是先查找本做用域的变量,找不到才沿做用域链往上找吗?为何 函数内有var a = 3;foo()仍是引用全局的a=2呢?java

  2. 为啥foo会是10,难道var foo = 10 被执行了?浏览器

针对这种代码中的现象,以前笔者也是查找各类帖子文章,在查看这些文章的时候,scope、词法环境、静态做用域、执行上下文等名词不断出现。从中大概知道,哦,js中变量是有提高的现象,哦,还有函数的做用域是在它建立的时候的词法环境决定的,哦,还有this是在运行是决定的不是在建立时决定的(what?TMD究竟是怎么决定的)。。。等等,再加上原型链、做用域链、闭包、匿名函数、当即执行函数。。。什么鬼!相信小伙伴们都有相似的经历,对这些概念听过,知道,可是模糊。bash

为了完全了解本身写的代码到底再干些啥,G哥决定一探究竟,花了一些时间去探索,现现在我以为已经真相大白了,能够把我所了解的分享给你们。不是为了搞些奇技淫巧,而是为了更加透彻了此言语特性。闭包

注意:须要说明的是本系列文章是居于ECMAScript 5.1(ES5) 为参考的,因此如何有小伙伴会疑惑,“为啥你说的运行环境和我看大不大同样,我看到的有变量对象(ES3),有realm(ES6)”。ES6的我会另出出一个系列来专门聊聊。本系列大部分只涉及ES5。函数

户口登记制度

在开始以前,咱们先来想一个问题?JS引擎在执行代码时,是从哪找到要引用的变量的值、函数调用时是如何找到要执行的函数的呢?答案其实简单,简单得有点意外---就是先登记,后使用。就像小伙伴们小时候上学,报道第一天先注册,把名字、性别啥先登记了。登记完了,上课时,老师喊“小明”,小明才能回应“嗯,哥在这”,是否是。好比说没有登记小红“这个”,登记簿上根本没小红,老师要怎么叫,因此无法叫(ReferenceError “not defined”)。可是若是“小李”开学有登记,表上也有它得名字“小李”,可是这天小李打电动旷课了,老师喊“小李”,没人应(默认值undefined,这里小伙伴要搞清楚了,undefined不是说没定义,而是说这个变量定义了,它的值为undefined,undefined是js语言中的一个值,记得否?)。ui

开学时,学生找老师来注册登记入学,那咱们JS里的变量名、函数名找谁注册登记呢?找Lexical Environments(词法环境)登记!this

Lexical Environments(词法环境)

Lexical Environments(词法环境),之因此叫词法环境,是由于它是和源程序的结构对应,就是和你所写的那些源码的文字的结构对应,你写代码的时候这个环境就定了。Lexical Environments(词法环境)和四个类型的代码结构相对应:spa

  • Global code:通俗点讲就是源文件代码,就是一个词法环境
  • 函数代码 :一个函数块内本身是一个新的词法环境
  • eval:进入eval调用的代码有时会建立一个新的词法环境
  • with结构:一个with结构块内也是本身一个词法环境
  • catch结构:一个catch结构快内也是本身一个词环境

为何?不知道,ES5就是这么规定设计的。。。prototype

读到这里有些小伙伴急了,“不对,不对,我记得只有在全局代码、函数代码、和eval代码三种状况,才会建立运行上下文,你专门有5种”。

对,你说的没错,只有在全局代码、函数代码、和eval代码三种状况,才会建立运行上下文,但我这里说的是词法环境,Lexical Environments。不是运行上下文。

看起有点像是这样的:

图1

这个有点像一个学校,每一个班级由班主任负责登记班级学生,年段段长负责登记本年段老师,校长负责登记学校其余行政人员。

可是这其中有些微妙的差别,with结构,catch结构 的词法环境只用来检索,不用来登记,就是说with结构,catch结构的var声明和函数声明也是到其余(到哪?后续说明)词法环境上登记绑定的,它其实就是一个户口检索代理机构(代理嘛,有可能没有户口登记的给你返回有此人。。),这些细节后续会深刻解释。

观察上面第一张图,有几点须要注意:

  1. 图中画了和4种结构对应的词法环境是为了展现,并非说一会儿就创建4个词法环境,记住,词法环境是在进入上述几种代码结构以前以前建立的,就像学校开课以前这些学生登记工做就要作了。
  2. 从图中,能够发现,词法环境是嵌套的,那怎么实现嵌套呢?这就须要了解词法环境的结构了。

老师把学生信息登记到登记簿, Lexical Environments(词法环境)要把变量信息登记在哪呢?哈哈, Lexical Environments(词法环境)也有本身的“登记簿”-Environment Records。先看下Lexical Environments(词法环境)结构是什么样:

词法环境由两部分组成:

  • Environment Records(环境记录):这个就是变量登记的地方了
  • outer:outer 是个指向,包含(包围)本词法环境的外部词法环境

用伪代码表示就是相似这样的:

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

这个outer很重要,它是做用域链可以链起来的关键。那outer是什么呢,我又要比喻了,比如是班主任手里有一个登记簿,还有一个本年段段长的电话号码?这个outer就是段长的电话号码。什么做用呢?想一下,班主任要找一个叫“9527”的人,发现本身班级里没有,这时候,就能够打outer这个电话问问段长:“我找一个9527的人,我这名单上找不到,可能不是学生,是老师,你能在你登记簿上找一下吗?”。段长就开始在本身的登记簿上找,若是找不到,段长也有一个电话号码-校长的电话号码,这时段长就给校长打电话:“校长,我在找一个9527的人,我登记簿上找不到,会不会是学校的行政人员,您能在您的登记簿上找一下这我的吗”。就这样一级一级网上连接,到了校长,他的电话号码就是空了(null),他那找不到,就找不到了。

扯这么多,一句话,outer 就是指向外部Lexical Environments(词法环境)的引用。

在咱们JS中,global Lexical Environments(全局词法环境)就是“校长”,它还有一个专门的名称叫GlobalEnvironment 由于GlobalEnvironment 是最外层的词法环境,因此GlobalEnvironment的outer = null。而在与GlobalEnvironment关联的代码中定义的函数的Lexical Environments(词法环境)法环境的outer就是GlobalEnvironment,with和catch同理,它们如何关联起来咱们后续还要细讲。

如今来看看“登记簿”---EnvironmentRecord。EnvironmentRecord就是真是登记变量信息的地方了。ES5中EnvironmentRecord分为两类,就像有的老师把信息登记在本子上,有的把信息登记在电脑里:

  • declarative environment records 主要用于函数 、catch词法环境
  • object environment records. 主要用于with 和global的词法环境

declarative environment records能够简单理解为字典类型的结构,key-value形式结论变量等对应的名字和值。

而object environment records会关联一个对象,用这个对象的属性-值来登记变量等对应的名字和值。

用伪代码表示(这些代码只是用来讲明相关结构用,不保证严谨性和可行性,伪代码!):

function EnvironmentRecord(obj) {

    if(isObject(obj)) {
        this.bindings = object;
        this.type = 'Object';
    }
    this.bindings = new Map();
    this.type = 'Declarative';
}


EnvironmentRecord.prototype.register = function(name) {
    if (this.type === 'Declarative')
        this.bindings.set(name,undefined)
    this.bindings[name] = undefined;
}

EnvironmentRecord.prototype.initialize = function(name,value) {
      if (this.type === 'Declarative')
        this.bindings.set(name,value);
    this.bindings[name] = value;
}

EnvironmentRecord.prototype.getValue = function(name) {
    if (this.type === 'Declarative')
        return this.bindings.get(name);
    return this.bindings[name];
}
复制代码

全局环境(Global Environment)

全面提过,GlobalEnvironment,其就是一个特殊的Lexical Environments(词法环境),它的outer为null,它的EnvironmentRecord 是一个object environment records,且与全局对象global object(浏览器:window对象)关联。看起来像这样。

var GlobalEnvironment = new LexicalEnvironment();
GlobalEnvironment.outer = null;
GlobalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject); ;//globalobject能够看做是浏览器环境下的window
复制代码

上面提到词法环境(Lexical Environments),是用来登记变量和相关函数名字的,也知道这个名字是登记在 词法环境的 EnvironmentRecord上的。

那问题又来了,何时登记,怎么登记?是直接找老师(Lexical Environments)登记,仍是设置一个办公厅,办公厅设置登记窗口提供登记服务?

咱们下一篇来细细说明!

总结

//Lexical Environment
function LexicalEnvironment() {
    this.EnvironmentRecord = undefined;
    this.outer = undefined; //outer Environment Reference
}

//EnvironmentRecord
function EnvironmentRecord(obj) {

    if(isObject(obj)) {
        this.bindings = object;
        this.type = 'Object';
    }
    this.bindings = new Map();
    this.type = 'Declarative';
}

EnvironmentRecord.prototype.register = function(name) {
    if (this.type === 'Declarative')
        this.bindings.set(name,undefined);
    this.bindings[name] = undefined;
}

EnvironmentRecord.prototype.initialize = function(name,value) {
      if (this.type === 'Declarative')
        this.bindings.set(name,value);
    this.bindings[name] = value;
}

EnvironmentRecord.prototype.getValue = function(name) {
    if (this.type === 'Declarative')
        return this.bindings.get(name);
    return this.bindings[name];
}

//全局环境GlobalEnvironment

function creatGlobalEnvironment(globalobject) {
	var globalEnvironment = new LexicalEnvironment();
	globalEnvironment.outer = null;
	globalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject);
	return globalEnvironment;
}

GlobalEnvironment = creatGlobalEnvironment(globalobject)//能够看做是浏览器环境下的window

复制代码
相关文章
相关标签/搜索