前端面试 JS 手写

126 阅读7分钟

juejin.cn/post/696316…

手写 apply

function apply(func, context, argsArray) {
    // 检查 func 是否是函数
    if (typeof func !== 'function') {
        throw new TypeError('First argument must be a function');
    }

    // 如果没有传递上下文,则默认为全局对象
    context = context || globalThis;

    // 如果未提供参数数组,则默认为空数组
    argsArray = argsArray || [];

    // 创建一个唯一的键以避免与现有上下文属性冲突
    const uniqueKey = Symbol();

    // 在上下文中创建一个临时函数,以便我们可以调用 apply 方法
    context[uniqueKey] = func;

    // 调用函数并返回结果
    const result = context[uniqueKey](...argsArray);

    // 清理上下文中的临时函数
    delete context[uniqueKey];

    return result;
}

// 示例用法:
function add(a, b) {
    return a + b;
}

const numbers = [2, 3];
const sum = apply(add, null, numbers);
console.log(sum); // 输出:5

手写 call

function call(func, context, ...args) {
    // 检查 func 是否是函数
    if (typeof func !== 'function') {
        throw new TypeError('First argument must be a function');
    }

    // 如果没有传递上下文,则默认为全局对象
    context = context || globalThis;

    // 创建一个唯一的键以避免与现有上下文属性冲突
    const uniqueKey = Symbol();

    // 在上下文中创建一个临时函数,以便我们可以调用 apply 方法
    context[uniqueKey] = func;

    // 调用函数并返回结果
    const result = context[uniqueKey](...args);

    // 清理上下文中的临时函数
    delete context[uniqueKey];

    return result;
}

// 示例用法:
function greet(name) {
    return `Hello, ${name}!`;
}

const greeting = call(greet, null, 'John');
console.log(greeting); // 输出:Hello, John!

手写 bind

Function.prototype.myBind = function (context, ...args) {
    // 将当前函数保存在变量 fn 中
    const fn = this;
    // 如果未提供 args 参数,则将其设置为空数组
    args = args ? args : [];
    // 返回一个新的函数
    return function newFn(...newFnArgs) {
        // 如果新函数被用作构造函数(通过 new 关键字调用),则将 this 绑定到新创建的对象上,并调用原始函数 fn
        if (this instanceof newFn) {
            return new fn(...args, ...newFnArgs);
        }
        // 否则,使用 apply 方法将函数 fn 绑定到指定的上下文 context,并传入参数数组
        return fn.apply(context, [...args, ...newFnArgs]);
    };
};

寄生式组合继承

function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
// 这一步是继承的关键
Person1.prototype = Object.create(Person.prototype);
Person1.prototype.constructor = Person1;

Person1.prototype.play = function(value){
    console.log(value)
}
var p2 = new Person1({name:"鸡蛋", age: 118, sex: "男"})

手写 new

function myNew(constructor, ...args) {
    // 创建一个新对象,并将其原型指向构造函数的原型
    const instance = Object.create(constructor.prototype);
    // 调用构造函数,并将新对象作为上下文(this)
    const result = constructor.apply(instance, args);
    // 如果构造函数返回了一个对象,则返回该对象,否则返回新对象
    return Object(result) === result ? result : instance;
}

手写 instanceof的实现

function myInstanceof(obj, constructor) {
    // 如果 obj 或 constructor 不是对象,则返回 false
    if (typeof obj !== 'object' || obj === null || typeof constructor !== 'function') {
        return false;
    }

    // 获取 constructor 的原型对象
    let proto = Object.getPrototypeOf(obj);

    // 循环查找原型链
    while (proto !== null) {
        // 如果 constructor 的原型对象出现在原型链中,则返回 true
        if (proto === constructor.prototype) {
            return true;
        }
        // 继续向上查找原型链
        proto = Object.getPrototypeOf(proto);
    }

    // 如果未找到 constructor 的原型对象,则返回 false
    return false;
}

Object.create()的实现

function myCreate(proto, propertiesObject) {
    // 如果 proto 不是对象或者为 null,则抛出 TypeError
    if (typeof proto !== 'object' && proto !== null) {
        throw new TypeError('Object prototype may only be an Object or null');
    }

    // 创建一个新的空对象
    const obj = {};

    // 将新对象的原型设置为 proto
    Object.setPrototypeOf(obj, proto);

    // 如果提供了属性描述符对象,则定义属性到新对象上
    if (propertiesObject !== undefined) {
        Object.defineProperties(obj, propertiesObject);
    }

    // 返回新创建的对象
    return obj;
}

Object.assign

function myAssign(target, ...sources) {
    // 如果目标对象为 null 或 undefined,则抛出错误
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object');
    }

    // 将目标对象转换为对象
    const to = Object(target);

    // 遍历每个源对象
    sources.forEach(source => {
        // 如果源对象为 null 或 undefined,则跳过
        if (source != null) {
            // 遍历源对象的所有可枚举属性
            for (const key in source) {
                // 检查源对象是否具有该属性
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    // 将源对象的属性复制到目标对象中
                    to[key] = source[key];
                }
            }
        }
    });

    // 返回目标对象
    return to;
}

Promise的实现

Promise 是链条式的,一个接一个,所以 _handle 就会先处理,即便没有处理函数,也会执行自身的 resolve 传递到下一个上,而 then 就是保持一个接一个主因,return 以后,下一个 then 会挂在自己身上

class Promise {
    callbacks = [];
    state = 'pending';//增加状态
    value = null;//保存结果
    constructor(fn) {
        fn(this._resolve.bind(this), this._reject.bind(this));
    }
    then(onFulfilled, onRejected) {
        return new Promise((resolve, reject) => {
            this._handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    }
    _handle(callback) {
        if (this.state === 'pending') {
            this.callbacks.push(callback);
            return;
        }
 
        let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
 
        if (!cb) {//如果then中没有传递任何东西
            cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
            cb(this.value);
            return;
        }
 
        let ret = cb(this.value);
        cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
        cb(ret);
    }
    _resolve(value) {
 
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            var then = value.then;
            if (typeof then === 'function') {
                then.call(value, this._resolve.bind(this), this._reject.bind(this));
                return;
            }
        }
 
        this.state = 'fulfilled';//改变状态
        this.value = value;//保存结果
        this.callbacks.forEach(callback => this._handle(callback));
    }
    _reject(error) {
        this.state = 'rejected';
        this.value = error;
        this.callbacks.forEach(callback => this._handle(callback));
    }
}

Promise.resolve

class Promise {
    // 其他代码...

    static resolve(value) {
        if (value instanceof Promise) {
            // 如果传入的值是一个 Promise 对象,则直接返回该 Promise 对象
            return value;
        }

        // 如果传入的值不是一个 Promise 对象,则创建一个新的 Promise 对象
        return new Promise((resolve) => {
            resolve(value);
        });
    }

    // 其他代码...
}

Promise.reject

Promise.reject = function(reason) {
    return new Promise((resolve, reject) => reject(reason))
}

Promise.all

Promise.all = function(promiseArr) {
    let index = 0, result = []
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                index++
                result[i] = val
                if (index === promiseArr.length) {
                    resolve(result)
                }
            }, err => {
                reject(err)
            })
        })
    })
}

Promise.race

Promise.race = function(promiseArr) {
    return new Promise((resolve, reject) => {
        promiseArr.forEach(p => {
            Promise.resolve(p).then(val => {
                resolve(val)
            }, err => {
                rejecte(err)
            })
        })
    })
}

Promise.allSettled

Promise.allSettled = function(promises) {
  return new Promise(resolve => {
    const results = [];
    let settledCount = 0;

    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];
      Promise.resolve(promise)
        .then(value => {
          results[i] = { status: "fulfilled", value };
        })
        .catch(reason => {
          results[i] = { status: "rejected", reason };
        })
        .finally(() => {
          settledCount++;
          if (settledCount === promises.length) {
            resolve(results);
          }
        });
    }
  });
};

Promise.any

