170+道钉钉前端扫盲知识点(1),2024年最新腾讯Web前端开发面试凉凉

function add(a: number, b: number): number {

return a + b;

}

function multiply(a: number, b: number): number {

return a * b;

}

const a: number = 1;

const b: number = 2;

add(multiply(add(a, b), b), multiply(a, b));

复制代码

以上程序计算的结果是 8,完全符合预期。我们知道了 add 和 multiply 两个函数的实际对应关系,通过将对应关系进行有效的组合和传递,达到了最终的计算结果。除此之外,这两个函数还可以根据数学定律得出更优雅的组合方式:

add(multiply(add(a, b), b), multiply(a, b));

// 根据数学定律分配律:a * b  +  a * c = a * (b + c),得出:

// (a + b) * b + a * b = (2a + b) * b

// 简化上述函数的组合方式

multiply(add(add(a, a), b), b);

复制代码

我们完全不需要追踪类似于 OOP 编程范式中可能存在的内部状态数据,事实上对于数学定律中的结合律、交换律、同一律以及分配律,上述的函数式编程代码足可以胜任。

原则

通过上述简单的例子可以发现,要实现高可复用的函数**(对应关系)**,一定要遵循某些特定的原则,否则在使用的时候可能无法进行高效的传递和组合,例如

  • 高内聚低耦合

  • 最小意外原则

  • 单一职责原则

如果你之前经常进行无原则性的代码设计,那么在设计过程中可能会出现各种出乎意料的问题(这是为什么新手老是出现一些稀奇古怪问题的主要原因)。函数式编程可以有效的通过一些原则性的约束使你设计出更加健壮和优雅的代码,并且在不断的实践过程中进行经验式叠加,从而提高开发效率。

特点

虽然我们在使用函数的过程中更多的不再关注函数如何实现(对应关系),但是真正在使用和设计函数的时候需要注意以下一些特点:

  • 声明式(Declarative Programming)

  • 一等公民(First Class Function)

  • 纯函数(Pure Function)

  • 无状态和数据不可变(Statelessness and Immutable Data)

声明式

我们以前设计的代码通常是命令式编程方式,这种编程方式往往注重具体的实现的过程(对应关系),而函数式编程则采用声明式的编程方式,往往注重如何去组合已有的**对应关系。**简单举个例子:

// 命令式

const array = [0.8, 1.7, 2.5, 3.4];

const filterArray = [];

for (let i = 0; i < array.length; i++) {

const integer = Math.floor(array[i]);

if (integer < 2) {

continue;

}

filterArray.push(integer);

}

// 声明式

// map 和 filter 不会修改原有数组,而是产生新的数组返回

[0.8, 1.7, 2.5, 3.4].map((item) => Math.floor(item)).filter((item) => item > 1);

复制代码

命令式代码一步一步的告诉计算机需要执行哪些语句,需要关心变量的实例化情况、循环的具体过程以及跟踪变量状态的变化过程。声明式代码更多的不再关心代码的具体执行过程,而是采用表达式的组合变换去处理问题,不再强调怎么做,而是指明**做什么。**声明式编程方式可以将我们设计代码的关注点彻底从过程式解放出来,从而提高开发效率。

一等公民

在 JavaScript 中,函数的使用非常灵活,例如可以对函数进行以下操作:

interface IHello {

(name: string): string;

key?: string;

arr?: number[];

fn?(name: string): string;

}

// 函数声明提升

console.log(hello instanceof Object); // true

// 函数声明提升

// hello 和其他引用类型的对象一样,都有属性和方法

hello.key = ‘key’;

hello.arr = [1, 2];

hello.fn = function (name: string) {

return hello.fn, ${name};

};

// 函数声明提升

// 注意函数表达式不能在声明前执行,例如不能在这里使用 helloCopy(‘world’)

hello(‘world’);

// 函数

// 创建新的函数对象,将函数的引用指向变量 hello

// hello 仅仅是变量的名称

function hello(name: string): string {

return hello, ${name};

}

console.log(hello.key); // key

