JavaScript高频手写面试题

1.手写instanceof

2.实现数组的map方法

3.使用数组内置的方法reduce方法实现map方法

4.手写reduce方法

 5.数组扁平化

6.深拷贝

7.浅拷贝

8.手动实现call,apply,bind

9.手写new

10.手写primose

11.手写ajax

12.手写防抖

13.手写节流

14.手写promise加载图片




1.手写instanceof:

instanceof在查找的过程中会遍历左边的原型链,直到找到右边变量的prototype,如果查找失败就返回false
            // instanceof的作用是判断当前目标对象是否是指定的类型
            // 解决思路是:instanceof在查找的过程中会遍历左边的原型链,直到找到右边变量的prototype,如果查找失败就返回false
            let myInstanceof = (target, origin) => {
                while (target) {
                    if (target.__proto__ === origin.prototype) {
                        return true
                    }
                    target = target.__proto__
                }
                return false
            }

            let arr = [2, 3, 5]
            console.log(myInstanceof(arr, Array)) // true
            console.log(myInstanceof(arr, Object)) // true

2.实现数组的map方法

        <script>
            // map() 方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。
            Array.prototype.myMap = function (fn, context) {
                let res = [] // 用来保存返回的新数组
                context = context || []
                let arr = this //由于方法是添加到数组原型上的,所以谁调用这个方法this就指向哪个数组
                for (let i = 0; i < arr.length; i++) {
                    res.push(fn(arr[i]))
                }
                return res
            }

            let arr = [1, 2, 3]
            let res = arr.myMap((item) => item * 2)
            console.log(res)
        </script>

 3.使用数组内置的方法reduce方法实现map方法:

            Array.prototype.myMap = function (fn, thisValue) {
                var res = []
                thisValue = thisValue || []
                //   pre:累加的值,cur:当前数组的值,index:当前的值的索引,arr:当前的数组
                // this指的是当前调用这个方法是数组
                this.reduce(function (pre, cur, index, arr) {
                    return res.push(fn.call(thisValue, cur, index, arr))
                }, [])
                return res
            }
            var arr = [2, 3, 1, 4]
            var res = arr.myMap(function (item, index, arr) {
                return item + 1
            })
            console.log(res) // 3,4,2,5

 4.手写reduce方法:

    <body>
        <!-- 
            reduce方法的使用,
            参数1传递一个函数作为参数,函数中有四个参数:
            total:累加项的和
            currValue:当前循环的项
            currIndex:当前循环项的索引
            arr:当前的数组

            参数2是一个初始值:如果传递了值,它就会作为total的初始值,arr的第一项就会作为currvalue的值
            如果没有传递值:arr的第一项就是作为total的值,第二项就会作为currvalue的值
            
         -->
        <!-- <script>
            var arr = [1, 2, 3, 4]
            var i = 0
            var y = 0
            const res1 = arr.reduce(function (total, currValue, currIndex, arr) {
                console.log(i)
                i++
                return total + currValue
            })
            const res2 = arr.reduce(function (total, currValue, currIndex, arr) {
                console.log(y)
                y++
                return total + currValue
            }, 10)
            console.log(res1) // 10
            console.log(res2) // 20
        </script> -->

        <script>
            //实现reduce方法
            //如果有initValue,那么total的值就是initValue,currValue的值就是arr的第一项
            //如果没有initValue,那么total的值就是arr的第一项
            var arr = [1, 2, 3, 4]
            Array.prototype.myReduce = function (fn, initValue) {
                var i = 0
                // 1.先判断initValue的值是否存在
                if (
                    Object.prototype.toString.call(initValue) === '[object Undefined]'
                ) {
                    // 如果不存在,initvalue的值就是初始值
                    initValue = this[0]
                    i = 1
                    console.log('false')
                }

                for (i; i < this.length; i++) {
                    initValue = fn(initValue, this[i], i, this)
                }
                return initValue
            }

            const res = arr.myReduce(function (total, currValue, currIndex, arr) {
                return total + currValue
            }, 10)
            console.log(res)
        </script>
    </body>

 5.数组扁平化

    <body>
        数组扁平化就是将多维数组转为一维数组
        <!-- <script>
            // 方法一:使用flat,这是es6新增方法方法,flat方法的参数是需要转换的维度
            var arr = [1, 3, 4, [3, [3, 5, [6, 1, 8]]]] //这是四维数组
            console.log(arr.flat())
            console.log(arr.flat(Infinity)) // 如果不知到数组的维度可以使用Infinity
        </script> -->

        <!-- 利用concat实现数组扁平化,不能和flat一样指定维度,concat可以链接两个数组或者多个数组-->

        <script>
            var arr = [1, 3, 4, [3, [3, 5, [6, 1, 8]]]] //这是四维数组
            function myFlat(arr) {
                let res = []
                for (let i = 0; i < arr.length; i++) {
                    if (Array.isArray(arr[i])) {
                        res = res.concat(myFlat(arr[i]))
                    } else {
                        res.push(arr[i])
                    }
                }

                return res
            }
            console.log(myFlat(arr))
        </script>
    </body>

 6.深拷贝:

