JavaScript学习笔记(五)——函数的作用域

作用域决定了变量、常量和参数被定义的时间和位置。函数参数的作用域仅限于函数体中。来看一个例子:

function f(x){
	return x + 3;
}
f(5); // 8
x; // ReferenceError: x 未定义

单从代码上来看,x的确存在过(否则它就无法计算出x + 3的值),但是,在函数体外引用x的时候,会报错。因此,x是函数 f 的一个内部变量,它的作用域就是函数f。

当某个变量的作用域是一个给定的函数时,必须要记住:形参只有在函数被调用的时候财讯在(变成实参)。一个函数可能被调用多次:每次函数调用开始时,参数才是真实存在的,在函数返回后参数就失去作用域了。

作用域和存在

当一个变量不存在时,它是没有作用域的。也就是说,尚未声明的变量,或者只存在于函数内部的变量,是没有作用域的。

反过来呢?如果变量没有作用域,就意味着它不存在吗?不一定,这就需要区分作用域和存在这两个不同的概念。

作用域(或者可见性)指的是当前可见并且可以被正在执行的代码块(称作执行上下文)访问的标识符。
存在也指的是标识符,只不过它们保存了已经分配(预留)过内存的数据。

当某些变量不复存在时,JavaScript不一定会立即回收内存:它只是标记这些条目不需要保留,垃圾回收进程会定期去回收。在JavaScript中,不需要关心垃圾回收,垃圾回收是自动化的,除非应用有较高的性能要求,才需要考虑垃圾回收。

作用域

  • 作用域:规定变量起作用的范围
  • 划分规则:只有全局作用域和函数的私有作用域
  • 作用域是根据书写的单词和语法来确定的,所以又叫做词法作用域
  • 所以,通常我们把代码写完,就可以确定每一个作用域的范围

注意: script标签之间是全局作用域,多个script标签共享同一个作用域,但是每一个script标签中的变量、函数,它们的提升,只能够在本script标签的范围之内

注意: 浏览器在加载script标签的代码的时候,是一个script标签加载并执行完毕之后,再去加载执行后面的一个。

看下面例子:

    <script>
        var a = 10;
    </script>
    <script>
        console.log(a); // 输出10
    </script>


    <script>
        console.log(a); // ReferenceError: a is not defined
    </script>
    <script>
        var a = 10;
    </script>

作用域的机制

作用域是变量的生效的范围。
变量操作又分为使用变量赋值变量

  • 使用变量
    • 出现在表达式中,赋值语句右侧。
    • 访问变量规则
      • 当访问变量的时候,会先查看当前作用域中是否存在该变量
      • 如果有,就使用,并终止查找。
      • 如果没有,就将会向上一层级作用域中寻找。
      • 依次向上,直到找到,或者到了全局作用域中还没有找到,就会报错。
  • 赋值变量
    • 只出现在赋值语句左侧
    • 赋值变量规则
      • 当对一个变量进行赋值的时候,会先查看当前作用域中是否存在该变量
      • 如果有,就赋值。
      • 如果没有,就向上一层级查找。
      • 依次向上,直到找到,或者到了全局作用域中还没有找到, 就会在全局作用域中悄悄的声明这个变量并赋值
// 当前是全局作用域
var num = 100; // 在全局作用域中定义的变量

// 定义一个函数
function demo() {
    var num = 101; // 这是在函数作用域中定义的变量
    console.log(num);
}
// 执行函数
demo(); // 101 

解释:当在函数内部访问变量num的时候,先看当前作用域中是否有num找到了!于是就用101输出101。

var num = 100; // 在全局作用域中定义的变量
// 定义一个函数
function demo() {
    // var num;  // 因为声明提升的原因 代码其实是这样子的
    console.log(num); 
    var num = 101; // 这是在函数作用域中定义的变量 虽然定义是在这里 但是提升了
}
// 执行函数
demo(); // undefined

解释:依旧是作用域的问题,但是因为输出代码在前面,所以有些同学可能会误以为会输出100 但是不要忘记变量的查找规则与预解析!!!

