ES6 及以上最新规范
let 和 const
在 ES6 之前,声明变量的关键字只有var
,并且没有块级作用域,只有函数作用域和全局作用域。
let
、const
和var
的区别
- 不允许声明提升
- 不允许重复声明
- 不覆盖全局变量
- let
let 声明的变量,其声明语句不会再提升。
console.log(outer);
console.log(inner); // Uncaught ReferenceError: inner is not defined
{
console.log(outer); //
console.log(inner); // Uncaught ReferenceError: Cannot access 'inner' before initialization
var outer = true;
let inner = true;
console.log(outer); // true
console.log(inner); // true
}
console.log(outer); // true
console.log(inner); // Uncaught ReferenceError: inner is not defined
临时死区(Temporal Dead Zone, TDZ),也叫暂时性死区。用let
或const
声明的变量,在声明前都会放到 TDZ 中,而声明前访问这些变量就会触发运行时错误。
其次,let 不允许重复声明同一个变量。这里有个限制:必须是在同一个作用域时,才不允许同一个变量重复声明。
最后是let
在全局作用域中的特性。当用 var
在全局作用域中声明变量的时候,该变量不但会成为全局变量,而且会成为全局对象(如浏览器中 window 对象)的一个属性。ES6 规定用 let 可将全局变量和全局对象断开联系。下面用两组代码分别演示断开联系(第一组)和覆盖已有属性(第二组),注意,在第二组代码中为了方便对比,忽略了重复声明的错误。
// group 1
var global = true;
console.log(window.global); // true
let whole = true;
console.log(window.whole); // undefined
// group 2
var Math = true;
console.log(window.Math); //true
let Math = true;
console.log(window.Math);
- const
const 不但拥有上面所述的 let 的 3 个特性,并且还能声明一个常量。常量是指一个定义了初始值后固定不变的只读变量。const
与let
不同,声明时必须初始化(即赋值)。并且这设定后其值无法更改。
注意,const 限制的是变量与内存地址之间的绑定。也就是说const
让变量无法更改内存地址。如果是基本类型(如布尔值、数字等)的变量,那么对应的内存地址中保存的就是值;如果是引用类型(如对象)的变量,那么对应的内存地址中保存的是指向实际数据的一个指针。由此可知,当用 const 声明的变量,其初始化的值是对象时,可以修改对象中的属性或方法。
- 循环中的
let
和const
ES6 规定了 let 声明在循环内部的行为,以 for 循环为例,
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
在控制台输出的结果依次为 0、1、2,没有出现循环中的异步回调问题。这是因为在每次循环的时候,都会重新创建一个叫作 i 的同名变量,并将其初始化为计算后的值,而循环体内调用的 i 变量不会受其他同名变量的影响,所以能够在定时器的回调函数中正确显示该变量的值。在 ES5 及之前的版本中,如果要解决异步回调问题,可以借助立即执行函数表达式(IIFE)才能得到预期的效果
for (var i = 0; i < 3; i++) {
(function (n) {
setTimeout(function () {
console.log(n);
}, 0);
})(i);
}
箭头函数
特性:
- 由于不能作为构造函数,因此也就没有元属性(new.target)和原型(prototype属性),
- 函数体内不能存在arguments、super和this,即没有为它们绑定值。
- 当需要包含多个参数时,它们的名称不可重复。
- 语法:
var a = (param) => {
return param
}
// 如果函数体中只包含一个表达式,可以省略花括号和return关键字。
var a = (param) => param
// 当返回值是一个对象字面量时,需要为其包裹圆括号
var a = (param) => ({param: 'test'})
// 如果只有一个参数,那么可以省略包裹参数的圆括号。
var a = param => param
// 在参数缺失的情况下,还是需要一对空的圆括号占位。
var a = () => "arrow func"
// 持ES6新增的参数解构、剩余参数和默认参数
箭头函数的this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
。
在vue中,methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this自动绑定为 Vue 实例。所以,在vue实例的方法中的调用匿名箭头函数,this指向为这个vue实例。
class
- ES5写法:
function People(name) {
this.name = name
}
People.prototype.getName = function() {
return this.name
}
var people = new People("tom");
people.getName();
- ES6写法:
class People {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
typeof People; // "function"
typeof People.prototype //
people.hasOwnProperty("name") // true
ES6的类仅仅是个语法糖
特性:
- 类声明和即将要讲解的类表达式都不会被提升
- 类中的代码在执行时,会强制开启严格模式。
访问器属性
在类中的访问器属性,其存取语法和ES5对象字面量中的相同,也需要使用get和set两个关键字,具体实现如下所示。
class People {
get prop() {
return `getter:${this.name}`;
}
set prop(value) {
this.name = value
}
}
- 可以建立只读属性
- 修改只读属性不会报错
建立私有属性
私有属性是面向对象编程(OOP)中非常常见的一个特性,一般满足以下的特点:
- 能被class内部的不同方法访问,但不能在类外部被访问;
- 子类不能继承父类的私有属性。
class Foo {
#a; // 私有属性
constructor(a, b) {
this.#a = a;
this.b = b
}
}
静态方法
解决全局对象变量问题
ES6新增了static关键字,可把类中的方法(除了构造函数)定义成静态的。要调用静态方法只能通过类本身,而不是实例化的类,代码如下所示。除了方法之外,static关键字还适用于访问器属性。
class People {
static getName() {
return "name"
}
}
虽然ES6明确提出了静态方法,但是没有将静态属性一并标准化。如果要使用静态属性,代码如下所示,用变通的方式定义。
// 使用ES7最新的静态属性语法(目前还是草案,但可用babel编译)
class People {
static age = 38
}
// or
People.age = 38
// or
访问器属性还有一个便捷的地方,就是它和原型方法一样,也能被子类继承。
继承
ES6的继承依然是基于原型的继承,但语法更为简洁、清晰。通过一个extends关键字,就能描述两个类之间的继承关系(代码如下所示),在此关键字之前的Man是子类(即派生类),而在其之后的People是父类(即基类或超类)
class People {
constructor() {
this.age = 28
}
getAge() {
return this.age
}
static getName() {
return "static"
}
}
class Man extends People {
constructor() {
super();
}
}
var man = new Man();
Man.getName(); // static
man.getAge(); // 28
1.super:当super作为方法使用时,有以下6个注意点。
1)super()方法相当于父类的构造函数。
2)只有在子类的构造函数中才能调用super()方法。
3)如果子类显式地定义了构造函数,那么必须调用super()方法,否则会报错。
4)如果子类没有定义构造函数,那么会自动调用super()方法。
5)当子类的构造函数显式地返回一个对象时,就能避免调用super()方法。
6)在使用this之前,必须先调用super()方法。
练习
class People {
static age = 1;
constructor() {
this.age = 28
}
getAge() {
return this.age
}
static getAge() {
return this.age
}
}
class Man extends People {
constructor() {
super();
this.age = 29
}
getAge() {
return super.getAge();
}
static getAge() {
return super.getAge();
}
}
let man = new Man()
Man.getAge() // undefined
man.getAge() // 29
// 静态方法中的this,指向的是一个类,即调用这个方法的People类。而构造函数中的this,指向的却是类的实例。
// 当子类通过super调用父类的普通方法时,普通方法内部的this指向的是子类的实例
Symbol
Symbol,可以像字符串那样作为对象的属性,且具有唯一性的特点,(重要)可以避免属性冲突。
1.创建
Symbol
没有字面量形式,只能通过Symbol()
函数创建。该函数有一个可选参数,只是用来描述当前符号,除了便于阅读,没有其他用途。即使 2 个Symbol
的描述相同,它们也不相等。Symbol()
不是构造函数,不能组合new
运算符使用。
var sym1 = Symbol();
var sym2 = Symbol("name");
var sym3 = Symbol("name");
var sym4 = new Symbol(); // 抛出异常错误
cosole.log(sym2 === sym3); // true
typeof
识别Symbol
typeof Symbol() === "symbol"; // true
- 类型转换
Symbol 无法与字符串和数字进行运算
var sym = Symbol("age");
Number(sym);
parseInt(sym);
1 + sym;
"" + sym;
但可以显性转成字符串或布尔值。
Boolean(sym); // true
!sym; // false
sym.toString(); // "Symbol(age)"
String(sym); // "Symbol(age)"
- 全局共享
ES6 会在内部维护一张全局符号注册表,通过 Symbol.for()方法,可以登记指定符号,使其变成一个全局有效的符号,从而达到全局共享。该方法只接收一个参数,这个参数既是注册表中的键值,同时也是此符号的描述。下面的代码调用了两次 Symbol.for()方法,传递了相同的参数,返回的却是同一个全局符号。
var sym1 = Symbol.for("name");
var sym2 = Symbol.for("name");
console.log(sym1 === sym2); // true
- 属性名
常见三种属性名赋值方式:
- 内置符号
ES6 提供了一些内置符号,也叫知名符号(Well-Known Symbol)。它们暴露了语言的内部逻辑,允许开发人员修改或拓展规范所描述的对象特征或行为。每一个内置符号对应 Symbol 对象的一个属性,如 Symbol.hasInstance、Symbol.iterator 等.
属性名称 | 值类型 | 描述 |
---|---|---|
hasInstance | 方法 | 当使用 instanceof 运算符,该方法会被调用 |
isConcatSpreadable | 布尔值 | 当对象作为 Array.protorype.concat()方法的参数时,控制改对象是否被展开 |
iterator | 方法 | 返回一个迭代器用于定义一个可迭代对象 |
match | 方法 | 当对象作为 String.prototype.match() 方法的参数时,该方法会被调用 |
replace | 方法 | 当对象作为 String.prototype.replace() 方法的参数时,该方法会被调用 |
search | 方法 | 当对象作为 String.prototype.search() 方法的参数时,该方法会被调用 |
split | 方法 | 当对象作为 String.prototype.split() 方法的参数时,该方法会被调用 |
species | 方法 | 创建派生类的构造函数 |
toPrimitive | 方法 | 当对象需要转换成原始值(即执行 toPrimitive 抽象操作)时,该方法会被调用 |
toStringTag | 字符串 | 指定对象的类型,可在调用 Object.prototype.toString()方法的时候返回 |
unscopables | 对象 | 保存在这个对象中的属性将不能被 width 语句引用 |
下面给出 4 个内置符号示例:hasInstance
、isConcatSpreadable
、match
和toStringTag
:
- 实现一个可迭代的对象
var people = {
name: "test",
sex: "male",
hobbies: ["ball", "paint", "sing"],
[Symbol.iterator]() {
const _this = this;
const keys = Reflect.ownKeys(_this); // 获取到对象的key值列表
let index = 0;
return {
next() {
if (index < keys.length - 1) {
return {
value: _this[keys[index++]], // 想返回什么 就返回什么 keys[index++]
done: false,
};
}
return {
value: keys[index++],
done: true, // 迭代结束
};
},
};
},
};
for (let h of people) {
console.log(h);
}
ES7(ECMAScript 2016)
- Array.prototype.includes(arr,index)
检查一个元素是否存在于数组中,但是它返回的是一个布尔值,第二个参数index,可以从数组特定的索引开始寻找。
- Exponentiation Operator(求幂运算符)
let a = 2**3
console.log(a == Math.pow(2, 3))
ES13(ECMAScript 2022)
1. 私有属性和私有方法
class User {
name = "Tom";
#lastName = "Brown"
getFullName(){
return `${this.name} ${this.#lastName}`
}
}
const user = new User();
user.name
// "Tom"
user.getFullName();
// "Tom Brown"
user.#lastName
// SyntaxError - cannot be accessed or modified from outside the class
2. 私有属性的检测
当我们尝试访问对象上不存在的私有属性时会报错,所以就需要一种方法来检测对象是否具有某私有属性,此时,我们可以在class中使用in关键字来完成此工作。
class Person {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}
Person.check(new Person()), // true
3.类静态初始化块(Class Static Block)
类在初始化的时候会执行静态初始化块(Class Static Block),一个类可以拥有任意个静态初始化块,它们将会按照声明的顺序执行。静态初始块内还可以访问父类的静态属性。
class Dictionary {
static words = [ "yes", "no", "maybe" ];
}
class Words extends Dictionary {
static englishWords = [];
static #localWord = 'ok';
// 第一个静态初始化块
static {
const words = super.words;
this.englishWords.push(...words);
}
// 第二个静态初始化块
static {
this.englishWords.push(this.#localWord);
}
}
console.log(Words.englishWords)
//输出 -> ["yes", "no", "maybe", "ok"]
4.Regexp Match Indices
所以ES13通过新增/d修饰符,向匹配结果添加一个属性 .indices,此属性是一个索引数组,其中包含每个捕获的子字符串的一对开始索引和结束索引。
const fruits = 'Fruits: apple, banana, orange'
const regex = /(banana)/gd;
const matchObj = regex.exec(fruits);
console.log(matchObj);
// [
// 'banana',
// 'banana',
// index: 15,
// indices:[
// [15, 21],
// [15, 21]
// ]
// input: 'Fruits: apple, banana, orange',
// groups: undefined
// ]
- Await operator at the top-level
之前await关键词只能在aysnc function里进行使用,想要在顶层使用await就必须要加个aysnc自执行函数,这样十分的不方便, 所以ES13中,引入了可以直接在顶层使用Await关键字的特性。
const strings = await import(`./example.mjs`);
at()方法
ES13引入了
at()
方法,无论正序还是倒序获取元素都非常的优雅,有了这个方法,我们可以在数组、字符串、TypedArray上通过索引值方便的获取元素
const arr = [100,200,300,400]
arr.at(0) // 100
arr.at(-2) // 300
const str = "ABCD"
str.at(-1) // 'D'
str.at(0) // 'A'
7.Object.hasOwn(obj, propKey)
const object = { name: "Mark" };
Object.hasOwn(object, "name"); // true
const object2 = Object.create({ name: "Roman" });
Object.hasOwn(object2, "name"); // false
Object.hasOwn(object2.__proto__, "name"); // true
const object3 = Object.create(null);
Object.hasOwn(object3, "name"); // false
8.Error Cause
try {
apiCallThatCanThrow();
} catch (err) {
throw new Error('New error message', { cause: err });
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。