ES6 及以上最新规范

let 和 const

在 ES6 之前,声明变量的关键字只有var,并且没有块级作用域,只有函数作用域和全局作用域。

letconstvar的区别

  • 不允许声明提升
  • 不允许重复声明
  • 不覆盖全局变量
  1. 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),也叫暂时性死区。用letconst声明的变量,在声明前都会放到 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);
  1. const

const 不但拥有上面所述的 let 的 3 个特性,并且还能声明一个常量。常量是指一个定义了初始值后固定不变的只读变量。constlet不同,声明时必须初始化(即赋值)。并且这设定后其值无法更改。
注意,const 限制的是变量与内存地址之间的绑定。也就是说const让变量无法更改内存地址。如果是基本类型(如布尔值、数字等)的变量,那么对应的内存地址中保存的就是值;如果是引用类型(如对象)的变量,那么对应的内存地址中保存的是指向实际数据的一个指针。由此可知,当用 const 声明的变量,其初始化的值是对象时,可以修改对象中的属性或方法。

  1. 循环中的letconst

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,即没有为它们绑定值。
  • 当需要包含多个参数时,它们的名称不可重复。
  1. 语法:
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的类仅仅是个语法糖

特性:

  1. 类声明和即将要讲解的类表达式都不会被提升
  2. 类中的代码在执行时,会强制开启严格模式。

访问器属性

在类中的访问器属性,其存取语法和ES5对象字面量中的相同,也需要使用get和set两个关键字,具体实现如下所示。

class People {
  get prop() {
    return `getter:${this.name}`;
  }
  set prop(value) {
    this.name = value
  }
}
  1. 可以建立只读属性
  2. 修改只读属性不会报错

建立私有属性

私有属性是面向对象编程(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
  1. 类型转换

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)"
  1. 全局共享

ES6 会在内部维护一张全局符号注册表,通过 Symbol.for()方法,可以登记指定符号,使其变成一个全局有效的符号,从而达到全局共享。该方法只接收一个参数,这个参数既是注册表中的键值,同时也是此符号的描述。下面的代码调用了两次 Symbol.for()方法,传递了相同的参数,返回的却是同一个全局符号。

var sym1 = Symbol.for("name");
var sym2 = Symbol.for("name");
console.log(sym1 === sym2); // true
  1. 属性名

常见三种属性名赋值方式:

  1. 内置符号

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 个内置符号示例:hasInstanceisConcatSpreadablematchtoStringTag:

  • 实现一个可迭代的对象
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)

  1. Array.prototype.includes(arr,index)

检查一个元素是否存在于数组中,但是它返回的是一个布尔值,第二个参数index,可以从数组特定的索引开始寻找。

  1. 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
// ]
  1. Await operator at the top-level

之前await关键词只能在aysnc function里进行使用,想要在顶层使用await就必须要加个aysnc自执行函数,这样十分的不方便, 所以ES13中,引入了可以直接在顶层使用Await关键字的特性。

const strings = await import(`./example.mjs`);
  1. 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 });
}

参考文章


看见了
876 声望16 粉丝

前端开发,略懂后台;


« 上一篇
Electron
下一篇 »
Docker相关