console.log(hello.arr); // [1,2]

console.log(hello.name); // hello

// 函数表达式

const helloCopy: IHello = hello;

helloCopy(‘world’);

function transferHello(name: string, hello: Hello) {

return hello(‘world’);

}

// 把函数对象当作实参传递

transferHello(‘world’, helloCopy);

// 把匿名函数当作实参传递

transferHello(‘world’, function (name: string) {

return hello, ${name};

});

复制代码

通过以上示例可以看出,函数继承至对象并拥有对象的特性。在 JavaScript 中可以对函数进行参数传递、变量赋值或数组操作等等,因此把函数称为一等公民。函数式编程的核心就是对函数进行组合或传递,JavaScript 中函数这种灵活的特性是满足函数式编程的重要条件。

纯函数

纯函数是是指在相同的参数调用下,函数的返回值唯一不变。这跟数学中函数的映射关系类似,同样的 x 不可能映射多个不同的 y。使用函数式编程会使得函数的调用非常稳定,从而降低 Bug 产生的机率。当然要实现纯函数的这种特性,需要函数不能包含以下一些副作用:

  • 操作 Http 请求

  • 可变数据(包括在函数内部改变输入参数)

  • DOM 操作

  • 打印日志

  • 访问系统状态

  • 操作文件系统

  • 操作数据库

从以上常见的一些副作用可以看出,纯函数的实现需要遵循最小意外原则,为了确保函数的稳定唯一的输入和输出,尽量应该避免与函数外部的环境进行任何交互行为,从而防止外部环境对函数内部产生无法预料的影响。纯函数的实现应该自给自足,举几个例子:

// 如果使用 const 声明 min 变量(基本数据类型),则可以保证以下函数的纯粹性

let min: number = 1;

// 非纯函数

// 依赖外部环境变量 min,一旦 min 发生变化则输入和返回不唯一

function isEqual(num: number): boolean {

return num === min;

}

// 纯函数

function isEqual(num: number): boolean {

return num === 1;

}

// 非纯函数

function request<T, S>(url: string, params: T): Promise {

// 会产生请求成功和请求失败两种结果,返回的结果可能不唯一

return $.getJson(url, params);

}

// 纯函数

function request<T, S>(url: string, params: T) : () => Promise {

return function() {

return $.getJson(url, params);

}

}

复制代码

纯函数的特性使得函数式编程具备以下特性:

  • 可缓存性(Cacheable)

  • 可移植性(Portable)

  • 可测试性(Testable)

可缓存性和可测试性基于纯函数输入输出唯一不变的特性,可移植性则主要基于纯函数不依赖外部环境的特性。这里举一个可缓存的例子:

interface ICache {

}

interface ISquare {

(x: T): T;

}

// 简单的缓存函数(忽略通用性和健壮性)

function memoize(fn: ISquare): ISquare {

const cache: ICache = {};

return function (x: T) {

const arg: string = JSON.stringify(x);

cache[arg] = cache[arg] || fn.call(fn, x);

return cache[arg];

};

}

// 纯函数

function square(x: number): number {

return x * x;

}

const memoSquare = memoize(square);

memoSquare(4);

// 不会再次调用纯函数 square,而是直接从缓存中获取值

// 由于输入和输出的唯一性,获取缓存结果可靠稳定

// 提升代码的运行效率

memoSquare(4);

复制代码

无状态和数据不可变

在函数式编程的简单示例中已经可以清晰的感受到函数式编程绝对不能依赖内部状态,而在纯函数中则说明了函数式编程不能依赖外部的环境或状态,因为一旦依赖的状态变化,不能保证函数根据对应关系所计算的返回值因为状态的变化仍然保持不变。

这里单独讲解一下数据不可变,在 JavaScript 中有很多数组操作的方法,举个例子:

const arr = [1, 2, 3];

console.log(arr.slice(0, 2)); // [1, 2]

console.log(arr); // [1, 2, 3]

console.log(arr.slice(0, 2)); // [1, 2]

console.log(arr); // [1, 2, 3]

console.log(arr.splice(0, 1)); // [1]

console.log(arr); // [2, 3]

console.log(arr.splice(0, 1)); // [2]

console.log(arr); // [3]

复制代码

这里的 slice 方法多次调用都不会改变原有数组,且会产生相同的输出。而 splice 每次调用都在修改原数组,且产生的输出也不相同。在函数式编程中,这种会改变原有数据的函数已经不再是纯函数,应该尽量避免使用。

阅读链接:如果想要了解更深入的函数式编程知识点,可以额外阅读函数式编程指北[8]。

21、响应式编程的使用场景有哪些?

响应式编程是一种基于观察者(发布 / 订阅)模式并且面向异步(Asynchronous)数据流(Data Stream)和变化传播的声明式编程范式。响应式编程主要适用的场景包含:

  • 用户和系统发起的连续事件处理,例如鼠标的点击、键盘的按键或者通信设备发起的信号等

  • 非可靠的网络或者通信处理(例如 HTTP 网络的请求重试)

  • 连续的异步 IO 处理

  • 复杂的继发事务处理(例如一次事件涉及到多个继发的网络请求)

  • 高并发的消息处理(例如 IM 聊天)

语法

22、如何实现一个上中下三行布局,顶部和底部最小高度是 100px,中间自适应?
23、如何判断一个元素 CSS 样式溢出,从而可以选择性的加 title 或者 Tooltip?
24、如何让 CSS 元素左侧自动溢出(… 溢出在左侧)?

The direction CSS property sets the direction of text, table columns, and horizontal overflow. Use rtl for languages written from right to left (like Hebrew or Arabic), and ltr for those written from left to right (like English and most other languages).

具体查看:developer.mozilla.org/en-US/docs/…[9]

25、什么是沙箱?浏览器的沙箱有什么作用?
26、如何处理浏览器中表单项的密码自动填充问题?
27、Hash 和 History 路由的区别和优缺点?
28、JavaScript 中对象的属性描述符有哪些?分别有什么作用?
29、JavaScript 中 console 有哪些 api ?

The console object provides access to the browser’s debugging console (e.g. the Web console[10] in Firefox). The specifics of how it works varies from browser to browser, but there is a de facto set of features that are typically provided.

这里列出一些我常用的 API:

  • console.log

  • console.error

  • console.time

  • console.timeEnd

  • console.group

具体查看:developer.mozilla.org/en-US/docs/…[11]

30、 简单对比一下 Callback、Promise、Generator、Async 几个异步 API 的优劣?

在 JavaScript 中利用事件循环机制[12](Event Loop)可以在单线程中实现非阻塞式、异步的操作。例如

  • Node.js 中的 Callback、EventEmitter[13]、Stream[14]

  • ES6 中的 Promise[15]、Generator[16]

  • ES2017 中的 Async[17]

  • 三方库 RxJS、Q[18] 、Co、[19]Bluebird[20]

我们重点来看一下常用的几种编程方式(Callback、Promise、Generator、Async)在语法糖上带来的优劣对比。

Callback

Callback(回调函数)是在 Web 前端开发中经常会使用的编程方式。这里举一个常用的定时器示例:

export interface IObj {

value: string;

deferExec(): void;

deferExecAnonymous(): void;

console(): void;

}

export const obj: IObj = {

value: ‘hello’,

deferExecBind() {

// 使用箭头函数可达到一样的效果

setTimeout(this.console.bind(this), 1000);

},

deferExec() {

setTimeout(this.console, 1000);

},

console() {

console.log(this.value);

},

};

obj.deferExecBind(); // hello

obj.deferExec(); // undefined

复制代码

回调函数经常会因为调用环境的变化而导致 this 的指向性变化。除此之外,使用回调函数来处理多个继发的异步任务时容易导致回调地狱(Callback Hell):

