1、块级作用域
ES6 引入了 let 和 const 关键字,从而使 JavaScript 具备了块级作用域。在块级作用域中声明的变量和常量只在块级作用域内部有效,不会污染全局作用域。
function foo() {
if (true) {
let x = 1;
const PI = 3.14;
}
console.log(x); // Uncaught ReferenceError: x is not defined
console.log(PI); // Uncaught ReferenceError: PI is not defined
}
2、箭头函数
ES6 引入了箭头函数 =>,它可以更简洁地定义匿名函数,并具备词法作用域绑定 this 对象的能力。
let nums = [1, 2, 3, 4];
let result = nums.map(num => num * 2);
console.log(result); // [2, 4, 6, 8]
3、解构赋值
ES6 引入了解构赋值,可以快速方便地将数组或对象中的值解构出来,分别赋给变量。
let nums = [1, 2, 3, 4];
let [a, b, c, d] = nums;
console.log(a, b, c, d); // 1 2 3 4
let obj = { name: "Tom", age: 20 };
let { name, age } = obj;
console.log(name, age); // Tom 20
4、默认参数
ES6 允许在函数定义时指定默认参数,当调用函数时,如果没有传递该参数,则使用默认参数。
function foo(x, y = 10) {
console.log(x, y);
}
foo(5); // 5 10
5、剩余参数
ES6 中的剩余参数(Rest Parameters)允许我们将不定数量的参数表示为一个数组。它允许我们将多个参数表示为一个数组,因此在函数中可以方便地使用。
剩余参数的语法是在函数声明的时候,在最后一个参数前面加上三个点号(…)。
下面是一个简单的函数,它使用剩余参数语法:
function myFunc(a, b, ...others) {
console.log(a); // 输出:1
console.log(b); // 输出:2
console.log(others); // 输出: [3, 4, 5]
}
myFunc(1, 2, 3, 4, 5);
在上面的示例中,我们使用剩余参数语法来在函数中传递任意数量的参数。具体来说,我们定义了一个函数 myFunc,其前两个参数 a 和 b 是有名字的参数,后面的参数 others 是用剩余参数语法表示的,该参数将收集除了前两个参数以外的任何数量的参数,并将它们作为数组传递给函数参数 others。
当我们调用该函数 myFunc 时,传递了五个参数,其中前两个参数 1 和 2 被分别分配给参数 a 和 b,其余参数 3、4 和 5 被复制到数组 others 中,然后打印输出出来。
总之,剩余参数解析允许我们定义函数来捕获任意数量的参数,从而增加了 JavaScript 语言的灵活性和可扩展性,在开发过程中非常有用。
6、扩展运算符
ES6 引入了 ... 扩展运算符,可以扩展数组和对象,从而简化代码逻辑。
let nums1 = [1, 2, 3];
let nums2 = [4, 5, 6];
let nums3 = [...nums1, ...nums2];
console.log(nums3); // [1, 2, 3, 4, 5, 6]
let obj1 = { name: 'Tom' };
let obj2 = { age: 20 };
let obj3 = { ...obj1, ...obj2 };
console.log(obj3); // {name: "Tom", age: 20}
7、模板字符串
ES6 引入了一些新的字符串处理方法,例如模板字符串和字符串判断等。
模板字符串可以在字符串中插入变量,从而更方便地拼接字符串和生成动态内容。
let name = 'Tom';
let str = `Hello, ${name}!`;
console.log(str); // Hello, Tom!
字符串判断方法包括startsWith(), endsWith(), 和 includes()。
let str = 'hello world!';
console.log(str.startsWith('hello')); // true
console.log(str.endsWith('world')); // false
console.log(str.includes('o')); // true
8、对象字面量属性名简写
ES6 中引入了对象字面量属性名简写,让你在定义对象时可以更加简洁和方便地使用属性。
在 ES5 中,我们需要这样定义一个对象:
var name = "Tom";
var age = 20;
var person = {
name: name,
age: age
};
而在 ES6 中,对象字面量属性名简写提供了一种更加简洁的写法:
let name = "Tom";
let age = 20;
let person = { name, age };
这样,我们可以直接使用变量名作为对象的属性名,而不需要额外地写出属性名和变量名。
对象字面量属性名简写不仅可以简化代码,还可以使代码更加易读和易维护。例如,如果我们需要在不同的对象中使用相同的属性名,我们只需要定义一个变量,并将其用于不同的对象。
let name = "Tom";
let age = 20;
let person1 = { name, age };
let person2 = { name, age };
在上面的示例中,我们可以看到,name 和 age 变量被定义了一次,然后在 person1 和 person2 对象中被重复使用。由于对象字面量属性名简写,在定义 person1 和 person2 对象时,我们不需要再次定义属性名和变量名的对应关系,只需要使用变量名作为属性名即可。
除了使用变量名作为属性名之外,对象字面量属性名简写还支持使用计算属性名定义对象。例如:
let prop = 'name';
let person = {
[prop]: 'Tom'
};
console.log(person.name); // 输出:Tom
在上面的代码中,我们使用方括号表达式 [] 定义一个计算属性名,将变量 prop 的值作为属性名,并将其赋值为 'Tom'。最后,我们打印输出属性 name 的值,验证对象被成功定义。
总之,对象字面量属性名简写是 ES6 中非常有用的一项新特性,它可以简化属性定义的代码,让你的代码更加干净和易读,减少了因为繁琐的代码而出现语法错误的概率,从而提高开发效率。
9、对象字面量方法定义
ES6 引入了一种新的语法,使得在对象字面量中定义方法变得更加容易。这种语法被称为对象字面量方法定义。
在 ES5 中,你需要使用函数表达式或者命名函数来定义对象的方法。例如:
var myObj = {
myMethod: function(param1, param2) {
// ...
},
myOtherMethod: function() {
// ...
}
};
在 ES6 中,你可以使用更简单的语法来定义对象的方法。例如:
let myObj = {
myMethod(param1, param2) {
// ...
},
myOtherMethod() {
// ...
}
};
这样,你只需声明一个函数,并将其作为对象的属性,就可以在对象中定义方法了。对象字面量方法定义语法还支持使用 this 关键字来引用对象属性。
let myObj = {
myProperty: 'value',
myMethod () {
console.log(this.myProperty);
}
};
myObj.myMethod(); // 输出:'value'
在上面的示例中,我们定义了一个包含 myProperty 属性和 myMethod() 方法的对象。在 myMethod() 函数中,我们使用 this 关键字来引用对象的 myProperty 属性,然后输出它的值。
对象字面量方法定义让我们可以更加方便地定义对象方法,同时使得代码更加简洁易读。通过使用可读性更高的语法,我们能够更好地描述对象,提供更好地代码组织和可维护性。
10、Symbol数据类型
Symbol 是 ES6 引入的一种新的原始数据类型,用于表示唯一标识符。每个 Symbol 都是独一无二的,可以用于对象属性的唯一性标识。
通常情况下,我们可以使用字符串作为对象属性名,例如:
let obj = {
name: 'Tom',
age: 20
};
但是,如果多个对象需要共享一个属性,或者对象的属性名可能会被其他脚本修改,我们就需要使用 Symbol 来保证属性名的唯一性。
下面是一个使用 Symbol 保证属性名唯一性的示例:
let name1 = Symbol('name');
let name2 = Symbol('name');
let obj = {};
obj[name1] = 'Tom';
obj[name2] = 'Jerry';
console.log(obj[name1]); // 输出:Tom
console.log(obj[name2]); // 输出:Jerry
在上面的代码中,我们使用两个不同的 Symbol 对象 name1 和 name2,它们的描述文本分别为 'name',用作对象的属性名。将 name1 和 name2 作为对象的属性名,可以保证二者的唯一性。
Symbol 还可以用于定义对象的私有属性和方法,例如:
let Person = (function () {
let _name = Symbol('name');
let _age = Symbol('age');
class Person {
constructor(name, age) {
this[_name] = name;
this[_age] = age;
}
getName() {
return this[_name];
}
getAge() {
return this[_age];
}
}
return Person;
})();
let p = new Person('Tom', 20);
console.log(p.getName()); // 输出:Tom
console.log(p.getAge()); // 输出:20
console.log(p._name); // 输出:undefined,因为 name 是私有属性
在上面的代码中,我们使用 Symbol 定义了对象 Person 中的私有属性 _name 和 _age,并使用它们创建了对象的公开方法 getName() 和 getAge()。由于 _name 和 _age 属性名是 Symbol 实例,因此在外部是无法直接访问对象的私有属性,可以保证对象的封装性和安全性。
总之,Symbol 是一种非常有用的数据类型,其可以用于实现对象属性的唯一性、定义对象的私有属性和方法等场景。需要注意的是,由于 Symbol 是一种新的数据类型,其在部分浏览器和 Node.js 版本可能不兼容,需要进行适当的兼容性处理。
11、Map和Set数据类型
ES6 引入了 Map 和 Set 两个新的数据结构,可以更加方便地操作键值对和不重复的元素集合。
Map 是一种键值对的集合,其中的键和值可以是任意类型的数据。
let map = new Map();
map.set('name', 'Tom');
map.set('age', 20);
console.log(map.get('name')); // Tom
console.log(map.get('age')); // 20
Set 是一种不重复元素的集合,其中的元素可以是任意类型的数据。
let set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) { 1, 2, 3 }
12、Promise对象
ES6 引入了 Promise 对象,用于处理异步操作,使得异步操作更加方便。Promise 可以让异步操作的返回结果更加灵活和规范。
let promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
resolve('hello');
}, 1000);
});
promise.then(result => {
console.log(result); // hello
}).catch(error => {
console.error(error);
})
Promise 是 ES6 中用于处理异步操作的对象,它可以将异步操作封装成一个 Promise 对象,可以更加方便地处理回调函数风格的异步代码,使得代码更加清晰易读。
function fetchData() {
return new Promise((resolve, reject) => {
fetch('https://api.github.com/users')
.then(response => response.json())
.then(data => resolve(data))
.catch(err => reject(err));
});
}
fetchData().then(data => console.log(data)).catch(err => console.error(err));
在以上代码中,fetchData 函数返回一个 Promise 对象,在 Promise 对象中进行异步操作的处理,在 then 方法中进行成功情况的处理,在 catch 方法中进行错误情况的处理。
13、Proxy对象
ES6 引入了 Proxy 对象,可以用于创建一个代理对象,用于控制对目标对象的访问,从而实现更加灵活和安全的对象操作方式。
let obj = {
name: 'Tom',
age: 20
};
let proxy = new Proxy(obj, {
get(target, key) {
console.log(`access ${key}`);
return target[key];
},
set(target, key, value) {
console.log(`set ${key} = ${value}`);
target[key] = value;
}
});
proxy.name; // access name
proxy.age = 30; // set age = 30
在上面的代码中,代理对象 proxy 可以拦截目标对象的读写操作,并在此基础上进行额外的逻辑处理,从而控制对目标对象的访问方式。
14、模块化
ES6 引入了模块化的概念,可以方便地将各个功能模块化,从而实现代码的复用和管理。
// 模块化定义
export function add(x, y) {
return x + y;
}
export function sub(x, y) {
return x - y;
}
export const PI = 3.14;
// 模块化引入
import { add, sub } from './math.js';
import * as math from './math.js';
console.log(add(1, 2)); // 3
console.log(sub(3, 1)); // 2
console.log(math.PI); // 3.14
15、默认导出和命名导出
ES6 中,可以使用 export default 和 export 关键字来默认导出一个模块或命名导出一个模块的多个部分。
通过 export default 关键字可以将一个模块的主要功能默认导出。
// module.js
export default function add(x, y) {
return x + y;
}
// 其他JS文件引用module.js
import add from './module.js';
console.log(add(1, 2)); // 3
通过 export 关键字可以导出多个模块部分。
// module.js
export function add(x, y) {
return x + y;
}
export function sub(x, y) {
return x - y;
}
// 其他JS文件引用module.js
import { add, sub } from './module.js';
console.log(add(10, 5)); // 15
console.log(sub(10, 5)); // 5
需要注意的是,export default 和 export 只能出现在模块的顶层作用域,不能出现在函数、块级作用域或循环体内部。
16、for…of 循环
ES6 引入了 for…of 循环,用于遍历数据集合中的元素,例如数组、字符串、Map 和 Set 等。
let nums = [1, 2, 3];
for (let num of nums) {
console.log(num);
}
// 输出:1 2 3
let str = 'hello';
for (let ch of str) {
console.log(ch);
}
// 输出:h e l l o
需要注意的是,普通的 for 循环和 forEach 循环无法遍历字符串,而 for…of 循环可以遍历字符串中的单个字符。
17、async/await
ES6 引入了 async/await 语法,可以更加方便地处理异步操作,使得代码更加清晰易读。
async 关键字用于定义一个异步函数,该函数会返回一个 Promise 对象。
async function fetchUsers() {
let response = await fetch('https://api.github.com/users');
let users = await response.json();
return users;
}
fetchUsers().then(users => {
console.log(users);
});
await 关键字只在异步函数中使用,用于等待异步操作的结果。
需要注意的是,async/await 语法需要 Promise 对象的支持,因此部分浏览器和 Node.js 版本可能不兼容该语法,需要进行适当的兼容性处理。
18、数字字面量改进
ES6 对数字字面量进行了改进,可以使用二进制、八进制和十六进制的字面量来表示数字。
let binary = 0b1010; // 二进制 10
let octal = 0o777; // 八进制 511
let hex = 0xFF; // 十六进制 255
19、Object 新方法
ES6 在 Object 类中引入了一些新的方法,例如 Object.assign()、Object.keys()、Object.values() 等,可以方便地处理对象的属性。
Object.assign() 方法可以用于合并多个对象的属性。
let obj1 = { name: 'Tom' };
let obj2 = { age: 20 };
let obj3 = Object.assign({}, obj1, obj2);
console.log(obj3); // {name: "Tom", age: 20}
Object.keys() 方法可以用于获取对象的属性名。
let obj = { name: 'Tom', age: 20 };
let keys = Object.keys(obj);
console.log(keys); // ["name", "age"]
Object.values() 方法可以用于获取对象的属性值。
let obj = { name: 'Tom', age: 20 };
let values = Object.values(obj);
console.log(values); // ["Tom", 20]
需要注意的是,这些方法在部分浏览器和 Node.js 版本可能不兼容,需要进行适当的兼容性处理。
20、数组新方法
ES6 引入了一些新的数组处理方法,包括 Array.from()、find()、findIndex()、includes()、fill() 等,可以更加便捷地处理数组操作。
Array.from() 方法可以将类数组对象或可迭代对象转换为数组。
let arr1 = Array.from('hello');
console.log(arr1); // [ 'h', 'e', 'l', 'l', 'o' ]
let arr2 = Array.from({ length: 5 }, (v, i) => i + 1);
console.log(arr2); // [1, 2, 3, 4, 5]
find() 方法可以查找数组中符合条件的第一个元素,并返回该元素。
let nums = [1, 3, 5, 7, 9];
let num = nums.find(n => n > 5);
console.log(num); // 7
findIndex() 方法可以查找数组中符合条件的第一个元素,并返回该元素的索引。
let nums = [1, 3, 5, 7, 9];
let index = nums.findIndex(n => n > 5);
console.log(index); // 3
includes() 方法可以判断数组中是否包含某个元素。
let nums = [1, 3, 5, 7, 9];
let has1 = nums.includes(1);
console.log(has1); // true
fill() 方法可以将数组中的元素填充为指定值。
let nums = new Array(5);
nums.fill(0);
console.log(nums); // [0, 0, 0, 0, 0]
21、class关键字
ES6 中引入了 class 关键字,它是一个更加简明、易读的类定义方式,能够帮助我们更加方便地使用面向对象的编程范式。
类是ES6中提供的新的代码组织方式,可用来定义新的类型或扩展现有类型,这些新类型可以像普通函数一样使用和调用。例如我们可以使用 class 关键字来定义一个简单的 Person 类:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
在这个示例中,我们使用了 class 关键字来声明一个 Person 类。这个类具有两个属性,name 和 age,一个构造函数用来初始化这些属性,以及一个 greet() 方法来打印问候语。 constructor 方法是在实例化类时被调用的函数,用于初始化实例对象的属性。 greet() 方法则是一个普通方法,可在实例对象上调用。
现在我们可以使用这个类创建实例:
let person = new Person('Tom', 20);
person.greet();
创建实例对象的过程和常规函数调用一样,我们调用 new 关键字,然后就能够使用 Person 构造函数来初始化 person 对象。最后我们使用 person.greet() 方法来调用类中定义的方法。
在ES6中,类还支持继承,方法的静态定义(static methods)、属性的定义等等。通过 class 关键字,开发者可以更好地组织代码,实现面向对象编程的众多特性。紧凑的代码和简洁的语法也能够使你更快地编写可读性更高的代码,提高代码的可维护性和复用性。
22、继承和super关键字
在ES6中,class 关键字不仅提供了创建类的新语法,还提供了一种更简单、更可控、可读性更高的继承机制。这使得开发者可以使用面向对象的编程范式更轻松地组织复杂的代码。
ES6的继承采用了和其他面向对象编程语言类似的语法,使用 extends 关键字来指定父类,super 关键字用于调用父类的构造函数和方法。下面是一个简单的使用继承的例子:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks!`);
}
getBreed() {
console.log(`${this.name} is a ${this.breed}.`);
}
}
let doge = new Dog('Doge', 'Shiba Inu');
doge.getBreed(); // 输出:Doge is a Shiba Inu.
doge.speak(); // 输出:Doge barks!
上面的示例演示了如何定义一个 Animal 类,以及如何扩展一个 Dog 类,在 Dog 类中使用 super 关键字来调用 Animal 类的构造函数和方法。
在 Dog 类中,我们先调用 super(name) 来调用父类的构造函数来设置 name 属性。之后,我们添加了自己的 breed 属性和 speak() 方法,并使用 getBreed() 方法来输出狗的品种。
在上面的代码中,我们使用了父类的构造函数和子类的构造函数以及方法,从而实现了继承的概念。这样,我们就能够方便地定义类的层次结构,重用已有的代码,并使用具有多态性质的类。
总之,ES6 的继承和 super 关键字让面向对象编程变得更加简单明了,开发者可以更轻松地组织和重用代码,更好地表达程序的逻辑结构和组件间的依赖关系。
23、类和继承
ES6 引入了类和继承机制,可以更加便捷地创建对象和进行对象间的继承操作。
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}!`);
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
sayGrade() {
console.log(`My grade is ${this.grade}.`);
}
}
let s = new Student('Tom', 3);
s.sayHello(); // Hello, Tom!
s.sayGrade(); // My grade is 3.
需要注意的是,ES6 的类和继承机制对于低版本的浏览器和 Node.js 并不兼容,需要进行适当的兼容性处理。
24、迭代器和生成器
ES6 引入了迭代器和生成器两个概念,可以让 JavaScript 中的数据结构更加灵活和方便。
迭代器是一个对象,提供了访问数据集合的方法,可以使用 for..of 循环来访问一个迭代器对象。
let nums = [1, 2, 3];
let iterator = nums[Symbol.iterator]();
for (let num of iterator) {
console.log(num); // 1 2 3
}
生成器是一个特殊的函数,可以自动实现迭代器接口来产生一个值序列,并且可以支持暂停和继续执行,使代码更加易读和优美。
// 创建斐波那契数列生成器
function* fibonacci() {
let x = 0, y = 1;
while (true) {
yield y;
[x, y] = [y, x + y];
}
}
// 使用生成器获取数列中的值
let fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
25、正则表达式的增强
ES6 对正则表达式增添了一些非常实用的功能,让我们能够更方便地使用和操作正则表达式。
下面是 ES6 对正则表达式的增强功能:
- 增强正则表达式的 Unicode 支持
在 ES6 中,正则表达式增强了 Unicode 的支持,允许我们直接通过 Unicode 字符来匹配字符串。我们使用新的 Unicode 字符类来匹配字符:
\p{Unicode property} 表示 Unicode 属性;
\p{Script=scriptname} 表示指定脚本中的字符
\p{Script=scriptname}: 表示指定脚本中的某个 Unicode 属性。
例如,下面这个正则表达式匹配希腊字符集和一致符号协定中指定的全部字符:
const greekRegEx = /\p{Script=Greek}\p{Script=Common}\p{Script=Inherited}/u;
- 命名捕获组
ES6 引入了一种新的语法,使我们能够为正则表达式定义命名捕获组。这样我们可以用名称而不是位置来引用匹配的子串。
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const match = regex.exec('2018-04-30');
console.log(match.groups.year); // 2018
console.log(match.groups.month); // 04
console.log(match.groups.day); // 30
在上面的示例中,我们使用 (?<groupName>pattern) 语法来定义命名捕获组。当调用 exec() 方法时,我们可以通过 match.groups.groupName 来引用匹配结果。
- 看前瞻和后顾
ES6 还引入了看前瞻和后顾这两个新的语法元素,它们可以使用匹配规则来确定需要匹配的模式在字符串中的位置。
// 例一:看前瞻
const fileName = 'file.txt.jpg';
const match = fileName.match(/.+?(?=\.)/);
console.log(match); // 'file'
在上面的代码中,使用看前瞻的方式查找了文件名中第一个点号之前的所有字符。 (?=pattern) 表示查找后面跟着 pattern 的结果。
// 例二:后顾
const fileName = 'file.txt.jpg';
const match = fileName.match(/(?<=\.).+/);
console.log(match); // 'txt.jpg'
在上面的示例中,使用后顾的方式查找了文件名中最后一个点号之后的所有字符。 (?<=pattern) 表示查找前面跟着 pattern 的结果。
ES6 对正则表达式进行了很多实用的增强,包括 Unicode 的支持、命名捕获组、看前瞻和后顾等功能。这些新功能让开发者能够更灵活、更高效地使用正则表达式来处理字符串,提高编码开发效率,减少代码错误。
26、线程
ES6 引入了 Worker 线程,可以创建 JavaScript 运行的额外线程,从而不会阻塞主线程的执行。Worker 线程间的通信通常采用消息传递的方式进行。
// 主线程中创建 Worker 线程
let worker = new Worker('./worker.js');
// 主线程向 Worker 线程发送消息
worker.postMessage('hello');
// Worker 线程向主线程发送消息
worker.onmessage = function (event) {
console.log(event.data);
}