【深入理解ES6】块级作用域绑定

1. var声明及变量提升机制

提升(Hoisting)机制:通过关键字var声明的变量,都会被当成在当前作用域顶部生命的变量。

复制代码
function getValue(condition){
	if(condition){
		var value = "blue";
		console.log(value);
	}else{
		// 此处可访问变量value,其值为undefined
		return null
	}
	// 此处可访问变量value,其值为undefined
}

JavaScript引擎会将上面的getValue函数修改为下面这样。变量value的声明被提升至函数顶部,初始化操作依旧保留在原处执行。为此,ES6引入了块级作用域来强化对变量声明周期的控制。

复制代码
function getValue(condition){
	var value;
	if(condition){
		value = "blue";
		console.log(value);
	}else{
		return null
	}
}

2. 块级声明

块级声明用于声明在指定块的作用域之外无法访问的变量。

块级作用域存在于:

  • 函数内部
  • 块中('{}'之间的区域)。

2.1. let声明

  • 用法同var相同,但声明不会被提升;

  • 禁止在同一作用域内重声明;

  • 如果当前作用域内嵌另一个作用域,便可在内嵌的作用域中用let声明同名变量。

    function getValue(condition){
    if(condition){
    var value = "blue";
    console.log(value);
    }else{
    // 变量value在此处不存在
    return null
    }
    // 变量value在此处不存在
    }

    /*禁止重声明/

    var count = 30;
    // 抛出语法错误
    let count = 40;

    if(condition){
    // 不会抛出错误
    let count = 40;
    }

2.2. const声明

  • 声明的是常量,必须初始化;

  • 禁止在同一作用域内重声明;

  • 不可再赋值(常量对象可修改值;

  • const声明对象时,不允许修改绑定,但可修改值。

    // 有效的常量
    const maxItems = 30;
    // 语法错误,未初始化
    const name;

    if(condition){
    const cnt = 40;
    }
    // 在此处无法访问cnt

    let age = 20;
    // 抛出错误,重声明
    const age = 15;

    const pos = 30;
    // 抛出语法错误,不能重新赋值
    pos = 35;

    const person = {
    name: 'Nicholas'
    };
    // 可以直接修改对象属性的值
    person.name = 'Fleur';
    // 直接给person赋值,即要改变person的绑定,会抛出语法错误。
    person = {
    name: 'DpprZ'
    }

3. 临时死区(Temppral Dead Zone, TDZ)

由于 console.log(typeof value) 语句会抛出错误,因此用 let 定义并初始化变量 value 的语句不会执行。此时的 value 还位于 JavaScript 社区所谓的"临时死区"。TDZ 通常用来描述 let 和 const 的不提升效果。

复制代码
if(condition){
	console.log(typeof value);  // 引用错误
	let value = 40;
}

console.log(typeof value);  // "undefined"
if(condition){
	let value = 40;
}

JS引擎扫描代码发现变量声明时:

  • var声明:将他们提升至作用域顶部。
  • let和const声明:将声明放在TDZ中。访问TDZ中的变量会触发运行时错误。只有执行过变睾声明语句后,变量才会从TDZ中移除,然后方可正常访问。

4. 循环中的块级作用域绑定

for循环中通过let将计数器变量限制在循环内部。

复制代码
for(var i = 0; i < 10; i++){
	// 更多代码
}
// 在这里仍然可以访问变量i
console.log(i); // 10

for(let i = 0; i < 10; i++){
	// 更多代码
}
// i在这里不可访问,抛出错误
console.log(i); 

5. 循环中的函数、let声明、const声明

复制代码
let funcs = [];
for(var i = 0; i < 10; i++){
    funcs.push(function(){
        console.log(i)
    })
}
/*
每个funcs[]中都存在一个函数:
	ƒ (){ console.log(i) }
*/
funcs.forEach(function(func){
    func(); // 10个10
})

因为这里的循环里的每次迭代同时共享着变量i,循环内部创建的函数都保留了对相同变量的引用。

解决该问题的两种方案:

在循环中使用立即调用函数表达式(IIFE),以强制生成计数器变量的副本。

复制代码
let funcs = [];

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

funcs.forEach(function(func){
    func(); // 0 1 2 3......9
})

用let声明计数器:每次迭代循环都会创建一个新变量 i,并以之前迭代同名变量的值将其初始化。所以循环内部创建的每一个函数都能够得到属于自己的 i 的值。let 声明在循环内部的行为是标准中专门定义的,它不一定与let的不提升特性相关,理解这一点至关重要!

复制代码
let funcs = [];

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

funcs.forEach(function(func){
    func(); // 0 1 2 3...... 9
})
  • let声明:声明计数器、for-in、for-of
  • const声明:生命循环内不改变的值、for-in、for-of

6. 全局块作用域绑定

var、let、const在全局作用域中的行为区别:

var会创建一个新的变量作为全局对象(浏览器环境中的window对象),会无意中覆盖已存在的全局属性。

let、const会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,不能覆盖只是遮蔽。

相关推荐
南囝coding2 分钟前
命令行神器 The Fuck,敲错命令的后悔药
前端·后端
coding随想3 分钟前
揭秘前端世界的“水下炸弹”:DOM事件流的三大阶段与实战秘籍!
前端
程序员小续3 分钟前
再也不写 Class:React Hooks 完整实战与最佳实践
前端·javascript·react.js
芝士加7 分钟前
一个有趣的搜索神器:pinyin-match
前端·javascript·开源
得物技术14 分钟前
基于TinyMce富文本编辑器的客服自研知识库的技术探索和实践|得物技术
前端·aigc·openai
一只大黑洋16 分钟前
Clipboard.js 复制内容
前端·javascript·vue.js
前端灵派派16 分钟前
openlayer绘制图形
前端
moyu8417 分钟前
ES6 Set与Map完全指南:从入门到性能优化
前端
然我19 分钟前
从 “只会聊天” 到 “能办实事”:OpenAI Function Call 彻底重构 AI 交互逻辑(附完整接入指南)
前端·javascript·人工智能