fs.readFile(fileA, ‘utf-8’, function (err, data) {

fs.readFile(fileB, ‘utf-8’, function (err, data) {

fs.readFile(fileC, ‘utf-8’, function (err, data) {

fs.readFile(fileD, ‘utf-8’, function (err, data) {

// 假设在业务中 fileD 的读写依次依赖 fileA、fileB 和 fileC

// 或者经常也可以在业务中看到多个 HTTP 请求的操作有前后依赖(继发 HTTP 请求)

// 这些异步任务之间纵向嵌套强耦合,无法进行横向复用

// 如果某个异步发生变化,那它的所有上层或下层回调可能都需要跟着变化(比如 fileA 和 fileB 的依赖关系倒置)

// 因此称这种现象为 回调地狱

// …

});

});

});

});

复制代码

回调函数不能通过 return 返回数据,比如我们希望调用带有回调参数的函数并返回异步执行的结果时,只能通过再次回调的方式进行参数传递:

// 希望延迟 3s 后执行并拿到结果

function getAsyncResult(result: number) {

setTimeout(() => {

return result * 3;

}, 1000);

}

// 尽管这是常规的编程思维方式

const result = getAsyncResult(3000);

// 但是打印 undefined

console.log('result: ', result);

function getAsyncResultWithCb(result: number, cb: (result: number) => void) {

setTimeout(() => {

cb(result * 3);

}, 1000);

}

// 通过回调的形式获取结果

getAsyncResultWithCb(3000, (result) => {

console.log('result: ', result); // 9000

});

复制代码

对于 JavaScript 中标准的异步 API 可能无法通过在外部进行 try...catch... 的方式进行错误捕获:

try {

setTimeout(() => {

// 下述是异常代码

// 你可以在回调函数的内部进行 try…catch…

console.log(a.b.c)

}, 1000)

} catch(err) {

// 这里不会执行

// 进程会被终止

console.error(err)

}

复制代码

上述示例讲述的都是 JavaScript 中标准的异步 API ,如果使用一些三方的异步 API 并且提供了回调能力时,这些 API 可能是非受信的,在真正使用的时候会因为执行反转(回调函数的执行权在三方库中)导致以下一些问题:

  • 使用者的回调函数设计没有进行错误捕获,而恰恰三方库进行了错误捕获却没有抛出错误处理信息,此时使用者很难感知到自己设计的回调函数是否有错误

  • 使用者难以感知到三方库的回调时机和回调次数,这个回调函数执行的权利控制在三方库手中

  • 使用者无法更改三方库提供的回调参数,回调参数可能无法满足使用者的诉求

举个简单的例子:

interface ILib {

params: T;

emit(params: T): void;

on(callback: (params: T) => void): void;

}

// 假设以下是一个三方库,并发布成了npm 包

export const lib: ILib = {

params: ‘’,

emit(params) {

this.params = params;

},

on(callback) {

try {

// callback 回调执行权在 lib 上

// lib 库可以决定回调执行多次

callback(this.params);

callback(this.params);

callback(this.params);

// lib 库甚至可以决定回调延迟执行

// 异步执行回调函数

setTimeout(() => {

callback(this.params);

}, 3000);

} catch (err) {

// 假设 lib 库的捕获没有抛出任何异常信息

}

},

};

// 开发者引入 lib 库开始使用

lib.emit(‘hello’);

lib.on((value) => {

// 使用者希望 on 里的回调只执行一次

// 这里的回调函数的执行时机是由三方库 lib 决定

// 实际上打印四次,并且其中一次是异步执行

console.log(value);

});

lib.on((value) => {

// 下述是异常代码

// 但是执行下述代码不会抛出任何异常信息

// 开发者无法感知自己的代码设计错误

console.log(value.a.b.c)

});

复制代码

Promise

Callback 的异步操作形式除了会造成回调地狱,还会造成难以测试的问题。ES6 中的 Promise (基于 Promise A +[21] 规范的异步编程解决方案)利用有限状态机[22]的原理来解决异步的处理问题,Promise 对象提供了统一的异步编程 API,它的特点如下:

  • Promise 对象的执行状态不受外界影响。Promise 对象的异步操作有三种状态: pending(进行中)、 fulfilled(已成功)和 rejected(已失败) ,只有 Promise 对象本身的异步操作结果可以决定当前的执行状态,任何其他的操作无法改变状态的结果

  • Promise 对象的执行状态不可变。Promise 的状态只有两种变化可能:从 pending(进行中)变为 fulfilled(已成功)或从 pending(进行中)变为 rejected(已失败)