(1)使用递归实现深拷贝

        <script>
            //创建一个引用类型的数据
            var oldObj = {
                name: 'zs',
                happy: ['吃饭', '睡', '打豆豆'],
                parent: {
                    father: 'li',
                    mather: 'wang'
                }
            }
            //创建一个新对象
            var newObject = {}

            //创建一个函数用于实现深拷贝,主要使用了递归
            function cloneDeep(target, source) {
                //先循环获取每一项
                for (let i in source) {
                    let item = source[i]
                    //判断每一项中的数据是否是数组
                    if (item instanceof Array) {
                        target[i] = []
                        cloneDeep(target[i], item)
                    } else if (item instanceof Object) {
                        //如果当前项是对象,就返回
                        target[i] = {}
                        cloneDeep(target[i], item)
                    } else {
                        target[i] = item
                    }
                }
                return target
            }
            cloneDeep(newObject, oldObj)

            oldObj.name = '李四'
            console.log(oldObj)
            console.log(newObject)
        </script>

(2)使用Object.create()方法实现深拷贝

        <script>
            //创建一个引用类型的数据
            // var oldObj = {
            //     name: "zs",
            //     happy: ['吃饭', '睡', '打豆豆'],
            //     parent: {
            //         father: 'li',
            //         mather: 'wang'
            //     }
            // };

            // //使用方法获取一个拷贝来的方法
            // var newObj = Object.create(oldObj);
            // console.log(newObj);
            // console.log(newObj.__proto__);

            //注意使用该方法实现的深拷贝它的属性是放在它的原型上的,这些属性不是newObj自己拥有的

            // object.create()方法的实现原理:利用了原型是继承
            function create2(o) {
                function NewObj() {}
                NewObj.prototype = o
                return new NewObj()
            }

            //创建一个引用类型的数据
            var oldObj = {
                name: 'zs',
                happy: ['吃饭', '睡', '打豆豆'],
                parent: {
                    father: 'li',
                    mather: 'wang'
                }
            }

            //使用方法获取一个拷贝来的方法
            var newObj = create2(oldObj)
            console.log(newObj)
            console.log(newObj.__proto__)
        </script>

 (3)使用jquery的extend方法实现深拷贝

    <script src="./query.js"></script>
    <script>
        //创建一个引用类型的数据
        var oldObj = {
            name: "zs",
            happy: ['吃饭', '睡', '打豆豆'],
            parent: {
                father: 'li',
                mather: 'wang'
            }
        };
        var newObj = $.extend({}, oldObj);
        console.log(newObj);

        extend方法的使用方法
        // extend(obj1,obj2,obj3):她将obj2,obj3的值复制给obj1,如果有重复的属性就覆盖她
    </script>

(4)使用JSON.parse和JSON.stringify方法实现深拷贝

    <script>
        //创建一个引用类型的数据
        var oldObj = {
            name: "zs",
            happy: ['吃饭', '睡', '打豆豆'],
            parent: {
                father: 'li',
                mather: 'wang'
            }
        };

        var newObj = JSON.parse(JSON.stringify(oldObj));
        newObj.name = "李四";
        console.log(oldObj);
        console.log(newObj)
    </script>

7.浅拷贝

(1)使用扩展运算符

    <!-- 什么是扩展运算符?
        扩展运算符就是三个点,扩展运算符的作用就是将数据或者类数组对象转为用逗号隔开的值
    -->
    <script>
        var arr = [1, [7, [9]], {
            a: "1"
        }, function() {}, null, undefined, NaN];


        var result = [...arr];
        console.log(result);
        //修改源对象中的某个值
        arr[2].a = "222";
        console.log(arr);
        console.log(result);
    </script>

