Maison >interface Web >js tutoriel >Maîtrise complète des nouvelles fonctionnalités d'ECMAScript
La chronique javascript d'aujourd'hui vous présentera les nouvelles fonctionnalités d'ECMAScript.
Après qu'ES6 soit devenu le standard de nouvelle génération pour JavaScript, le comité des normes (TC39) publiera une nouvelle version d'ES chaque année, et de nombreuses nouvelles versions sont introduites dans chaque version.De nouvelles fonctionnalités pratiques.Dans le développement quotidien de projets, si nous maîtrisons ces nouvelles fonctionnalités pratiques, l'efficacité du développement sera grandement améliorée. Apprécions pleinement ces nouvelles fonctionnalités d'ES~
Auparavant, var était utilisé pour définir des variables, ce qui nous offre une nouvelle façon
let est utilisé pour déclarer des variables, et const est utilisé pour déclarer des constantes.
const TAG = "我是常量";let a; a = 2;console.log(TAG, "a=" + a); //我是常量 a=2复制代码
laissez et const ajouter de nouvelles fonctions au niveau du bloc à JavaScript. Domaine, généralement, la portée appartenant au code enveloppé par {} est la portée au niveau du bloc. Les variables ou constantes déclarées ne sont valides que dans la portée au niveau du bloc et ne sont pas accessibles de l'extérieur.
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复制代码
Il y a deux portées au niveau du bloc ci-dessus, qui déclarent toutes deux la variable a, mais la portée externe au niveau du bloc n'a rien à voir avec la portée interne au niveau du bloc, donc la sortie en (1) est la variable externe Valeur 1, (2) accède à une variable qui n'est pas définie dans une portée au niveau du bloc, une erreur sera donc signalée.
Un autre exemple de compréhension de la portée du bloc.
//for循环体内的定时器//在ES6之前,是没有块级作用域的,变量用var声明,直接挂载在全局作用域上for (var i = 0; i <h4 data-id="heading-4">2. La zone morte temporaire </h4><p> ne peut pas être utilisée avant que les variables et les constantes ne soient déclarées. <br>Les commandes let et const entraîneront la formation du bloc dans une portée fermée. Si elles sont utilisées avant la déclaration, une erreur sera signalée dans la syntaxe. </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 ne sont pas accessibles aux objets globaux (window, global, self)
let a = 10;console.log(window.a); //undefined复制代码
ES6 a quelques extensions de chaînes, comme suit :
ES6 ajoute de nouvelles chaînes de modèle (字符串
) Définissez les chaînes de manière à résoudre les problèmes rencontrés auparavant lorsque les chaînes sont trop longues et doivent être enveloppées, ou lorsqu'il y a des variables ou des expressions dans les chaînes. Voici des scénarios d'utilisation spécifiques
//一、打印长字符串且换行,直接在模板字符串中保留格式即可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世界复制代码
Détermine si une chaîne contient une chaîne spécifiée et renvoie un type booléen.
const str = "ECMAScript"console.log(str.includes("EC")); //true 找不到返回false 复制代码
startsWith() est utilisé pour déterminer si la chaîne commence par la chaîne spécifiée et renvoie un type booléen.
endsWith() est utilisé pour déterminer si une chaîne se termine par la chaîne spécifiée et renvoie un type booléen.
const str = "ECMAScript"console.log(str.startsWith("ECM")); //true console.log(str.endsWith("Script")); //true 复制代码
Répétez la chaîne d'origine n fois pour obtenir une nouvelle chaîne
const str = "ECMAScript";console.log(str.repeat(3)); //ECMAScriptECMAScriptECMAScript复制代码
ES6 Les méthodes globales ont commencé être progressivement réduit, rendant le langage progressivement modulaire, de sorte que certaines méthodes de traitement des valeurs numériques ont été transférées à l'objet Number et le comportement fonctionnel est resté inchangé.
//将目标转换为整数//ES5parseInt("5.6") //5//ES6Number.parseInt("5.6") //5//将目标转换为浮点数//ES5parseFloat("12.45str") //12.45//ES6Number.parseFloat("12.45str") //12.45复制代码
De plus, afin de faciliter le développement, Number a également ajouté quelques méthodes et attributs
一、判断一个数值是否是整数Number.isInteger(25) // trueNumber.isInteger(25.1) // false二、获取JavaScript最大安全值和最小安全值Number.MAX_SAFE_INTEGER=9007199254740991Number.MIN_SAFE_INTEGER=-9007199254740991三、判断一个数值是否是在安全范围Number.isSafeInteger(9007199254740992) //false复制代码
De nouveaux types de données primitifs sont introduits pour représenter des valeurs uniques.
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) 打印描述需要转换成字符串复制代码
1. Éliminer les chaînes magiques
Si nous devons créer un menu contextuel pour effectuer différentes fonctions de traitement, C’est ainsi que nous procédons habituellement.
const clickMenu = function (menu) { switch (menu) { case "home": break; case "me": break; } }; clickMenu("home")复制代码
"home" peut apparaître plusieurs fois. La chaîne qui forme un couplage fort avec le code est la chaîne magique. Dans le projet, nous devrions faire de notre mieux pour éliminer les chaînes magiques. strings
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);复制代码
2. Valeurs d'attribut uniques en tant qu'objets
Si nous voulons générer un objet de nom d'entreprise et utiliser le nom de chaque personne comme valeur clé, alors il y aura un problème si quelqu'un a le même nom. Symbol peut résoudre ce problème
const scores = { [Symbol("张三")]: { age: 22, }, [Symbol("李四")]: { age: 21, }, [Symbol("张三")]: { age: 20, }, };复制代码
Notez que les attributs définis par Symbol ne peuvent être parcourus que des deux manières suivantes, sinon les attributs ne peuvent pas être obtenus.
for (let key of Object.getOwnPropertySymbols(scores)) { console.log(key, key); }for (let key of Reflect.ownKeys(scores)) { console.log(key, scores[key]); }复制代码
Afin d'implémenter plus facilement les opérations sur les données, ES6 ajoute deux nouvelles structures de données : Set et Map.
Set est une structure de données similaire à un tableau, mais les valeurs des membres sont uniques.
Créez une structure de données Set pour stocker le mois Vous pouvez définir une instance Set vide, ou elle peut avoir des données par défaut sous la forme d'un tableau.
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) {}复制代码
La structure de données définie comporte de nombreux scénarios d'application dans des projets réels.
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与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是一种键值对集合,与对象类似,但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通常可以用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与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()方法从一个类似数组或可迭代对象创建一个新的浅拷贝的数组实例,通常有以下四种实用场景。
//一、克隆一个数组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复制代码
ES6引入reset参数,形式为...变量名,可以用来获取传递给函数的多余参数。
function sum(a, ...values) { console.log(a, values); //1 [2, 3, 4, 5]} sum(1, 2, 3, 4, 5);复制代码
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”。
在后续的开发过程中,我们将会经常使用到箭头函数,在使用的过程中,我们需要有以下几点注意
解构赋值是一种表达式,可以将属性和值从对象和数组中取出,赋值给其他变量。
假如我们拿到一个对象,需要获取指定的属性值。则解构赋值让我们无需通过调用属性的方式赋值,而是通过指定一个与对象结构相同模板的方式,获取想要的属性值。
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()之前,有“==”和“===”两种方式判断值是否相等,但这两个方式都有一定缺陷,如下
//== 在判断相等前会对不是同一类型的变量进行强制转换,最终导致“”与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(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})复制代码
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: ƒ}复制代码
异步编程其实就是处理异步任务,在进行异步编程之前,我们需要了解JavaScript是单线程的,在同一时间只能做一件事。
JavaScript之所以设计成单线程,是与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动以及操作DOM。这决定了它只能是单线程,否则会带来很多复杂的同步问题。例如,如果JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器就不晓得以哪个线程为准。所以,为了避免复杂,从一诞生,JavaScript就是单线程的。
单线程就意味着,所有任务都需要排队,前一个任务结束,后一个任务才会执行。那么如果前一个任务很长的话,那么后面一个任务不是就一直需要等待了吗?于是乎,JS将所有任务分成了两类,同步和异步。
同步:只有前一个任务执行完毕,才能执行后一个任务
异步:当同步任务执行到某个需要耗时执行的运行环境API时,就会触发异步任务,此时运行环境(浏览器或Node)就会单独开线程去处理这些异步任务。
下面是JavaScript运行原理图,同步任务在JS主线程完成,异步任务则新开一个线程
疑问:不是说JavaScript是单线程的吗,那为什么又新开了一条线程处理任务呢,这不是多线程方式吗?
有这个疑问不奇怪,我们需要清楚JavaScript单线程其实说的是JavaScript引擎是单线程的,开发者只能通过单线程的方式进行JavaScript开发,而新开了一条线程处理任务是底层执行环境决定的,JavaScript执行环境是多线程。
在实际项目中,异步编程使用场景极其之多,请求个接口数据、创建个定时器、缓存个数据都离不开异步编程的身影,为了更好的处理异步任务,ES6给我们提供两种新的方式,分别是Promise和Generator。
Promise 是一个代理对象,代表了一个异步任务的最终成功或者失败状态。Promise允许你为异步任务的成功或失败分别设置对应的处理方法,以类似同步的方式便捷的进行异步编程。
一个Promise有三种状态:
const promise = new Promise(function (resolve, reject) { let result=执行异步任务; if(result){ //如果异步任务成功完成 resolve() }else{ //如果异步任务执行失败 reject(); } });复制代码
创建Promise对象需要传递一个executor参数,executor是带有resolve和reject两个参数的函数,这两个参数是JavaScript引擎提供的两个函数,Promise构造函数执行会立即调用executor函数。
实例好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提供了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.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.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函数实现异步编程呢,在学习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可以通过自定义行为来改变对象的基本操作,例如属性赋值、查找、枚举、函数调用等。
const p=new Proxy(target,handler); 参数说明: target:需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至是另外一个代理) handler:代理目标对象基本操作的对象 返回值: p:target被代理后不可以直接访问,而只能访问
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属性不可读复制代码
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);复制代码
**
ES6在语言标准上,通过Module实现了模块功能,现阶段几乎取代之前用来实现JavaScript模块化的CommonJS和AMD规范,成为了浏览器环境和node环境通用的模块化解决方案。
Module实现的模块化属于“编译时加载”,即在编译时就完成了模块之间的加载,通过这种“编译时加载”的方式,使得在不运行代码的情况下就可以通过词法分析、语法分析等对程序代码进行扫描,以验证代码的规范性、安全性和可维护性,让静态分析成为了可能。
Module实现的模块化功能主要有两个命令构成:
一个模块就是一个独立的文件,该文件内的所有变量,外部无法获取,如果想要外部获取模块内的某些变量,就必须使用export关键字导出变量,在需要引入该导出变量的的模块中必须使用import关键字引入变量。
下面举例说明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 };复制代码
使用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()复制代码
//导出const People = { say: function () { console.log("Hello Module"); }, };export default People;//导入import People from "./ExportModule"; People.say(); //Hello Module复制代码
//假如有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(视频)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!