js面试手写系列————持续更新......

本文是JavaScript面试的代码实践系列,涵盖了防抖与节流、深浅拷贝、数组去重与扁平化、模拟实现new、call、apply、bind、Object.create()、instanceof以及Promise的A+规范实现。通过这些手写练习,深入理解JavaScript核心概念。
摘要由CSDN通过智能技术生成

JavaScript面试手写系列:

1.防抖与节流

//防抖与节流
//定义:出发时间后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。
//搜索框搜索输入,只需用户最后一次输入完,再发送请求
//手机号、邮箱验证输入检测onchange  oninput事件
//窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
var btn = document.getElementById('input');
btn.addEventListener('click', debounce(submit), false);
​
function submit(e) {
    console.log(e)
}
​
function debounce(fn) {
    var timer = null;
    return function() {
        if (timer) {
            clearTimeout(timer)
        };
        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, 1000);
    }
}
​
//如果有一个需求,需要我们第一次点击不存在延时而是立刻发送请求,往后的 点击才需要发送请求,应该怎么去变形呢?
function debounce(fn, timer) {
    var t = null;
    return function() {
        var firstClick = !t;
        if (t) {
            clearTimeout(t);
        }
        if (firstClick) {
            fn.apply(this, arguments);
        }
​
        t = setTimeout(() => {
            t = null;
        }, timer);
    }
}
​
//节流:当持续触发事件时,保证间隔时间触发一次事件。
//懒加载 、滚动加载、加载更多或监听滚动条位置
//百度搜索框,搜索联想功能
//防止高频点击提交,防止表单重复提交
​
var btn = document.getElementById('input')
btn.addEventListener('click', throttle(submit, 2000), false)
​
function submit(e) {
    console.log(e)
}
​
function throttle(fn, delay) {
    var begin = 0;
    return function() {
        var cur = Date.now();
        if (cur - begin > delay) {
            fn.apply(this, arguments);
            begin = cur;
        }
    }
}

2.深浅拷贝

对象:JSON.stringfy(无法赋值undefined和fn)、object.assign、扩展运算符(无法复制二级以上引用属性)
数组:JSON.stringfy(无法赋值undefined和fn)、arr.slice、arr.concat、扩展运算符(无法复制二级以上引用属性)
​使用递归实现简单深拷贝:

 
function DeepClone(data) {
    const newData = Array.isArray(data) ? [] : {};
    for (let key in data) {
        if (data[key] && typeof data[key] == 'object') {
            newData[key] = DeepClone(data[key])
        } else {
            newData[key] = data[key]
        }
    }
    return newData;
}
​
const arr1 = [1, 2, 3, 4, { a: 1, b: 2, c: 3 },
    [1, 2, 3], undefined, { func: () => {} }
]
const arr2 = DeepClone(arr1)
​
arr2[0] = 666;
arr2[4].a = 666;
arr2[5][0] = 666;
console.log(arr1, arr2)
效果如下实现深拷贝:


