变量界说新形式

从ES6开端,JS引进了let和const关键字来界说变量,这是ECMAScript新增的两种声明变量的办法,相较于var关键字具有更多的优势:

let

let关键字用于声明一个块级效果域的局部变量,能够将let声明的变量从头赋值。let声明的变量只在代码块内部有用。eg:

if(true){
    let i=1;
    console.log(i);//输出1
}
console.log(i);//报错,i未界说

const

const关键字用于声明一个块级效果域只读常量,一旦const声明晰某个变量,就不能运用赋值句子改动它的值。常量必修在声明时初始化。eg:

const PI=3.1415926535
PI=3//报错,无法修正常量

可是!!!
const 只确保变量的引证不会改动,但并不确保引证的方针的内容不会改动。

Const obj={a:1,b:2}
a=yyy; //不报错

方针是经过引证传递的,修正方针的特点时,实际上是在修正方针的内部结构,而不是修正变量自身所持有的引证。所以能够修正 obj 方针的特点,但不能从头分配一个新的方针给 obj 变量。

优势

  1. 愈加安全
    运用let和const能够有用的防止一些变量效果域混杂的问题。经过运用块级效果域,咱们能够让变量只在指定代码块内部有用,防止了不必要的变量污染和抵触
  2. 愈加简练
    运用let和const能够大量减少代码量,而且愈加易于保护。在运用var时,由于变量效果域问题,经常需求额定增加句子进行变量界说、查看和清楚等操作,而运用let和const能够直接在代码中进行界说和运用,愈加简练和高效。
  3. 愈加规范,易于保护

实际开发中的运用

1. 循环中运用let声明变量防止问题

for(let i=0;i<5;i++){
    setTimeout(function(){
        console.log(i); //0 1 2 3 4
    },1000)
}

运用let声明的变量i有块级效果域,在每一次循环中都会从头界说并赋值,防止了运用var声明变量或许导致的变量同享问题。

-> 假如把let换为var,则输出5次5。
造成这个问题的原因,便是var关键字声明的标识符的效果域范围是函数效果域
setTimeout是一次履行函数,这儿是1s后履行,仅仅履行一次;for(var i=0;i<5;i++),i的每次取值都是履行setTimeout这个函数,并没有履行setTimeout里边的function(即闭包函数),setTimeout里边的function是有setTimeout的定时触动的,也便是1s后履行,也便是说i从0~4时,一共履行了5次的setTimeout()函数,当setTimeout的回调函数履行的时分,i标识符寄存的值现已是5了(5是循环完毕的结尾),因而将输出5个5。

-> 为什么setTimeout履行的时分i现已是5了,是由于等候了1秒钟嘛?

for(var i = 0;i < 5;i++) {
    setTimeout(() => console.log(i),0); //5 5 5 5 5
}

由上述代码段可知并不是由于等候了1秒钟导致setTimeout履行的时分i中寄存的值变成了5。
for循环是同步代码,而setTimeout是异步代码, 而咱们是先履行一切同步代码再履行宏使命,终究履行微使命的。
因而js引擎在履行这个for循环的时分,遇到了setTimeout,它会将setTimeout放入宏使命行列,之后接着履行同步代码,当同步代码履行完毕后去查看微使命行列,鄙人一轮工作循环开端的时分才会将setTimeout从宏使命行列中取出。
因而,setTimeout的履行必定在一切同步代码,也便是整个for循环履行完毕之后,即在setTimeout履行的时分,i现已是5了。

2. 运用const声明常量

const PI=3.1415926;
const url="https://www.baidu.com";

用const声明常量能够防止被修正,确保代码的可靠性和稳定性

3. 运用const声明方针特点防止误修正

const user = {
    name: "张三" ,
    age: 18,
    gender: "男"
};
user.name="李四";
console.log(user); //{name:"李四",age:18,gender:"男"}
Object.freeze(user);
user.age=20;
console.log(user); //{name:"李四",age:18,gender:"男"}

用const声明方针特点能够防止误修正,一同运用Object.freeze()办法能够将方针冻结,防止意外修正方针特点。

深化了解原理

底层完结上,let和const的工作办法是经过JS引擎来完结的。在JS引擎中,每一个变量都会被封装在一个称为”变量方针”的容器中,这个方针包括了一切当时上下文中界说的变量与函数。变量方针类似于一个键/值对的容器,其间键是变量名,值是变量的值。在JS引擎中,运用let和const界说变量,实际上是将该变量界说在一个块级效果域中,而块级效果域是由编译器在编译阶段中完结的。

let的底层完结进程

  1. 编译阶段
    在代码编译阶段,编译器会扫描整个函数体(或大局效果域),查找一切运用let界说的变量,为这些变量生成一个初始值为undefined的词法环境,并将其保存在效果域链中。
  2. 进入履行上下文
    当进入履行块级效果域(包括for、if、while、和switch等句子块)后,会创立一个新的词法环境。假如履行块级效果域中包括let变量声明句子,这些变量将被增加到整个词法环境的环境记载中。
  3. 绑定变量值
    运转到let界说的变量时,JS引擎会在当时词法环境中查找该变量。首要在当时运转环境记载中找到这个变量,假如不存在变量,则向包括当时环境的外部环境记载查找变量,直到大局效果域为止。假如变量值没有被绑定,JS引擎会将其绑定为undefined,不然持续履行其他操作。
  4. 完结块级效果域
    运用let界说变量时,在运转时不会在当时效果域之外创立独自的履行上下文,而是会创立子遮盖新环境。在子遮盖的词法环境中,变量的值只在最接近的块级效果域内有用。

const的底层完结进程

const具有与let相同的底层完结原理,差异在于const界说的变量被界说为常量(在赋值之后无法更改),因而变量声明时必须初始化。此外,运用const声明的方针是能够修正特点的。在界说const方针时,方针自身是常量,而不是方针的特点。只需方针自身不可修正,而方针包括的特点能够任意修正。
样例在本文最开端介绍const处有描绘,此处不再复述。

面向方针编程——class语法

JS的类终究也是一种函数,运用class关键字创立的类会被编译成一个函数,因而其底层原理与函数有一些类似之处。

类、结构函数

运用class关键字来界说类时,在内部会创立一个特别的函数,称为结构函数(constructor)。结构函数用于在创立方针时初始化方针的特点,类似于传统的根据原型的代码中的结构函数。

class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }
}
const p=new Preson("Tom",20);
console.log(p.name,p.age);// Tom 20

class中界说的特点和办法别离界说在这个结构函数的prototype特点中。而且与原型办法不同的是,类的办法是不可枚举的,因而无法运用for…in循环遍历类实例方针的特点和办法。

-> 面试或许会问:for…of 和 for…in 的差异
for...of能够遍历数据结构,可是能够遍历的条件是该数据结构具有Symbol.iterator特点,也便是说不管数组或许方针,只需具有Symbol.iterator特点就能够遍历
原理:遍历数据结构的Iterator接口,将for...of循环分解成最原始的for循环

//数组
const arr = ['a','b','c']
for(let item of arr){ 
    console.log(item) //a,b,c
} 
//方针
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr)
for(let item of obj){
    console.log(item) //a,b,c
}

数组默许具有Symbol.iterator特点,方针没有默许部署该特点,是由于方针特点的遍历次序不能确定,需求手动指定。 也便是说,单纯的声明一个Object方针是不能够用for…of去遍历的。
默许Symbol.iterator特点的有:Array、Map、Set、String、TypedArray、函数的 arguments 方针、NodeList 方针

for...in是遍历数据结构key值的,关于方针和数组都能够遍历,可是会将当时数据结构的原型链上的可枚举特点都遍历出来,能够经过hasOwnProperty来判别是否是私有特点

//数组 
const arr = ['a','b','c'];
for(let key in arr){ 
    console.log(key) //0,1,2 
} 
//方针 
const obj = {name:'alhh',age:18}; 
for(let key in obj){ 
    console.log(key) //name,age 
}

总的来说:

for...of遍历的是item,for...in遍历的是key值;
具有Symbol.iterator特点的才能够运用for...of进行遍历,for...in不需求,能够直接遍历数组、方针;
for...in会将原型链上的特点都遍历出来,for...of不会;
for...of支持returnfor...in不支持。

关于可枚举

在 JS 中,方针的特点分为两种类型:可枚举特点不可枚举特点
可枚举特点是指那些能够经过 for…in 循环遍历到的特点,不可枚举特点则是指那些不能经过 for…in 循环遍历到的特点。

假如想要将某个特点设置为不可枚举特点,能够运用 Object.defineProperty()办法或 Object.defineProperties() 办法,对特点的 enumerable 特征进行设置。示例如下:

const obj = {};
Object.defineProperty(obj, 'prop1', {
  value: 'value1',
  enumerable: false
});
for(let key in obj) {
  console.log(key); // 不会输出 'prop1'
}
//此处啥也不输出,由于 obj 方针的 prop1 特点被设置为不可枚举特点

在这个示例中,咱们运用 Object.defineProperty() 办法将 obj 方针的 prop1 特点设置为不可枚举特点。终究,for…in 循环句子只会输出 obj 方针中的可枚举特点

通常情况下,方针的一切一般特点和办法都是可枚举的,例如:

const obj = {
  prop1: 'value1',
  prop2: 'value2',
  func1: function() {}
};
for(let key in obj) {
  console.log(key); // 'prop1', 'prop2', 'func1'
}

在这个示例中,prop1 和 prop2 是 obj 方针的可枚举特点,而 func1 是 obj 方针的可枚举办法。

需求留意的是,在 ES6 中,经过 class 界说的方针默许不可枚举,就算没有显式地设置 enumerable 特点。这与运用 Object.defineProperty() 办法在 ES5 中设置不可枚举特点的办法不同。假如需求将 class 中的某个特点或办法设置为可枚举特点,需求运用 Object.defineProperty() 办法来进行设置。

承继

在JS中,承继是经过类的prototype特点完结的。界说类时,能够用extends关键字来承继其他的类:

class Student extends Person{
    constructor(name,age,grade){
        super(name,age);
        this.grade=grade;
    }
}

这段代码中,子类Student承继了父类Person的结构函数办法并增加了自己的特点和办法。

静态办法和特点

类中的静态办法和特点能够运用static关键字来界说,它们不是类实例的特点,而是类自身的特点和办法。

class Person{
    static species="human";
    static saySpecies(){
        console.log('We are ${this.species}.');
    }
}

这段代码中界说了一个静态办法和一个静态特点,能够经过类自身直接调用静态办法和特点。

getter和setter

在类中界说getter和setter办法能够让咱们封装实例的内部数据特点,使得这些特点的读写行为愈加的安全和合理。

class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }
    get name(){
        return this._name.toUpperCase();
    }
    set age(){
        if(age>0 && age<120){
            this._age=age;
        }else{
            console.log("Invalid age value");
        }
    }
    get age(){
        return this._age;
    }
}

在类的完结中,getter和setter其实是被界说在结构函数的prototype特点上,然后能够被该类的一切实例方针拜访。

class表达式

ES6还引进了class表达式,能够经过这种表达式来创立函数方针

const Person = class{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }
    sayHi(){
        console.log('Hi,my name is ${this.name},I'm ${this.age} years');
    }
};

JS的类实质上是一个函数,运用class关键字来声明类只是伪代码,这些代码终究都会被转成函数,并存在函数方针的特点和原型特点上

模板字符串

JS模板字符串是ES6中新增的一种特别的字符串语法,它允许嵌入表达式和变量,经过${}拼接字符串和表达式,比较传统的字符串拼接来说,模板字符串更具有可读性和可保护性。

模板字符串根底概念和用法

运用模板字符串时,需求运用反斜杠(`)来界说字符串,并在字符串中运用${expression}的办法来嵌入表达式和变量,能够运用多行办法来创立较长的字符串。

const name = "Tom";
const age = 20;
const str = `My name is ${name}, I'm ${age} years old.
I'm from China.`;
console.log(str);

在这个示例中,运用了一个模板字符串来创立一个包括变量和表达式的字符串。

底层完结原理

模板字符串的完结原理,能够大致分为两个进程:首要,JS引擎会将模板字符串解析成一个函数调用表达式;接着,这个表达式会被履行,并输出一个终究的字符串。

关于第一步,当 JS引擎解析模板字符串时,它会将特别字符和变量值分割成多个参数,并将它们作为函数调用的参数传递给一个名为 Tagged Template 的函数。该函数的第一个参数是一个数组,其间包括原始模板字符串中的一切字符文字,除了一切插入字符。其他参数则是与模板字符串插值表达式相对应的插入值。

也便是说,上面的示例能够被解析为如下调用:

const result = tagFn(["My name is ", ", I'm ", " years old.\nI'm from China."], name, age);

其间,tagFn 是一个可被调用的函数,用于完结对模板字符串的自界说处理。咱们能够经过这个函数对模板字符串和变量进行任意的处理和操作。也正是由于这种规划,模板字符串才能够像函数相同完结愈加杂乱的逻辑,比如核算、转化等操作。

Q:完结一下这个tagFn

const tagFn(temp,..args) =>{
    let str=''
    for(let i-0;i<temp.length;i++){
    //此处未考虑容错情况
        str+=temp[i]+args[0]
    }
    return str
}

运用 tagged template

除了默许运用模板字符串外,咱们还能够自界说 tagged template 来处理模板字符串。符号模板只需求界说一个函数,这个函数的第一个参数为一个符号数组,剩余其他参数是字符串中替换表达式的值,回来值是终究拼接好的字符串。这种形式在一些库中运用广泛,例如 styled-components

function upperCase(strings, ...values) {
  let result = '';
  strings.forEach((str, i) => {
    if (i > 0) {
      result += String(values[i - 1]).toUpperCase();
    }
    result += str;
  });
  return result;
}
const name = 'rocky';
const age = 18;
const str = upperCase`my name is ${name}, i'm ${age} years old.`;
console.log(str);//my name is ROCKY, i'm 18 years old.
/*
假如想要这部分悉数大写,可将return result更改:
return result.toUpperCase();
这样打印出来的为:MY NAME IS ROCKY, I'M 18 YEARS OLD.
*/

这儿咱们完结的 upperCase 函数,能够把字符串替换的内容转化成大写。

解构语法

根底概念与原理

JS的解构是 ES6 中新增的一个语法特性,它能够将数组或方针中的元素提取出来并赋值给变量。解构语法使得对数组和方针的操作愈加灵活和快捷。

数组解构底层原理

对数组解构的底层完结分为两个进程:第一步是运用取值函数(getter)读取数组中对应位置的值,第二步是将取得的值赋值给方针变量。

以以下代码为例:

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

在这个比如中,JS引擎背面产生的工作如下:

const tempArray = [1, 2, 3];
const a = tempArray[0];
const b = tempArray[1];
const c = tempArray[2];
console.log(a, b, c); // 1 2 3

关于数组解构而言,将每个方针变量赋值的进程是独立的,它们并不会相互影响

方针解构底层原理

解构方针变量的进程与解构数组变量十分类似。将会遍历方针中的每一个特点,然后在解构表达式中查找同名的变量。

const {firstname: first, lastname: last} = { firstname: 'John', lastname: 'Doe' };
console.log(first, last); // John Doe

在这个比如中,解构方针并赋值给变量的进程能够了解为:

const tempObject = { firstname: 'John', lastname: 'Doe' };
const first = tempObject.firstname;
const last = tempObject.lastname;
console.log(first, last); // John Doe

需求留意的是,在方针解构语法中,方针变量的称号需与要求的特点名坚持相同。当方针中有未界说的方针变量时会被设为 undefined。

嵌套解构底层原理

嵌套解构是指解构表达式中还包括其他的数组或方针解构。嵌套解构的底层完结和单层解构类似,操作每个元素或特点时都需求顺次运用取值函数(getter)进行操作。

例如:

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

在这个比如中,解构进程类似于如下代码:

const tempArray = [1, [2, [3]]];
const a = tempArray[0];
const tempArray2 = tempArray[1];
const b = tempArray2[0];
const tempArray3= tempArray2[1];
const c = tempArray3[0];
console.log(a, b, c); // 1 2 3

默许值

解构语法还支持给方针变量供给默许值,在无法解构出对应的值时会运用默许值。默许值能够是任何 JS 表达式,包括函数调用、变量名、运算符等。

const [a = 1, b = 2] = [];
console.log(a, b); // 1 2

在这个比如中,解构表达式的值为空数组(也便是未界说的时分,假如右边是null的话并不会回来默许值),因而解构无法成功。但由于设置了默许值,a 和 b 的值会别离设为 1 和 2。

JS 的解构语法的实质是运用 getter 函数和赋值操作将数组或方针的值依照指定的格局赋值给方针变量。这种语法简化了编程的流程,进步了代码的可读性和可保护性。

JS 引擎关于解构语法完结的细节

在 JS 引擎处了解构语法时,会履行以下进程:

  1. 假如右边(要解构的方针)是一个具有 Iterator 接口的方针,则需求调用其 @@iterator 办法,为解构进程创立一个迭代器。迭代器能够让咱们对要解构的方针进行遍历,并将其每个特点的取值传递给左面(解构的方针)中相应的变量。
  2. 对要解构的方针进行判别,假如为无法解构的值 (如若为 undefined 或 null),则抛出 TypeError。
  3. 假如解构语法中指定了默许值,则在方针无法解构到值时运用默许值。
  4. 嵌套解构会在方针中持续求值以解构嵌套的变量。
  5. 解构数组和字符串元素时,会依照索引次序进行赋值;而关于方针中的元素,会依照特点名进行赋值。
  6. 关于方针的解构,会从方针中取出相应特点的值,然后复制到与解构表达式中相应的变量中。假如解构表达式中指定的变量名与特点名不同,需求运用 “key: value” 表明法进行界说。
  7. 解构表达式是完全能够包括剩余运算符的,这样即可匹配方针或数组中剩余的特点,将其赋值到相应的变量上。
  8. 假如解构表达式中不存在取值函数(getter)和设置函数(setter),则此时解构赋值能够在功用上比原生赋值句子快一些。

JS引擎在处了解构表达式时,会比运用惯例的变量赋值句子多一些进程。当咱们运用解构表达式时,咱们能够依据语法的特点编写愈加简练、易读的代码,JS引擎会主动为咱们履行相应的处理进程。

箭头函数的原理与运用场景

JS中的箭头函数是ES6中新增的一种函数界说办法,它能够创立一个函数并赋值给变量,运用箭头语法’->’。在箭头函数中,this关键字的效果域与它周围代码效果域相同,因而有时也被称为 “词法效果域函数”

根底概念与运用

箭头函数的原理是根据JS中的闭包、this和参数效果域。在箭头函数中,this关键字始终指向函数地点上下文的this指针,而不是地点效果域的this指针。

举个比如:

const add = (a, b) => {
  return a + b;
};
console.log(add(2, 3));

在这个示例中,箭头函数会将a和b作为参数,并将其回来值赋值给add。箭头函数的this指针在这儿是指向大局方针(即window,在浏览器中)的,而不是函数效果域。

箭头函数首要运用场景

箭头函数的运用场景有以下几个:

  1. 当咱们需求运用更简练的语法来界说函数时,能够运用箭头函数代替传统的函数界说语法。箭头函数更为简练、易读,能够运用单行语法来代替多行语法。
// 传统函数界说
function multiply(x, y) {
  return x * y;
}
// 箭头函数
const multiply = (x, y) => x * y;

2. 当咱们需求引证地点父级的this指针时,能够运用箭头函数,由于箭头函数中的this指向的是大局方针,而不是函数调用时的上下文方针。而不运用箭头函数时,这儿会由于this指针指向的问题而带来一些不便。

const obj = {
  name: 'Tom',
  age: 20,
  say: function() {
    console.log(`My name is ${this.name}, I'm ${this.age} years old`);
  }
};
setTimeout(obj.say, 1000);//My name is , I'm undefined years old

在这个示例中,由于setTimeout中的this指针的问题,函数say中的name特点和age特点无法正常被引证。而运用箭头函数就能处理这个问题。

const obj = {
  name: 'Tom',
  age: 20,
  say: function() {
    setTimeout(() => {
      console.log(`My name is ${this.name}, I'm ${this.age} years old`);
    }, 1000);
  }
};
obj.say();//My name is Tom, I'm 20 years old

3. 在函数式编程中,箭头函数用于编写函数式代码时,简化函数的界说和函数调用的进程。

不能运用箭头函数的场景

虽然箭头函数在许多运用场景中表现出极大的优势,但在某些情况下还是不能正常运用。常见的限制场景如下:

  1. 不能用作结构函数:箭头函数没有自己的this指针,所以不能作为结构函数来运用。因而,不能运用new关键字来调用箭头函数来创立一个新方针。
const Person = (name, age) => {
  this.name = name;
  this.age = age;
};
const person1 = new Person('Tom', 20); // 报错,Person不是结构函数

2. 不能运用 arguments 关键字:在箭头函数中,函数的参数为指定的参数,没有额定的 arguments 方针。假如需求运用 arguments 参数,必须运用惯例的函数语法。

const func1 = () => {
 console.log(arguments); // 报错,arguments未界说
};
func1(1, 2, 3);

假如需求运用 arguments,则需求运用function函数界说办法:

const func2 = function() {
 console.log(arguments);
};
func2(1, 2, 3); // 输出[1, 2, 3]

3. 不能经过 call()、apply()办法修正this指向:关于箭头函数,它的 this 指针指向词法效果域中的this值,无法经过call()、apply()办法来修正

const obj = {
  name: 'Tom',
  age: 20,
  say: () => {
    console.log(`My name is ${this.name}, I'm ${this.age} years old`);
  }
};
obj.say.call({ name: 'Bill', age: 30 }); // 输出 My name is undefined, I'm undefined years old

在这个示例中,虽然咱们经过call()办法强制修正了say()的this值,但成果表明this值的实际成果并没有得到改动。这是由于箭头函数自身并没有自己的this值,在这儿依然运用的是上面大局方针的this值。

生成器 generator

JS中的生成器(Generator)是在ES6中引进的一种函数类型,它与传统的函数不同之处在于,在生成器中,咱们能够半途中止函数的履行,并保存相关的上下文信息,待下次持续履行时能够从保存的上下文信息处持续履行。

根本概念

生成器的界说与传统函数十分类似,不同之处在于生成器函数的关键字为“function*”(留意是带星号的function),并运用yield操作符来指定生成器函数的履行进程。yield操作符能够看做是一个暂停器,它能够和外部程序交换数据,并在函数履行阻滞时暂停函数的操作,并记载下履行状况信息以备之后康复时运用。

下面是一个简略的生成器示例:

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}
const sequence = generateSequence(); // 获取生成器实例
console.log(sequence.next().value); // 输出 1
console.log(sequence.next().value); // 输出 2
console.log(sequence.next().value); // 输出 3

在这个示例中,咱们界说了一个 generateSequence 的生成器函数,并在其间运用 yield 中止程序的履行,生成器函数履行到 yield 的时分,就会中止履行而且将 yield 后面的值回来作为值,并记载下当时运转的上下文信息, 等候下一次调用 next() 办法时康复上下文信息和yield后面的值,并持续履行,直到生成器函数履行完毕。

生成器能够看做是一种特别的迭代器,它与一般迭代器不同之处在于,它的 yield 关键字能够回来多个值,而且它具备暂停及康复履行状况的功用。通常情况下,咱们能够运用生成器来处理那些状况化的问题,在文件读写、网络恳求、流式核算等处理办法中都能够运用生成器的特性来优化代码功率。

运用场景

生成器的运用场景首要是在需求处理大量异步操作并坚持状况的情况下。生成器不仅能够使代码简练易懂,还能够防止回调阴间和 Promise 降解的问题。

生成器能够经过 yield (产出)操作暂停函数的履行,并回来一个值,等候下一次调用 next (下一个)办法从头发动函数的履行,并持续履行到下一个 yield 操作或函数退出。这样就能够在暂停和康复的进程中,坚持函数的状况,防止了频频的创立/销毁该函数的内部变量,进步了功用。

下面是一个运用生成器处理异步操作的示例:

function* myGenerator() {
  const result1 = yield asyncOperation1(); // 建议异步操作1
  const result2 = yield asyncOperation2(result1); // 建议异步操作2,并将异步操作1的成果作为参数
  return result2; // 回来异步操作2的成果
}
function asyncOperation1() {
  return new Promise(resolve => setTimeout(() => resolve('result1'), 1000));
}
function asyncOperation2(arg) {
  return new Promise(resolve => setTimeout(() => resolve(`result2-${arg}`), 1000));
}
const gen = myGenerator(); // 获取生成器实例
const p1 = gen.next(); // 发动异步操作1
p1.value.then(result1 => {
  const p2 = gen.next(result1); // 发动异步操作2,将异步操作1 的成果作为参数传递
  p2.value.then(result2 => {
    console.log(result2); // 输出 result2-result1
  });
});

在这个示例中,咱们界说了 myGenerator 生成器函数,它运用 yield 操作暂停履行,并在异步操作完结后康复履行,并传递相应的参数。咱们运用 Promise 实例来完结异步操作,并运用then() 办法来获取异步操作的成果,并将成果作为参数传递给下一个yield操作。

在上面这个示例中,咱们运用生成器函数来方便地完结了两个异步操作的串联和参数传递。这种办法的优点在于代码可读性和可保护性都得到了大大的进步。一同,生成器自身的状况坚持特性,也使得代码的功用得到了提高。

异步处理——callback、Promise、async & await

异步编程是一种处理工作循环等候成果回来的办法,常见的完结办法有 callback、Promise 和 async/await。

  1. callback
    callback 是一种异步编程形式,经过回调函数的办法完结,通常用于处理一次性异步恳求。callback 的优点在于完结起来简略,但由于回调函数嵌套层数简略过多,使得代码可读性和可保护性受到影响。

    function fetchData(callback) {
        setTimeout(() => {
            const data = 'Hello World!';
            callback(data);
        }, 1000);
    }
    fetchData((data) => {
        console.log(data); // 输出 Hello World!
    })
  1. Promise
    Promise 是 ES6 供给的一种处理异步操作的机制,用于处理 callback 回调函数嵌套过多的问题。Promise 能够链式调用,经过 then() 办法来处理回来值,一同还供给了 catch() 办法来处理过错。

    function fetchData() {
        return new Promise(function(resolve) {
            setTimeout(() => {
                const data = 'Promise';
                resolve(data);
            }, 1000);
        });
    }
    fetchData().then(function(data){
        console.log(data); // 输出 Promise
    });
  1. async/await
    async/await 是 ES8 的新特性,是根据 Promise 的一种异步编程办法,它能够使异步代码看起来像同步代码,语法简略易懂,可读性较高。async 是用于界说一个异步函数,await 用于等候一个异步操作完结。async 函数回来一个 Promise 方针,await 关键字只能在 async 函数中运用。
    async function fetchData() {
        return new Promise(resolve => {
            setTimeout(() => {
                const data = 'async/await';
                resolve(data);
            }, 1000);
        });
    }
    async function printData() {
        const data = await fetchData();
        console.log(data); // 输出 async/await
    }
    printData();

总的来说,Promise 和 async/await 相关于 callback 函数来说愈加强壮和易用,能够有用防止回调阴间的问题。而 async/await 比较 Promise 的链式调用,则能够更好地表达异步操作的联系,让代码更易懂。

Reflect

JS 反射(reflection)是一种能够在运转时查看、修正方针、类和函数等程序结构的能力,经过反射,咱们能够读取修正方针特点、调用方针办法、界说新特点、修正原型等。

JS 经过 Reflect 方针供给了一组操作方针API,能够拜访、查看和修正方针上的特点和办法Reflect API 办法与对应方针的同名办法具有相同的功用,例如 Reflect.get() 办法对应方针的 obj.get() 办法,Reflect.set() 办法对应方针 obj.set() 办法等。

下面是一些反射 API 的常用示例:

// 获取方针的特点称号列表
const obj = {a: 1, b: 2, c: 3};
console.log(Reflect.ownKeys(obj)); // 输出 ["a", "b", "c"]
// 验证特点存在
const obj2 = {a: 1};
console.log(Reflect.has(obj2, 'a')); // 输出 true
// 获取方针的原型
const obj3 = {a: 1};
console.log(Reflect.getPrototypeOf(obj3)); // 输出 {}
// 修正方针的原型
const obj4 = {a: 1};
const proto = {b: 2};
Reflect.setPrototypeOf(obj4, proto);
console.log(obj4.__proto__); // 输出 {b: 2}
// 代替call和apply办法
function myFunction(a, b, c) {
  console.log('My function is called with:', a, b, c);
}
Reflect.apply(myFunction, null, [1, 2, 3]); // 输出 My function is called with: 1, 2, 3
// 运用set函数和get函数来监督方针特点的读取和设置
const obj5 = {a: 1};
const handler = {
  get(target, key, receiver) {
    console.log(`Getting property ${key}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`Setting property ${key} to ${value}`);
    return Reflect.set(target, key, value, receiver);
  }
};
const proxy = new Proxy(obj5, handler);
proxy.a; // 输出:Getting property a
proxy.a = 2; // 输出:Setting property a to 2

Reflect API 完结了方针的反射、署理等功用,它为咱们供给了一些强壮而快捷的工具,使得咱们能够在运转时动态地查看、查看和修正方针的特点和行为。Reflect 反射在 JavaScript 中的运用十分广泛,能够用于类似呼应式编程、面向方针编程等各种场景。

首要运用场景

Nest.js 中,注解和 reflect 是严密相关的,它们常常一同运用来处理一些实际问题。下面是一些常见的运用场景:

  1. 界说控制器和路由
    Nest.js 中,控制器和路由的界说通常运用注解进行描绘,而 reflect API 能够用于存储和读取控制器和路由的元数据。例如,@Controller() 注解表明一个控制器,@Get()、@Post() 等注解表明控制器的路由和恳求办法。而 Reflect.getMetadata()Reflect.defineMetadata() 则用于读取和存储它们的各种装备和元数据,例如路由地址、是否需求鉴权、权限等级等。
  2. 完结拦截器
    Nest.js 中的拦截器通常运用注解和 reflect 进行完结。拦截器常用于完结恳求的预处理和后处理,例如在恳求产生之前,咱们能够运用拦截器来查看用户是否现已登录,或许经过拦截器来记载恳求生命周期中的各种日志等。而拦截器实际上便是一个装饰器,能够运用 @Injectable() 注解声明,而且能够完结某些拦截器特有的接口和办法。
  3. 运用大局过滤器
    Nest.js 中,大局过滤器运用注解和 reflect 能够方便地完结恳求和呼应的统一过滤,例如过滤掉灵敏信息、安全信息、非法恳求等。大局过滤器运用 @UseFilters() 注解进行界说,能够经过 reflect API 来读写过滤器的元信息,例如过滤器的地址、恳求头、状况码等。

总的来说,在 Nest.js 中,注解和 reflect 是严密相关的,常常一同运用来完结控制器、路由、拦截器、过滤器等一些行为或功用。它们强调了代码的可保护性和可读性,能够使代码愈加简略、灵活。

代码示例

1. 运用注解和 reflect 完结大局过滤器

import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
@Catch()
export class MyGlobalFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    console.log('MyGlobalFilter is running!');
    super.catch(exception, host);
  }
}
// 在 main.ts 中注册大局过滤器
app.useGlobalFilters(new MyGlobalFilter());

这个示例演示了怎么运用注解和 reflect 完结大局过滤器。在这个示例中,咱们界说了一个名为 MyGlobalFilter 的类,运用了 @Catch() 注解来符号这个类为一个过滤器类。这个类承继了 BaseExceptionFilter 类,完结了 catch() 办法。在 catch() 办法内部,咱们调用了 super.catch() 办法来处理反常,并打印了一条日志。

当运用中产生任何反常时,都会主动触发 MyGlobalFilter 这个过滤器,让它来处理反常。这个过滤器会打印一条日志,然后调用基类 BaseExceptionFilter 中界说的处理办法进行反常处理。

2. 运用注解和 reflect 完结恳求日志记载拦截器

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler 
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log(`Request made to ${context.getClass().name}.${context.getHandler().name}`);
    return next.handle().pipe(
      tap(() => console.log(`Request response from ${context.getClass().name}`))
    );
  }
}
// 在控制器办法中运用 @UseInterceptors() 注解注入拦截器
@Controller()
@UseInterceptors(RequestLoggingInterceptor)
export class AppController {
  @Get()
  getData() {
    return 'Hello World!';
  }
}

这个示例演示了怎么运用注解和 reflect 完结一个恳求日志记载拦截器。在这个示例中,咱们界说了一个名为 RequestLoggingInterceptor 的拦截器类,拦截器完结了 NestInterceptor 接口,它的首要效果是在每次恳求产生时记载恳求日志

intercept() 办法内部,咱们经过 ExecutionContextCallHandler 参数获取当时恳求的类名和办法名,并打印了一条恳求日志。一同,咱们将 CallHandler 方针传递给 pipe 中的 tap() 办法,然后在恳求呼应进程中打印呼应信息。

在控制器中,咱们运用了 @UseInterceptors() 注解将拦截器注入到 getData() 办法中。当控制器的 getData() 办法被调用时,这个拦截器就会主动被触发,然后记载恳求日志。

以上这两个示例都证明晰在 Nest.js 中,能够运用注解和 reflect 完结一些实用的功用,例如大局过滤器、拦截器等。在实际编程中,咱们能够依据需求自行完结更多的注解和 reflect 功用,来增强程序的灵活性和可保护性。

BigInt

JS Number 类型的限制——2^53 – 1。

BigInt 是在 ES10 中引进的一种新类型,它能够用来表明任意大的整数,不受 JS中 Number 类型的 2^53 – 1 限制。

在 JS中,Number 类型运用 IEEE 754 标准表明,且占据 64 位内存。其间 1 位是符号位,11 位是指数位,剩余 52 位是有用数字位。因而,在 Number 类型中最大的安全整数为 2^53 – 1,超越这个值就会丢掉精度。而 BigInt 类型则能够表明任意大的精度整数,其内存运用量要大于 Number 类型,可是比字符串表明更节省空间。

BigInt 类型的运用办法和 Number 类型类似,首要差异在于在数字后加 “n” 标志表明 Bigint 类型。BigInt 类型能够进行加、减、乘、除等根本数学运算,而且能够运用 BigInt() 结构办法将字符串或 Number 类型数据转化为 BigInt 类型。

以下是一个简略的 BigInt 示例:

console.log(Number.MAX_SAFE_INTEGER); // 输出 9007199254740991
console.log(10000000100000001); // 输出 10000000100000000,丢掉了精度
const bigNum1 = BigInt(Number.MAX_SAFE_INTEGER);
console.log(bigNum1 + 1n); // 输出 9007199254740992n,有用防止了精度丢掉
const bigNum2 = BigInt('10000000100000001');
console.log(bigNum2); // 输出 10000000100000001n

需求留意的是,BigInt 类型和 Number 类型不能进行混合运算。假如企图在 BigInt 和 Number 之间进行算术运算,会抛出类型过错。