深入理解JavaScript作用域链

要讲清楚作用域链,我们首先要从JS中的一个重要概念讲起——执行环境

执行环境与变量对象

执行环境定义了变量或者函数有权访问的其它数据。这是JavaScript高级程序设计中的定义。它是从执行环境的作用来定义执行环境这个概念的,也就是说:正是因为有了执行环境的存在,才能保证一个变量或者函数有权访问哪些数据,而无权访问哪些数据。

执行环境分两种:全局执行环境局部执行环境。全局执行环境不用说了,在web浏览器端就是window对象。而局部执行环境就是当执行流进入到函数内部时,就会创建一个当前函数的执行环境,这个执行环境就是局部执行环境,也叫函数执行环境。

执行环境存储在一个堆栈数据结构中,这个堆栈,我们可以叫它环境栈,每当执行流创建一个执行环境时,就会将该执行环境推入到环境栈中。当当前执行环境中所有的代码都执行完毕,就会从环境栈中弹出该执行环境,把控制权返回给之前的执行环境,也就是环境栈中下一个执行环境。

环境栈.png

如上就是执行环境的环境栈,当当前函数的执行环境被pop弹出时,指向当前执行环境的指针就会下移,将控制权交给上一个(前一个)执行环境,在图中就是指向函数X的执行环境。

执行环境有一个与之关联的对象叫变量对象,执行环境中定义的变量和函数都会挂载到这个变量对象上。创建函数执行环境时,会将函数的活动对象作为函数环境的变量对象,这个变量对象一开始只包含一个变量,即arguments对象。

当执行环境被销毁时,也就是前面说过的从环境栈中弹出时,与之关联的变量对象也会被销毁,因此在这个执行环境中申明的所有变量和函数都会被销毁,从而释放内存。

有了前面的执行环境的概念,接下来我们看看作用域链。

作用域链

作用域链用来保证对执行环境有权访问的所有变量和函数的有序访问。这是作用域链的定义。那么执行环境有权访问哪些变量和函数,作用域链又是怎么保证有序访问的呢,我们接下来看看:

首先,当代码在一个环境中执行时,就会创建变量对象的一个作用域链。我的理解就是,它的本质就是变量对象上的一个属性([[Scopes]] ? ),它是一个指针,指向上一个执行环境的变量对象,也就是说作用域链是由环境栈中所有执行环境的变量对象线性链接而成的一个线性表,线性表的第一个元素是当前执行环境的变量对象,下一个元素来自前一个执行环境(包含执行环境)的变量对象,再下一次元素来自再下一个执行环境的变量对象。。。。。。,依次类推,直到最后一个元素是来自全局执行环境的变量对象。这样就线性的组成了作用域链。(当然这一块是我个人的理解,如有理解有误的地方,欢迎大佬指正)

image.png

现在我们知道了作用域链是由变量对象按照创建顺序串联起来的,而变量对象上又保存着对应的执行环境中声明的变量和函数。标识符解析正式利用作用域链来查找变量的。

标识符解析的过程:沿着作用域链,从作用域链的前端开始依次查找变量对象,如果变量对象中存在该标识符,则找到,反之查看作用域链中下一个变量对象。。。直到全局变量对象,如果全局变量对象上也没有找到,说明该变量或函数没有申明。

现在让我们反过来理解作用域链的定义:保证对执行环境有权访问的所有变量和函数的有序访问

执行环境有权访问的变量和函数就存储在当前执行环境的作用域链上的每一个变量对象上,只有存在在这条作用域链上的变量和函数,当前执行环境才能访问到,因为标识符解析就是沿着这条作用域链进行查找的。

同时,通过沿着作用域链依次查找,找到就返回,没找到就接着往下找,正是这种策略保证了对变量和函数的有序访问。假设作用域链中存在多个同名变量或函数,如果没有这种策略,那么应该解析到哪一个同名变量呢?所以说这种有序访问的策略就是就近原则(就近 执行环境中定义的变量/函数)

块级作用域

最后我们来简单说一下js中的块级作用域,因为这个关系到标识符的解析以及合理的变量申明。

我们都知道在es6之前是没有块级作用域的,声明在大花括号中的变量,在花括号外边也可以访问,只有函数比较特殊,声明在函数中的变量在函数外部是无法访问的。如下:

if (true) {
    var color = 'red';
}
console.log(color)  // 'red'  可以访问,因为js没有会计作用域

for(var i = 0; i < 10; i++){
    doSomething(i);
}
console.log(i);  // 10  可以访问,因为js没有会计作用域

function block() {
    var color = 'red';
}
console.log(color);  // 报错,不可以访问,因为函数有块级作用域

function block() {
    color = 'red';
}
console.log(color);  // 'red'  可以访问

上面的例子中,最后一个例子比较特殊,这是因为在js中,如果一个变量没有声明而直接初始化,那么这个变量就会被自动添加到全局变量对象上去,而不论这个变量是不是在函数体中进行初始化的,这个其实是相当于下面这种写法:

var color;

function block(){
    color = 'red';
}

console.log(color);

所以,在js中不声明而直接初始化变量是一个常见的错误做法,不建议这样做,因为这会导致意外。在js的严格模式下初始化未经声明的变量会导致错误。

所以综上可以看出:
使用var关键字声明的变量会被自动添加到最接近的执行环境中

  1. 在函数内部,最接近的执行环境就是该函数的执行环境;
  2. 在with语句,最接近的环境是函数的执行环境
  3. 如果初始化未声明的变量,该变量会被自动添加到全局执行环境

然而es6引入了let/const两个声明变量的关键字,来弥补js在声明变量时没有块级作用域的不足,也就是说这两个关键字声明的变量是有块级作用域的:

if(true) {
    let color = 'red';
}
console.log(color);  // 报错,因为let声明的变量是有块级作用域的,在作用域外部无法访问

for(let i = 0; i < 10; i++) {
    doSomething(i);
}
console.log(i);  // 报错,因为let声明的变量是有块级作用域的,在作用域外部无法访问

有了let/const的块级作用域之后,我们可以将一个变量的作用局限在一个给定的局部范围内,不会再污染全局变量,也不会导致一些意外,并且随着块级作用域的执行结束,位于块级作用域内的变量会被销毁,从而释放内存。因此尽量使用let/const来声明变量,少用var声明变量。

本文是根据《JavaScript高级程序设计》结合自己的理解来写的,如果有不对的地方,欢迎大佬指正,谢谢。

后续有空了会写个人对于垃圾回收机制以及闭包的理解,感兴趣的同学可以点个关注留意一下,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容