函数作用域 | 块作用域 | 提升

一.函数作用域和块作用域

1.1函数中的作用域
  • 函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(其实在嵌套的作用域中也可以使用)。
1.2隐藏内部实现

①是什么?

  • 挑选出函数内部的一个任意的代码片段,然后用函数声明对它进行包装,实际上就是将这些代码"隐藏"起来了。

②对原始函数带来的影响?

  • 在原始函数内部嵌套了一个新的包装函数的作用域。

③那么,为什么要选择将一个函数作用域隐藏于另一个函数作用域内部呢?

  • 最小特权原则:在软件设计中,应该最小限度地暴露必要内容,而将其他内容都"隐藏"起来。
function doSomething(a){
    b = a + doSomethingElse( a * 2 );
    console.log( b * 3 );
}
function doSomethingElse(a){
    return a-1;
}
var b = 0;
doSomething(2);
>>>15


function doSomething(a){
    function doSomethingElse(a){
        return a - 1;
    }
    var b = 0;
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}
doSomething(2);
>>>15

④规避冲突

  • "隐藏"作用域中变量和函数所带来的另一个好处是,可以避免同名标识符之间的冲突,标识符同名但用途却不一样,可能会造成命名冲突,最终会导致变量的值被意外覆盖:
function foo(){
    function bar(a){
        i = 3;//修改了for循环所属属性的i
        console.log(a + i);
    }
    for(var i = 0;i < 10;i++){
        bar(i*2);//因为i的值被修改为3,所以永远都小于10,导致无限循环!
    }
}
foo();


//解决
function foo(){
    function bar(a){
        var i = 3;//"遮蔽变量"
        console.log(a + i);
    }
    for(var i = 0;i < 10;i++){
        bar(i*2);
    }
}
foo();

另外两种解决方式:

1.全局命名空间

var MyReallyCoolLibrary = {
    awesome: 'stuff',
    doSomething: function(){
        // ...
    },
    doAnotherThing: function(){
        //...
    }
};

2.模块管理

  • 使用模块管理器,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器的机制将库的标识符显式地导入到另一个特定的作用域中。
1.3函数作用域

var a = 2;
var a = 3;
console.log(a);
>>>3

②使用所学内容:"隐藏内部实现",将最后两行代码"隐藏"掉:

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

③虽然解决了部分问题,但也带来了其他的麻烦:

  • 必须要声明一个具名函数foo(),导致其函数名"污染"了所在作用域(这里是全局作用域)。
  • 必须显式通过函数名来调用这个函数才能运行其中的代码。

④解决之法:

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

⑤如何区分函数声明和函数表达式?

  • 最简单的方法就是看function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明的位置)
function foo(){...}  //函数声明

var test = function(){...}   //函数表达式

(function foo(){...})()  //函数表达式
  • 若function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

⑥函数声明和函数表达式的区别?

  • 两者最主要的区别在于它们的名称标识符会绑定在何处。

  • (function foo(){ ... })作为函数表达式意味着foo只能在...所代表的位置中被访问,外部作用域则不行。foo变量名被隐藏在自身中意味着不会"污染"外部作用域。

1.4块作用域

let关键字,除了var以外的另一种变量声明的方式,用于在任何代码块中声明变量。

②存在的原因?

  • let关键字可以将变量绑定到所在的任意作用域中(通常是{...}内部)。
if(true){
    var b = 20;
}
console.log(b);
>>>20


if(true){
    let b = 20;
}
console.log(b);
>>>Uncaught ReferenceError: b is not defined

③提升是什么?

  • 在某个作用域任意位置的声明,都会被"转移"到该作用域的顶部。但是使用let进行的声明不会在块作用域中进行提升!
console.log(a);
var a = 2;
>>>2

{
    console.log(a);
    let a = 2;
}
>>>Uncaught ReferenceError: a is not defined

for(let i = 0;i < 5;i++){

}
console.log(i);
>>>Uncaught ReferenceError: i is not defined

⑤除了let之外,ES6还引入了const,同样可以用来创建块作用域变量,但其值是固定的(常量)。之后任何试图修改其值的操作都会导致错误(Uncaught TypeError:Assignment to constant variable)

if(true){
    const b = 5;
}
console.log(b);
>>>Uncaught ReferenceError: b is not defined


if(true){
    const b = 5;
    b = 10;
}
>>>Uncaught TypeError: Assignment to constant variable.

二.提升

1.所有声明(变量+函数)都会在任何代码被执行前,首先被编译器处理。
var a = 2;
//分解为两个阶段
//第一个声明:定义声明,在编译阶段进行
var a;

//第二个声明:赋值声明,原地待命,等待执行阶段
a = 2;
2.先有鸡还是先有蛋?
  • 反正JavaScript里是先有声明再有赋值。
3.只有定义声明会被提升,而赋值声明或其他运行逻辑则会原地待命。
4.每个作用域都会进行提升操作:
foo();
function foo(){
    console.log(a);//undefined
    var a = 2;
}

//其实是这样的
function foo(){
    var a;
    console.log(a);//undefined
    a = 2;
}
foo();
5.
foo();// 不是ReferenceError,而是TypeError!
var foo = function bar(){
    // do something
};

//还原一下
var foo;
foo();//TypeError
foo = function bar(){
    // do something
};

//More
var foo;
foo();//Uncaught TypeError: foo is not a function
foo = function bar(){
    // do something
};

ReferenceErrorTypeError的区别?

  • 如果RHS查询(找到变量并获取其值)在任何作用域中都查找不到所需的变量,引擎就会抛出ReferenceError异常。

  • 如果RHS查询找到了所需变量,但是当你对该变量的值进行不合理的操作时,比如试图对一个非函数类型的值进行函数调用,或者引用nullundefined类型的值中的属性,那么引擎就会抛出另外一种类型的异常,叫做TypeError

  • RHS:Retrieve His Source.

6.即使是具名的函数表达式,名称标识符在被赋值之前也无法在所在作用域中使用:
foo();//TypeError
bar();//ReferenceError
var foo = function bar(){
    // do something
};

//还原一下
var foo;
foo();//TypeError
bar();//ReferenceError,压根在全局作用没有找个这个函数
foo = function(){
    var bar = ...self...
    // do something
};
7.函数优先

①函数声明和变量声明都会被提升,但是有个先来后到,函数会首被提升,然后才是变量。

foo();
var foo;
function foo(){
    console.log('我是foo的函数声明');
}
foo = function(){
    console.log('我是foo的函数表达式');
};
>>>我是foo的函数声明

//还原一下
function foo(){
    console.log('我是foo的函数声明');
}
foo();
//var foo;被同名的foo函数声明"覆盖"掉
foo = function(){
    console.log('我是foo的函数表达式');
};
>>>我是foo的函数声明

②尽管重复的var声明会被忽略掉,但出现在后面的函数声明还是可以覆盖掉前面:

//后面的函数声明
foo();
function foo(){
    console.log('我是foo的函数声明');
}
function foo(){
    console.log('我是后面的函数声明');
}
>>>我是后面的函数声明

③杜绝在同一个作用域进行重复定义。

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

推荐阅读更多精彩内容