简答题
- 谈谈你是如何理解JS异步编程的,EventLoop、消息队列都是做什么的,什么是宏任务,什么是微任务?
javascript 为了实现浏览器页面的交互,不得不使用单线程的方式渲染页面,以保证 dom 可以被正确的渲染。然而单线程就以为着浏览器有多个任务的时候需要排队依次执行,这样就会造成阻塞。event loop 主线程从消息队列中读取事件,这个过程是一直重复循环的,所以整个机制被称作event loop。消息队列是暂时存放异步任务的地方,我们的异步代码会存放到消息队列中,等到同步代码执行完毕以后,event loop会从消息队列中依次取出异步任务放到调用栈中再次执行。宏任务可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。微任务可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个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 genPromise (text) {
return new Promise(resolve => {
setTimeout(() => {
resolve(text)
}, 10)
})
}
(async () => {
const a = await genPromise('hello')
const b = await genPromise('lagou')
const c = await genPromise('I ❤️ U')
console.log(a + b + c)
})()
二、基于以下代码完成下面的四个练习
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 },
]
- 练习1:使用函数组合 fp.flowRight() 重新实现下面这个函数
let isLastInStock = function (cars) {
// 获取最后一条数据
let last_car = fp.last(cars)
// 获取最后一条数据的 in_stock 属性值
return fp.prop('in_stock', last_car)
}
const isLastInStock = fp.flowRight(fp.prop("in_stock"), fp.last);
console.log(isLastInStock(cars));
- 练习2:使用 fp.flowRight()、fp.prop()和fp.first()获取第一个 car 的 name
const lastCarName = fp.flowRight(fp.prop("name"), fp.first);
console.log(lastCarName(cars));
- 练习3:使用帮助函数 _average 重构 averageDollarValue, 使用函数组合的方式实现
let _average = function (xs) {
return fp.reduce(fp.add, 0, xs) / xs.length
} // <- 无须改动
let averageDollarValue = function (cars) {
let dollar_values = fp.map(function (car) {
return car.dollar_value
}, cars)
return _average(dollar_values)
}
const dollar_value = fp.map(function(car) {
return car.dollar_value;
});
const averageDollarValue = fp.flowRight(_average, dollar_value);
- 练习4:使用flowRight写一个sanitizeNames()函数,返回一个下划线连接的小写字符串,把数组中的 name 转换为这种形式:例如:sanitizeNames([“Hello World”]) => [‘hello_world’]
let _underscore = fp.replace(/\W+/g, '_') // <-- 无须改动,并在 sanitizeNames 中使用它
const sanitizeNames = fp.map(fp.flowRight(_underscore, fp.toLower));
三、基于下面提供的代码,完成后续的四个练习
// 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 }
- 练习1:使用fp.add(x,y) 和 fp.map(f, x)创建一个能让 functor 里的值增加的函数ex1
// app.js
const fp = require('lodash/fp')
const {Maybe,Container} = require('./support')
let maybe = Maybe.of([5,6,1])
let ex1 = ()=>{
// 你需要实现的函数。。。
}
const addSome = fp.map(fp.add(1));
maybe.map(addSome).map(console.log);
- 练习2:实现一个函数 ex2, 能够使用 fp.first 获取列表中的第一个元素
// app.js
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
let ex2 = () => {
// 你需要实现的函数。。。
}
xs.map(fp.first).map(console.log);
- 练习3:实现一个函数ex3,使用safeProp和fp.first 找到user的名字和首字母
// app.js
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')
let safeProp = fp.curry(function (x, o) {
return Maybe.of(o[x])
})
let user = { id: 2, name: 'Albert' }
let ex3 = () => {
// 你需要实现的函数。。。
}
safeProp("name", user).map(fp.first).map(console.log)
- 练习4: 使用Maybe重写 ex4 ,不要有 if 语句
// app.js
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')
let ex4 = function (n) {
if (n) {
return parseInt(n)
}
}
const { Maybe } = require("./support");
let ex4 = function(n) {
Maybe.of(n).map(parseInt).map(console.log)
};
ex4(null)
四、手写实现 MyPromise 源码
要求:尽可能还原Promise中的每一个API,并通过注释的方式描述思路和原理
const PENDING = 'pending' // pending
const FULFILLED = 'fulfilled' // fulfilled
const REJECTED = 'rejected' // rejected
export default class MyPromise {
constructor (executor) {
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
// promsie 状态
status = PENDING
// 成功之后的值
value = undefined
// 失败后的原因
reason = undefined
// 成功回调
successCallback = []
// 失败回调
failCallback = []
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
// 判断失败回调是否存在 如果存在 调用
// this.failCallback && this.failCallback(this.reason);
while (this.failCallback.length) this.failCallback.shift()()
}
then (successCallback, failCallback) {
// 参数可选
successCallback = successCallback ? successCallback : value => value
// 参数可选
failCallback = failCallback ? failCallback : reason => { throw reason }
let promsie2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value)
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason)
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value)
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason)
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promsie2
}
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) {
let 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++) {
let current = array[i]
if (current instanceof MyPromise) {
// promise 对象
current.then(value => addData(i, value), reason => reject(reason))
} else {
// 普通值
addData(i, array[i])
}
}
})
}
static resolve (value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
}
function resolvePromise (promsie2, x, resolve, reject) {
if (promsie2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(value), reason => reject(reason));
x.then(resolve, reject)
} else {
// 普通值
resolve(x)
}
}