js作用域(三):函数作用域和块级作用域

图文无关.jpg

函数作用域

从之前我们讨论的作用域是负责声明并维护由所有声明的标识符(变量)组成一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。作用域包含着一系列的规则。很多函数嵌套的时候,最里面的函数的作用域包含着对外层函数作用域的引用,这些引用包含着对标识符(变量、函数)的定义。

我们知道。JavaScript有基于函数的作用域,这意味着每一个函数都可以创建属于自己的一套作用域。属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。例如

function foo(){
    var a =1;
    function bar(){
        var b=2;
    }
}

这里面,函数foo有两个标识符a和bar(),在函数外部是访问不到foo里面的标识符的,但是在foo里面的bar()是可以访问到foo的标识符的,这也是作用域链的体现。这样通过函数作用域将代码类似于‘隐藏’起来了,这是一个很有用的技术。通过声明一个函数,把我们需要的标识符都定义在函数里面,成为了这个函数的私有变量了,这样做有几个好处

1.避免使用过多的全局变量。

2.限制了标识符的访问权限,起到封装的作用

3.避免同名标识符之间的冲突

我们知道做项目有可能不仅仅是一个人写代码。项目是很多人参与的,如果我们的全局变量太多个了,那后面,有可能会造成变量重名了,那后果不用我说大家也知道。当然后期也不好维护。

当然也有很多方法能够避免这种问题,比如命名空间、模块管理等等。

我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的问题。首先,必须声明一个具名函数,意味着这个函数 的名称本身“污染”了所在作用域(一般是全局作用域)。其次,必须显式地通过函数名调用这个函数才能运行其中的代码。如果函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行,这将会更加理想。

这里我们需要弄清楚一下函数声明和函数表达式的区别

包装函数的声明以(function... 而不仅是以function... 开始。尽管看上去这并不是一个很显眼的细节,但实际上却是非常重要的区别。函数会被当作函数表达式而不是一个标准的函数声明来处理。区分函数声明和表达式最简单的方法是看function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处。换句话说,(function foo(){ .. }) 作为函数表达式意味着foo 只能在.. 所代表的位置中被访问,外部作用域则不行。foo 变量名被隐藏在自身中意味着不会非必要地污染外部作用域

一、匿名函数和具名函数

简单来说匿名函数就是没有名字的函数,最大的用处就是回调函数了

var timer=setTimeout(function(){
   console.log(123);
},1000);

因为function().. 没有名称标识符。函数表达式可以是匿名的,而函数声明则不可以省略函数名——在JavaScript 的语法中这是非法的。当然匿名函数还有几个缺点需要自己衡量下

  1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。尤其是代码量多的时候。

  2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee 引用,比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。

  3. 匿名函数的可读性比较差,因为没有函数名,没法直观的看清匿名函数起到的作用。

所有始终给一个函数表达式命名是最佳实践(匿名函数具体看需求)。

二、自执行函数

var a = 2;
(function foo() {
    var a = 3;
     console.log( a ); // 3
})();
console.log( a ); // 2

还有一种方式也很多人推荐

var a = 2;
(function foo() {
    var a = 3;
     console.log( a ); // 3
}());
console.log( a ); // 2

当然啦,这两种形式在功能上是一致的。选择哪个全凭个人喜好。

由于函数被包含在一对( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个( ) 可以立即执行这个函数,比如(function foo(){ .. })()。第一个( ) 将函数变成表达式,第二个( ) 执行了这个函数。

另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。

例如:

var a = 2;
(function fn( global ) {
    var a = 3;
    console.log( a ); // 3
    console.log( global.a ); // 2
})( window );
console.log( a ); // 2

我们将window 对象的引用传递进去,但将参数命名为global,因此在代码风格上对全局对象的引用变得比引用一个没有“全局”字样的变量更加清晰。当然可以从外部作用域传递任何你需要的东西,并将变量命名为任何你觉得合适的名字。这对于改进代码风格是非常有帮助的。

块级作用域

首先我们来看个例子,例如

for(var i=0;i<5;i++){
   console.log(i);
}
alert(i);   //5

我们在for 循环的头部直接定义了变量i,在for{}包含的地方使用了变量i,但是在for{}外部还是可以访问变量i,从而i 会被绑定在外部作用域(函数或全局)中。同样道理

var flag=ture;
if(flag){
   var a=1;
   console.log(a);   //1
}
console.log(a)    //a

在if语句里面,我们定义了一个变量a,在if里面能够访问,但是在外部还是可能访问a的。

但是with,它不仅是一个难于理解的结构,同时也是块作用域的一个例子(块作用域的一种形式),用with 从对象中创建出的作用域仅在with 声明中而非外部作用域中有效。

还有就是JavaScript 的ES3 规范中规定try/catch 的catch 分句会创建一个块作用域,其中声明的变量仅在catch 内部有效。

try {
    undefined(); // 执行一个非法操作来强制制造一个异常
}
catch (err) {
    console.log( err ); // 能够正常执行!
}

console.log( err ); // ReferenceError: err not found

到目前为止,我们知道JavaScript 在暴露块作用域的功能中有一些奇怪的行为。如果仅仅是这样,那么JavaScript 开发者多年来也就不会将块作用域当作非常有用的机制来使用了。

目前,ES6 改变了现状,引入了新的let 关键字,提供了除var 以外的另一种变量声明方式。let 关键字可以将变量绑定到所在的任意作用域中(通常是{ .. } 内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。

var foo = true;
if (foo) {
    let bar = foo * 2;
bar = something( bar );
    console.log( bar );
}
console.log( bar ); // ReferenceError

用let 将变量附加在一个已经存在的块作用域上的行为是隐式的。在开发和修改代码的过程中,如果没有密切关注哪些块作用域中有绑定的变量,并且习惯性地移动这些块或者将其包含在其他的块中,就会导致代码变得混乱。

还有一点值得注意的是:只要声明是有效的,在声明中的任意位置都可以使用{ .. } 括号来为let 创建一个用于绑定的块

if (foo) {
{ // <-- 显式的快
    let bar = foo * 2;
    bar = something( bar );
    console.log( bar );
    }
}
console.log( bar ); // ReferenceError

在这个例子中,我们在if 声明内部显式地创建了一个块,如果需要对其进行重构,整个块都可以被方便地移动而不会对外部if 声明的位置和语义产生任何影响。

我们通过自执行函数还可以模拟块级作用。

(function(){
   //代码
})()

自执行函数里面的标识符在外面是访问不到的。

js是ES6之前是没有块级作用域的,但是通过一些手段我们可以模拟块级作用域。
欢迎访问我的个人网站zhengyepan
学习笔记,欢迎交流探讨~

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

推荐阅读更多精彩内容