Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

块级作用域绑定 #1

Open
K-Kevin opened this issue Sep 14, 2019 · 0 comments
Open

块级作用域绑定 #1

K-Kevin opened this issue Sep 14, 2019 · 0 comments
Labels

Comments

@K-Kevin
Copy link
Owner

K-Kevin commented Sep 14, 2019

var 声明及变量提升(Hoisting)机制

在函数作用于或全局作用域中通过关键字 var 声明的变量,无论是哪里声明的,都会被当成当前作用域顶部声明的变量。

function getValue(condition) {
    if (condition) {
        var value = "blue";
        return value;
    } else {
        // value 存在 undefined
        return null;
    }
	// value 存在 undefined
}

可能你会觉得很奇怪,一般来说只有当 condition === true 时才创建变量 value。事实上在预编译阶段,JavaScript 引擎会将上面的 getValue 函数修改为下面这样:

//变量 value 的声明被提升至函数顶部,而初始化操作留在原处执行,这就意味着 else 里面也可以访问到
function getValue(condition) {
    var value;
    if (condition) {
        value = "blue";
        return value;
    } else {
        return null;
    }
}
注意:省略 var 操作符

先看下面的代码

function test() {
    var message = "hi";
}
test();
alert(message);//错误

这里的 message 是在函数中使用 var 定义的,当函数被调用后,这个变量就立即被销毁了,所以执行下一行会导致错误。不过,像下面这样省略 var 操作符,从而创建一个全局变量:

function test() {
    message = "hi";
}
test();
alert(message);//"hi"

这个例子省略了 var 操作符,因而 message 就成了全局变量。调用过一次 test() 函数,这个变量就有了定义,就可以再函数外部的任何地方被访问到。

虽然省略 var 操作符可以定义全局变量,但是不建议这么做。因为在局部作用域中定义的全局变量很难维护,而且忽略 var 操作符,也会由于相应的变量不会马上就有定义而导致不必要的混乱。另外,在严格模式下会抛出 ReferedceError 错误。

if 语句中的 var
if(true) {
    var color = "red";
}
alert(color);  //"blue"	

if 语句中的变量声明会将变量添加到当前执行环境,在这里是全局环境,切记。

ES6 中的块级声明

块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域(也被称作词法作用域)存在于:

  • 函数内部
  • 块中(方括号 {} 包裹的代码块)

const 和 let 声明都是块级标识符,所以只在当前代码块内有效,一旦执行到块外会被立即销毁。也不会被提升至作用域顶部。

let 声明

let 与 var 用法相同,用 let 代替 var 可以把变量的作用域限制在当前代码块中,且不会被提升。如下代码

function getValue(condition) {
    if (condition) {
        let value = "blue";
        return value;
    } else {
        // value 不存在
        return null;
    }

    // value 也不存在
}

禁止重声明

假设作用域中已经存在某个标识符,此时再使用 let 关键字声明就会报错,举个例子:

var count = 30;

// Syntax error
let count = 40;

但是如果把重声明的动作放在内嵌的作用域中则是可以的:

var count = 30;
if (condition) {
    //不会报错
    let count = 40;
}

这一点需要注意

const 声明

const 声明的是常量,一旦值确定了不可更改。因此,通过 const 声明的常量必须进行初始化。

const maxItems = 30;

// 错误示例
const name;

与 let 一样,在同一作用域用 const 声明已经存在的标识符也会导致语法错误,无论已经声明的这个标识符是 let 还是 var:

var message = "Hello!";
let age = 25;

// 这两条语句都会报错
const message = "Goodbye!";
const age = 30;

const 和 let 很相似,但是有一个很大的不同,即无论在严格模式还是非严格模式下,都不可以为 const 定义的常量再赋值。

用 const 声明对象

const 声明不允许修改绑定,但允许修改值:

const person = {
    name: "kai"
};

// 可以修改
person.name = "jack";

// 报错
person = {
    name: "are you ok?"
};

临时死区(TDZ)

什么是 TDZ 呢? 先看代码:

if (condition) {
    console.log(typeof value);  // 引用错误
    let value = "blue";
}

JavaScript 引擎在扫描代码发现变量声明时,要么将他们提升至作用域顶部(var),要么将声明放到 TDZ 中(let、const)。访问 TDZ 中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从 TDZ 中移出,然后才可访问。

循环中的块作用域绑定

先看下这个问题,大家肯定不陌生,这里我们把 var 换成 let 就不可以被访问到了:

for (var i = 0; i < 10; i++) {
    process(items[i]);
}

// 此时的 i 依然可以访问到
console.log(i); // 10

循环中的函数

使用 var 声明在循环中创建函数,代码如下:

var funcs = [];

for (var i = 0; i < 10; i++) {
    funcs.push(function() { console.log(i); });
}

funcs.forEach(function(func) {
    func();     // 输出了 10 次数字 10
});

上面的输出结果是 10 次数字 10,实际上我们想要的是 0~9。这是因为循环里的每次迭代同时共享着 i,循环内部的函数全部保留了对相同变量的引用。

通常为了解决这个问题,会使用立即执行函数(IIFE)包裹,以强制生成计数器变量的副本:

var funcs = [];

for (var i = 0; i < 10; i++) {
    funcs.push((function(value) {
        return function() {
            console.log(value);
        }
    }(i)));
}

funcs.forEach(function(func) {
    func();     // 0~9
});

循环中的 let、const 声明

使用 let 和 const 声明就会解决不适用 IIFE 来达到效果。

var funcs = [];

for (let i = 0; i < 10; i++) {
    funcs.push(function() {
        console.log(i);
    });
}

funcs.forEach(function(func) {
    func();     // 0~9
})

因为每次循环的时候 let 声明都会创建一个新的变量 i,并将其初始化为 i 的当前值。

ES6 并没有不允许在循环中使用 const 声明,前提是你只要不修改其值就可以,比如在 for-in 或 for-of 循环中使用 const 和使用 let 的效果是一致的

全局块作用域绑定

let 和 const 与 var 的另一个区别是它们在全局作用域中的行为。当 var 被作用域全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的 window)的属性。这就意味着 var 可能会覆盖 window 中我们创建的某些全局属性:

// 在浏览器环境
var appid = "123";
console.log(window.appid);     // "123"

如果在全局作用域中使用 let 或 const,会在全局作用域下创建一个新的绑定,但不会添加为全局对象的属性。

如果希望在全局对象下定义对象,依然可以用 var。这种情况常见于在浏览器中跨 frame 或跨 window 访问代码

总结

  • 优先使用 const(大部分的变量的值初始化后不再改变,要养成习惯)
  • 注意 TDZ
@K-Kevin K-Kevin added the ES6 label Sep 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant