搜尋
首頁web前端js教程全面掌握,ECMAScript的新特性

全面掌握,ECMAScript的新特性

Oct 22, 2020 pm 06:34 PM
ecmascript

今天javascript專欄帶大家認識ECMAScript的新特性。

全面掌握,ECMAScript的新特性

ES6成為JavaScript的下一代標準後,標準委員會(TC39)在每年都會發布一個ES的新版本,每個版本都引入了很多實用的新特性,在日常的專案開發中,如果我們掌握這些實用的新特性,將會大大的提升開發效率,以下讓我們全面的掌握這些ES的新特性吧~

Let 和Const

之前使用var來定義變量,為我們提供了新的方式
let用來聲明變量,const用來聲明常數。

如何使用

const TAG = "我是常量";let a;
a = 2;console.log(TAG, "a=" + a); //我是常量   a=2复制代码

四個特點

一、只在區塊級作用域內有效

let和const為JavaScript新增了區塊級作用域,通常情況下,{}包裹的程式碼擁有的作用域就是區塊級作用域,而宣告的變數或常數只在區塊層級作用域內有效,外部不能存取。

if (true) { //外层块级作用域
  let a = 1;  const A = 1;  if (true) {   //内层块级作用域
    let a = 2;
  }  console.log(a,A); //(1)输出:1 , 1}console.log(a); //(2)Uncaught ReferenceError: a is not defined复制代码

上面有兩個區塊級作用域,都宣告了變數a,但外層區塊層級作用域與內層區塊層級無關,所以(1)處輸出的是外層的變數值1,(2)處訪問了不在一個區塊級作用域定義的變量,所以會報錯。

另外一個理解區塊級作用域的範例。

//for循环体内的定时器//在ES6之前,是没有块级作用域的,变量用var声明,直接挂载在全局作用域上for (var i = 0; i <h4 id="二-暫時性死區">二、暫時性死區</h4><p>不能在變數和常數宣告之前使用。 <br>let和const指令會使區塊形成封閉作用域,若在聲明之前使用,就會報錯,這個在語法上,稱為「暫時性死區」(簡稱TDZ)。 </p><pre class="brush:php;toolbar:false">if (true) {
  tmp = "abc"; // ReferenceError
  let tmp; 
}复制代码

三、不能重複宣告

let a = 1;let a = 2;//报错  SyntaxError: Identifier 'a' has already been declared const B=1;const B=2;//报错  SyntaxError: Identifier 'B' has already been declared 复制代码

四、不屬於頂層物件

let宣告的變量,全域物件(window,global,self)不能存取到

let a = 10;console.log(window.a);  //undefined复制代码

String

ES6對字串進行了一些擴展,如下:

模板字串

ES6新增了模板字串(字符字串)的方式定義字串,用來解決之前字串很長要換行、字串中有變數或表達式遇到的問題,下面是具體的使用場景

//一、打印长字符串且换行,直接在模板字符串中保留格式即可let welcome=`
  你好
    欢迎来到ES6
      ——谢谢