温馨提示:有限状态机提供了一种优雅的解决方式,异步的处理本身可以通过异步状态的变化来触发相应的操作,这会比回调函数在逻辑上的处理更加合理,也可以降低代码的复杂度。

Promise 对象的执行状态不可变示例如下:

const promise = new Promise((resolve, reject) => {

// 状态变更为 fulfilled 并返回结果 1 后不会再变更状态

resolve(1);

// 不会变更状态

reject(4);

});

promise

.then((result) => {

// 在 ES 6 中 Promise 的 then 回调执行是异步执行(微任务)

// 在当前 then 被调用的那轮事件循环(Event Loop)的末尾执行

console.log('result: ', result);

})

.catch((error) => {

// 不执行

console.error('error: ', error);

});

复制代码

假设要实现两个继发的 HTTP 请求,第一个请求接口返回的数据是第二个请求接口的参数,使用回调函数的实现方式如下所示(这里使用 setTimeout 来指代异步请求):

// 回调地狱

const doubble = (result: number, callback: (finallResult: number) => void) => {

// Mock 第一个异步请求

setTimeout(() => {

// Mock 第二个异步请求(假设第二个请求的参数依赖第一个请求的返回结果)

setTimeout(() => {

callback(result * 2);

}, 2000);

}, 1000);

};

doubble(1000, (result) => {

console.log('result: ', result);

});

复制代码

温馨提示:继发请求的依赖关系非常常见,例如人员基本信息管理系统的开发中,经常需要先展示组织树结构,并默认加载第一个组织下的人员列表信息。

如果采用 Promise 的处理方式则可以规避上述常见的回调地狱问题:

const firstPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

// Mock 异步请求

// 将 resolve 改成 reject 会被 catch 捕获

setTimeout(() => resolve(result), 1000);

});

};

const nextPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

// Mock 异步请求

// 将 resolve 改成 reject 会被 catch 捕获

setTimeout(() => resolve(result * 2), 1000);

});

};

firstPromise(1000)

.then((result) => {

return nextPromise(result);

})

.then((result) => {

// 2s 后打印 2000

console.log('result: ', result);

})

// 任何一个 Promise 到达 rejected 状态都能被 catch 捕获

.catch((err) => {

console.error('err: ', err);

});

复制代码

Promise 的错误回调可以同时捕获 firstPromise 和 nextPromise 两个函数的 rejected 状态。接下来考虑以下调用场景:

const firstPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

// Mock 异步请求

setTimeout(() => resolve(result), 1000);

});

};

const nextPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

// Mock 异步请求

setTimeout(() => resolve(result * 2), 1000);

});

};

firstPromise(1000)

.then((result) => {

nextPromise(result).then((result) => {

// 后打印

console.log('nextPromise result: ', result);

});

})

.then((result) => {

// 先打印

// 由于上一个 then 没有返回值,这里打印 undefined

console.log('firstPromise result: ', result);

})

.catch((err) => {

console.error('err: ', err);

});

复制代码

首先 Promise 可以注册多个 then(放在一个执行队列里),并且这些 then 会根据上一次返回值的结果依次执行。除此之外,各个 Promise 的 then 执行互不干扰。我们将示例进行简单的变换:

const firstPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

// Mock 异步请求

setTimeout(() => resolve(result), 1000);

});

};

const nextPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

// Mock 异步请求

setTimeout(() => resolve(result * 2), 1000);

});

};

firstPromise(1000)

.then((result) => {

// 返回了 nextPromise 的 then 执行后的结果

return nextPromise(result).then((result) => {

return result;

});

})

// 接着 nextPromise 的 then 执行的返回结果继续执行