3.数组去重、数组对象去重

  •  数组去重可以写多少种?

 1.ES6的set方法
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
​
function uniqueArr(array) {
    var newArr = [];
​
    //return Array.from(new Set(array))
    //也可以用扩展运算符
    return [...new Set(array)]
}
​
console.log(uniqueArr(arr))
​
2.双重for循环+标志位
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
​
function uniqueArr(array) {
    var newArr = [];
​
    // for (let i = 0; i < array.length; i++) {
    //     let isRepeat = false;
    //     for (let j = i + 1; j < array.length; j++) {
    //         if (array[i] == array[j]) {
    //             isRepeat = true;
    //             break;
    //         }
    //     }
    //     if (!isRepeat) {
    //         newArr.push(array[i]);
    //     }
    // };
    //有一种利用遍历新数组的变形,本质差不多
​
    for (let i = 0; i < array.length; i++) {
        let isRepeat = false;
        for (let j = 0; j < newArr.length; j++) {
            if (array[i] == newArr[j]) {
                isRepeat = true;
                break;
            }
        }
        if (!isRepeat) {
            newArr.push(array[i]);
        }
    };
​
​
​
​
    return newArr;
}
​
console.log(uniqueArr(arr));
​
3. 新对象+新数组(利用对象的键名不能重复)
​
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
​
function uniqueArr(array) {
    var newArr = [],
        obj = {};
​
    for (let i = 0; i < array.length; i++) {
        if (!obj[array[i]]) {
            obj[array[i]] = 1;
            newArr.push(array[i])
            console.log(obj)
        } else {
            obj[array[i]]++;
        }
    }
    return newArr;
}
​
console.log(uniqueArr(arr))
​
4.reduce条件统计
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
​
function uniqueArr(array) {
    let newArr = [];
    newArr = array.sort((a, b) => a - b).reduce((pre, cur) => {
        if (pre.length === 0 || pre[pre.length - 1] !== cur) {
            pre.push(cur);
        }
        return pre;
    }, [])
​
    return newArr;
​
​
}
​
console.log(uniqueArr(arr));
​
5.新数组+indexOf
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
​
function uniqueArr(array) {
    let newArr = [];
    for (let i = 0; i < array.length; i++) {
        if (newArr.indexOf(array[i]) == -1) {
            newArr.push(array[i]);
        }
    }
    return newArr;
​
}
​
console.log(uniqueArr(arr))
​
6.新数组+includes方法
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
​
function uniqueArr(array) {
    let newArr = [];
    for (let i = 0; i < array.length; i++) {
        if (!newArr.includes(array[i])) {
            newArr.push(array[i]);
        }
    }
    return newArr;
​
}
​
console.log(uniqueArr(arr))
​
7.双指针思想+splice(改变原数组的方式)
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
​
function distinct(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] == arr[j]) {
                arr.splice(j, 1)
                j--
            }
        }
    }
​
    return arr
}
​
console.log(distinct(arr))
​
8.forEach、filters、lastIndexOf都是可以的
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
​
function uniqueArr(array) {
    let newArr = [];
    array.forEach((item) => {
        newArr.includes(item) ? '' : newArr.push(item)
    });
​
    return newArr;
}
​
console.log(uniqueArr(arr))
​
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
​
function uniqueArr(array) {
    let newArr = [];
    newArr = array.filter((item) => {
        return newArr.includes(item) ? '' : newArr.push(item)
    });
​
    return newArr;
}
​
console.log(uniqueArr(arr))
  • ​ 数组对象去重

1.reduce条件统计去重
let arr = [
    { id: 1, name: '周瑜' },
    { id: 3, name: '王昭君' },
    { id: 2, name: '亚瑟' },
    { id: 1, name: '小乔' },
    { id: 2, name: '大桥' },
    { id: 3, name: '韩信' }
];
let obj = {}
arr = arr.reduce((preVal, curVal) => {
    if (!obj[curVal.id]) {
        obj[curVal.id] = 1;
        preVal.push(curVal);
        console.log(preVal)
    } else {
        obj[curVal.id]++;
    }
    return preVal;
}, [])
console.log(arr)
​
2.利用原生js+空对象+数组
let arr = [
    { id: 1, name: '周瑜' },
    { id: 3, name: '王昭君' },
    { id: 2, name: '亚瑟' },
    { id: 1, name: '小乔' },
    { id: 2, name: '大桥' },
    { id: 3, name: '韩信' }
];
​
function uniqueArr(array, key) {
    let newObj = {};
    let newArr = [];
    for (let i = 0; i < array.length; i++) {
        let temp = array[i];
        if (!newObj[temp[key]]) {
            newObj[temp[key]] = newArr.push(temp);
​
        }
    }
    return newArr;
}
​
console.log(uniqueArr(arr, 'id'))
​
​
3.利用原生js+标志位+空数组
let arr = [
    { id: 1, name: '周瑜' },
    { id: 3, name: '王昭君' },
    { id: 2, name: '亚瑟' },
    { id: 1, name: '小乔' },
    { id: 2, name: '大桥' },
    { id: 3, name: '韩信' }
];
​
function uniqueArr(array, key) {
    let newArr = [];
    for (let i = 0; i < array.length; i++) {
        let isRepeat = false;
        for (let j = i + 1; j < arr.length; j++) {
            if (array[i][key] === array[j][key]) {
                isRepeat = true;
                break;
            }
        }
        if (!isRepeat) {
            newArr.push(array[i])
        }
    }
    return newArr
}
​
console.log(uniqueArr(arr, 'id'))
​
4.利用双指针思想+splice方法
​
let arr = [
    { id: 1, name: '周瑜' },
    { id: 3, name: '王昭君' },
    { id: 2, name: '亚瑟' },
    { id: 1, name: '小乔' },
    { id: 2, name: '大桥' },
    { id: 3, name: '韩信' },
    { id: 3, name: 'zxg' }
];
​
function uniqueArr(array, key) {
​
    for (let i = 0; i < array.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (array[i][key] === array[j][key]) {
                array.splice(j, 1)
                j--;
            }
        }
    }
    return array
}
​
console.log(uniqueArr(arr, 'id'))