`console.log(welcome);/*
    输出结果为:
  你好
    欢迎来到ES6
      ——谢谢
*///二、字符串中有变量或者表达式,直接在模板字符串中使用${变量/表达式}即可let type = "ES6";let name1 = "mango";let name2 = "和goman";let welcome = `欢迎${name1 + name2}来到${type}世界`;  

console.log(welcome);   //learn1.js?c1a0:7 欢迎mango和goman来到ES6世界复制代码

方法

String.prototype.includes()

判斷字串是否包含一個指定字串,傳回boolean類型。

const str = "ECMAScript"console.log(str.includes("EC")); //true 找不到返回false  复制代码

startsWith()和endsWith()

startsWith()用來判斷字串是否以指定字串作為開頭,傳回boolean類型。
endsWith()用來判斷字串是否以指定字串作為結尾,傳回boolean類型。

const str = "ECMAScript"console.log(str.startsWith("ECM")); //true console.log(str.endsWith("Script")); //true 复制代码

String.prototype.repeat()

將原始字串重複n遍,得到一個新的字串

const str = "ECMAScript";console.log(str.repeat(3)); //ECMAScriptECMAScriptECMAScript复制代码

Number

ES6開始逐步減少全域性方法,使得語言逐步模組化,所以把一些處理數值的方法轉移到了Number物件上,功能行為保持不變。

//将目标转换为整数//ES5parseInt("5.6") //5//ES6Number.parseInt("5.6")  //5//将目标转换为浮点数//ES5parseFloat("12.45str")  //12.45//ES6Number.parseFloat("12.45str")   //12.45复制代码

另外,為了方便開發,Number也增加了一些方法和屬性

一、判断一个数值是否是整数Number.isInteger(25) // trueNumber.isInteger(25.1) // false二、获取JavaScript最大安全值和最小安全值Number.MAX_SAFE_INTEGER=9007199254740991Number.MIN_SAFE_INTEGER=-9007199254740991三、判断一个数值是否是在安全范围Number.isSafeInteger(9007199254740992)  //false复制代码

Symbol

新引入原始資料類型,用來表示獨一無二的值。

宣告方式

let sym = Symbol();let sym2 = Symbol();console.log(sym == sym2); //false   生成的值是独一无二的,所以不相等console.log(typeof sym);  //symbol  typeof查看值的类型为symbollet symWithDesc = Symbol("name"); //Symbol()括号内可以添加描述console.log(symWithDesc.toString()); //输出:Symbol(name)   打印描述需要转换成字符串复制代码

專案中應用


一、消除魔術字串
假如我們需要做一個點擊選單,做不同處理的功能,我們通常會這樣實現。

const clickMenu = function (menu) {  switch (menu) {    case "home":      break;    case "me":      break;
  }
};

clickMenu("home")复制代码

"home"這種可能會多次出現,與程式碼形成強耦合的字串就是魔術字串,在專案中我們應該盡量消除魔術字串,下面使用Symbol消除魔術字串

const MENU_TYPE = {  home: Symbol(),  me: Symbol(),
};const clickMenu = function () {  switch (menu) {    case MENU_TYPE.home:      break;    case MENU_TYPE.me:      break;
  }
};

clickMenu(MENU_TYPE.home);复制代码


二、作為對象獨一無二的屬性值
假如我們想產生一個公司人名對象,並以每個人名為key值,這時候如果有人名重名便會有問題,而Symbol能解決這個問題

const scores = {
  [Symbol("张三")]: {    age: 22,
  },
  [Symbol("李四")]: {    age: 21,
  },
  [Symbol("张三")]: {    age: 20,
  },
};复制代码

注意,透過Symbol定義的屬性,只能透過下面兩種方式進行遍歷,否則無法取得屬性。

for (let key of Object.getOwnPropertySymbols(scores)) {  console.log(key, key);
}for (let key of Reflect.ownKeys(scores)) {  console.log(key, scores[key]);
}复制代码

Set和Map


為了更方便實現資料操作,ES6新增了Set和Map兩種資料結構。

Set

Set是類似數組,但成員的值都是唯一的資料結構。

新建

新建一個儲存月份的Set資料結構,可以定義一個空的Set實例,也可以是帶有陣列形式的預設資料。

let monthSets = new Set();let monthSets2 = new Set(["一月","二月","三月"]);复制代码

基本上使用

//添加数据monthSets.add("一月");
monthSets.add("二月").add("三月");console.log(monthSets); //Set(3) {"一月", "二月", "三月"}//遍历集合Set//forEach():使用回调函数遍历每个成员monthSets.forEach((item) => console.log(item)); //一月 二月  三月//for...of:直接遍历每个成员for (const item of monthSets) {  console.log(item);    //一月 二月  三月}//删除数据monthSets.delete("二月");console.log(monthSets); // Set(2) {"一月", "三月"}monthSets.clear(); //console.log(monthSets); // Set(0) {}复制代码

常見應用程式

Set資料結構在實際專案中還有很多應用場景。

let monthSets = new Set(["一月", "二月", "三月"]);//一、快速判断数据元素是否存在monthSets.has("一月"); //true//二、统计数据元素个数monthSets.size; //3console.log(monthSets.size); //3//三、数组去重let arr = [1, 2, 3, 2, 3, 4, 5];let set = new Set(arr);console.log(set); // {1, 2, 3, 4, 5}//四、合并去重let arr = [1, 2, 3];let arr2 = [2, 3, 4];let set = new Set([...arr, ...arr2]);console.log(set); // {1, 2, 3, 4}//五、取数组交集let arr1 = [1, 2, 3];let arr2 = [2, 3, 4];let set1 = new Set(arr1);let set2 = new Set(arr2);let resultSet = new Set(arr1.filter((item) => set2.has(item)));console.log(Array.from(resultSet)); // [2, 3]//六、取数组差级let arr1 = [1, 2, 3];let arr2 = [2, 3, 4];let set1 = new Set(arr1);let set2 = new Set(arr2);let arr3 = arr1.filter((item) => !set2.has(item));let arr4 = arr2.filter((item) => !set1.has(item));console.log([...arr3, ...arr4]);  //[1, 4]复制代码

WeakSet

WeakSet与Set类似,也是不重复的值的集合,但WeakSet的成员只能是对象。WeakSet引用的对象都是弱引用,如果其他对象不再引用该对象,那么垃圾回收机制就会自动回收这些对象所占用的内存,不考虑该对象还存在于WeakSet之中。
React源码中有很多地方使用到了WeakSet,例如在react-reconciler/src/ReactFiberHotReloading.new.js中。

export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {  if (__DEV__) {    if (resolveFamily === null) {      // Hot reloading is disabled.
      return;
    }    if (typeof WeakSet !== 'function') {      return;
    }    if (failedBoundaries === null) {
      failedBoundaries = new WeakSet();
    }
    failedBoundaries.add(fiber);
  }
}复制代码

Map

Map是一种键值对集合,与对象类似,但Object只支持“字符串:值”,而Map支持“各种类型的值:值”,map给我们提供了更合适的“键值对”数据结构。

基本使用

//定义let map = new Map();//添加数据let address = { address: "江苏" };
map.set("name", "ES6");
map.set(27, "年龄信息");
map.set(address, "地址信息");console.log(map); //{"name" => "ES6", 27 => "年龄信息", {…} => "地址信息"}//获取数据let name = map.get("name");let age = map.get(27);let addressObj = map.get(address);console.log(name, age, addressObj);//获取成员数量console.log(map.size);  //3//判断是否指定key成员console.log(map.has("name")); //true复制代码

Map的遍历

map通常可以用forEach和for...of的方式进行遍历。

//定义let map = new Map();

map.set("id", 1);
map.set("name", "mango");
map.set("address", {  province: "江苏",  city: "南京",
});


map.forEach((key, value) => console.log(key, value));for (const [key, value] of map) {  console.log(key, value);
}//输出  id 1      name mango      address {province: "江苏", city: "南京"}复制代码

WeakMap

WeakMap与Map类似,也是用来生成键值对的集合。但WeakMap只接受对象作为键名,并且键名所指向的对象,属于弱引用对象。

数组的扩展

ES6对数组进行了很多的扩展,具体如下

扩展运算符

扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列,通常用在函数参数中。
假如我们需要一个求和函数,并且支持传入任意数量的值。

function sum(...params) {  let sum = arr.reduce(function (prev, cur) {    return prev + cur;
  });  return sum;
}let arr = [1, 2, 3, 4, 5];console.log(sum(arr)); //输出 15复制代码

Array.from()

Array.from()方法从一个类似数组或可迭代对象创建一个新的浅拷贝的数组实例,通常有以下四种实用场景。

//一、克隆一个数组let num = [1, 2, 3];let newNum = Array.from(num);console.log(newNum, num === newNum);  //[1, 2, 3] false//二、使用指定值,初始化一个数组//给定长度为10,默认值是数组2和对象{key:1}let length = 4;let defaultValue = 2;let defaultObj = { key: 1 };let arrValue = Array.from({ length }, (item,index) => defaultValue);let arrObj = Array.from({ length }, (item,index) => defaultObj);console.log(arrValue); // [2, 2, 2, 2]console.log(JSON.stringify(arrObj)); //[{"key":1},{"key":1},{"key":1},{"key":1}]//三、生成值范围数组function range(end) {  return Array.from({ length: end }, (item, index) => index);
}let arr = range(4);console.log(arr); // [0, 1, 2, 3]//四、数组去重,结合set使用let arr = [1, 1, 2, 3, 3];let set = new Set(arr);console.log(Array.from(set));复制代码

创建数组

如何创建一个数组,有下面几种常用方式

//一、数组字面量const arr1 = [];//二、构造函数const arr2 = Array(3);  //[null,null,null]const arr3 = Array("3");  //["3"]//这时想要用构造函数创建一个数字为7的数组,发现上面方式是无法满足的,而ES6提供了Array.of()能满足我们的需求const arr3 = Array.of(7);  //[7]复制代码

数组查找


find()方法返回数组中满足提供的测试函数的第一个元素的值,若没有找到对应元素返回undefined
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引,若没有找到对应元素则返回-1。

假如我们想要在一个成绩数组中,找到达到及格分数的最低分。

const score = [34, 23, 66, 12, 90, 88, 77, 40];const passMin = score.find((value) => value > 60);console.log(passMin); //66const pass = score.findIndex((value) => value > 60);console.log(pass); //2复制代码

数组遍历

ES6新增 for...of 数组遍历方式

const score = [34, 23, 66, 12,];for (let value of score) {  console.log(value); //  34, 23, 66, 12}复制代码

函数的扩展


ES6对函数进行了很多的扩展,具体如下

函数参数设置默认值

ES6允许为函数的参数设置默认值,即可以直接写在参数定义的后面。

//参数b设置了默认值为2,在方法调用的时候并没有传值,所以b直接使用默认值function sum(a, b = 2) {  return a + b;
}console.log(sum(1)); //3复制代码

Rest参数

ES6引入reset参数,形式为...变量名,可以用来获取传递给函数的多余参数。

function sum(a, ...values) {  console.log(a, values); //1   [2, 3, 4, 5]}

sum(1, 2, 3, 4, 5);复制代码

name和length属性

name属性返回函数名,length属性返回没有指定默认值的参数个数。

function sum(a, b, c, d = 1) {  console.log(a, values); //1   [2, 3, 4, 5]}console.log(sum.name);  //sumconsole.log(sum.length);  //3复制代码

箭头函数

ES6允许使用箭头(=>)的方式定义函数,有下面几种箭头函数实现形式。
想要实现一个加法函数,ES5的形式如下

function sum(a, b) {  return a + b;
}复制代码

而如果使用箭头函数实现的话,则如下

sumArrow = (a, b) => {  return a + b;
};复制代码

上面是箭头函数的基本变现形式,不同的场景还有不同的实现形式。

//对于上面的sumArrow函//一、如果只有一个参数,可以省略括号sumArrow = a => {  return a;
};

二、如果返回值是表达式,可以省略return和{}
sumArrow = a => a;

三、如果返回值是字面量对象,一定要用小括号包起来
sumArrow = () => ({ a: 1, b: 2 });复制代码

箭头函数与普通函数除了实现方式不同外,还有个不同的点就是对this的处理方式。

//普通函数let math = {  name: "mathName",  sum: function (a, b) {    console.log(this.name); //math
    return a + b;
  },
};

math.sum();//箭头函数globalThis.name = "globalName";let math = {  name: "mathName",  sum: (a, b) => {    console.log(this.name); //globalName
    return a + b;
  },
};

math.sum();复制代码

从上面示例可以看到,箭头函数和普通函数最终打印的this.name不一致。对于普通函数,this指向的是调用sum方法的math对象,所以this.name打印的是“mathName”。而对于箭头函数,this指向的是定义sum方法的全局对象,所以this.name打印的是“globalName”。

在后续的开发过程中,我们将会经常使用到箭头函数,在使用的过程中,我们需要有以下几点注意

  1. 箭头函数中this指向定义时所在的对象,而不是调用时所在的对象
  2. 不可以当作构造函数
  3. 不可以使用yield命令,不能作用generator函数

解构赋值


解构赋值是一种表达式,可以将属性和值从对象和数组中取出,赋值给其他变量。

如何使用

对象解构赋值

假如我们拿到一个对象,需要获取指定的属性值。则解构赋值让我们无需通过调用属性的方式赋值,而是通过指定一个与对象结构相同模板的方式,获取想要的属性值。

const people = {  name: "ES6",  age: 27,  sex: "male",
};//如果通过调用属性赋值,则需要这么做let name = people.name;let age = people.age;let sex = people.sex;console.log(name, age, sex); //ES6 27 male//而使用解构赋值的方式,代码会更加的清晰简单const { name, age } = People;console.log(name, age); //ES6 27 male复制代码

除了上面这种基本用法,还有其他使用方式

const people = {  name: "ES6",  age: 27,  sex: "male",
};// 一、属性顺序不需保持一致,名称相同即可const { age, name, sex } = people;console.log(name, age, sex);  //ES6 27 male//二、取值时,重新定义变量名const { age: newAge, name: newName, sex: newSex } = people;console.log(name, age, sex); //Uncaught ReferenceError: age is not definedconsole.log(newName, newAge, newSex); //ES6 27 male//三、赋值过程中设置默认值const { nickName = "昵称", age } = people;console.log(nickName, age); //昵称 27//四、reset运算符。只获取想要的属性,其他属性都放在新的变量里。const { name, ...peopleParams } = people;console.log(name, peopleParams); //ES6 {age: 27, sex: "male"}//五、嵌套对象取值const people = {  name: "ES6",  address: {    province: "江苏",
  },
};const { address: { province }} = people;console.log(province); //江苏复制代码

数组解构赋值

假如我们拿到一个数组,需要获取指定的元素值。

const [a, b, c] = [1, 2, 3];console.log(a, b, c);   //1 2 3复制代码

除了上面这种基本用法,还有其他使用方式

//一、待解构的除了是数组,还可以是任意可遍历的对象const [a, b, c] = new Set([1, 2, 3]);console.log(a, b, c); //1 2 3//二、被赋值的变量还可以是对象的属性,不局限于单纯的变量const num = {};
[num.a, num.b, num.c] = [1, 2, 3];console.log(num); //{a: 1, b: 2, c: 3}//三、解构赋值在循环体中的应用const num = {  a: 10,  b: 20,  c: 30,
};for (const [key, value] of Object.entries(num)) {  console.log(key, value); //a 10    b 20    c 30}//四、跳过赋值元素const [a, , c] = [1, 2, 3]; //存在空位的数组叫稀疏数组console.log(a, c);  //1 3//五、rest 参数const [a,...other] = [1, 2, 3];console.log(a, other);  //1    [2, 3]//六、赋值过程中设置默认值const [a, , , d = 10] = [1, 2, 3];console.log(d); //10复制代码

字符串解构赋值

字符串解构赋值可以当成数组解构赋值

const [a, b, c, d] = "ECMAScript2015";console.log(a, b, c, d); //E C M A复制代码

对象的扩展


ES6对对象进行了很多的扩展,具体如下

属性的简洁表示法

从ES6开始,如果对象的属性名和属性值相同,则有简写的方式。

let province = "江苏";const address = {
  province, //等同于 province: province
  city: "南京",
};复制代码

属性名表达式

从ES6开始,可以使用变量或表达式定义对象的属性。

let key = "province";const address = {
  [key]: "省份",  city: "南京",
};console.log(address); //{province: "省份", city: "南京"}复制代码

Object.is()

判断两个值是否是同一个值。在Object.is()之前,有“==”和“===”两种方式判断值是否相等,但这两个方式都有一定缺陷,如下

//== 在判断相等前会对不是同一类型的变量进行强制转换,最终导致“”与false相等console.log("" == false);   //true//=== 会将-0与+0视为相等,而将Number.NaN与NaN视为不相等console.log(-0 === +0); //trueconsole.log(Number.NaN === NaN);    //false复制代码

所以,需要一种运算,在所有场景下,只要两个值是一样的,那么就应该相等,在实际项目开发过程中,推荐使用Object.is()来判断值相等。

console.log(Object.is(-0, +0)); //falseconsole.log(Object.is(Number.NaN, NaN)); //truelet a = { value: 1 };let b = { value: 1 };console.log(Object.is(a, b)); //false  对象都是同一个引用才相等复制代码

Object.assign()


用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。

语法:

Object.assign(target, ...sources)     参数说明: target:目标对象 sources:源对象 返回值:合并后的目标对象

const target = { a: 1,};const source = { b: "B", c: "C" };const assignObj = Object.assign(target, source);console.log(assignObj); //{a: 1, b: "B", c: "C"}  //其他应用//一、如果目标对象与源对象属性具有相同值,则源对象属性值会覆盖目标对象属性值const target = { a: 1,b: 2};const source = { b: "B", c: "C" };const assignObj = Object.assign(target, source);console.log(assignObj); //{a: 1, b: "B", c: "C"}    //目标对象的b属性值被覆盖//二、源对象可以有多个值const target = { a: 1 };const source1 = { b: "B", c: "C" };const source2 = { d: "D", e: "E" };const assignObj = Object.assign(target, source1, source2);console.log(assignObj); //{a: 1, b: "B", c: "C", d: "D", e: "E"}复制代码

对象遍历

假如我们想要循环遍历一个对象的键与值,则可以使用下面几种方式进行遍历

const score = {  name: "mango",  age: "25",  score: 80,
};//for...infor (let key in score) {  console.log(key, score[key]); // 分别输出:name mango 、 age 25 、score 80}//Object.keys()用来获取所有key组成的数组Object.keys(scoreObj).forEach(key => {  console.log(key, scoreObj[key]) //分别输出:name mango 、 age 25 、score 80})//Object.getOwnPropertyNames()用来获取所有key组成的数组Object.getOwnPropertyNames(scoreObj).forEach(key => {  console.log(key, scoreObj[key]) //分别输出:name mango 、 age 25 、score 80})//Reflect.ownKeys()用来获取所有key组成的数组Reflect.ownKeys(scoreObj).forEach(key => {  console.log(key, scoreObj[key]) //分别输出:name mango 、 age 25 、score 80})复制代码

Class

JavaScript是一种基于对象的语言,我们遇到的所有东西几乎都是对象,但ES6之前是没有class的,而在ES6版本中正式引入了class,让JavaScript成为了一种真正的面向对象语言,我们可以像下面这样在JavaScript中进行面向对象编程。

//通过class关键字定义类class People{  
  //类的构造函数
  constructor(name, age) {    this.name = name;    this.age = age;
  }  //实例方法
  getName() {    return this.name;
  }  //静态方法
  static say() {    console.log("Hello ES6");
  }
}//继承class Student extends People {  constructor(name, age) {    super(name, age);
  }
}//对象创建与调用let student = new Student("mango", "27");
student.getName();
Student.say();复制代码

通过上面的代码,我们具体说明下JavaScript中进行面向对象编程。

类的声明

通过class关键字声明类,支持构造函数construct做对象初始化。

class People{  
  constructor() {    //初始化
  }
}复制代码

属性

Class对象中有两种对象属性,分别是实例属性和静态属性。实例属性必须定义在类的方法里,而静态属性必须定义在类的外面。

class People{  constructor() {    //定义实例属性
    this.name = "";    this.age = 0;
  }
}

People.desc="类描述";  //定义的静态属性//访问People people=new People();console.log(people.name);console.log(People.name);复制代码

类中定义的属性,默认都是可读可写的,但是如果这时候我们想指定属性不可被修改该如何实现呢?那么便要用到set和get了,set和get可以定义一个属性,但是如果只有get而没有set,则属性不可以进行修改。

class People {  get sex() {    return "男";
  }
}let people = new People();console.log(people.sex);
people.sex="女" //Uncaught TypeError: Cannot set property sex of #<people> which has only a getter复制代码</people>

方法

Class对象中有三种方法,分别是构造方法、实例方法还有静态方法。

class People {  //构造方法
  constructor(name, age) {    this.nameA = name;    this.age = age;
  }  //实例方法
  getName() {    return this.nameA;
  }  //静态方法
  static say() {    console.log("Hello " + People.desc);
  }
}

People.desc = "类描述";let people = new People("mango", "27");let name = people.getName();console.log(name); //mangoPeople.say(); //Hello 类描述复制代码

继承

继承是面向对象语言很重要的一大特征,ES6新加入了extends和super关键字来实现继承。

class People {  constructor(name) {    this.name = name;
  }

  getName() {    return this.name;
  }
}//继承class Student extends People {  constructor(name, age) {    super(name, age);
  }
}//Student类继承了People类,student对象中super调用了父类的构造函数,并传递了name参数,因为继承的特性,student也拥有了父类的getName()方法let student = new Student("ES6");console.log(student.getName());复制代码


通过以上对class的学习,我们得知道其实class并不是新引入的数据类型,其实class只是一种语法糖,它的实质完全可以看作构造函数的另一种写法。

class People {  constructor(name) {    this.name = name;
  }

  getName() {    return this.name;
  }
}console.log(typeof People); //functionconsole.log(People.prototype);  //{constructor: ƒ, getName: ƒ}复制代码

JS是单线程的

异步编程其实就是处理异步任务,在进行异步编程之前,我们需要了解JavaScript是单线程的,在同一时间只能做一件事。

JavaScript之所以设计成单线程,是与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动以及操作DOM。这决定了它只能是单线程,否则会带来很多复杂的同步问题。例如,如果JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器就不晓得以哪个线程为准。所以,为了避免复杂,从一诞生,JavaScript就是单线程的。

单线程就意味着,所有任务都需要排队,前一个任务结束,后一个任务才会执行。那么如果前一个任务很长的话,那么后面一个任务不是就一直需要等待了吗?于是乎,JS将所有任务分成了两类,同步和异步。
同步:只有前一个任务执行完毕,才能执行后一个任务
异步:当同步任务执行到某个需要耗时执行的运行环境API时,就会触发异步任务,此时运行环境(浏览器或Node)就会单独开线程去处理这些异步任务。

JavaScript运行原理

下面是JavaScript运行原理图,同步任务在JS主线程完成,异步任务则新开一个线程

全面掌握,ECMAScript的新特性

疑问:不是说JavaScript是单线程的吗,那为什么又新开了一条线程处理任务呢,这不是多线程方式吗?
有这个疑问不奇怪,我们需要清楚JavaScript单线程其实说的是JavaScript引擎是单线程的,开发者只能通过单线程的方式进行JavaScript开发,而新开了一条线程处理任务是底层执行环境决定的,JavaScript执行环境是多线程。


在实际项目中,异步编程使用场景极其之多,请求个接口数据、创建个定时器、缓存个数据都离不开异步编程的身影,为了更好的处理异步任务,ES6给我们提供两种新的方式,分别是Promise和Generator。

Promise

Promise 是一个代理对象,代表了一个异步任务的最终成功或者失败状态。Promise允许你为异步任务的成功或失败分别设置对应的处理方法,以类似同步的方式便捷的进行异步编程。

一个Promise有三种状态:

  • pending:初始状态,既不是成功,也不是失败状态。
  • fulfilled:成功状态,代表着任务执行完成
  • rejected:失败状态,代表着任务执行失败

基本使用

创建Promise对象

const promise = new Promise(function (resolve, reject) {    let result=执行异步任务;    if(result){        //如果异步任务成功完成
        resolve()
    }else{        //如果异步任务执行失败
        reject();
    }
});复制代码

创建Promise对象需要传递一个executor参数,executor是带有resolve和reject两个参数的函数,这两个参数是JavaScript引擎提供的两个函数,Promise构造函数执行会立即调用executor函数。

  • 当Promise中的异步任务执行成功时,调用resolve(),将Promise对象的状态从pending改为fulfilled(未完成到成功)
  • 当Promise中的异步任务执行失败时,调用reject(),将Promise对象的状态从pending改为rejected(未完成到失败)


全面掌握,ECMAScript的新特性

如何使用

实例好promise对象后,我们可以使用下面的语法来对异步任务完成后的状态进行处理。

promise.then(onFulfilled,onRejected)

promise.then(  (result) => {    console.log("异步任务处理成功,执行相应方法");
  },  (error) => {    console.log("异步任务处理失败,执行相应方法");
  }
);复制代码

但在具体的项目开发中,我们通常都是使用已经存在的Promise对象,下面我们就通过使用常用的promise对象fetch获取接口数据。

案例实现

我们需要调用接口,获取一个用户列表数据

fetch("http://jsonplaceholder.typicode.com/users")
  .then(function (response) {    return response.json();
  })
  .then(function (res) {    console.log(res);   // [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
  });复制代码

response是一个包含响应结果的Response对象,它只是一个HTTP响应,而不是真正的JSON。为了获取JSON的内容,需要使用json()方法获取一个Promise对象,然后再使用then获取JSON数据。

其他使用

Promise.prototype.catch()

Promise提供了catch()方法,用来捕获异步操作过程中遇到的错误异常,使用场景如下

const CatchPromise = new Promise(function (resolve, reject) {
  reject(new Error("error msg"));
});

CatchPromise.then().catch((e) => {  console.error(e); //Error: error msg});复制代码

在Promise对象中,除了可以使用reject(new Error())的方式触发异常,还可以使用throw new Error()的方式触发异常,但不建议使用throw new Error()的方式,因为这种方式不会改变Promise的状态。

Promise.prototype.all()

Promise.all()用于处理多个异步任务,例如处理多张图片上传。Promise.all()接受一个promise对象数组作为参数,执行完毕返回一个Promise对象。

Promise.all()的状态变化:
传入的promise对象数组全部变为fulfill状态则返回成功,调用resolve()
传入的promise对象数组有一个变为reject状态则返回失败,调用reject()

const promise1 = new Promise(function (resolve, reject) {  setTimeout(function () {
    resolve("promise1");
  }, 2000);
});const promise2 = new Promise(function (resolve, reject) {  setTimeout(function () {
    resolve("promise2");
  }, 1000);
});const promise3 = new Promise(function (resolve, reject) {  setTimeout(function () {
    resolve("promise3");
  }, 3000);
});const promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then(function (results) {  console.log(results); // ["promise1", "promise2", "promise3"]});复制代码

Promise.prototype.race()

Promise.race()也是用于处理多个异步任务,与Promise.all()一样,Promise.race()接受一个promise对象数组作为参数,执行完毕返回一个Promise对象。

Promise.race()的状态变化:
传入的promise对象数组有一个变为resolve状态则返回成功,调用resolve()
传入的promise对象数组有一个变为reject状态则返回失败,调用reject()

const promise1 = new Promise(function (resolve, reject) {  setTimeout(function () {
    reject("promise1");
  }, 2000);
});const promise2 = new Promise(function (resolve, reject) {  setTimeout(function () {
    resolve("promise2");
  }, 1000);
});const promise3 = new Promise(function (resolve, reject) {  setTimeout(function () {
    resolve("promise3");
  }, 3000);
});const promiseAll = Promise.race([promise1, promise2, promise3]);
promiseAll.then(function (results) {  console.log(results); // promise2});复制代码

Generator函数

Generator函数是用来处理异步任务的函数,函数内部包裹的就是异步任务的处理。Generator不同于普通函数,当执行到异步任务,可以暂停,直到异步任务执行完毕再继续往下执行,类似同步的方法进行异步编程。

如何使用

Generator函数在使用上具体有以下特点

  • :定义generator函数时,function后面需要加上星号,例如function generatorFuncName()
  • yield:需要暂停去执行异步任务时,需要在代码前面加上yield标识,例如yield fetch()
  • next():调用generator函数后获得的是一个指针对象,调用指针的next方法,可以用来分阶段逐步执行generator函数。


那么具体如何使用generator函数实现异步编程呢,在学习Promise的时候,我们实现了一个获取一个用户列表数据的案例,下面我们看看如何使用generator函数实现吧。

function* loadUsers() {  const API = "http://jsonplaceholder.typicode.com/users";  console.log("等待数据请求");  yield fetch(API); //暂停,开始执行异步任务
  console.log("数据请求完成");  console.log("继续其他逻辑操作");
}const generator = loadUsers();const promise = generator.next().value;console.log(promise);

promise
  .then(function (response) {    return response.json();
  })
  .then(function (result) {    console.log(result); //[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
    generator.next(); //打印:数据请求完成  继续其他逻辑操作。异步任务执行完毕后调用next(),继续执行generator函数中后续代码
  });复制代码

Proxy

Proxy翻译过来叫代理,Proxy可以通过自定义行为来改变对象的基本操作,例如属性赋值、查找、枚举、函数调用等。

基本语法

const p=new Proxy(target,handler); 参数说明: target:需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至是另外一个代理) handler:代理目标对象基本操作的对象 返回值: p:target被代理后不可以直接访问,而只能访问

Proxy使用场景

设置对象属性的读写权限

let people = {  name: "mango",  //设置属性不可读
  age: 27,  //设置属性不可被修改};let peopleHandler = {  //设置属性值读取的捕捉器
  get: function (target, prop, receiver) {    if (Object.is(prop, "name")) {      return prop + "属性不可读";
    }
  },  //设置属性值操作的捕捉器
  set: function (target, prop, value) {    if (Object.is(prop, "age")) {      throw new Error(prop + "属性不可写");
    }
  },
};let peopleProxy = new Proxy(people, peopleHandler);
peopleProxy.age = 10; // Uncaught Error: age属性不可写console.log(peopleProxy.name); //输出:name属性不可读复制代码

给接口返回的对象中字段为null的属性设置默认值

let people = {  address: null,
};let peopleHandler = {  //设置属性值读取的捕捉器
  get: function (target, prop, receiver) {    return target[prop] ?? "默认值";  //空值合并操作符
  },
};let peopleProxy = new Proxy(people, peopleHandler);console.log(peopleProxy.address); //输出:默认值复制代码

拦截函数调用

//handler.apply()用于拦截函数的调用function sum(a, b) {  console.log(a + b);  return a + b;
}////对于sum方法,关注的是处理数据相加的逻辑//通过代理则可以处理在调用方法时候,对参数的校验,数据打点等const sumProxy = new Proxy(sum, {  apply: function (target, thisArg, argumentsList) {    console.log("调用了方法", "打点");
  },
});

sumProxy(1, 2);复制代码

**

Module


ES6在语言标准上,通过Module实现了模块功能,现阶段几乎取代之前用来实现JavaScript模块化的CommonJS和AMD规范,成为了浏览器环境和node环境通用的模块化解决方案。
Module实现的模块化属于“编译时加载”,即在编译时就完成了模块之间的加载,通过这种“编译时加载”的方式,使得在不运行代码的情况下就可以通过词法分析、语法分析等对程序代码进行扫描,以验证代码的规范性、安全性和可维护性,让静态分析成为了可能。

如何使用


Module实现的模块化功能主要有两个命令构成:

  • export:导出命令,用于提供模块的对外接口
  • import:导入命令,用于引入其他模块提供的接口


一个模块就是一个独立的文件,该文件内的所有变量,外部无法获取,如果想要外部获取模块内的某些变量,就必须使用export关键字导出变量,在需要引入该导出变量的的模块中必须使用import关键字引入变量。

export

下面举例说明export命令导出对外接口的几种方式,在ExportDemo.js文件中,

  • 导出变量
//方法一export let a = 1;//方法二let b = 2;export { b };复制代码
  • 导出函数
//方法一export function test(){    console.log("name");
}//方法二let test2=function test(){    console.log("name");
}export {test2 as newName}复制代码

注意在方法二中,使用了as关键字,as关键字可以在导出时重命名对外的接口名。

  • 导出类
//方法一export class People {
  say() {      console.log("Hello Module");
  }
}//方法二export { People };复制代码

import

使用export导出了模块中的对外接口后,其他JS文件就可以通过import关键字加载这个模块,使用如下。

//大括号中的变量名必须与被导出对外接口名一致import { a, b, test as newTest, People } from "./ExportDemo";//导入a和b的变量console.log(a, b);//导入test方法,同样可以使用as关键字在导入的时候重命名newTest();//导入People类let people = new People();
people.say();复制代码


其他使用方式**
在日常开发过程中,Module模块化还有下面几种常见的使用方式。

  • 使用*指定一个对象,加载模块中的所有导出
//import { a, b, test, People } from "./ExportDemo";//上面的导入方式可以改写成下面方式import * as ExportModule from "./ExportModule";//使用的使用,加上前缀即可ExportModule.test()复制代码
  • 通过export default,为模块指定默认导出名
//导出const People = {  say: function () {    console.log("Hello Module");
  },
};export default People;//导入import People from "./ExportModule";

People.say(); //Hello Module复制代码
  • export和import一起使用,处理先输入后输出的情况
//假如有a、b、c三个文件模块,//c文件模块如下 c.jslet people={name:"mango",age:27};let address="南京";export { people, address };//有下面几种使用场景//一、在b中导入c中的people和address,并导出给a使用export {people,address} from 'c'//二、在b中整体导入c,并导出给a使用export * from 'c'//三、在b中导入people,并作为b的导出名称【具名接口改为默认接口】export {people as default} from 'c'//当c的导出方式为export default的时候,并可以使用【默认接口改为具名接口】export {default as NewPeople} from 'c'复制代码

相关免费学习推荐:javascript(视频)

以上是全面掌握,ECMAScript的新特性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:juejin。如有侵權,請聯絡admin@php.cn刪除
從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能