Promise.any = function(promises) {
  return new Promise((resolve, reject) => {
    const errors = [];
    let settledCount = 0;

    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];
      Promise.resolve(promise)
        .then(value => {
          resolve(value);
        })
        .catch(reason => {
          errors.push(reason);
          settledCount++;
          if (settledCount === promises.length) {
            reject(new AggregateError("All promises were rejected", errors));
          }
        });
    }
  });
};

防抖

let debounce = (fn,time = 1000) => {
    let timeLock = null

    return function (...args){
        clearTimeout(timeLock)
        timeLock = setTimeout(()=>{
            fn(...args)
        },time)
    }
}

实现节流函数(throttle)

function throttle(func, delay) {
  let lastCall = 0;
  return function (...args) {
    const now = new Date().getTime();
    if (now - lastCall < delay) return;
    lastCall = now;
    func(...args);
  };
}

深拷贝(deepclone)

function deepClone(obj, hash = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') return obj;
    
    // 处理循环引用的情况
    if (hash.has(obj)) {
        return hash.get(obj);
    }
    
    // 处理 RegExp 类型
    if (obj instanceof RegExp) {
        return new RegExp(obj);
    }
    
    // 处理 Date 类型
    if (obj instanceof Date) {
        return new Date(obj);
    }

    // 创建一个新的对象或数组来存储克隆后的数据
    let clone = Array.isArray(obj) ? [] : new obj.constructor();
    
    // 存储到 WeakMap,避免循环引用
    hash.set(obj, clone);
    
    // 遍历原始对象的所有属性或者数组的所有元素,进行递归克隆
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key], hash);
        }
    }

    // 考虑 Symbol 的情况
    let symbolObj = Object.getOwnPropertySymbols(obj);
    for (let i = 0; i < symbolObj.length; i++) {
        if (obj.hasOwnProperty(symbolObj[i])) {
            clone[symbolObj[i]] = deepClone(obj[symbolObj[i]], hash);
        }
    }
    
    return clone;
}

数组扁平化的实现(flat)

let arr = [1,2,[3,4,[5,[6]]]]
console.log(arr.flat(Infinity))//flat参数为指定要提取嵌套数组的结构深度,默认值为 1


//用reduce实现
function fn(arr){
   return arr.reduce((prev,cur)=>{
      return prev.concat(Array.isArray(cur)?fn(cur):cur)
   },[])
}

函数柯里化

fn.length 是 JavaScript 函数对象的一个属性,它返回了函数声明时的形参个数。

function sumFn(a,b,c){return a+ b + c};
let sum = curry(sumFn);
sum(2)(3)(5)//10
sum(2,3)(5)//10
function curry(fn,...args){
  let fnLen = fn.length,
      argsLen = args.length;
  //对比函数的参数和当前传入参数
  //若参数不够就继续递归返回curry
  //若参数够就调用函数返回相应的值
  if(fnLen > argsLen){
    return function(...arg2s){
      return curry(fn,...args,...arg2s)
    }
  }else{
    return fn(...args)
  }
}

手写一个 jsonp

function jsonp(url, callbackName, callback) {
  // 使用 new URL 构建 URL 对象
  const urlObject = new URL(url);
  // 设置查询参数 callback
  urlObject.searchParams.set('callback', callbackName);

  // 创建 script 标签
  const script = document.createElement('script');
  // 设置 script 标签的 src 属性为构建好的 URL
  script.src = urlObject.toString();
  // 将 script 标签添加到页面中
  document.body.appendChild(script);

  // 定义全局回调函数
  window[callbackName] = function(data) {
    // 执行回调函数,并传入从服务器返回的数据
    callback(data);
    // 移除 script 标签
    document.body.removeChild(script);
    // 删除全局回调函数
    delete window[callbackName];
  };
}

20.EventEmitter 实现

class EventEmitter {
  constructor() {
    // 存储事件及其对应的回调函数
    this.events = {};
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  // 发布事件
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => {
        callback.apply(this, args);
      });
    }
  }

  // 移除事件的指定回调函数
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
    }
  }

  // 移除指定事件的所有回调函数
  removeAllListeners(eventName) {
    delete this.events[eventName];
  }
}

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) { // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的结构
  }
  return template; // 如果模板没有模板字符串直接返回
}