前端面试手写代码大全

229 阅读6分钟

1、fn.call(obj, args),call函数接收多个参数,第一个参数是指定函数执行时候的this,后面的一个或多个参数是调用函数fn的传参

Function.prototype.call = function() {
    // 使用解构赋值获取要绑定的this对象和其他参数
    let [context, ...args] = [...arguments]
    
    // 要绑定的对象this为空
    if (!context || typeof context !== 'object') {
        args.unshift(context);
        context = typeof window === 'undefined' ? global : window
    }
    
    let sym = Symbol()

    context[sym] = this
    
    // 保存返回值
    let result = context[sym](...args)
    
    // 删除该属性
    delete(context[sym])
    
    return result;
}

2、fn.apply(obj, args),第一个参数是函数执行时候的this,第二个参数是fn调用时候的参数组成的数组。

Function.prototype.apply = function(context, args) {
    // 如果传入的参数不是Object的实例,如‘123’,抛出错误
    if (args && !(args instance Object)) {
        throw Error('wrong')
    }
    
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    
    let sym = Symbol()
    context[sym] = this
    
    let result = args ? context[sym](...args) : context[sym]();

    delete context[sym]
    
    return result;
}

3、fun.bind()

Function.prototype.bind = function() {
    //将参数拆解为数组
    const args = Array.prototype.slice.call(arguments);
    //获取this
    const t = args.shift();
    //fn.bind(...)中的fn
    const self = this;
    
    return function() {
        return self.apply(t, args)
    }
}

4、new

function _new(fn, ...args) {
    // 创建一个空的对象,空对象的__proto__属性指向构造函数的原型
    var obj = Object.create(fn.prototype);
    // 把上面创建的空对象赋值构造函数内部的this,用构造函数内部的方法修改空对象
    const result = fn.apply(obj, ...args);
    // 如构造函数返回一个非基本类型的值,则返回这个值,否则返回上面创建的对象
    return Object.prototype.toString.call(result) === '[Object Object]' ? result : obj;
}

5、实现Object.create():它返回了一个新的空对象,并且这个空对象的构造函数的原型(prototype)是指向obj的

function _create = function(obj) {
    let fn = function() {}
    obj && typeof obj === 'object' && (fn.prototype = obj)
    return new fn()
}

6、数组去重,使用Set或遍历

//第一种
function arraySet(arr) {
    if (!Array.isArray(arr)) {
        throw Error('not array')
    }
    return [...new Set(arr)]
}

//第二种
function arraySet(arr) {
    if (!Array.isArray(arr)) {
        throw Error('not array')
    }
    
    let obj = {};
    let tmpArr = [];
    
    for (var i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            obj[arr[i]] = 1
            tmpArr.push(arr[i])
        }
    }
    
    return tmpArr;
}

7、实现instanceof:遍历左边变量的原型链,直到等于右边变量的prototype,如果没有找到返回false

function new_instance_of(leftValue, rightValue) {
    let rightProto = rightValue.prototype
    leftValue = leftValue.__proto__
    
    while(leftValue) {
        if (leftValue === null) {
            return false;
        }
        if (leftValue === rightProto) {
            return true;
        }
        leftValue = leftValue.__proto__;
    }
    return false;
}

8、实现深拷贝deepClone

function deepClone(obj={}) {
    if (typeof obj !== 'object' || obj == null) {
        return obj;
    }
    let result;
    if (obj instanceof Array) {
        result = [];
    } else {
        result = {};
    }
    
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            result[key] = deepClone(obj[key]);
        }
    }
    return result;
}

9、实现 a == 1 && a == 2 && a == 3

实现原理:要实现这个效果,变量a必然是一个引用类型的数据,巧妙利用==在比较时两边的数据会发生隐式转换这一特性

  • 如果引用类型a部署了[Symbol.toPrimitive]接口,那么调用此接口,若返回的不是基本类型的数据,则抛出错误
  • 如果引用类型a没有部署[Symbol.toPrimitive]接口,那么会根据需要转换的类型转换,如果和数值比较,则默认转换成number类型,会先调用a的valueof方法,是基本类型的数据则返回,否则调用a的toString方法,返回基本类型的数据则结束,否则抛出错误
  • 非Date类型的引用类型在转换成原始类型的时候默认转换成numer类型,但是Date类型的数据转换成原始类型的时候默认转换成String类型
//写法一
var a = {
    value: [3,2,1],
    valueof: function() {
        return this.value.pop()
    }
}

//写法二
var a = {
    value: [3,2,1],
    [Symbol.toPrimitive]: function() {
        return this.value.pop()
    }
}

// 可以通过对Object.defineProperty和Proxy的使用来实现
var obj = {age: 1};
var age = 1;
Object.defineProperty(obj, 'a', {
    get: function() {
      return age++;
    }
});

10、简易Promise