(2)使用assgin方法

    <script>
        var arr = [1, [7, [9]], {
            a: "1"
        }, function() {}, null, undefined, NaN];


        var result = Object.assign(arr);
        console.log(result);
        //修改源对象中的某个值
        arr[2].a = "222";
        console.log(arr);
        console.log(result);
    </script>

 8.手动实现call,apply,bind

        <script>
            // 手动实现apply(obj, [arg1, arg2, arg3])
            Function.prototype.myApply = function (context) {
                let obj = context || window
                obj.fn = this
                const arg = arguments[1] || [] // 如果传递的有参数,那么第二个就是传毒的参数数组
                let res = obj.fn(...arg)
                delete obj.fn
                return res
            }
            function f(a, b) {
                console.log(a, b)
                console.log(this.name)
            }
            let obj = {
                name: '这是'
            }
            f.myApply(obj, [1, 2])
        </script>

        <script>
            // 手动实现call(obj,arg1,arg2)
            Function.prototype.myCall = function (context) {
                let obj = context || window
                obj.fn = this
                // 获取参数,由于参数不确定传递过来的个数,就先去掉第一个参数,他就是要指向的this
                let arr = [...arguments].slice(1)
                res = obj.fn(...arr)
                delete obj.fn
                return res
            }
            function f(a, b) {
                console.log(a, b)
                console.log(this.name)
            }
            let obj = {
                name: '栗色'
            }
            f.myCall(obj, 1, 2)
        </script>

        <script>
            // 手动实现bind(obj,[arg1,arg2])由于bind函数支持柯里化
            Function.prototype.myBind = function (context) {
                let obj = context || window
                let fn = this
                // 获取第一次传递过来的参数
                let arg = Array.prototype.slice.call(arguments, 1) // 由于第一位是obj,所以需要将裁剪
                return function () {
                    let arr = Array.prototype.slice.call(arguments)
                    fn.apply(obj, arg.concat(arr))
                }
            }
            let obj = {
                name: '展示'
            }
            function fn(a, b) {
                console.log(a, b)
            }

            let call = fn.bind(obj, [2, 3])
            call(20)
        </script>

 9.手写new

    <body>
        new的操作: (1)创建一个空对象,
        (2)将对象的__proto__指向构造函数的原型对象 (3)利用apply改变this的指向,
        (4)返回结果
        <script>
            function Person(name, age) {
                this.age = age
                this.name = name
            }
            Person.prototype.sayHi = function () {
                console.log('hi,' + this.name)
            }

            var p1 = new Person('张三', 20)
            console.log(p1)

            // 手动实现new
            function myCreate() {
                // 创建一个空对象
                let obj = {}
                // 获取传递过来的构造函数
                let fn = [].shift.call(arguments) // shift方法将数组的第一项弹出来并且返回弹出的第一项,这里的[]填不填元素都没有关系
                // 将空对象的__proto__指向构造函数的prototype(获取构造函数原型上的方法)
                obj.__proto__ = fn.prototype
                // 使用apply修改this的指向,将传递过来的参数赋值给obj(获取属性)
                console.log(arguments)
                let res = fn.apply(obj, arguments)
                // 确保返回的是一个对象(以防传递进的fn不是构造函数)
                return typeof res === 'object' ? res : obj
            }

            var p2 = myCreate(Person, '李四', 30)
            console.log(p2)
        </script>
    </body>
View Code

10.手写primose

    <body>
        <script>
            // promise 是一个容器,存放的是 未来的某个值
            // 语法上来说,就是一个对象

            // console.log(1);
            // const promise = new Promise((resolve, reject) => {
            //   console.log(2);
            // let num = Math.random();
            // if (num > 0.5) {
            //   resolve('成功');
            // } else {
            //   reject('失败');
            // }
            // });
            // promise.then(
            //   (res) => {
            //     console.log(4, res);
            //   },
            //   (err) => {
            //     console.log(5, err);
            //   }
            // );
            // console.log(3);

            // promise 有几个状态,是常量,先定义起来
            const PENDDING = 'pendding' // 等待状态
            const FULFILLED = 'fulfilled' // 执行状态
            const REJECTED = 'rejected' // 拒绝状态

            class MyPromise {
                // cb为创建的promise的时候传递进来的函数
                constructor(cb) {
                    // 一个promise要关注几个点
                    this.status = PENDDING // 当前的promise的状态
                    this.value = undefined // 成功以后的返回值
                    this.reason = undefined // 失败以后的返回值

                    // 使用箭头函数可以固定this的指向,不然在cb中调用resolve,this就找不到指向
                    const resolve = (val) => {
                        if (this.status === PENDDING) {
                            // 改变状态
                            this.status = FULFILLED
                            this.value = val
                        }
                    }
                    const reject = (err) => {
                        if (this.status === PENDDING) {
                            this.status = REJECTED
                            this.reason = err
                        }
                    }

                    try {
                        // 传递cb进来的时候,它是需要两个参数的,这两个参数分别是函数,所以在上面定义了两个函数
                        cb(resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                }

                then(onFulfilled, onRejected) {
                    if (this.status === FULFILLED) {
                        onFulfilled(this.value)
                    }

                    if (this.status === REJECTED) {
                        onRejected(this.reason)
                    }
                }
            }

            const p = new MyPromise((resolve, reject) => {
                let num = Math.random()
                if (num > 0.5) {
                    resolve('成功')
                } else {
                    reject('失败')
                }
            })
            p.then(
                (res) => {
                    console.log('请求成功了:', res)
                },
                (err) => {
                    console.log('请求失败了:', err)
                }
            )
        </script>
    </body>
View Code

 11.手写ajax

    <body>
        ajax请求的五个步骤: (1)创建xmlHttpRequest对象
        (2)设置请求方法和请求的地址 (3)使用send发送情求 (4)监听状态变化
        (5)最后接受返回的数据
        <script>
            function myAjax() {
                let xhr = new XMLHttpRequest()
                xhr.open('get', 'url')
                xhr.onreadystatechange = () => {
                    // xhr的readyState的状态有0-4:0(请求未就绪)1(已经建立服务器链接)2(请求发送)3(请求处理中) 4(已经接受了响应)
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            let string = xhr.responseText
                            let object = JSON.parse(string)
                        }
                    }
                }

                // 发送请求
                xhr.send()
            }
        </script>

        <!-- 使用promise实现ajax -->
        <script>
            function myAjax(url) {
                const p = new Promise(function (resolve, reject) {
                    // 创建xhr对象
                    let xhr = new XMLHttpRequest()
                    // 创建链接
                    xhr.open('get', url)
                    // 监听状态改变
                    xhr.onreadystatechange = () => {
                        if (xhr.readyState === 4) {
                            if (xhr.status >= 200 && xhr.status < 300) {
                                resolve(JSON.parse(xhr.responseText))
                            } else {
                                reject('请求失败了')
                            }
                        }
                    }
                    // 发送请求
                    xhr.send()
                })

                return p
            }
        </script>
    </body>