.then((result) => {

// 2s 后打印 2000

console.log('nextPromise result: ', result);

})

.catch((err) => {

console.error('err: ', err);

});

复制代码

上述例子中的执行结果是因为 then 的执行会返回一个新的 Promise 对象,并且如果 then 执行后返回的仍然是 Promise 对象,那么下一个 then 的链式调用会等待该 Promise 对象的状态发生变化后才会调用(能得到这个 Promise 处理的结果)。接下来重点看下 Promise 的错误处理:

const promise = new Promise((resolve, reject) => {

// 下述是异常代码

console.log(a.b.c);

resolve(‘hello’);

});

promise

.then((result) => {

console.log('result: ', result);

})

// 去掉 catch 仍然会抛出错误,但不会退出进程终止脚本执行

.catch((err) => {

// 执行

// ReferenceError: a is not defined

console.error(err);

});

setTimeout(() => {

// 继续执行

console.log(‘hello world!’);

}, 2000);

复制代码

从上述示例可以看出 Promise 的错误不会影响其他代码的执行,只会影响 Promise 内部的代码本身,因为Promise 会在内部对错误进行异常捕获,从而保证整体代码执行的稳定性。Promise 还提供了其他的一些 API 方便多任务的执行,包括

  • Promise.all:适合多个异步任务并发执行但不允许其中任何一个任务失败

  • Promise.race :适合多个异步任务抢占式执行

  • Promise.allSettled :适合多个异步任务并发执行但允许某些任务失败

Promise 相对于 Callback 对于异步的处理更加优雅,并且能力也更加强大, 但是也存在一些自身的缺点:

  • 无法取消 Promise 的执行

  • 无法在 Promise 外部通过 try...catch... 的形式进行错误捕获(Promise 内部捕获了错误)

  • 状态单一,每次决断只能产生一种状态结果,需要不停的进行链式调用

温馨提示:手写 Promise 是面试官非常喜欢的一道笔试题,本质是希望面试者能够通过底层的设计正确了解 Promise 的使用方式,如果你对 Promise 的设计原理不熟悉,可以深入了解一下或者手动设计一个。

Generator

Promise 解决了 Callback 的回调地狱问题,但也造成了代码冗余,如果一些异步任务不支持 Promise 语法,就需要进行一层 Promise 封装。Generator 将 JavaScript 的异步编程带入了一个全新的阶段,它使得异步代码的设计和执行看起来和同步代码一致。Generator 使用的简单示例如下:

const firstPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

setTimeout(() => resolve(result * 2), 1000);

});

};

const nextPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

setTimeout(() => resolve(result * 3), 1000);

});

};

// 在 Generator 函数里执行的异步代码看起来和同步代码一致

function* gen(result: number): Generator<Promise, Promise, number> {

// 异步代码

const firstResult = yield firstPromise(result)

console.log('firstResult: ', firstResult) // 2

// 异步代码

const nextResult = yield nextPromise(firstResult)

console.log('nextResult: ', nextResult) // 6

return nextPromise(firstResult)

}

const g = gen(1)

// 手动执行 Generator 函数

g.next().value.then((res: number) => {

// 将 firstPromise 的返回值传递给第一个 yield 表单式对应的 firstResult

return g.next(res).value

}).then((res: number) => {

// 将 nextPromise 的返回值传递给第二个 yield 表单式对应的 nextResult

return g.next(res).value

})

复制代码

通过上述代码,可以看出 Generator 相对于 Promise 具有以下优势:

  • 丰富了状态类型,Generator 通过 next 可以产生不同的状态信息,也可以通过 return 结束函数的执行状态,相对于 Promise 的 resolve 不可变状态更加丰富

  • Generator 函数内部的异步代码执行看起来和同步代码执行一致,非常利于代码的维护

  • Generator 函数内部的执行逻辑和相应的状态变化逻辑解耦,降低了代码的复杂度

next 可以不停的改变状态使得 yield 得以继续执行的代码可以变得非常有规律,例如从上述的手动执行 Generator 函数可以看出,完全可以将其封装成一个自动执行的执行器,具体如下所示:

const firstPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

setTimeout(() => resolve(result * 2), 1000);

});

};

const nextPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

setTimeout(() => resolve(result * 3), 1000);

});

};

type Gen =  Generator<Promise, Promise, number>

function* gen(): Gen {

const firstResult = yield firstPromise(1)

console.log('firstResult: ', firstResult) // 2

const nextResult = yield nextPromise(firstResult)

console.log('nextResult: ', nextResult) // 6

return nextPromise(firstResult)

}

// Generator 自动执行器

function co(gen: () => Gen) {

const g = gen()

function next(data: number) {

const result = g.next(data)

if(result.done) {

return result.value

}

result.value.then(data => {

// 通过递归的方式处理相同的逻辑

next(data)

})

}

// 第一次调用 next 主要用于启动 Generator 函数

// 内部指针会从函数头部开始执行,直到遇到第一个 yield 表达式

// 因此第一次 next 传递的参数没有任何含义(这里传递只是为了防止 TS 报错)

next(0)

}

co(gen)

复制代码

温馨提示:TJ Holowaychuk[23] 设计了一个 Generator 自动执行器 Co[24],使用 Co 的前提是 yield 命令后必须是 Promise 对象或者 Thunk 函数。Co 还可以支持并发的异步处理,具体可查看官方的 API 文档[25]。

需要注意的是 Generator 函数的返回值是一个 Iterator 遍历器对象,具体如下所示:

const firstPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

setTimeout(() => resolve(result * 2), 1000);

});

};

const nextPromise = (result: number): Promise => {

return new Promise((resolve, reject) => {

setTimeout(() => resolve(result * 3), 1000);

});

};

type Gen = Generator<Promise>;

function* gen(): Gen {

yield firstPromise(1);

yield nextPromise(2);

}

// 注意使用 next 是继发执行,而这里是并发执行

Promise.all([…gen()]).then((res) => {

console.log('res: ', res);

});

for (const promise of gen()) {

promise.then((res) => {

console.log('res: ', res);

});

}

复制代码

Generator 函数的错误处理相对复杂一些,极端情况下需要对执行和 Generator 函数进行双重错误捕获,具体如下所示:

const firstPromise = (result: number): Promise => {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

文末

我一直觉得技术面试不是考试,考前背背题,发给你一张考卷,答完交卷等通知。

首先,技术面试是一个 认识自己 的过程,知道自己和外面世界的差距。

更重要的是,技术面试是一个双向了解的过程,要让对方发现你的闪光点,同时也要 试图去找到对方的闪光点,因为他以后可能就是你的同事或者领导,所以,面试官问你有什么问题的时候,不要说没有了,要去试图了解他的工作内容、了解这个团队的氛围。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

=> {

console.log('res: ', res);

});

}

复制代码

Generator 函数的错误处理相对复杂一些,极端情况下需要对执行和 Generator 函数进行双重错误捕获,具体如下所示:

const firstPromise = (result: number): Promise => {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-f9DPIhJp-1712672440790)]
[外链图片转存中…(img-IstWQ2o8-1712672440791)]
[外链图片转存中…(img-raKGgrJJ-1712672440791)]
[外链图片转存中…(img-QAGvVdQX-1712672440792)]
[外链图片转存中…(img-6LMsXhuD-1712672440792)]
[外链图片转存中…(img-FwB49xfl-1712672440792)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-uVRsI9S3-1712672440793)]

文末

我一直觉得技术面试不是考试,考前背背题,发给你一张考卷,答完交卷等通知。

首先,技术面试是一个 认识自己 的过程,知道自己和外面世界的差距。

更重要的是,技术面试是一个双向了解的过程,要让对方发现你的闪光点,同时也要 试图去找到对方的闪光点,因为他以后可能就是你的同事或者领导,所以,面试官问你有什么问题的时候,不要说没有了,要去试图了解他的工作内容、了解这个团队的氛围。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-61Ulvq9O-1712672440793)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值