代码手写题

94 阅读4分钟

递归

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。

尾递归


function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

factorial(4, 5*1)
factorial(3, 4*5*1)
factorial(2, 3*4*5*1)
factorial(1, 2*3*4*5*1) -> return 2*3*4*5*1
改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

数组求和

function sumArray(arr, total = 0) {
  if(arr.length === 0) {
      return total
  }
  return sumArray(arr, total + arr.pop())
}

使用尾递归优化求斐波那契数列

function factorial2 (n, start = 1, total = 1) {
  if(n <= 2){
      return total
  }
  return factorial2 (n -1, total, total + start)
}

数组扁平化

let a = [1,2,3, [1,2,3, [1,2,3]]]
// 变成
let a = [1,2,3,1,2,3,1,2,3]
// 具体实现
function flat(arr = [], result = []) {
    arr.forEach(v => {
        if(Array.isArray(v)) {
            result = result.concat(flat(v, []))
        }else {
            result.push(v)
        }
    })
    return result
}

使用js生成1-10000的数组

除了使用循环(for,while,forEach等)外,最简单的是使用Array.from

// 方法一
Array.from(new Array(10001).keys()).slice(1) 
// 方法二 
Array.from({length: 1000}, (node, i) => i+1)

手写一个Promise

const PENDING = 'pending';
const FULFULLED = 'fulfilled';
const REJECTED = 'rejected';
class MPromise {
    constructor(fn) {
        this.status = PENDING;
        this.value = '';
        this.reason = '';
        this.resolveMicroQueueTaskList = [];
        this.rejectMicroQueueTaskList = [];
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFULLED;
        }
    }
    reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED;
        }
    }
    get status() {
        return this._status;
    }

    set status(newStatus) {
        this._status = newStatus;
        if (newStatus === FULFULLED) {
            this.resolveMicroQueueTaskList.forEach(cb => {
                cb()
            });
        } else if (newStatus === REJECTED) {
            this.rejectMicroQueueTaskList.forEach(cb => {
                cb()
            });
        }

    }

    then(resolve, reject) {
        const resolveFunction = resolve ? resolve : (value) =>  value;
        const rejectFunction = reject ? reject : (reason) =>  reason;
        const nextPromse = new MPromise((resolve, reject) => {
            const resolveMicroQueueTask = () => {
                queueMicrotask(() => {
                    const x = resolveFunction(this.value);
                    this.resolveNextPromise(x, resolve);
                })
            }
            const rejectMicroQueueTask = () => {
                queueMicrotask(() => {
                    const y = rejectFunction(this.reason)
                    this.resolveNextPromise(y, resolve);
                })
            }
            switch (this.status) {
                case PENDING: {
                    this.resolveMicroQueueTaskList.push(resolveMicroQueueTask);
                    this.rejectMicroQueueTaskList.push(rejectMicroQueueTask);
                    break;
                }
                    
                case FULFULLED: {
                    resolveMicroQueueTask();
                    break;
                }
                    
                case REJECTED: {
                    rejectMicroQueueTask();
                }
            }
        })
        return nextPromse;
    }

    catch(reject) {
        this.then(null, reject);
    }

    resolveNextPromise (x, resolve) {
        resolve(x);
    }

    static resolve (value) {
        if(value instanceof MPromise) {
           return value; 
        } 
        return new MPromise((resolve, reject) => {
            resolve(value);
        })
        
    }
    static reject(value) {
        if(value instanceof MPromise) {
            return value;
        } else {
            return new MPromise((resolve, reject) => {
                reject(value);
            })
        }
    }
    static race (promiseList) {
        let promiseListLen = promiseList.length;
        
        return new MPromise((resolve, reject) => { 
            if(promiseListLen === 0) {
                resolve()
            }
            for(var i = 0; i< promiseList.length; i++){
                MPromise.resolve(promiseList[i]).then(res=> {
                    resolve(res)
                }).catch(err => {
                    reject(err)
                })
            }
        })
    }

    static all (promiseList) {
        let promiseListLen = promiseList.length;
        let j = 0;
        let promiseValList = [];
        return new MPromise((resolve, reject) => { 
            if(promiseListLen === 0) {
                resolve()
            }
            for(var i = 0; i< promiseList.length; i++){
                MPromise.resolve(promiseList[i]).then(res=> {
                    j++
                    promiseValList.push(res);
                    if(promiseListLen === j) {
                        resolve(promiseValList)
                    }
                }).catch(err => {
                    reject(err)
                })
            }
        })
    }
}

实现mergePromise函数

实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。

const time = (timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timer)
  })
}
const ajax1 = () => time(2000).then(() => {
  console.log(1);
  return 1
})
const ajax2 = () => time(1000).then(() => {
  console.log(2);
  return 2
})
const ajax3 = () => time(1000).then(() => {
  console.log(3);
  return 3
})

function mergePromise () {
  // 在这里写代码
}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
  console.log("done");
  console.log(data); // data 为 [1, 2, 3]
});

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]

这道题有点类似于Promise.all(),不过.all()不需要管执行顺序,只需要并发执行就行了。但是这里需要等上一个执行完毕之后才能执行下一个。 解题思路:

  • 定义一个数组data用于保存所有异步操作的结果
  • 初始化一个const promise = Promise.resolve(),然后循环遍历数组,在promise后面添加执行ajax任务,同时要将添加的结果重新赋值到promise上。
function mergePromise (ajaxArray) {
  // 存放每个ajax的结果
  const data = [];
  let promise = Promise.resolve();
  ajaxArray.forEach(ajax => {
  	// 第一次的then为了用来调用ajax
  	// 第二次的then是为了获取ajax的结果
    promise = promise.then(ajax).then(res => {
      data.push(res);
      return data; // 把每次的结果返回
    })
  })
  // 最后得到的promise它的值就是data
  return promise;
}

使用Promise封装一个异步加载图片的方法

这个比较简单,只需要在图片的onload函数中,使用resolve返回一下就可以了。

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });
}

使用Promise实现:限制异步操作的并发个数,并尽可能快的完成全部

有8个图片资源的url,已经存储在数组urls中。 urls类似于['https://image1.png', 'https://image2.png', ....] 而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。

但有一个要求,任何时刻同时下载的链接数量不可以超过3个。

请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

var urls = [
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一张图片加载完成");
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });
}



// 答案
function limitLoad(urls, handler, limit) {
  let sequence = [].concat(urls); // 复制urls
  // 这一步是为了初始化 promises 这个"容器"
  let promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => {
      // 返回下标是为了知道数组中是哪一项最先完成
      return index;
    });
  });
  // 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
  return sequence
    .reduce((pCollect, url) => {
      return pCollect
        .then(() => {
          return Promise.race(promises); // 返回已经完成的下标
        })
        .then(fastestIndex => { // 获取到已经完成的下标
        	// 将"容器"内已经完成的那一项替换
          promises[fastestIndex] = handler(url).then(
            () => {
              return fastestIndex; // 要继续将这个下标返回,以便下一次变量
            }
          );
        })
        .catch(err => {
          console.error(err);
        });
    }, Promise.resolve()) // 初始化传入
    .then(() => { // 最后三个用.all来调用
      return Promise.all(promises);
    });
}
limitLoad(urls, loadImg, 3)
  .then(res => {
    console.log("图片全部加载完毕");
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });