1. 作用域(Scope)的概念

作用域指一个变量的作用范围。在 js 中,一共有两种作用域:

全局作用域:直接编写在 script 标签中的 JS 代码,都在全局作用域。

  • 全局作用域在页面打开时创建,在页面关闭时销毁。

  • 在全局作用域中有一个全局对象 window,它由浏览器创建我们可以直接使用。

函数作用域:函数体内部

2. 全局变量和局部变量

2.1 全局变量

在全局作用域中:

  • 创建的变量都会作为 window 对象的属性保存。

  • 创建的函数都会作为 window 对象的方法保存。

  • 全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到。

2.2 局部变量

  • 函数体内部的声明的变量

  • 如果一个变量在函数体内部声明,则该变量只能在该函数体内使用,在函数体外不可引用该变量。

var y = 10; // 全局变量
function foo() {
    var x = 1; //局部变量
    console.log(y); // 10
}
foo();
console.log(x); // 报错
// 也就是说,函数体内部声明的,在函数体外部是不能使用的。
// 在全局作用域下声明的全局变量,可以在页面任意部分访问得到。

3. 作用域的上下级关系(作用域链)

当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。

如果没有则向上一级作用域中寻找,直到找到全局作用域。

如果全局作用域中依然没有找到,则会报错 。


function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // 会找到 foo函数里边创建的 x 使用
        var z = 100;
    }
    var z = y + 1; // 找不到y, 不可以访问bar函数的变量y!
    function ins() {
        x++; // 会找到 foo函数的变量 x
        y++; // 找不到y,不可以访问 bar函数的变量y
        var z = 1000;
    }
    // 注意 bar 和 ins 中声明的 变量z 是相互独立的,互不影响
}
var x = 10;
bar();
function foo() {
    console.log(x);
    // 找x,先在自身函数作用域查找,到自己上一级作用域 (此时是全局作用域)查找
    // 虽然foo函数是在bar函数里边调用的,但是 bar函数并不是foo的上一层作用域
    // 因为作用域是在 定义函数时确定的
}
function bar() {
    var x = 30;
    foo(); // 运行foo函数
}

4. 变量和函数声明提升

4.1 预解析

JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的。JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程

预解析过程:

  1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。

  2. 把函数的声明提升到当前作用域的最前面,函数声明代表函数整体,所以函数提升后,函数名代表整个函数

  3. 先提升var,再提升function。

  4. 预解析会把变量和函数的声明在代码执行之前执行完成。

预解析过程就是变量提升的过程。

4.2 变量声明提升

  1. 使用 var 关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前(预解析)被声明(但是不会赋值)
  2. 但是如果声明变量时不是用 var 关键字(比如直接写 a = 1),则变量不会被声明提前。(不推荐)
console.log(a); // 打印 undefined ,说明a已经被声明了,只是没有赋值
var a = 10;

console.log(b); // b is not defined  没有声明提升
b = 2; // 此时 b 相当于 window.b (全局变量)
console.log(b); // 2

以上代码相当于:

var a;
console.log(a);
a = 10;

console.log(b);
b = 2;
console.log(b);

4.3 函数声明提升

  1. 使用函数声明的形式创建的函数function foo(){},会被声明提前。也就是说,整个函数会在所有的代码执行之前就被创建完成,所以我们可以在函数声明之前,调用函数。

  2. 使用函数表达式创建的函数 var foo = function(){},不会被声明提前,所以不能在声明前调用。很好理解,因为此时 foo 被声明了,且为 undefined,并没有把function(){} 赋值给 foo

foo(); //执行
bar(); //报错 此时  bar 为undefined
function foo() {
    console.log("我是foo函数");
}
var bar = function() {
    console.log("我是bar函数");
};

在函数作用域也有声明提前的特性:会提升到该函数作用域的最前边。

5. 练习

function f1() {
    var num = 123;

    function f2() {
        var num = 0;
        console.log(num); // 站在目标出发,一层一层的往外查找0
    }
    f2();
}
var num = 456;
f1();
var a = 1;

function fn1() {
    var a = 2;
    var b = "22";
    fn2();
    function fn2() {
        var a = 3;
        fn3();
        function fn3() {
            var a = 4;
            console.log(a); //4
            console.log(b); //22
        }
    }
}
fn1();

var boy = 20,
    girl = 18,
    marry = 22;
function love(marry) {
    // 指定的形参marry,相当于在函数内部创建了变量 marry , var marry;
    boy = 24; // 修改了全局变量 boy  改成了 24
    var girl = 22; // 创建了局部变量 girl
    marry = 25; // 修改的是函数内部的marry
    console.log(marry); // 25
}
love(22);
console.log(boy, girl, marry); // 24,18,22
f1();
function f1() {
    var b = 9;
    console.log(b); // 9
}
var num = 10;
fun();
function fun() {
    console.log(num);//undefined
    var num = 20;
    console.log(num);//20
}
var b = 1;
function fun1() {
    b = 2;        //修改了全局变量 b
    function fun2() {
        var b = 3;
        console.log(b);   //3
    }
    fun2();
    console.log(b);   //2
}
fun1();
console.log(b);     //2
var b = 1;
function fun1(b) {
    //var b;
    //b = 'yy';
    b = 2;                 // 相当于修改了参数b
    console.log(b);        //2
}
fun1('yy');
console.log(b);            //1
var b = 2;
function test2() {
    window.b = 3;          //修改了全局变量 b
    console.log(b);        //3
}
test2();
console.log(b);            //3
c = 5;
function test3() {
    window.c = 3;           //修改了全局变量 c
    console.log(c);         //找到局部变量 c,所以是undefined
    var c;
    console.log(window.c);  //3
}
test3();
console.log(c);             //3
文档更新时间: 2023-07-19 14:08   作者:孙老师