简答题
一、谈谈你是如何理解js异步编程的,EventLoop、消息队列都是做什么的,什么是宏任务,什么是微任务?
- js异步编程理解:因为javascript语言的执行环境是单线程的,代码的执行顺序是从上至下的,如果有多个任务,必须要前面一个任务完成才能继续执行下一个任务,这样的话,如果中间有某一个任务比较耗时就会阻塞主进程。使用异步编程可以解决多个任务的阻塞问题,异步任务把一个任务分成两个阶段,先执行第一阶段,然后继续执行后面的任务,等做好了准备,再回过头执行第二段,排在异步任务后面的代码,不用等异步任务执行结束就会马上运行。
- EventLoop、消息队列:javascript主线程遇到异步任务触发之后,由事件触发线程将异步对应的回调函数加入到消息队列中,消息队列中的回调函数等待被执行。javascript的主线程不断的循环往复的从任务队列任务中读取任务,执行任务,这种运行机制称为事件循环,也就是EventLoop。
- 宏任务:macro-task,像主代码块、setTimeout、setInterval等都属于宏任务,可以理解是每次执行栈执行的代码就是一个宏任务,包括每次从事件队列中获取一个事件回调并放到执行栈中执行的任务。
- 微任务:micro-task,是在当前task执行结束后立即执行的任务。也就是说,在当前macro-task任务后,下一个macro-task之前执行。响应速度比setTimeout更快,因为无需等待渲染。可以理解为,在某一个macro-task执行完后,就会将在它执行期间产生的所有micro-task都执行完毕(在渲染前)。
代码题
一、将下面异步代码使用Promise的方式改进
setTimeout(function() {
var a = 'hello'
setTimeout(function() {
var b = 'lagou'
setTimeout(function () {
var c = 'i ❤️ U'
console.log(a + b + c)
}, 10);
}, 10);
}, 10)
改进后
function p1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 10);
})
}
function p2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lagou')
}, 10);
})
}
function p3 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('i ❤️ U')
}, 10);
})
}
Promise.all([p1(), p2(), p3()]).then(res => {
console.log(res.join(' '))
})
二、基于以下代码完成下面的四个练习
const fp = require('lodash/fp')
// 数据
// horsepower 马力,dollar_value 价格,in_stock 库存
const cars = [
{ name: 'Ferrari FF', horsepower: 660, dollar_value: 700000, in_stock: true},
{ name: 'Spyker C12 Zagato', horsepower: 650, dollar_value: 648000, in_stock: false},
{ name: 'Jaguar XKR-S', horsepower: 550, dollar_value: 132000, in_stock: false},
{ name: 'Audi R8', horsepower: 525, dollar_value: 114200, in_stock: false},
{ name: 'Aston Martin One-77', horsepower: 750, dollar_value: 1850000, in_stock: true},
{ name: 'Pagani Huayra', horsepower: 700, dollar_value: 1300000, in_stock: false}
]
答题
// 练习一:使用函数组合fp.flowRight()重新实现下面这个函数
const lastCarInStock = fp.flowRight(fp.prop('in_stock'), fp.last)
console.log(lastCarInStock(cars))
// 练习二:使用fp.flowRight()、fp.prop()和fp.first()获取第一个car的name
const getFirstCarName = fp.flowRight(fp.prop('name'), fp.first)
console.log(getFirstCarName(cars))
// 练习三:使用帮助函数_average重构averageDollarValue,使用函数组合的方式实现
let _average = function (xs) {
return fp.reduce(fp.add, 0, xs) / xs.length
}
let averageDollarValue = fp.flowRight(_average ,fp.map(car => car.dollar_value))
console.log('averageDollarValue: ', averageDollarValue(cars))
const trace = fp.curry((tag, v) => {
console.log(tag, v)
return v
})
// 练习四:使用flowRight写一个sanitizeNames()函数,返回一个下划线链接的小写字符串,把数组中的name转换成为这种形式:
// 例如:sanitizeNames(["Hello World"]) => ["hello_world"]
let _underscore = fp.replace(/\W+/g, '_')
function sanitizeNames (arr) {
return fp.flowRight(fp.map(_underscore), fp.map(fp.lowerCase))(arr)
}
console.log(sanitizeNames(["Hello World"]))
三、基于下面提供的代码,完成后续的四个练习
// support.js
class Container {
static of(value) {
return new Container(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Container.of(fn(this._value))
}
}
class MayBe {
static of (x) {
return new MayBe(x)
}
isNothing() {
return this._value === null || this._value === undefined
}
constructor (x) {
this._value = x
}
map (fn) {
return this.isNothing() ? this : MayBe.of(fn(this._value))
}
}
module.exports = { MayBe, Container }
答题
const fp = require('lodash/fp')
const { MayBe, Container } = require('./support')
let maybe = MayBe.of([5, 6, 1])
// 练习1:使用fp.add(x, y)和fp.map(f, x)创建一个能让functor里的值增加的函数ex1
let ex1 = () => {
return maybe.map(fp.map(fp.add(2)))
}
console.log(ex1())
// 练习2:实现一个函数ex2,能够使用fp.first获取列表的第一个元素
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
let ex2 = () => {
return xs.map(fp.first)
}
console.log(ex2())
// 练习3:实现一个函数ex3,使用safeProp和fp.first找到user的名字的首字母
let safeProp = fp.curry(function(x, o) {
return MayBe.of(o[x])
})
let user = { id: 2, name: 'Albert' }
let ex3 = () => {
return fp.first(safeProp('name')(user)._value)
}
console.log(ex3())
// 练习4:使用Maybe重写ex4,不要有if语句
let ex4 = function (n) {
return MayBe.of(n).map(parseInt)
}
console.log(ex4('12')._value)
四、手写实现MyPromise源码
要求:尽可能还原promise中的每一个API,并通过注释的方式描述思路和原理。
答题
/**
* 1.Promise就是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
* 2.Promise中有三个状态,分别为:fulfilled,rejected,pending
* pending -> fulfilled
* pending -> rejected
* 一旦状态确定就不可更改
* 3.resolve和reject函数是用来更改状态的
* resolve: fulfilled
* reject: rejected
* 4.then方法内部做的事情就是判断状态,如果状态是成功,调用成功回调,失败调用失败回调
* 5.then成功回调有一个参数,表示成功之后的值,失败有一个参数,表示失败后的原因
* 6.同一个promise对象下面的then方法是可以被调用多次的
* 7.then方法是可以被链式调用的,后面then方法的回调函数拿到值是上一个then方法的回调函数的返回值
*/
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
const PENDING = 'pending'
class MyPromise {
status = PENDING;
value = undefined;
reason = undefined;
successCallback = []
failCallback = []
constructor(executor) {
try {
// 立即执行执行器
executor(this.resolve, this.reject)
} catch (error) {
// 捕获执行器异常
this.reject(error)
}
}
resolve = (value) => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 从数组中取成功回调函数调用,每次调用完就移除该函数
while (this.successCallback.length) {
this.successCallback.shift()()
}
}
reject = (reason) => {
// 如果不是等待,阻止程序向下执行
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 从数组中取失败回调函数调用,每次调用完就移除该函数
while (this.failCallback.length) {
this.failCallback.shift()()
}
}
then = (successCallback, failCallback) => {
successCallback = successCallback ? successCallback : value => value
failCallback = failCallback ? failCallback : reason => { throw reason}
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
const x = successCallback(this.value)
// 判断x的值是普通值还是promise对象
// 如果是普通值,直接调用resolve
// 如果是promise对象,查看promise对象的返回结果
// 再根据promise对象返回的结果,决定调用resolve,还是调用reject
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
const x = failCallback(this.reason)
// 判断x的值是普通值还是promise对象
// 如果是普通值,直接调用resolve
// 如果是promise对象,查看promise对象的返回结果
// 再根据promise对象返回的结果,决定调用resolve,还是调用reject
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else {
// 等待状态
this.successCallback.push(() => {
setTimeout(() => {
try {
const x = successCallback(this.value)
// 判断x的值是普通值还是promise对象
// 如果是普通值,直接调用resolve
// 如果是promise对象,查看promise对象的返回结果
// 再根据promise对象返回的结果,决定调用resolve,还是调用reject
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
try {
const x = failCallback(this.reason)
// 判断x的值是普通值还是promise对象
// 如果是普通值,直接调用resolve
// 如果是promise对象,查看promise对象的返回结果
// 再根据promise对象返回的结果,决定调用resolve,还是调用reject
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
finally (callback) {
return this.then(value => {
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
return MyPromise.resolve(callback()).then(() =>
{throw reason})
})
}
catch (failCallback) {
return this.then(undefined, failCallback)
}
static all (array) {
const result = []
let index = 0
return new MyPromise((resolve, reject) => {
function addData(key, value) {
result[key] = value
index++
if (index === array.length) {
resolve(result)
}
}
for (let i = 0; i < array.length; i++) {
const element = array[i];
if (element instanceof MyPromise) {
element.then(value => addData(i, value), reason => reject(reason))
} else {
addData(i, element)
}
}
})
}
static resolve (value) {
if (value instanceof MyPromise) {
return value
}
return new MyPromise(resolve => resolve(value))
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
module.exports = MyPromise