JavaScript ES6语法新特性概述
块级作用域与 let 和 const 关键字
在 ES6 之前,JavaScript 只有函数作用域和全局作用域,没有块级作用域。这就导致了一些不符合预期的行为。例如:
function test() {
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
}
test();
// 输出 5 5 5 5 5
这里使用 var
声明的 i
是函数作用域,在循环结束后 i
的值变为 5,所以在定时器回调中输出的都是 5。
ES6 引入了 let
和 const
关键字,它们具有块级作用域。以 let
为例:
function test() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
}
test();
// 输出 0 1 2 3 4
在这个例子中,let
声明的 i
具有块级作用域,每次循环都会创建一个新的 i
变量,所以定时器回调中输出的是循环的当前值。
const
用于声明常量,一旦声明,值就不能被改变。例如:
const PI = 3.14159;
// PI = 3.14; // 这会报错,常量不能重新赋值
const
声明的变量也具有块级作用域。并且,如果 const
声明的是一个对象或数组,虽然不能重新赋值整个对象或数组,但可以修改其内部属性或元素。例如:
const obj = { name: 'John' };
obj.age = 30;
console.log(obj); // { name: 'John', age: 30 }
箭头函数
箭头函数是 ES6 引入的一种简洁的函数定义方式。传统函数定义方式如下:
function add(a, b) {
return a + b;
}
使用箭头函数可以写成:
const add = (a, b) => a + b;
当箭头函数只有一个参数时,可以省略参数的括号:
const square = num => num * num;
如果箭头函数没有参数,需要保留括号:
const getRandom = () => Math.random();
箭头函数与传统函数有几个重要区别。首先,箭头函数没有自己的 this
绑定,它的 this
继承自外层作用域。例如:
const obj = {
name: 'John',
sayName: function() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
};
obj.sayName(); // 输出 'John'
在这个例子中,箭头函数中的 this
指向 obj
,因为箭头函数没有自己的 this
,它继承了外层 sayName
函数的 this
。
而传统函数在 setTimeout
中 this
会指向全局对象(在浏览器中是 window
),除非使用 bind
、call
或 apply
改变 this
的指向。
其次,箭头函数没有 arguments
对象。可以使用剩余参数(rest 参数)来替代获取函数的所有参数。例如:
const sum = (...nums) => nums.reduce((acc, num) => acc + num, 0);
console.log(sum(1, 2, 3)); // 输出 6
模板字符串
ES6 之前,拼接字符串通常使用 +
运算符,这在处理复杂字符串时会变得很繁琐。例如:
const name = 'John';
const age = 30;
const message = 'My name is'+ name +'and I am'+ age +'years old.';
ES6 引入了模板字符串,使用反引号()来定义。模板字符串可以包含变量和表达式,通过
${}` 语法嵌入。例如:
const name = 'John';
const age = 30;
const message = `My name is ${name} and I am ${age} years old.`;
console.log(message);
模板字符串还支持多行字符串。例如:
const html = `
<div>
<h1>Hello, World!</h1>
<p>This is a multi - line string in JavaScript.</p>
</div>
`;
console.log(html);
解构赋值
解构赋值是一种从数组或对象中提取值并赋给变量的简洁方式。
数组解构
从数组中提取值:
const numbers = [1, 2, 3];
const [a, b, c] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
可以跳过某些值:
const numbers = [1, 2, 3];
const [a,, c] = numbers;
console.log(a); // 1
console.log(c); // 3
还可以使用剩余参数来获取剩余的值:
const numbers = [1, 2, 3, 4, 5];
const [a, b,...rest] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]
对象解构
从对象中提取值:
const person = { name: 'John', age: 30, city: 'New York' };
const { name, age, city } = person;
console.log(name); // 'John'
console.log(age); // 30
console.log(city); // 'New York'
可以给变量取不同的名字:
const person = { name: 'John', age: 30, city: 'New York' };
const { name: fullName, age: yearsOld, city: livingCity } = person;
console.log(fullName); // 'John'
console.log(yearsOld); // 30
console.log(livingCity); // 'New York'
也可以设置默认值:
const person = { name: 'John' };
const { name, age = 18 } = person;
console.log(name); // 'John'
console.log(age); // 18
函数参数的默认值
在 ES6 之前,如果要给函数参数设置默认值,通常需要在函数内部进行判断。例如:
function greet(name) {
name = name || 'Guest';
console.log('Hello,'+ name);
}
greet(); // Hello, Guest
greet('John'); // Hello, John
ES6 可以直接在函数参数中设置默认值:
function greet(name = 'Guest') {
console.log('Hello,'+ name);
}
greet(); // Hello, Guest
greet('John'); // Hello, John
这样代码更加简洁明了。当函数调用时,如果没有传递参数,就会使用默认值。
剩余参数与扩展运算符
剩余参数
剩余参数允许将不定数量的参数表示为一个数组。例如,传统方式获取函数的所有参数需要使用 arguments
对象:
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
使用剩余参数可以这样写:
function sum(...nums) {
return nums.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3)); // 6
这里的 ...nums
就是剩余参数,它将所有传入的参数收集到一个数组 nums
中。
扩展运算符
扩展运算符也是 ...
,但它的作用与剩余参数相反。它可以将一个数组展开成多个元素。例如,合并两个数组:
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1,...arr2];
console.log(combined); // [1, 2, 3, 4]
扩展运算符还可以用于函数调用,将数组作为参数传递。例如:
function add(a, b) {
return a + b;
}
const numbers = [1, 2];
const result = add(...numbers);
console.log(result); // 3
在对象中也可以使用扩展运算符来合并对象:
const obj1 = { name: 'John' };
const obj2 = { age: 30 };
const combinedObj = {...obj1,...obj2 };
console.log(combinedObj); // { name: 'John', age: 30 }
对象字面量增强
在 ES6 中,对象字面量有了一些增强的功能。
属性简写
当对象的属性名和变量名相同时,可以简写。例如:
const name = 'John';
const age = 30;
const person = { name, age };
console.log(person); // { name: 'John', age: 30 }
这等价于:
const name = 'John';
const age = 30;
const person = { name: name, age: age };
console.log(person);
方法简写
定义对象方法时可以省略 function
关键字。例如:
const person = {
name: 'John',
sayHello() {
console.log('Hello, my name is'+ this.name);
}
};
person.sayHello(); // Hello, my name is John
这比传统的写法更加简洁:
const person = {
name: 'John',
sayHello: function() {
console.log('Hello, my name is'+ this.name);
}
};
person.sayHello();
计算属性名
在 ES6 中,可以在对象字面量中使用计算属性名。例如:
const key = 'name';
const person = {
[key]: 'John',
age: 30
};
console.log(person); // { name: 'John', age: 30 }
这在需要动态生成属性名时非常有用。
类的引入
ES6 引入了类的概念,虽然 JavaScript 本质上仍然是基于原型的面向对象语言,但类的语法让代码更加清晰和易于理解。
类的定义
定义一个简单的类:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
这里的 constructor
是类的构造函数,用于初始化对象的属性。sayHello
是类的方法。
类的实例化
使用 new
关键字创建类的实例:
const john = new Person('John', 30);
john.sayHello(); // Hello, my name is John and I am 30 years old.
继承
类可以通过 extends
关键字实现继承。例如:
class Student extends Person {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
study() {
console.log(`${this.name} is studying in grade ${this.grade}`);
}
}
const tom = new Student('Tom', 15, 9);
tom.sayHello(); // Hello, my name is Tom and I am 15 years old.
tom.study(); // Tom is studying in grade 9
在子类的构造函数中,需要先调用 super()
来调用父类的构造函数,以初始化继承自父类的属性。
模块
ES6 引入了模块系统,用于更好地组织和管理代码。
模块的定义
一个模块可以是一个单独的 JavaScript 文件。例如,创建一个 math.js
文件:
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
这里使用 export
关键字导出函数。也可以使用默认导出:
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
export default { add, subtract };
模块的导入
在另一个文件中导入模块:
// main.js
import { add, subtract } from './math.js';
console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 2
如果是默认导出,可以这样导入:
// main.js
import math from './math.js';
console.log(math.add(1, 2)); // 3
console.log(math.subtract(5, 3)); // 2
Promise
Promise 是 ES6 引入的用于处理异步操作的一种机制,它解决了回调地狱(callback hell)的问题。
创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation successful');
} else {
reject('Operation failed');
}
}, 1000);
});
Promise
构造函数接受一个回调函数,该回调函数有两个参数 resolve
和 reject
。resolve
用于表示异步操作成功,reject
用于表示异步操作失败。
处理 Promise
promise.then(value => {
console.log(value); // Operation successful
}).catch(error => {
console.error(error);
});
then
方法用于处理成功的情况,catch
方法用于处理失败的情况。
Promise 链
可以将多个 then
方法连接起来形成 Promise 链。例如:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
const promise2 = promise1.then(value => {
return value * 2;
});
const promise3 = promise2.then(value => {
return value + 3;
});
promise3.then(finalValue => {
console.log(finalValue); // 5
}).catch(error => {
console.error(error);
});
在这个例子中,promise1
成功后返回 1
,promise2
将其乘以 2
得到 2
,promise3
再将其加 3
得到 5
。
Async/await
async/await
是基于 Promise 的更简洁的异步处理方式,它让异步代码看起来更像同步代码。
async 函数
async
函数总是返回一个 Promise。例如:
async function getData() {
return 'Data fetched';
}
const result = getData();
result.then(value => {
console.log(value); // Data fetched
});
这里 getData
函数返回的是一个已解决(resolved)的 Promise。
await
await
只能在 async
函数内部使用,它用于暂停 async
函数的执行,直到 Promise 被解决(resolved)或被拒绝(rejected)。例如:
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncFunction() {
console.log('Start');
await delay(2000);
console.log('End');
}
asyncFunction();
在这个例子中,await delay(2000)
会暂停 asyncFunction
的执行,直到 delay
返回的 Promise 被解决,也就是 2 秒后,然后继续执行后面的代码。
处理错误
在 async
函数中,可以使用 try...catch
来处理错误。例如:
async function asyncFunction() {
try {
await Promise.reject('Error occurred');
} catch (error) {
console.error(error); // Error occurred
}
}
asyncFunction();
这样可以更优雅地处理异步操作中的错误,而不是像 Promise 那样使用 catch
方法。
Symbol
Symbol 是 ES6 引入的一种新的原始数据类型。每个 Symbol 值都是唯一的。
创建 Symbol
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false
也可以给 Symbol 提供一个描述:
const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false
使用 Symbol 作为对象属性
Symbol 可以作为对象的属性,并且这些属性不会被常规的对象遍历方法(如 for...in
、Object.keys
)枚举到。例如:
const mySymbol = Symbol('myProperty');
const obj = {
[mySymbol]: 'Value associated with symbol'
};
console.log(obj[mySymbol]); // Value associated with symbol
Map 和 Set
Map
Map
是一种键值对的集合,与普通对象不同的是,Map
的键可以是任意类型。例如:
const map = new Map();
const key1 = { name: 'John' };
const key2 = [1, 2, 3];
map.set(key1, 'Value for key1');
map.set(key2, 'Value for key2');
console.log(map.get(key1)); // Value for key1
console.log(map.get(key2)); // Value for key2
Map
还提供了一些有用的方法,如 size
获取键值对的数量,has
检查是否存在某个键,delete
删除某个键值对。
Set
Set
是一种无序的唯一值集合。例如:
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 重复值不会被添加
console.log(set.has(1)); // true
console.log(set.has(3)); // false
console.log(set.size); // 2
Set
也有一些方法,如 add
添加值,delete
删除值,clear
清空集合。
通过以上对 JavaScript ES6 语法新特性的详细介绍,我们可以看到这些新特性大大增强了 JavaScript 的功能和灵活性,使得代码更加简洁、易读和维护。无论是在前端开发还是后端开发中,合理运用这些新特性都能提高开发效率和代码质量。在实际项目中,应该根据具体需求和场景,充分发挥这些新特性的优势,打造出更优秀的 JavaScript 应用程序。