View Code

 12.手写防抖

    <body>
        <input id="input1" />
        <script>
            // 防抖就是在在触发了事件的一段时候后执行函数,如果在这段时间里又触发了,就重新计数
            // fn是指向的函数,而delay是执行的时间
            function debounce(fn, delay) {
                if (typeof fn !== 'function') {
                    throw new TypeError('fn不是函数')
                }
                let timer
                // 返回一个函数是因为它不是已返回就调用,而是当触发了时间的时候次啊会调用它
                return function () {
                    var _this = this // 这里将this赋值给_this是因为定时器中的this是指向window的
                    var args = arguments // 这里是获取调用函数时候传递进来的参数
                    // 如果定时器存在,就先清除定时器
                    if (timer) {
                        clearTimeout(timer)
                    }
                    // 重置定时器
                    timer = setTimeout(function () {
                        fn.apply(_this, args)
                    }, delay)
                }
            }

            var input1 = document.getElementById('input1')
            input1.addEventListener(
                'keyup',
                debounce(() => {
                    console.log(input1.value)
                }, 600)
            )
        </script>
    </body>
View Code

13.手写节流

<!--
 * @Description: 节流函数就是连续触发事件,只在n秒中执行一次,常用场景;如拖动dom,如果使用防抖的话据会出现卡顿的情况,因为它只在结束的 时候触发一次
 使用节流就流顺的多
 * @Version: 2.0
 * @Autor: Seven
 * @Date: 2021-02-16 12:25:47
 * @LastEditors: Gan
 * @LastEditTime: 2021-02-16 12:42:27
-->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>手写节流函数</title>
    </head>
    <body>
        <script>
            function throttle(fn, delay) {
                // 设置一个定时器
                let timer
                // 返回一个函数,当触发事件的时候,就回触发这个事件
                return function () {
                    // 将调用该函数的元素给_this,因为定时器中的this是执行window的
                    var _this = this
                    // 获取调用函数的时候,传递进来的参数
                    var args = arguments
                    // 判断定时器是否存在,如果存在就表示当前的时间还没有过
                    if (timer) {
                        return
                    }
                    timer = setTimeout(function () {
                        fn.apply(_this, args)
                        timer = null
                    }, delay)
                }
            }
        </script>
    </body>
</html>
View Code

14.手写promise加载图片

function getData(url) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url,
            success(data) {
                resolve(data)
            },
            error(err) {
                reject(err)
            }
        })
    })
}

const url1 = 'image1Ur1'
const url2 = 'image1Ur2'
const url3 = 'image1Ur3'

getData(url1)
    .then((data1) => {
        console.log(data1)
        getData(url2)
    })
    .then((data2) => {
        console.log(data2)
        getData(url3)
    })
    .then((data3) => {
        console.log(data3)
    })
    .catch((err) => {
        console.log(err)
    })
View Code
原文地址:https://www.cnblogs.com/zhilili/p/14394362.html