一定要坚持下去的前端面试题--手写代码

253 阅读5分钟

一、JavaScript基础

1. 手写instanceOf

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置

具体实现:

function _instanceOf (obj, constructor) {
    // constructor 的prototype在obj对象的原型链上则返回true 
    while (obj.__proto__) { 
        if (obj.__proto__ === constructor.prototype) {
            return true; 
        } 
        obj.__proto__ = obj.__proto__.__proto__; 
    } 
    return false; 
}

2. 手写Object.create

创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

function create(proto) {
    const F = new Function();
    F.prototype = proto;
    return new F();
}

3. 手写new操作符

function new(fn, ...args) {
    const obj = Object.create(fn.prototype);
    const res = fn.apply(obj, args);
    return typeof res === 'object' ? res : obj; 
}

4. 手写apply

Function.prototype.apply(context, ...args) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    const arguments = ...args;
    const result = context.fn(...arguments);
    delete context.fn;
    return result;
}

5. 手写call

// 与apply类似,只是参数传递形式不一样
Function.prototype.apply(context, ...args) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    const arguments = ...args;
    const result = context.fn(...args);
    delete context.fn;
    return result;
}

6. 手写bind

// 与apply和bind不同,不执行函数。返回改变了this指向的函数
Function.prototype.bind(context, ...args) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    const self = this;
    return function() {
        self.apply(context, [...args, ...arguments]);
    }
    
}

7. 手写Promise

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;

function MyPromise(executor) {
    const self = this;
    self.status = 0;
    self.data = undefined;
    self.onResolvedcallback = [];
    self.onRejectedCallback = [];
    
    function resolve (value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === 0) {
           self.status = 1;
           self.data = value;
           for (let i = 0; i < self.onResolvedcallback.length; i++) {
               self.onResolvedcallback[i](value);
           }
        }
    }
    
    function reject(reason) {
        if (self.status === 0) {
           self.status = 2;
           self.data = reason;
           for (let i = 0; i < self.onResolvedcallback.length; i++) {
               self.onRejectedCallback[i](reason);
           }
        }
    }
    
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e);
    }
}

8. 手写Promise.then

MyPromise.prototype.then = function (onResolved, onRejected) {
    const self = this;
    
    if (self.status === 0) {
        return new Promise(function(resolve, reject) {
            self.onResolvedcallback.push(function(value) {
                try {
                    const x = onResolved(value);
                    if (x instanceof MyPromise) {
                        x.then(resolve, reject)
                    }
                    resolve(x);
                    
                } catch(e) {
                    reject(e);
                }
            })
            self.onRejectedCallback.push(function(reason) {
                try {
                    const x = onRejected(reason);
                    if (x instanceof MyPromise) {
                        x.then(resolve, reject)
                    }
                    resolve(x);
                    
                } catch(e) {
                    reject(e);
                }
            })
        })
    }
    
    if (self.status === 1) {
        return new Promise(function(resolve, reject) {
            try {
                const x = onResolved(self.data);
                if (x instanceof MyPromise) {
                    x.then(resolve, reject)
                }
                resolve(x);
                
            } catch(e) {
                reject(e);
            }
        })
    }
    
    if (self.status === 2) {
        return new Promise(function(resolve, reject) {
            try {
                const x = onRejected(self.data);
                if (x instanceof MyPromise) {
                    x.then(resolve, reject)
                }
                reject(x);
                
            } catch(e) {
                reject(e);
            }
        })
    }
}

9. 手写Promise.all

function promiseAll(promises) {
    return new Promise((resolve, reject) => {
        const results = [];
        
        for (let i = 0 ; i < promises.length; i++) {
            Promise.resolve(promises[i]).then((res) => {
                results[i] = res;

                if (results.length === promises.length) {
                    resolve(results)
                }
            })
            .catch((err) => {
                reject(err);
            })
        } 
    });
}

10. 手写Promise.race

function promiseRace(promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0 ; i < promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    })
}

11. 手写Promise.prototype.catch

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
}

12. 手写Promise.prototype.finally

Promise.prototype.finally = function(callback) {
  return this.then((value) => {
      Promise.resolve(callback()).then(() => value)
  }, (error) => {
      Promise.resolve(callback()).then(() => throw error)
  });
}

13. 手写节流函数

// 节流: 指定时间间隔内只会执行一次任务
// 指连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率
function throttle(fn, delay) {
    let last = + new date();
    return function(...args) {
        const now = +new date(); 
        if (now - last >= delay) {
            last = now;
            fn.apply(this, args)
        }
    }
}

14. 手写防抖

// 防抖: 就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
            if (timer) { clearTimeout(timer)}
            timer = setTimeout(() => {
                fn.apply(this, args);
            }, delay);
    }
}

15. 手写Array.prototype.reduce()

语法:arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

Array.prototype.reduce = function(callback, initvalue) {
    // 如果提供了`initialValue`,则起始索引号为0,否则从索引1起始。
    let value = initvalue || this[0];
    const start = initvalue ? 0 : 1;
    for (let i = start; i < this.length; i++) {
        const item = this[i]; 
        value = callback(value, item, i, this);
    }
    
    return value;
    
}

实现对象for of遍历

对于普通的对象,for...of结构不能直接使用,必须部署了Iterator接口后才能使用。

// 方法一:使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组
for (let key of Object.keys(obj)) {
    console.log(`${key}: ${obj[key]}`);
}

// 方法二:使用Generator函数将对象包装一下
function* entries(obj) {
   for (let key of Object.keys(obj)) {
       yield [key, obj[key]]
   } 
}

for (let [key value] of entries(obj)) {
    console.log(`${key}: ${value}`);
}

二、数据处理

1. 数组扁平化

2. 对象扁平化

/* 题目*/
// 输入:
const obj = {
  a: {
    b: {
      c: {
        dd: "abcdd",
      },
    },
    d: {
      ee: "adee",
    },
    e: "ae",
  },
  f: "f",
  g: [{ h: 1 }, 2, 3],
};

// 输出
{'a.b.c.dd': 'abcdd', 'a.d.ee': 'adee', 'a.e': 'ae', f: 'f', 'g.0.h': 1, 'g.1': 2, 'g.2': 3}
function flattenObj () {
    const res = {};
    const process = (obj, prevKey) => {
        Object.keys(obj).forEach((key) => {
           if (typeof obj[key] !== 'object') {
               res[`${prevKey || ''}${key}`] = obj[key];
           } else {
               process(obj[key], `${prevKey || ''}${key}.`);
           }
        });
    }
    
    process(obj);
    return res;
}

3. js将扁平结构数据转换为树形结构

/* 题目*/
const data = [{id: 1, parent: 4}, {id: 2, parent: 4}, {id: 3}, {id: 4}];

转换后的树形结构为:[ 
    { id: 3 }, 
    { id: 4, 
        children: [ { id: 1, parent: 4 }, { id: 2, parent: 4 }, ], 
    }
];
function jsonToTree(data, options={}) {
    const {
        key = 'id',
        child = 'children', 
        parent = 'parent',
    } = options;
    
    const tree = [];
    const record = {}; // // 用来记录扁平化数据,key为id, value为子元素
    
    for (let i = 0; i < data.length; i++) {
        const item = data[i];
        const id = item[key];
        const pid = item[parent];
        
        if (!id) continue;
        
        if (record[id]) {
            item[child] = record[id];
        }
        
        if (pid) {
            if (!record[pid]) {
                record[pid] = [];
            }
            record[pid].push(item);
        } else {
            tree.push(item);
        }
    }
    
    return tree;
}

4. 实现一个add函数,满足以下功能

add(1); // 1
add(1)(2);  // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
// 隐式调用
function add(...a) {
    let sum = a.reduce((p, n) => p + n);
    
    function next(...b) {
        let _sum = b.reduce((p, n) => p + n);
        sum = sum + _sum;
        return next;
    }
    
    next.tostring = function() {
        return sum;
    }
    
    return next;
}

三、场景处理

1. 报数问题

题目: 13个人围坐一圈报数(123的报数),凡是报到3的人出队,最后剩下的人的序号是什么?

function count(num) {
    const arr = new Array(num).fill(true);
    let leftCount = num; // 剩余人数
    let countNum = 0; // 报数记录,为3时,出队
    let index = 0; // 记录报数人的序号,会超过num,超过后需要取余
    
    while (leftCount > 1) {
        if (arr[index % num]) {
            // 还在队列中,报数
            countNum++;
            if (countNum === 3) {
                // 报数为3的人出队
                countNum = 0;
                arr[index % num] = false;
                leftCount--;
            }
        }
        index++;
    }
    
    return arr.findIndex(item => item);
}

