作用域

什么是作用域?


在编程领域内,作用域是用来控制变量与参数的可见性及生命周期。对编程人员来说这是一项非常重要的服务,因为它减少了命名冲突以及提供了自动内存的管理。

类型


根据访问的范围不同,可以将作用域分为三种类型:全局作用域、局部作用域、块级作用域。

全局作用域

var a = 12; //全局变量
if (true) {
    console.log(a); //块级作用域范围内进行访问a
}
console.log(a); 
function f () {
    console.log(a); //局部作用域范围内进行访问a
}
f(); //执行f方法

如上所示的这样的,变量a就是全局变量,它被定义在了全局作用域里了。作为全局变量a,它可以在任意地方被访问到。

局部作用域(函数作用域)

所谓的局部作用域,指的是在函数范围内的作用域,故也可以称之为函数作用域

function f () {
    var a = 12; //局部作用域进行定义变量a
    console.log(a); //访问局部变量a
}
f(); //执行f方法
console.log(a); //全局范围内进行访问a,会是undefined
if (true) {
    console.log(a); //块级作用域访问a,依然是访问不到的
}

如上所示,变量a在函数方法里进行了定义,所以变量a作用范围只能是该函数内的局部作用域。跳出该作用域外部,无论是全局还是其他地方都访问不到该变量a

块级作用域(ES5不支持,ES6支持)

在大多数的类C的语言中都会拥有块级作用域的。所谓的块级作用域指的是在一个代码块中(括在一对花括号中的一组语句)定义的所有变量对外是不可见的。定义在代码块中的变量在代码块执行结束以后会被释放掉。这是一件好事,但是不幸的是在JS中,貌似语法上是支持块级作用域的,但实质是只支持全局作用域局部作用域不支持块级作用域,这会导致在块级作用域内定义的变量对外是可见的,会引发变量的外部泄露。这可能会成为一个错误之源。

if (true) {
    var a = 12;
} 
function f () {
    console.log(a);//可以访问到变量a
    for(var i = 0; i < 5; i++) {
          
    }
    console.log(i);//可以访问到变量i
}
f();//执行f方法

通过上面的示例可以看到这个问题是有多严重了,这会导致很多莫名其妙的问题出现。

注意事项

由于缺少块级作用域的支持,所以对于延迟变量的声明在JS中是不建议的,最好的做法是在函数体的顶部声明可能用到的所有变量。

词法作用域与动态作用域

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

var value = 1;
 
function foo() {
    console.log(value);
} 
function bar() {
    var value = 2;
    foo();
}
bar(); //1

如上所看到的,因为js采用的是词法作用域(静态作用域),所以在调用foo函数的时候,变量value首先在函数内部作用域找对应的变量,如果没有找到,则去foo函数定义的时候的外层的作用域找寻变量也就是全局变量value。所以最终输出的是全局变量1。
这就是词法作用域,如果说是动态作用域的话,则它会根据被调用的时候所处的作用域进行查找变量,也就是会输出2。

ES5与ES6的区别


变量声明

在ES6中对于不支持块级作用域的问题得到了有效缓解。我们可以在块级作用域内用let进行变量的声明。被声明的变量只在该作用域内可见,外部是访问不到该内部的变量。

// 1.外层作用域无法获取到内层作用域
function fn1() {
    let a = 41;
    if(1 == 1) {
        let a = 3;
        console.log(2,a); // 2 3
    }
    console.log(1,a); // 1 41
}
fn1();

{  
    {
        let food = 'apple';
    }
    console.log(food); // Uncaught ReferenceError: food is not defined
}

{  
    {
        let food = 'apple';
    }
    console.log(food); // Uncaught ReferenceError: food is not defined
}

// 2. 外层和内层都使用相同变量名,也都互不干扰
{  
    {
        let food = 'apple';
        console.log(food); // apple
    }
    let food = 'orange';
    console.log(food); // orange
}

函数声明

ES5中,函数只能在全局作用域函数作用域中声明,不能在块级作用域中声明。但是在ES6中,函数可以在块级作用域中声明。

但是会有一定的问题,因为函数声明会被提到代码的最前面。所以会报错,最好在ES6中用函数表达式来表示一个函数。

//1.函数声明报错
{
    if (4 < 2) {
        function fn() {
            console.log('我在函数里面!');
        }
    }
}
fn(); // Uncaught TypeError: fn is not a function
//2.函数表达式没错
{
    let fa = '111';
    let fn = function () {
        console.log('我在函数里面!');
    }
    console.log(fa,fn); // 111 ƒ () { console.log('我在函数里面!');}
}

作用域链

Function对象有一个仅供 JavaScript 引擎存取的内部属性。
这个属性就是[[Scope]][[Scope]]包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

关于作用域链,局部作用域可以访问到全局作用域中的变量和方法,而全局作用域不能访问局部作用域的变量和方法

var a = 0;

function fn () {
  var b = 1;
  console.log(a); // 输出 0
}

// 全局作用域并不能访问 fn 函数中定义的 b 变量
console.log(b); // 报错

fn();

当函数fn()创建时,它的作用域中插入了一个对象变量,这个全局对象代表所有在全局范围内定义的变量。

创建时作用域链.png

当函数fn()执行时,会创建一个名为执行环境的独一无二的内部对象。函数每执行一次,都会创建一个执行环境。当函数执行完毕,执行环境就会被销毁。

每个执行环境都有自己的作用域链,用来解析标识符。当执行环境被创建时,它的作用域就会初始化为当前运行函数的[[Scope]]属性中的对象。

执行环境创建完成之后,就会生成一个"活动对象",这个对象包含了当前函数的所有局部变量命名参数参数集合this。此对象会被推入作用域链的最前端。

当执行环境被销毁后,"活动对象"也会随之被销毁。

如下图所示:


fn函数执行时作用域链.png

fn()时,会使用到a变量,这时候就会搜索执行环境的作用域链,找到就使用,找不到就会到作用域链的下一个对象找,如果搜寻到最后,还没找到,那就认定这个变量是未定义的。

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

推荐阅读更多精彩内容