前端面试-手写代码(持续更新...)

226 阅读4分钟

手写代码

冒泡排序

时间复杂度:O(n^2),空间复杂度:O(1)

// 两个相邻的数比较,较大的往后挪,则每一趟都能找到最大的数
function bubbleSort(arr){
    // 趟数
    for (let i = 0; i < arr.length - 1; i ++) {
        // 次数
        for (let j = 0; j < arr.length - i - 1; j ++) {
            if(arr[j] > arr[j+1]){
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}
// 优化,加个标志位,如果在某一轮发现没有交换数据,那就说明排序已经完成了,可以 break
function bubbleSort1(arr){
    let flag = true;
    for (let i = 0; i < arr.length - 1; i ++) {
        for (let j = 0; j < arr.length - 1 - i; j ++) {
            if (arr[j] > arr[j + 1]) {
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = false;
            }
        }
        if (flag) {
            break;
        }
    }
}

选择排序

时间复杂度:O(n^2)

// 初始以第一个为最小值,然后与后面的每一个挨个比较,更小的交换到最小值的 index 上
function selectSort(arr){
   var minIndex = 0;
   for (let i = 0; i < arr.length; i ++){
       // 总是将未排序的第一个数字当作最小值的比对标准
       minIndex = i;
       for (let j = i + 1; j < arr.length; j ++) {
           if (arr[j] < arr[minIndex]) {
               // 找到比最小值更小的值,将索引换成更小值的索引
               minIndex = j;
           }
       }
       // 交换未排序的第一个值和最小值的位置
       var temp = arr[i];
       arr[i] = arr[minIndex];
       arr[minIndex] = temp;
   }
}

快排

function quickSort(arr){
    let pivotIndex = Math.floor(arr.length / 2);
    let pivotValue = arr[pivotIndex];
    let left = [];
    let right = [];
    for (let i = 0; i < arr.length; i ++) {
        if (arr[i] < pivotValue) {
            left.push(arr[i]);
        }
        if (arr[i] > pivotValue) {
            right.push(arr[i]);
        }
    }
    return quickSort(left).concat(pivotValue).concat(quickSort(right));
}

二分查找

function erfen(arr, value){
    let start = 0;
    let end = arr.length - 1;
    while(start < end){
        let mid = Math.floor((start + end) / 2);
        if (value === arr[mid]) {
            return mid;
        }
        if (value < arr[mid]) {
            end = mid - 1;
        }
        if (value > arr[mid]) {
            start = mid + 1;
        }
    }
}

深度遍历

// 递归
function deep(node, result){
    if (node) {
        result.push(node);
        const children = node.children;
        if (children) {
            for (let i = 0; i < children.length; i ++) {
                deep(children[i], result);
            }
        }
    }
    return result;
}
// 栈
function deep2(node){
    let result = [];
    let stack = [];
    stack.push(node);
    while(stack.length){
        const currentNode = stack.pop();
        let children = currentNode.children;
        result.push(currentNode);
        for (let i = children.length - 1; i >= 0; i --){
            stack.push(children[i]);
        }
    }
    return result;
}

广度遍历

// 队列
function queue2(node) {
    let result = [];
    let queue = [];
    result.push(node);
    while(queue.length){
        const currentNode = queue.shift();
        const children = currentNode.children;
        result.push(currentNode);
        for (let i = 0; i < children.length; i ++) {
            queue.push(children[i]);
        }
    }
    return result;
}

前序遍历二叉树

// 递归
function q1(treeNode){
    if (treeNode) {
        console.log(treeNode.value);
        treeNode.left && q1(treeNode.left);
        treeNode.right && q1(treeNode.right);
    }
}
// 栈
function q2(treeNode){
    let stack = [];
    stack.push(treeNode);
    while(stack.length){
        const currentNode = stack.pop();
        currentNode.right && stack.push(currentNode.right);
        currentNode.left && stack.push(currentNode.left);
        console.log(currentNode);
    }
}

中序遍历二叉树

// 递归
function z1(treeNode){
    if (treeNode) {
        z1(treeNode.left);
        console.log(treeNode.value);
        z2(treeNode.right);
    }
}
// 栈
// 把当前节点入栈,然后遍历左子树,如果左子树存在就一直入栈,直到左子树为空,访问但前节点,然后让右子树入栈
function z2(treeNode){
    let stack = [];
    while(treeNode || stack.length){
        if (node) {
            stack.push(node);
            node = node.left;
        } else {
            node = stack.pop();
            console.log(node.value);
            node = node.right;
        }
    }
}

后序遍历二叉树

// 递归
function h1(treeNode){
    if (treeNode) {
        h1(treeNode.right);
        h2(treeNode.left);
        console.log(treeNode.value);
    }
}
// 栈
// 先把根结点和左树推入栈,然后取出左树,再推入右树,取出,最后取根结点。
function h2(treeNode){
    if(treeNode){
        let stack = [];
        let topNode = null;
        stack.push(treeNode);
        while(stack.length){
            topNode = stack[stack.length - 1];
            if (topNode.left) {
                stack.push(topNode.left);
            } 
            if (topNode.right){
                stack.push(topNpde.right);
            }
            console.log(stack.pop().value);
            treeNode = topNode;
        }
    }
        
}

最长无重复子串

function d(str){
    let end = 0;
    let visit = new Map();
    let result = "";
    for (let i = 0; i < str.length; i ++){
        if (i > 0){
            visit.delete(str[i - 1]);
        }
        while(end < str.length && !visit.get(end + 1)){
            visit.set(str[end + 1], true);
            end ++;
        }
        result = Math.max(result, end - i + 1);
    }
    return result;
}

观察者模式

  • 只需要两个角色:观察者和被观察者,重点是被观察者的实现
// 被观察者
class Subject {
    constructor(){
        this.observers = [];
    }
    addObserver(observer) {
        this.observers.push(observer);
    }
    removerObserver(observer) {
        this.observers = this.observers.filter((o) => o.name === observer.name);
    }
    notifyObserver(message) {
        this.observers.forEach((observer) => {
            observer.notified(message);
        });
    }

}
// 观察者
class Observer {
    constructor(name) {
        this.name = name;
    }
    notified(message){
        console.log(this.name, message);
    }
    
}
// 使用
const subject = new Subject();
const observerA = new Observer("observerA");
const observerB = new Observer("observerB");
subject.addObserver(observerA);
subject.addObserver(observerB);
subject.notified("hello");

发布订阅模式

  • 需要三个角色:发布者、订阅者、发布订阅中心,重点是发布订阅中心的实现
// 发布订阅中心
class PubSub {
    constructor(){
        this.listners = {};
        this.message = {};
    }
    // 添加订阅者
    subscribe(type, cb){
        if (!this.listners[type]) {
            this.listners[type] = [];
        } 
        this.listners[type].push(cb);
    }
    // 发布消息
    publish(type, content){
        if (!this.message[type]){
            this.message[type] = [];
        }
        this.message[type].push(content);
    }
    notify(type){
        const message = this.message[type];
        const subscriber = this.listners[type];
        subscriber.forEach((sub, index) => {
            sub(message[index]);
        });
    }
    
}

// 订阅者
class Subscriber {
    constructor(name, subPub){
        this.name = name;
        this.subPub = subPub;
    }
    subscribe(type, cb){
        this.subPub.subscribe(type, cb);
    }
}

// 发布者
class Publisher {
    constructor(name, subPub){
        this.name = name;
        this.subPub = subPub;
    }
    publish(type, message){
        this.subPub.publish(type, message);
    }
}

const subPub = new SubPub();

const publisherA = new Publisher("publisherA", subPub);
const subscriberA = new Subscriber("subscriberA", subPub);

publisherA.publish("typeA", "AAAAA");
subscriberA.subscribe("typeA", (msg) => {
    console.log("typeA 发布的消息:", msg);
});

封装一个有时效性的 localStorage

// 和数据一起存一个过期时间,每次取值都判断一下是否过期
class timeEfficientLocalStorage {
    set(key, value, expires){
        const times = Date.now() + new Date(expires).getTime();
        localStorage.setItem(key, JSON.stringify({
            value: value,
            expires: times
        }));
    }
    get(key){
        const content = JSON.parse(localStorage.getItem(key));
        if (Date.now() > content.expires){
            localStorage.removeItem(key);
            return false;
        } else {
            return content.value;
        }
    }
}

实现 promise

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(executor){
    this.status = PENDING;
    this.value;
    this.reason;
    this.fulfilledCbs = [];
    this.rejectedCbs = [];
    
    function resolve(value){
        if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value;
        }
    }
    function reject(reason){
        if (this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
        }
    }
    try{
        excutor(resolve, reject);
    }catch(e){
        rejected(e)
    }
}
MyPromise.prototype.then = function(resolveCb, rejectCb){
    return new MyPromise(resolve, reject){
        if (this.status === FULFILLED) {
            resolve(this.value);
        }
        if (this.status === REJECTED) {
            reject(this.reason);
        }
        if (this.status === PENDING) {
            this.fulfilledCbs.push(function(){
                resolve(resolveCb(this.value));
            });
            this.rejectedCbs.push(function(){
                reject(rejectCb(this.reason));
            });
        }
    }
}

设计 Promise.all

Promise.all = (promises) => {
    let result = [];
    let count = 0;
    return new Promise((resolve, reject) => {
        promises.forEach((promise) => {
            promise.then((res) => {
                if (count === promises.length) {
                    resolve(result);
                } else {
                    result.push(res);
                }
                count ++;
            }).catch((err) => {
                reject(err);
            });
        });
    });
    
}
    

sum(2)(3)

function sum(){
    function add(m, n){
        return m + n;
    }
    const args = [...arguments];
    return function(){
        const _args = [...arguments];
        return add.apply(this, [...args, ..._args]);
    }
}

// 柯里化包装函数
function curry(fn){
    const result = () => {
        return [...arguments].length === fn.length 
            ? fn(...arguments) 
            : (..._args) => { result(...args, ..._args) }
    }
    return result;
}

比较两个对象

// 1.引用相等性(使用 `===`、 `==` 或 `Object.is()`)用来确定操作数是否为同一个对象实例
// 2.手动遍历比较每个属性
function isObject(v){
    return v !== null && typeof v === "object";
}
function deepEqual(obj1, obj2){
    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);
    
    if (keys1.length !== keys2.length) {
        return false;
    }
    
    for (let i = 0; i < keys1.length; i ++) {
        const v1 = obj1[keys1[i]];
        const v2 = obj2[keys2[i]];
        if (isObject(v1) && isObject(v2)) {
            deepEqual(v1, v2);
        } 
        if (v1 !== v2){
            return false;
        }
    }
    return true;
}

防抖

function debounce(fn, interval){
    let timer = null;
    return function() {
        if (timer) {
            clearTimeout(timer);
        } else {
            timer = setTimeout(() => {
                fn.call(this, arguments);
            }, interval);
        }
    }
}

节流

function throttle(fn, interval){
    let canRun = true;
    return () => {
        if (!canRun) {
            return;
        }
        canRun = false;
        setTimeour(() => {
            canRun = true;
            fn(arguments);
        }, interval);
    }
}

并发控制

class LimitRequest {
    constructor(allRequests, maxNum){
        this.allRequests = allRequests;
        this.maxNum = maxNum;
        this.count = 0;
    }
    run(){
        if (this.count < this.maxNum) {
            const p = this.allRequests.shift();
            p().then((res) => {
              console.log(res);
            }).finally(() => {
                this.count ++;
                this.run();
            });
        } else if (this.allRequests.length) {
          this.count = 0;
          this.run()
        }
    }
}

useState & useEffect

let states = [];
let setters = [];
let cursor = 0;
let firstRun = true;

function useState(value) {
    if (firstRun) {
        states.push(value);
        const setState = (newValue) => {
            state[cursor] = newValue;
        }
        setters.push(setState);
        firstRun = false;
    }
    cursor ++;
    return [states[cursor], setters[cursor]];
}

let depsList = [];
let index = 0;
let clearCbs = [];
function useEffect(cb, deps){
    const lastDeps = depsList[index];
    const canRun = !deps || !depsList[depIndex] || depsList.some((item, index) => item !== lastDeps[index]);
    if (canRun) {
        depsList[index] = deps;
        if (clearCbs[index]) {
            clearCbs[index]();
        }
        clearCbs[index](cb());
    }
    depIndex ++;
};

深拷贝

function deepClone(obj, map = new WeakMap()){
    // 防止循环引用
    if (map.get(obj)) {
        return map.get(obj);
    }
    let result = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === null || typeof obj[key] !== "object") {
                result[key] = obj[key];
            } else if (Array.isArray(obj[key])) {
                result[key] = obj[key].map(item => deepClone(item, map));
            } else if (obj[key] instanceof Set) {
                result[key] = new Set([...obj[key]]);
            } else if (Object.prototype.toString.call(obj[key] === "object Date")) {
                result[key] = new Date(obj[key]);
            
            } else if (Object.prototype.toString() === "object Object") {
                map.set(obj[key], obj[key]);
                result[key] = deepClone(obj[key], map);
            } else {
                result[key] = obj[key];
            }
        }
    }
    return result;
}