count(13);

2. 实现sleep函数

// Promise
function sleep(time) {
    return new Promise((resolve) => {
        setTimeout(resolve, time)
    });
}

sleep(3000);

3. 打乱一个数组

原理:循环遍历该数组,在每次遍历中产生一个(0 ~ length - 1)之间的随机下标的数,该数代表本次循环要随机交换的位置。 将本次循环当前位置的数和随机位置的数进行交换。

// 循环随机位交换法
let arr = [1, 2, 3, 4, 5];
function randFun(arr) {
    for (let i = 0; i < arr.length; i++) {
        let idx = parseInt(Math.random() * arr.length);
        let temp = arr[i];
        arr[i] = arr[idx];
        arr[idx] = temp;
    }
    return arr;
}

4. 每隔一秒输出数组中的一个元素

function output(arr) {
    let i = 0;
    return function timer() {
        setTimeout(() => {
            console.log(arr[i++]);
            i < arr.length && timer();
        }, 1000);
    }
}

// 使用promise实现
function output(arr) {
    arr.reduce((prev, item) => {
        return prev.then(() => {
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(console.log(item))
                }, 1000)
            })
        })
    }, Promise.resolve())
}

5. 使用Promise实现红绿灯交替重复

 // 红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次;如何让三个灯不断交替重复亮灯?
 function red() {
     console.log('red');
 }
 function green() {
     console.log('green');
 }
 function yellow() {
     console.log('yellow');
 }
 
 const light = function(timer, cb) {
     return new Promise((resolve) => {
        setTimeout(() => {
            cb();
            resolve();
        }, timer); 
     });
 }
 
 function step() {
     Promise.resolve()
         .then(() => {
             return light(3000, red)
         })
         .then(() => {
             return yellow(2000, yellow)
         })
         .then(() => {
             return green(1000, green)
         })
 }
 
 step();

6. 异步加载图片

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

7. 限制异步操作的并发个数并尽可能快的完成全部

题目: 有8个图片资源的url,已经存储在数组urls中。

urls类似于['https://image1.png', 'https://image2.png', ....]

而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject

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

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

function limitLoad(urls, handler, limit) {
  const sequence = [...urls];
  const promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => index);
  });

  return promises.reduce((prev, url) => {
      return prev.then(() => {
          return Promise.race(promises)
      })
      .then((fastIndex) => {
          promises[fastIndex] = handler(url).then(() => fastIndex)
      })
  }, Promise.resolve())
  .then(() => {
      Promise.all(promises);
  })
}

四、面试题

1. 微软一面【WIP】

答案有空整理

主要考察模板字符串语法

this指向等

<!DOCTYPE html>
<html>
    <head>
        <title>Parcel Sandbox</title>
        <meta charset="UTF-8" />
    </head>
    <body>
        <h1>Case1</h1>
        <div id="case1"></div>
        <hr />
        <h1>Case2</h1>
        <div id="case2"></div>
        <script src="src/index.js"></script>
</body>
</html>
function render(root, template, data) {
    const _data = Object.assign(data, { rerender });
    function rerender() {
        root.innerHTML = template(_data);
    }
    rerender();
}

const template1 = html`
    <div>
        <h1>${(x) => x.title}</h1>
        <p>${(x) => x.description}</p>
    </div>
`;

const template2 = html`
    <div>
        <h1>${(x) => x.title}</h1>
        <button ${(x) => x.update}>${(x) => x.number}</button>
        <button ${(x) => x.reset}>reset</button>
    </div>
`;

render(document.getElementById("case1"), template1, {
    title: "Paragraph",
    description: "Hi, template!"
});

render(document.getElementById("case2"), template2, {
    title: "Counter",
    number: 0,
    update: {
        type: "click",
        runner() {
            this.number = this.number + 1;
        }
    },
    reset: {
        type: "click",
        runner() {
            this.number = 0;
        }
    }
});

function html(strings, ...args) {
    return (x) => {
        const res = strings.reduce((prev, next, i) => {
            let dyncticString = '';
            if (args[i - 1]) {
                let result = args[i - 1](x);
                if (typeof result === "object") {
                    const { type, runner } = result;
                    result = `on${type} = ${runner}`
                } else {
                    dyncticString = result;
                }
            }
            prev = `${prev}${dyncticString}${next}`;
            return prev;
        });
        return res;
    };
}