ES6 核心特性详解:块级作用域、箭头函数、类与模块化

作者:代码拾荒时间:2026-10-30 12:38:44浏览:548

本文深入解析 ES6 对 JavaScript 核心机制的改进,涵盖块级作用域解决变量提升问题、箭头函数固定 this 指向、Class 语法糖简化继承以及模块化标准 import/export。通过对比 ES5 与 ES6 的差异,帮助开发者理解现代 JS 编程的最佳实践。

块级作用域与变量声明

块级作用域

在 ES5 版本中,JavaScript 仅存在全局作用域和函数作用域,缺乏块级作用域的概念。由于变量作用范围过大,变量在函数入口处即被创建,从而引发了所谓的“变量提升”现象。

ES5 中“变量提升”的特性容易引发以下常见错误:

1. 内层变量覆盖外层变量


var tmp = new Date();
function f() {
console.log(tmp);
if (false) { //执行则undefined
var tmp = "hello world";
}
}

2. 变量意外泄露,成为全局变量


var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5

过去,开发者通常利用闭包(如自执行函数)来规避此类问题。随着 ES6 引入块级作用域,自执行函数不再是解决此类问题的必要手段。

let 和 const

ES6 保持向后兼容性,这意味着 var 声明的变量依然遵循全局或函数作用域规则。因此,仅靠现有机制无法彻底解决 ES5 的“变量提升”问题。为此,ES6 引入了两个新的关键字:let 和 const。

1. let

let 被视为更完善的 var,它提供了更严格的作用域规则。

2. const

const 用于声明只读常量。一旦声明,其值不可更改,但需注意 const 声明的对象引用不可变,对象内部属性仍可修改(除非使用 Object.freeze 冻结)。


const a = [];
a.push('Hello'); // 可执行
a = ['Dave']; // 报错

也可以使用 Object.freeze 将对象完全冻结


const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;//

使用 let 和 const 声明变量时,需遵循以下规则:

• 变量仅在声明所在的块级作用域内有效

• 变量在声明前不可使用(存在暂时性死区)

• 不允许重复定义同一变量

• 声明的全局变量不会成为全局对象(window)的属性


var a = 1;
window.a // 1
let b = 1;
window.b // undefined

箭头函数与 this 指向

this 关键字

在 ES5 中,函数内的 this 指向函数执行时的作用域。例如:


function foo() {
setTimeout(function(){
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({id:42});//id: 21

在此例中,声明了一个函数 foo,内部包含一个延迟执行的 setTimeout,每 100ms 打印一次 this.id。通过 foo.call({id:42}) 调用并设定作用域。由于 setTimeout 在 100ms 后执行,此时 this 指向全局对象 window 而非函数 foo 本身。具体表现如下:

• 使用 call 改变 foo 的执行上下文,使其脱离 window,从而观察 setTimeout 中 this 的实际指向

• setTimeout 方法挂载在 window 对象下,因此其 this 指向执行时的作用域——即 window 对象。

根据《javascript高级程序设计》,超时调用的代码均在全局作用域中执行,因此函数中 this 的值在非严格模式下指向 window 对象,在严格模式下为 undefined。

为解决此问题,传统做法是将 this 赋值给其他变量:


function foo() {var that = this;
setTimeout(function(){
console.log('id:', that.id);
}, 100);
}
var id = 21;
foo.call({id:42});//id: 42

ES6 推出的箭头函数有效解决了这一痛点。

箭头函数

标识符 => 表达式


var sum = (num1, num2) => { return num1 + num2; }
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};

• 若函数仅有一个参数,可省略圆括号

• 若函数仅有一条返回语句,可省略大括号和 return

• 若函数直接返回对象,必须在对象外添加括号。(因为空对象 {} 与空代码块 {} 视觉相似,需用小括号包裹对象字面量以避免歧义)。

针对 this 指向问题,ES6 规定箭头函数中的 this 绑定定义时所在的作用域,而非运行时作用域。这使得 this 指向固定,有利于回调函数的封装。


function foo() {var that = this;
setTimeout(()=>{
console.log('id:', that.id);
}, 100);
} 
var id = 21;
foo.call({id:42});//id: 42

注意:箭头函数 this 指向固定化,并非因为内部有绑定机制,而是箭头函数本身没有自己的 this。其内部 this 继承自外层代码块的 this,这导致了以下限制:

• 不能用作构造函数

• 无法使用 call()、apply()、bind() 等方法改变 this 指向

类与继承机制

类与继承

传统 ECMAScript 没有类的概念,而是通过原型链实现继承。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,传统实现方式依赖于构造函数:


function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);

在此例中,构造函数 Point 拥有一个原型对象(prototype),该对象包含指向 Point 的指针(constructor),而实例 p 包含指向原型对象的内部指针(__proto__)。整个继承过程通过原型链完成。详情可参考文章:javascript中的prototype和constructor

class

ES6 提供了更接近传统编程语言的写法,引入了 Class(类)概念,作为对象的模板。通过 class 关键字定义类。尽管类仅是基于原型的面向对象模式的语法糖,且存在争议,但作者认为这是一种良好的语法改进,因为传统的原型链继承方式往往令人困惑。


//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var p = new Point(1, 2);

• 类内部包含一个 constructor 方法,作为默认方法。通过 new 命令生成对象实例时,会自动调用该方法。类必须拥有 constructor 方法,若未显式定义,系统将默认添加一个空的 constructor 方法。

• constructor 方法中的 this 关键字代表实例对象,

• 定义“类”的方法(如上述示例中的 toString)时,无需在函数前添加 function 关键字,直接将函数定义放入即可。此外,方法之间无需逗号分隔,添加逗号会导致报错。

• 使用时,直接对类使用 new 命令,用法与构造函数完全一致

• 类的所有方法均定义在类的 prototype 属性上

class 的继承——extend

Class 之间可通过 extends 关键字实现继承,相比 ES5 通过修改原型链实现继承,这种方式更加清晰便捷。


class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

• super 关键字:作为函数调用时(即 super(...args)),代表父类的构造函数;作为对象调用时(即 super.prop 或 super.method()),代表父类。此处表示父类的构造函数,用于新建父类的 this 对象。

• 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。原因是子类没有自己的 this 对象,而是继承父类的 this 对象并对其进行加工。若不调用 super 方法,子类将无法获得 this 对象。

模块化与字符串模板

模块化

历史上,JavaScript 缺乏模块(module)体系,难以将大型程序拆分为相互依赖的小文件并简单拼装,这对开发大型复杂项目构成了巨大障碍。为适应大型模块开发需求,社区制定了多种模块加载方案,如 CMD 和 AMD。

ES6 的模块化写法:


import { stat, exists, readFile } from 'fs';

上述代码实质是从 fs 模块加载 3 个方法,其他方法不加载。这种加载方式称为“编译时加载”,即 ES6 可在编译阶段完成模块加载,效率高于 CommonJS 模块的加载方式。这也导致无法直接引用 ES6 模块本身,因为它不是对象。

模块功能主要由两个命令构成:

• export

用于规定模块的对外接口,对外接口必须与模块内部的变量建立一一对应关系。


// 写法一
export var m = 1;
//错误
export 1;
// 写法二
var m = 1;
export {m};
//错误
export m;
// 写法三 重命名
var n = 1;
export {n as m};

• import

用于输入其他模块提供的功能,它接受一个对象(用大括号表示),其中指定要从其他模块导入的变量名(也可使用 * 号进行整体加载)

字符串插值

在 JavaScript 开发中,我们常需通过以下方式输出模板:


function sayHello(name){
return "hello,my name is "+name+" I am "+getAge(18);
}
function getAge(age){
return age;
}
sayHello("brand") //"hello,my name is brand I am 18"

我们需要使用 + 来连接字符串和变量(或表达式)。示例简单时看似无伤大雅,但在复杂场景下会显得繁琐不便,这种用法令人厌烦。对此,ES6 引入了模板字符串,可方便优雅地将 JS 的值插入到字符串中。

模板字符串

对于模板字符串,其特性包括:

• 使用反引号 `` 包裹;

• 使用 ${} 来输出值;

• ${} 内的内容可以是任何 JavaScript 表达式,因此函数调用和算术运算等均合法;

• 若值非字符串,将被自动转换为字符串;

• 保留所有空格、换行和缩进,并输出到结果字符串中(支持书写多行字符串)

• 内部使用反引号和大括号时需转义,转义使用反斜杠 \

针对上述示例,模板字符串的写法为:


function sayHello(name){
return `hello,my name is ${name} I am ${getAge(18)}`;
}
function getAge(age){
return age;
}
sayHello("brand") //"hello,my name is brandI am 18"

严格模式

严格模式的目标之一是允许更快地调试错误。帮助开发者调试的最佳途径是当确定问题发生时抛出相应的错误(throw errors when certain patterns occur),而非悄无声息地失败或表现出奇怪的行为(非严格模式下经常发生)。严格模式下的代码会抛出更多错误信息,能帮助开发者迅速注意到必须立即解决的问题。在 ES5 中,严格模式是可选项,但在 ES6 中,许多特性要求必须使用严格模式,这一习惯有助于我们书写更优质的 JavaScript。

以上所述是小编给大家介绍的ES6所改良的javascript“缺陷”问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!

作者最新文章