实现 call、apply、bind

Function.prototype.myCall = function(obj){
    obj = obj ? Object(obj) : window;
    obj.fn = this;
    const result = obj.fn(Array.prototype.slice(arguments, 1));
    return result;
}
Function.prototype.myApply = function(obj, argsArr){
    const obj = obj ? Object(obj) : window;
    obj.fn = this;
    const result = obj.fn(...argsArr);
    return result;
}
Function.prototype.bind = function(obj){
    const args = Array.prototype.slice(arguments, 1);
    const that = this;
    return function(){
        const _args = Array.prototype.slice(arguments);
        that.apply(obj, [...args, ..._args]);
    }
}

getUrlParam(url, key)

function getUrlParam(url, key){
    if (!url){
        return null;
    }
    const searchStr = window.location.search.slice(1);
    const searchStrToArr = searchStr.split("&");
    let result = {};
    searchStrToArr.forEach((item) => {
        const itemArr = item.split("=");
        result[itemArr[0]] = itemArr[1];
    });
    return result[key];
};

数组扁平化

function flat(arr){
    const result = arr.reduce((pre, curr) => {
        return pre.concat(Array.isArray(curr) ? flat(curr) : curr);
    }, []);
    return result;
}

柯里化

function curry(fn){
    const resultFn = (...args) => {
        if (args.length === fn.length) {
            return fn(...args);
        } else {
            return (..._args) => {
                resultFn(...args, ..._args);
            };
        }
    }
    return resultFn;
}

