手记

JavaScript作用域与作用域链

作用域

变量或函数可以发生作用的区域即是作用域。作用域的最大用途就是隔离变量或函数,并控制变量或函数的生命周期,超过该区域的变量或函数就不能被访问。

全局作用域

在整个程序生命周期内都是有效的,在任意的函数内部都能访问的变量或函数拥有全局作用域。以下几种情形拥有全局作用域:

1、在最外层定义的变量和函数拥有全局作用域。

例子:

var out = "hello";

function outFn(str) {
    console.log(str);
}

function demo() {
    outFn(out);
}

console.log(demo());
//输出:hello

2、未声明直接赋值的变量拥有全局作用域。

例子:

function demo() {
    out = "hello";
}
demo();
console.log(out);
//输出:hello

3、在浏览器中,定义在window对象上的属性和方法,拥有全局作用域。

例子:

window.out = "hello";

function demo() {
    console.log(out);
    console.log(window.parseInt("11A"));
}
demo();
//输出:hello,11

函数作用域

在某个函数内部声明的变量或函数拥有函数作用域。它们只能被该函数的语句使用,函数外部是不可访问的。函数在创建的时候,变量和函数的函数作用域已经确定下来。函数的参数也属于函数内部的变量,因此拥有函数作用域。

例子:

function demo(name) {
    var int = "hello";

    function intFn() {
        console.log(int + "," + name);
    }
}
demo("Tom");
console.log(int); //输出:错误
intFn(); //输出:错误

ES6的块级作用域

1、在某个块("{ }")的内部声明的变量拥有块级作用域(在块级作用域内部定义函数需要特殊对待,下面第3条会有讲解)。它们只能被该代码块内部的语句使用,代码块外部不可访问。代码块在创建的时候,变量的块级作用域已经确定下来。块级作用域和函数作用域也可以统称为局部作用域。
2、ES5 只有全局作用域和函数作用域,没有块级作用域。ES6的 let 声明的变量只在它所在的代码块内有效,实际上就是为JavaScript新增了块级作用域。
3、ES5 规定,函数只能在全局作用域和函数作用域之中声明。ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。但是这样的处理规则显然会对老代码产生很大的影响,出于兼容老代码的考虑,在块级作用域中声明的函数依然可以在作用域外部引用。如果需要函数只在块级作用域中起作用,可以写成函数表达式,而不是函数声明语句。

例子:

if (true) {
    let int = 1;
    let intFn = function() {
        console.log(int + 1);
    }
}
console.log(int); //输出:错误
intFn(); //输出:错误

let命令的用法类似于var,但是它们有以下4点不同之处:

1、let声明的变量拥有块级作用域,var声明的变量没有块级作用域。

例子:

{
  let a = 0;
  var b = 1;
}
console.log(b); //输出:1
console.log(a); //输出:错误

2、var命令会发生”变量提升“现象,即无论var声明的变量处于作用域的第几行,都会提升到作用域的头部,并初始化为undefined。而let命令声明的变量一定要在声明后使用,否则报错。

例子:
var声明:变量提升

function demo() {
    console.log(tmp); //输出:undefined
    if (true) {
        var tmp = "hello";
    }
}
demo();

let声明:没有变量提升

function demo() {
    console.log(tmp); //输出:错误
    if (true) {
        let tmp = "hello";
    }
}
demo();

3、只要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,凡是在声明之前就使用这些变量,就会报错。

例子:
var声明:变量可以在声明之前使用

var tmp = "hello";
if (true) {
    console.log(tmp); //输出:hello
    var tmp = "world";
}

let声明:变量不可以在声明之前使用

let tmp = "hello";
if (true) {
    console.log(tmp); //输出:错误
    let tmp = "world";
}

4、let不允许在相同作用域内,重复声明同一个变量。

例子:
var声明:可以重复声明同一个变量

{
    var i = 0;
    var i = 1;
}
console.log(i);
//输出:1

let声明:不可以重复声明同一个变量

{
    let i = 0;
    let i = 1;
}
//输出:错误

const 命令与 let 用法基本一致,唯一不同的 const 声明的是一个只读的常量。一旦声明,常量的值就不能改变:如果值为基本类型,值不可改变;如果值为引用类型,引用地址不可改变。另外,const命令只声明不赋值也会报错。

例子:
const 声明基本类型

const i = true;
i = false;
//输出:错误

const 声明引用类型

//引用类型的数据内容可以修改
const array = [];
array.push(0);
array.push(1);
console.log(array.join("")); //输出:01

//引用类型的引用地址不可以修改
array = [3, 4]; //输出:错误

局部作用域的遮蔽效应

局部作用域的变量和全局作用域的变量的名称可以相同,但是局部作用域变量的值会覆盖全局作用域变量的值,这种现象称为“遮蔽效应”。

例子:

let i = 0;

if (true) {
    let i = 1;
    console.log(i); //输出:1 
}

function demo() {
    let i = 2;
    console.log(i); //输出:2
}
demo();

作用域链

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法搜索到某个变量时,引擎就会在外层嵌套的作用域中继续搜索,直到搜索到该变量,或抵达最外层的作用域(也就是全局作用域)为止。这样一条有序的列表,称为作用域链,作用域链的最前端一定是当前作用域。

作用域链可以理解为下面这种数据格式:

{
    Scope: [
        { //当前作用域
            变量,
            函数名
        }, 
        { //下个作用域
            变量,
            函数名
        }, 
        { //下下个作用域
            变量,
            函数名
        },
        ... 
        { //全局作用域
            变量,
            函数名
        },
    ]
}

如有错误,欢迎指出。

24人推荐
随时随地看视频
慕课网APP