var num = 100; // 在全局作用域中定义的变量
// 定义一个函数
function demo() {
    var num = 101; 
}
// 执行函数
demo();
// num是多少
console.log(num); // 100

解释:作用域的问题,因为输出的代码是在全局作用域中的,所以会直接访问全局中的变量,而与函数内部的变量无关。

var num = 100; // 在全局作用域中定义的变量
// 定义一个函数
function demo() {
    num = 101; 
}
console.log(num); // 100
// 执行函数
demo();
// num是多少
console.log(num); // 101   

解释:第一次输出时,函数还没有被调用,所以输出的是全局作用域中的初始值100。在第二次输出前,调用了函数,在被调用的函数里面,num被重新赋值了101,所以第二次输出的是101.

        var a = 10;
        var b = 15;
        function demo(){
            console.log(a); // undefined
            console.log(b); // 15
            var a = 11;
            console.log(a); // 11
            function demo2(){
                console.log(a); // 11
            }
        demo2();
        }
        demo();
        console.log(a); // 10

解释:首先定义了两个全局变量a和b,但是根据预解析以及查找规则,函数提升了。在函数demo内定义了与全局变量一样的变量名a,但是函数demo内的变量a是在输出语句之后定义的,所以第一个输出语句输出的是undefined。

变量屏蔽(遮蔽效应):指的是如果当前作用域与上层作用域都有同一个变量,那么在当前作用域中访问该变量是,会用当前作用域的,而不会使用上层作用域的。可以理解为把上层作用域中的变量遮挡住了。 这里先看一个两个小例子,注意对比:

        var a = 10;
        function demo1(){
            var a = 11;
            console.log(a); // 11
        }
        demo1();
        console.log(a); // 10
        var b = 10;
        function demo2(){
            b = 11;
            console.log(b); // 11
        }
        demo2();
        console.log(b); // 11

变量屏蔽

在JavaScript开发中,有一个常常引发混淆的场景,不同作用域中存在相同名字的变量或者常量。而当作用域一个接着一个的时候,这种场景会显得相对直观一些:

        function f1(){
            var x = "yellow";
            console.log(x); // 打印"yellow"
        }
        f1();
        console.log(typeof x); // 打印"undefined",这是因为x不在作用域内
        function f2(){
            var x = 3;
            console.log(x); // 打印 3
        }
        f2();
        console.log(typeof x); //打印"undefined",这是因为x不在作用域内

这个例子很容易理解:有两个互相独立的变量,在不同的作用域中都以x命名。
那么,如果作用域嵌套在一起会发生什么呢?

        function f1(){
            var x = "yellow";
            console.log(x); // 打印"yellow"
            function f2(){
                var x = 3;
                console.log(x); // 打印 3
            }
            f2();
            console.log(x); // 打印"yellow"
        }
        f1();
        console.log(typeof x); //打印"undefined",这是因为x不在作用域内

这个例子演示了变量屏蔽。在内部中,x是一个独立于外部块的(同名)变量,这样实际上屏蔽(或隐藏)了外部定义的x。

这里有一个关键的地方需要理解,当执行到内部块时,一个新的变量x被定义,两个变量都在作用域内:只是没有办法访问到外部块的变量(因为具有相同的变量名),相对而言,在前一个例子中,一个变量x进入作用域,然后退出,接着进入第二个作用域,x做了相同的事情。

为了更好的理解,看下面例子:

        function f1(){
            var x = "yellow";
            var y = x; // y 和 x引用同一个对象
            var z = 3;
            function f2(){
                var x = 5; // 外部 x 被屏蔽
                console.log(x); // 5
                console.log(y); // 打印"yellow",y(以及x在外部作用域)指向的对象仍存在作用域内
                y = "red";
                console.log(z); // 3 ,因为 z 没有被屏蔽
                console.log(y); // 打印"red"
            }
            f2();
            console.log(z); // 3
        }
        f1();

变量屏蔽有时候也叫变量阴影(即同一个相同名字的变量会将外部作用域的变量屏蔽起来)。而当一个变量被屏蔽时,这个被屏蔽的变量就无法再通过名字去访问了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值