function MyPromise(excutor) {
    const self = this
    self.status = 'pending'
    self.value = null
    self.reason = null
    self.onFulfilledCallbacks = [] // 存储成功的方法
    self.onRejectedCallbacks = [] // 存储失败的方法
    
    //resolve函数接收成功时传的参数
    function resolve(value) {
        if (self.status === 'pending') {
            //改变当前执行函数的状态为成功状态
            self.status = 'fulfilled'
            //存储成功时的值
            self.value = value
            // promise实例状态成功,执行之前存储成功方法的数组,把传给resolve的参数传进去
            self.onFulfilledCallbacks.forEach(item => item(self.value))
        }
    }
    
    //reject函数接收失败时传的参数
    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected'
            self.reason = reason
            self.onRejectedCallbacks.forEach(item => item(self.reason)
        }
    }
    
    try {
        //如果没有错直接执行
        excutor(resolve, reject)
    } catch (error) {
        //一旦发现错误,直接扔给reject
        reject(error)
    }
}

//加上then的完整代码
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this
    if (self.status === 'fulfilled') {
        onFulfilled(self.value)
    }
    if (self.status === 'rejected') {
        onRejected(self.reason)
    }
    if (self.status === 'pending') {
        //处于pending状态也就是promise实例还在执行,异步状态情况下
        self.onFulfilledCallbacks.push(onFulfilled)
        self.onRejectedCallbacks.push(onRejected)
    }
}

11、手写ajax请求

function ajax(url, success) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        // true表示异步请求,false表示同步
        xhr.open("GET", "/api", true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.responseText));
                } else if (xhr.status === 404) {
                    reject(new Error('404 not found'));
                }
            }
        }
        xhr.send(null);
    })
    return p;
}

const url = ""
ajax(url).then(res => console.log(res)).catch(err => console.log(err))

12、防抖:最后一次触发

function debounce(fn, delay=500) {
    let timer = null
    
    return function() {
        //如果定时器存在清空,然后重新设置定时器
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

13、节流:定时触发

function throttle(fn, delay=100) {
    let timer = null
    
    return function() {
        //已经存在定时器,直接return
        if (timer) {
            return
        }
        timer = setTimer(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

14、trim

String.prototype.trim = function() {
    return this.replace(/^\s/, '').replace(/\s+$/, '');
}

15、max

function max() {
    const nums = Array.prototype.slice.call(arguments)
    let max = 0
    nums.forEach(n => {
        if (n > max) {
            max = n
        }
    })
    return max
}

16、数组扁平化

function flat(arr) {
    //验证arr中还有没有深层数组
    const isDeep = arr.some(item => item instanceof Array)
    
    if (!isDeep) {
        return arr
    }
    
    const res = Array.prototype.concat.apply([], arr)
    return flat(res)
}

17、requestAnimationFrame实现setInterval

实现原理:定义一个mySetInterval接收两个参数,回调函数callback和时间间隔interval。定义一个loop函数,实现每次在浏览器下次重绘之前调用。loop函数里面判断endTime和startTime的差值大于interval的时候,执行callback函数,并重置startTime,从而实现setInterval

function mySetInterval(callback, interval) {
    let timer = null
    let startTime = new Date()
    
    const loop = () => {
        let endTime = new Date()
        timer = window.requestAnimationFrame(loop)
        if (endTime - startTime >= interval) {
            startTime = new Date()
            callback(timer)
        }
    }
    
    loop()
    return timer
}

//代码
let a = 0
mySetInterval((timer) => {
    a++;
    console.log(a);
    if (a>10) {
        window.cancelAnimationFrame(timer)
    }
}, 1000)

18、实现一个简单的双向绑定

实现原理:给input绑定change事件,当数据发生变化,通知依赖该数据的地方更新

const input = document.getElementById('input')
const span = document.getElementById('span')

let obj = {}
input.onchange = function inputChange(e) {
    obj.text = t.target.value
}
Object.defineProperty(obj, 'text', {
    configurable: true,
    enumerable: true,
    get() {
        return obj.text
    },
    set(newVal) {
        span.innerText = newVal
    }
})

19、实现isEqual

function isObject(obj) {
    return typeof obj === 'object' && obj !== null;
}

function isEqual(obj1, obj2) {
    if (!isObject(obj1) || !isObject(obj2)) {
        return obj1 === obj2
    }
    if (obj1 === obj2) {
        return true
    }
    
    const obj1Keys = Object.keys(obj1);
    const obj2Keys = Object.keys(obj2);
    if (obj1Keys.length !== obj2Keys.length) {
        return false;
    }
    
    for (let key in obj1) {
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {
            return false;
        }
    }
    
    return true;
}

20、函数柯里化 实现原理: 函数柯里化,就是定义一个已知函数fn,该函数有n个,实现一个柯里化函数currying,接收参数fn函数,currying函数主要负责收集fn函数的参数,当收集的参数达到fn参数的个数的时候调用fn函数。

function currying(fn, ...args) {
    return args.length < fn.length ? (...arguments) => currying(fn, ...args, ...arguments) : fn(...args);
}
function countTotal(a, b, c, d) {
  return a * b *c * d;
}
var sum = currying(countTotal);
sum(1)(2)(3)(4);   // 24
sum(1, 2)(3)(4);   // 24
sum(1, 2, 3)(4);   // 24
sum(1, 2, 3, 4);   // 24