4.数组扁平化

let arr = [1, [2, [3, [4, [5]]]]];
​
1.利用数组原生flat方法,要传入Infinity,因为你并不知道有多少层嵌套
// let newArr = arr.flat(Infinity);
// console.log(newArr)
​
2.利用JSON.parse、JSON.strinfy、正则
// let brr2 = JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g, "") + "]");
// console.log(brr2);
​
​
3.reduce+递归,最传统的方法
function flatArr(array) {
    let newArr = array.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatArr(cur) : cur);
    }, [])
    return newArr
}
​
console.log(flatArr(arr))
效果如下:

 持续更新中......


5.如何模拟实现一个new的效果

(1)首先会创建一个 新的空对象 ,如obj

(2)obj对象会继承构造函数即Person的原型,即 obj.__proto===Person.prototype,这样obj就可以调用Person上的原型方法了。

(3)将Person的this指向obj,也就是将Person上的属性添加到新的obj对象上去,obj则可以调用Person中定义的属性了。

(4)如果Person构造函数没有返回对象,则返回新创建的对象(即this),否则返回return的对象。

 <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.prototype.say = function() {
            console.log('hello')
        }

        function myNew(cosntructor) {
            if (typeof cosntructor !== "function") {
                throw "myNew方法的第一个参数 必须是一个方法"
            }
            //改变原型
            let newObj = Object.create(cosntructor.prototype);

            //获取传入的参数
            console.log(arguments)
            let args = Array.from(arguments).slice(1);

            //执行constructor函数,获取结果,并将属性添加到新对象newObj上
            let result = cosntructor.apply(newObj, args);

            //判断result类型,如果是object或者function类型,则直接返回结果
            let originType = Object.prototype.toString.call(result); //获取内部属性值
            let isObject = originType === '[object Object]';
            let isFunction = originType === '[object Function]';
            if (isObject || isFunction) {
                return result;
            } else {
                //返回新对象
                return newObj;
            }
        }

        let obj = myNew(Person, 'zxg', 20)
        console.log(obj)
    </script>

6.如何模拟实现一个call、apply、bind的效果

//手写call
        Function.prototype.myCall = function(context, ...args) {
            if (typeof this !== 'function') {
                throw ('type error')
            };

            context.fn = this;
            let result = context.fn(...args);
            delete context.fn;
            return result
        }
        function foo() {
            console.log('bar():', ...arguments, this); //打印参数
            console.log(this)
            return 'hello'
        }

        let obj = {
            name: 'zxg',
            age: 23,
        }

        let res = foo.myCall(obj, obj.name, obj.age)
        let res1 = foo.call(obj,obj.name,obj.age)
        console.log(res)

//手写apply
        Function.prototype.myApply = function(context, args) {
            if (typeof this !== 'function') {
                throw ('type error')
            };

            context.fn = this;
            let result = context.fn(...args);
            delete context.fn;
            return result
        }

        function foo() {
            console.log('bar():', ...arguments, this); //打印参数
            console.log(this)
            return 'hello'
        }

        let obj = {
            name: 'zxg',
            age: 23,
        }

        let res = foo.myApply(obj, [obj.name, obj.age])
        let res1 = foo.apply(obj, [obj.name, obj.age])
        console.log(res);
        console.log(res1);

