一、简答题
1.谈谈你是如何理解JS异步编程的,EventLoop、消息队列都是做什么的,什么是宏任务,什么是微任务?
自己的不太全面的回答: js的执行环境是单线程的,大部分都是同步执行的。 如果全部都是同步执行的话整个页面会比较慢,所以可以把一些不影响页面显示的程序去异步执行,所以这就用到了异步编程。 EventLoop:js代码都是自上而下依次执行,有一个事件执行栈,依次把事件推入执行栈 消息队列:遇到异步函数类似于settimeout这种,就暂时挂起,放在消息队列中,等到上一轮的宏任务和微任务执行完成之后再执行这一轮 宏任务可以理解为服务器的主线任务,微任务是宏任务执行之余附带的任务。常见的微任务有promise.then,node中的nexttick等等。
人家给出的答案: JS异步编程:Javascript的执行环境是单线程的,一次只能执行一个任务,多任务需要排队等候,这种模式可能阻塞代码,导致执行效率低下。为了避免这个问题,出现异步编程。 一般是通过callback回调函数,promise,事件的发布/订阅等来组织代码,本质都是通过回调函数来实现异步代码的存放和执行 EventLoop、消息队列 EventLoop是一种循环机制,不断去轮询一些队列,从中找到需要执行的任务并按顺序执行的一个执行模型。 消息队列是用来存放宏任务的队列,比如定时器的时间到了,当前定时时间内传入的方法引用会存到该队列 一开始整个脚本作为一个宏任务执行。执行过程中同步代码直接执行,宏任务等待时间到达或者成功之后,将方法的回调放入宏任务队列中,微任务进入微任务队列 当前主线程的宏任务执行完出队,然后再check是否有未执行的微任务并执行完成。接着执行浏览器UI线程的渲染工作,检查web worker任务,有则执行 宏任务和微任务 宏任务可以理解为每次执行栈执行的,即每次从事件队列中获取一个时间回调并放到执行栈中执行 一个宏任务执行结束之后,页面会先渲染接着在执行下一个宏任务 宏任务:script整体代码,setTimeout,setInterval,I/O,UI交互事件,MessageChannel等 微任务可以理解是在当前任务执行结束后需要立即执行的任务。也就是说,在当前任务后,在渲染之前,执行清空微任务。所以它的响应速度相比宏任务会更快,因为无需等待 UI 渲染。 微任务包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)等
二、代码题
// 将下面的异步代码使用Promise的方式改进- setTimeout(function() { var a = 'hello' setTimeout(function() { var b = 'lagou' setTimeout(function() { var c = 'I ❤️ U' console.log(a + b + c) }, 5000); }, 10); }, 10);
——————————解答分界线—————————–
// 自我思考版,感觉绕来绕去的 不知道是不是自己想复杂了,因为setTimeout里面的异步在then方法里面是获取不到值的,所以用all方法可以依次执行
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
var a = 'hello'
resolve(a)
}, 10);
})
let p2 = p1.then((val)=>{
return new Promise((resolve, reject) => {
setTimeout(() => {
var b = 'lagou'
resolve(val + b)
}, 10);
})
})
let p3 = p2.then(val=>{
return new Promise((resolve, reject) => {
setTimeout(() => {
var c = 'I ❤️ U'
resolve(val + c)
}, 5000);
})
})
Promise.all([p1, p2, p3]).then((val)=>{
console.log('res', val[2])
})
// 我靠,果然是我想的太复杂,题目及其简单其实就是用promise代替settimeout而已,(我好菜啊)不知道我脑子里绕这么多弯做什么,重写
// ------------
let p = new Promise((resolve, reject)=> {
var a = 'hello'
resolve(a)
})
p.then(val=>{
var b = 'lagou'
return val+b
}).then(val=>{
var c = 'I ❤️ U'
console.log(val + c)
})
// ------------
async function getData(){
let p1 = await Promise.resolve('hello')
let p2 = await Promise.resolve('lagou')
let p3 = await Promise.resolve('I ❤️ U')
console.log(p1+p2+p3+'hahahah')
}
getData()
// ------------
// 基于以下代码完后才能下面的四个练习 const { first, last, prop } = require('lodash/fp') 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)
}
// -----answer-----
let isLastInStock = fp.flowRight(prop('in_stock'), last)
console.log(isLastInStock(cars))
// ----answer------
// 练习2:使用 fp.flowRight()、fp.prop() 和 fp.first() 获取第一个 car 的 name
let isLastInStock = fp.flowRight(prop('name'), first)
console.log(isLastInStock(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)
}
console.log(averageDollarValue(cars))
// ----------没懂,但应该是考函数组合的知识点,因为对fp函数的一些语法不太熟悉,就不纠结了
const total = cars=>fp.map(cars=>cars.dollar_value,cars)
const fn = fp.flowRight(_average,total);
console.log(fn(cars))
// 练习4:使用 flowRight 写一个 sanitizeNames() 函数,返回一个下划线连续的小写字符串,把数组中的 name 转换为这种形式,例如:sanitizeNames(["Hello World"]) => ["hello_world"]
let _underscore = fp.replace(/\W+/g, '_') // 无须改动,并在 sanitizeNames 中使用它
// function split1(val){
// return val.split(' ')
// }
let sanitizeNames = fp.flowRight(fp.map(_underscore),fp.map(fp.toLower),fp.map((car) => car.name))
console.log(sanitizeNames(cars))
基于下面提供的代码,完成后续的四个练习 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)) } } const fp = require('lodash/fp')
// 练习1:使用 fp.add(x, y) 和 fp.map(f,x) 创建一个能让 functor 里的值增加的函数 ex1
let maybe = Maybe.of([5,6,11])
// 思路: 函子对象的 map 方法可以运行一个函数对值进行处理,函数的参数为传入 of 方法的参数;接着对传入的整个数组进行遍历,并对每一项执行 fp.add 方法
let ex1 = maybe.map(e => fp.map(fp.add(2), e) )
console.log(ex1)
// 练习2:实现一个函数 ex2,能够使用 fp.first 获取列表的第一个元素
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
let ex2 = xs.map(e=>{ fp.first(e)} )
console.log(ex2._value)
// 练习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 = fp.flowRight(fp.map(i => fp.first(i)), safeProp('name'))
// let ex3 = fp.first(safeProp('name', user)._value)
let ex3 = safeProp('name', user).map(x => fp.first(x))
console.log(ex3)
// 练习4:使用 Maybe 重写 ex4,不要有 if 语句
let ex4 = function(n){
if(n){
return parseInt(n)
}
}
// let ex4 = Maybe.of(34.24).map(e=>parseInt(e))
let ex4 = (value)=>{ return Maybe.of(value).map(e=>parseInt(e)) }
console.log(ex4(78.87))
最后一题:手写实现Promise源码
尽可能还原 Promise 中的每一个 API,并通过注释的方式描述思路和原理。
写好多次了,就不再贴了,学习记录之三刚好是,没有实现race方法,但是race方法也蛮简单。主要需要考虑一些promise的特质。
原创文章,作者:一苇,如若转载,请注明出处:https://wsppx.cn/901/%e7%bd%91%e7%bb%9c%e5%bc%80%e5%8f%91/%e5%89%8d%e7%ab%af/