1. 循环打印红黄绿
下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
三个亮灯函数:
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
这道题复杂的地方在于需要“交替重复”亮灯,而不是“亮完一次”就结束了。
(1)用 callback 实现
const task = (timer, light, callback) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
callback()
}, timer)
}
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', Function.prototype)
})
})
这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?
上面提到过递归,可以递归亮灯的一个周期:
const step = () => {
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', step)
})
})
}
step()
注意看黄灯亮的回调里又再次调用了 step 方法 以完成循环亮灯。
(2)用 promise 实现
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = () => {
task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(2100, 'yellow'))
.then(step)
}
step()
这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。
(3)用 async/await 实现
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()
2. 实现每隔一秒打印 1,2,3,4
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
3. 小孩报数问题
有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?
function childNum(num, count){
let allplayer = []
for(let i = 0
allplayer[i] = i + 1
}
let exitCount = 0
let counter = 0
let curIndex = 0
while(exitCount < num - 1){
if(allplayer[curIndex] !== 0) counter++
if(counter == count){
allplayer[curIndex] = 0
counter = 0
exitCount++
}
curIndex++
if(curIndex == num){
curIndex = 0
}
}
for(i = 0
if(allplayer[i] !== 0){
return allplayer[i]
}
}
}
childNum(30, 3)
4. 用Promise实现图片的异步加载
let imageAsync=(url)=>{
return new Promise((resolve,reject)=>{
let img = new Image();
img.src = url;
img.οnlοad=()=>{
console.log(`图片请求成功,此处进行通用操作`);
resolve(image);
}
img.οnerrοr=(err)=>{
console.log(`失败,此处进行失败的通用操作`);
reject(err);
}
})
}
imageAsync("url").then(()=>{
console.log("加载成功");
}).catch((error)=>{
console.log("加载失败");
})
5. 实现发布-订阅模式
class EventCenter{
let handlers = {}
addEventListener(type, handler) {
if (!this.handlers[type]) {
this.handlers[type] = []
}
this.handlers[type].push(handler)
}
dispatchEvent(type, params) {
if (!this.handlers[type]) {
return new Error('该事件未注册')
}
this.handlers[type].forEach(handler => {
handler(...params)
})
}
removeEventListener(type, handler) {
if (!this.handlers[type]) {
return new Error('事件无效')
}
if (!handler) {
delete this.handlers[type]
} else {
const index = this.handlers[type].findIndex(el => el === handler)
if (index === -1) {
return new Error('无该绑定事件')
}
this.handlers[type].splice(index, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
6. 查找文章中出现频率最高的单词
function findMostWord(article) {
// 合法性判断
if (!article) return
// 参数处理
article = article.trim().toLowerCase()
let wordList = article.match(/[a-z]+/g),
visited = [],
maxNum = 0,
maxWord = ""
article = " " + wordList.join(" ") + " "
// 遍历判断单词出现次数
wordList.forEach(function(item) {
if (visited.indexOf(item) < 0) {
// 加入 visited
visited.push(item)
let word = new RegExp(" " + item + " ", "g"),
num = article.match(word).length
if (num > maxNum) {
maxNum = num
maxWord = item
}
}
})
return maxWord + " " + maxNum
}
7. 封装异步的fetch,使用async await方式来使用
(async () => {
class HttpRequestUtil {
async get(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
}
const httpRequestUtil = new HttpRequestUtil();
const res = await httpRequestUtil.get('http://golderbrother.cn/');
console.log(res);
})();
8. 实现prototype继承
所谓的原型链继承就是让新实例的原型等于父类的实例:
function SupperFunction(flag1){
this.flag1 = flag1;
}
function SubFunction(flag2){
this.flag2 = flag2;
}
var superInstance = new SupperFunction(true);
SubFunction.prototype = superInstance;
var subInstance = new SubFunction(false);
subInstance.flag1;
subInstance.flag2;
9. 实现双向数据绑定
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
10. 实现简单路由
class Route{
constructor(){
this.routes = {}
this.currentHash = ''
this.freshRoute = this.freshRoute.bind(this)
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
11. 实现斐波那契数列
// 递归
function fn (n){
if(n==0) return 0
if(n==1) return 1
return fn(n-2)+fn(n-1)
}
// 优化
function fibonacci2(n) {
const arr = [1, 1, 2]
const arrLen = arr.length
if (n <= arrLen) {
return arr[n]
}
for (let i = arrLen
arr.push(arr[i - 1] + arr[ i - 2])
}
return arr[arr.length - 1]
}
// 非递归
function fn(n) {
let pre1 = 1
let pre2 = 1
let current = 2
if (n <= 2) {
return current
}
for (let i = 2
pre1 = pre2
pre2 = current
current = pre1 + pre2
}
return current
}
12. 字符串出现的不重复最长长度
用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度:
var lengthOfLongestSubstring = function (s) {
let map = new Map()
let i = -1
let res = 0
let n = s.length
for (let j = 0
if (map.has(s[j])) {
i = Math.max(i, map.get(s[j]))
}
res = Math.max(res, j - i)
map.set(s[j], j)
}
return res
}
13. 使用 setTimeout 实现 setInterval
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
function mySetInterval(fn, timeout) {
var timer = {
flag: true
};
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
setTimeout(interval, timeout);
return timer;
}
14. 实现 jsonp
function addScript(src) {
const script = document.createElement('script');
script.src = src;
script.type = "text/javascript";
document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
function handleRes(res) {
console.log(res);
}
handleRes({a: 1, b: 2});
15. 判断对象是否存在循环引用
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()
对该类对象进行序列化,就会报错: Converting circular structure to JSON.
下面方法可以用来判断一个对象中是否已存在循环引用:
const isCycleObject = (obj,parent) => {
const parentArr = parent || [obj]
for(let i in obj) {
if(typeof obj[i] === 'object') {
let flag = false
parentArr.forEach((pObj) => {
if(pObj === obj[i]){
flag = true
}
})
if(flag) return true
flag = isCycleObject(obj[i],[...parentArr,obj[i]])
if(flag) return true
}
}
return false
}
const a = 1
const b = {a}
const c = {b}
const o = {d:{a:3},c}
o.c.b.aa = a
console.log(isCycleObject(o)
查找有序二维数组的目标值:
var findNumberIn2DArray = function(matrix, target) {
if (matrix == null || matrix.length == 0) {
return false;
}
let row = 0;
let column = matrix[0].length - 1;
while (row < matrix.length && column >= 0) {
if (matrix[row][column] == target) {
return true;
} else if (matrix[row][column] > target) {
column
} else {
row++;
}
}
return false;
};
二维数组斜向打印:
function printMatrix(arr){
let m = arr.length, n = arr[0].length
let res = []
// 左上角,从0 到 n - 1 列进行打印
for (let k = 0
for (let i = 0, j = k
res.push(arr[i][j])
}
}
// 右下角,从1 到 n - 1 行进行打印
for (let k = 1
for (let i = k, j = n - 1
res.push(arr[i][j])
}
}
return res
}
最后:
如果你现在正在找工作,可以私信“web”或者直接添加小助理进群领取前端面试小册、简历优化修改、大厂内推以及更多阿里、字节大厂面试真题合集,和p8大佬一起交流。