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

作者: 姚屹晨 | 来源:发表于2017-09-08 09:13 被阅读54次

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

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('我是后面的函数声明');
}
>>>我是后面的函数声明

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

相关文章

  • 2019-11-11-本周学习周报

    学习总览 JavaScript 函数作用域、块级作用域 变量提升、函数提升 CSS 新增属性transition ...

  • 全面理解作用域

    es5:函数作用域、全局作用域 (var 、function有变量提升)es6:块级作用域 (没有变量提升...

  • JS的作用域

    JS的作用域: 全局作用域、函数作用域、eval 作用域、块级作用域 全局作用域: 函数作用域: 结果截屏: 说...

  • js 的变量提升和函数提升

    1. 深入理解 js 的变量提升和函数提升 先了解:js没有块级作用域,只有全局作用域,和函数作用域 相同的函数名...

  • 1. let , const 块级作用域

    作用域全局作用域 => global函数作用域 => 因函数执行而产生的作用域 块级作用域形式 : { }特点 :...

  • C - 作用域

    C - 作用域 一个 C 变量的作用域可以是: 块作用域 函数作用域 函数原型作用域 或 文件作用域 作用域描述程...

  • JavaScript 作用域和闭包理解

    作用域: 分为函数作用域,和块级作用域; 函数作用域 函数作用域外面的无法访问函数作用域内部的变量和函数,这样就可...

  • es6块级作用域定义声明函数

    允许在块级作用域内声明函数。函数声明类似于var,即会提升到全局作用域或函数作用域的头部。同时,函数声明还会提升到...

  • 作用域

    标识符的作用域有函数原型作用域、局部作用域(块作用域)、类作用域和 命名空间(namespace) 作用域 函数原...

  • 一网打尽 JavaScript 的作用域

    JavaScript 的作用域包括:模块作用域,函数作用域,块作用域,词法作用域和全局作用域。 全局作用域 在任何...

网友评论

    本文标题:函数作用域 | 块作用域 | 提升

    本文链接:https://www.haomeiwen.com/subject/zqqgjxtx.html