//手写bind
        Function.prototype.myBind = function(context, ...args) {
            if (typeof this !== 'function') {
                throw ('type error')
            };
            context.fn = this;
            return function() {
                // console.log(arguments)
                // console.log(this)
                let newArgs = Array.from(arguments)
                let result = context.fn(...[...args, ...newArgs]);
                delete context.fn;
                return result
            }
        }



        function foo() {
            console.log('bar():', ...arguments, this); //打印参数
            console.log(this)
            return 'hello'
        }

        let obj = {
            name: 'zxg',
            age: 23,
        }

        let res = foo.myBind(obj, obj.name, obj.age)
        let res1 = foo.bind(obj, obj.name, obj.age)
        res(6, 8, 9, 10);
        res1(8, 9, 12);


7.手写Object.create();思想:将传入的对象obj作为原型

 function create(obj){
            function F(){}
            F.prototype = obj
            return new F();
        }

8.手写instanceof方法;

此运算符用来判断构造函数的prototype属性是否出现在了传入对象的原型链中的任何位置。

function Person(name, age) {
            this.name = name;
            this.age = age;
        }

        const person = new Person('zxg', 23)

        function myInstanceof(obj, className) {
            let pointer = obj;
            while (pointer) {
                if (pointer === className.prototype) {
                    return true;
                }
                pointer = pointer.__proto__;
            }
            return false;
        }

        console.log(myInstanceof(person, Person))

9.手写promise(实现A+规范,872passing)

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

class Promise {
    constructor(executor) {
            console.log('我的promise');
            this.status = PENDING;
            this.value = null;
            this.reason = null;
            //定义数组将函数存放起来 
            this.resolveCallbacks = [];
            this.rejectCallbacks = [];
            try {
                //将resolve,reject绑定在Promise实例上
                executor(this.resolve.bind(this), this.reject.bind(this))
            } catch (error) {
                this.reject(error)
            }
        }
        //放在原型上的resolve
    resolve(value) {
            if (this.status === PENDING) {
                this.value = value;
                this.status = RESOLVED;
                this.resolveCallbacks.forEach(onFulfilled => {
                    onFulfilled()
                })
            }
        }
        //放在原型上的reject
    reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED;
            this.rejectCallbacks.forEach(onRejected => {
                onRejected()
            })
        }
    }

    then(onFulfilled, onRejected) {
        // onFulfilled(this.value);
        // console.log('then的状态', this.status);
        //默认处理
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === PENDING) {
                this.resolveCallbacks.push(() => {
                    setTimeout(() => {
                        // console.log('promise2', promise2)
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    });
                });
                this.rejectCallbacks.push(() => {
                    setTimeout(() => {
                        // console.log('promise2', promise2)
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    });
                })

            }
            if (this.status === RESOLVED) {
                //利用异步任务的宏任务拿到promise2
                setTimeout(() => {
                    // console.log('promise2', promise2)
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(x, promise2, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                });
            }
            if (this.status === REJECTED) {
                setTimeout(() => {
                    // console.log('promise2', promise2)
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(x, promise2, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                });
            }
        })
        return promise2

    }


}

function resolvePromise(x, promise2, resolve, reject) {
    // console.log(1)
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise!'))
    }
    // console.log(2)
    if (x && (typeof x === 'object' || typeof x === 'function')) {
        let called;
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(x, value => {
                    if (called) return;
                    called = true;
                    resolvePromise(value, promise2, resolve, reject);
                }, reason => {
                    if (called) return;
                    called = true
                    reject(reason)
                })
            } else {
                resolve(x)
            }
        } catch (error) {
            if (called) return
            called = true
            reject(error)
        }
    } else {
        resolve(x);
    }
}

Promise.deferred = function() {
    let deferred = {};
    deferred.promise = new Promise((resolve, reject) => {
        deferred.resolve = resolve;
        deferred.reject = reject;
    })
    return deferred;
}

module.exports = Promise;

参考了很多前辈手写promise的过程,感觉一点点根据原生promise的功能慢慢推慢慢实现是最容易理解的,慢慢调试,附上调试界面:

 再加一张A+规范测试图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值