sleep 函数

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

instanceof

function instanceof(left, right){
    let leftProto = left.__proto__;
    const rightPrototype = right.prototype;
    while(leftProto){
        if (leftProto === rightPrototype) {
            return true;
        }
        leftProto = leftProto.__proto__;
    }
    if (leftProto === null) {
        return false;
    }
}

斐波那契数列

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

打平数组

let result = [];
function flatten(arr){
    for (let i = 0; i < arr.length; i ++){
        if (Array.isArray(arr[i])){
            flatten(arr[i]);
        } else {
            result.push(arr[i]);
        }
    }
}

// 2
function flatten2(arr){
    return arr.reduce((pre, curr) => Array.isArray(curr) ? flatten2(curr) : pre.concat(curr), []);
}

简单封装 fetch

function request(url, config){
  let reqUrl = url;
  const initial = {
    method: "GET",
    params: null,
    body: null,
    headers: {
      "Content-Type": "application/x-www-form-urlencode"
    },
    credentials: true,
    cache: "no-cache"
  }
  if (config.headers) {
    config.headers = Object.assign({}, initial.headers, config.headers);
  }
  let {
    method,
    params,
    body,
    credentials,
    headers
  } = Object.assign({}, initial, config);

  if (params !== null) {
    reqUrl = `${reqUrl}?${qs.stringify(params)}`;
  }

  if (body !== null) {
    let contentType = headers["Content-Type"] || "application/json";
    if (contentType.includes("urlencoded")) {
      body = qs.stringify(body);
    }
    if (contentType.includes("json")) {
      body = JSON.stringify(body);
    }
  }

  credentials = credentials ? "include" : "same-origin";
  method = method.toUpperCase();
  const controller = new AbortController();
  let resultConfig = {method,credentials, headers, body};
  fetch(reqUrl, {...resultConfig, signal: controller.signal}).then((res) => {
    let {status, statusText} = res;
    if (status >= 200 && status < 300) {
      res.json()
      return res.json();
    }
    return Promise.reject({
      status,
      code: "error",
      statusText
    });
  }).catch((error) => {
    if (error.code === "error") {
      return Promise.reject(error);
    }
  });

}

function get(url, params){
  return request(url, {
    methohd: "get",
    params,
  });
}
function post(url, params){
  return request(url, {
    headers: {
      'content-type': 'application/json',
    },
    method: 'POST',
    body: JSON.stringify(params)
  });
}

判断对象中是否有循环引用

function isCircle(obj){
    let map = new Map();
    let result = false;
    function fn(obj){
        if (typeof obj !== "object") {
            return;
        }
        if (map.get(obj)) {
            result = true;
        }
        map.add(obj, obj);
        for (let key in obj) {
            if (obj.hasOwnProperty(obj[key])) {
                fn(obj[key]);
            }
        }
        map.delete(obj);
    }
    fn(obj);
    return result;
}