前端面试题汇总之手写代码篇

本文详细介绍了手写Object.create方法创建原型对象,自定义instanceof判断原型链,实现new操作符及防抖、节流函数,还有浅拷贝与深拷贝的实现。深入浅出地展示了JavaScript核心技术的应用和优化技巧。
摘要由CSDN通过智能技术生成

1、手写Object.create方法

作用:用于创建一个以传递的对象作为原型的对象。
思路:将传入的对象作为原型

        // Object.create 创建一个以obj为原型的对象
        function create(obj){
            // 创建一个空对象
            function F(){}
            // 其原型为传递的对象
            F.prototype = obj
            // 返回对象
            return new F()
        }

2、手写instanceof方法

作用:判断构造函数的prototype属性是否出现在对象的原型链中任何位置。
思路:

  • 获取类型的原型
  • 获取对象的原型
  • 循环对象的原型是否等于类型的原型,直到对象原型为null
        function myinstanceof(left, right) {
            // 获取 
            let prototype = right.prototype
            let proto = Object.getPrototypeOf(left)
            // 判断原型是否存在
            while (true) {
                // 如果原型为null说明还没找到
                if (proto == null) return false
                // 如果相同则返回true
                if (proto == prototype) return true
                proto = Object.getPrototypeOf(proto)
            }
        }

3、手写new操作符

思路:

  • 创建一个空对象
  • 判断第一个参数是否为构造函数
  • 创建一个以构造函数原型为原型的对象并赋给空对象
  • 执行构造函数使this执行新对象
  • 返回结果
        function Person(name, age) {
            this.name = name
            this.age = age
        }
        Person.prototype.sayName = function () {
            return this.name
        }

        function objectFactory() {
            // 创建一个空对象
            let newObj = null
            // 获取arguments的第一项
            let constructor = Array.prototype.shift.call(arguments);
            // 判断传递的第一个参数是否为函数
            if (typeof constructor !== 'function') {
                console.error("type error");
                return
            }
            // 获取构造函数的原型,并赋值给新对象
            newObj = Object.create(constructor.prototype)
            // 执行构造函数
            let result = constructor.apply(newObj, arguments)
            // 判断返回结果是否是对象
            return result instanceof Object ? result : newObj
        }
        let p = objectFactory(Person, "hwm", 1)

4、手写防抖函数

思路:事件被触发,n秒后再执行回调,如果在这n秒内事件又被触发了,则重新计时。避免因为用户多次输入而触发事件多次。

        // 防抖函数
        function debounce(fn) {
            let timer = null
            // 返回一个函数
            return function () {
                // 获取触发的对象
                let context = this
                // 获取参数
                let args = arguments

                // 判断是否开启定时器
                if (timer) {
                    clearInterval(timer)
                    timer = null
                }
                // 设置定时器
                timer = setTimeout(() => {
                    // 执行函数
                    fn.apply(context, args)
                }, 500)
            }
        }
        // 输入框的回调函数
        function myInp(e) {
            console.log(inp.value);
        }
        var inp = document.querySelector('#inp')
        // 经过防抖处理函数
        inp.oninput = debounce(myInp)

5、手写节流函数

思路:事件被触发,n秒后再执行回调,如果在该时间内在次触发,事件不会生效,直到n秒后,再触发才会生效。

        function throttle(fn) {
            // 定义定时器
            let timer = null
            // 返回一个函数
            return function () {
                // 保存触发该函数的对象
                let context = this
                // 保存参数
                let args = arguments

                // 判断是否已经开启定时器
                if (timer) {
                    // 如果开启了定时器则直接返回不触发函数
                    return
                }
                // 设置定时器
                timer = setTimeout(() => {
                    fn.apply(args)
                    // 执行完,将timer重新赋值
                    timer = null
                }, 500)
            }
        }
        // 点击事件回调
        function MyBtn(e) {
            console.log('被点击');
        }

        var btn = document.querySelector('#btn')
        btn.onclick = throttle(MyBtn)

6、手写判断类型

思路:

  • 如果是null或undefined直接返回本身的字符串形式
  • 如果是引用类型,通过Object.prototype.toString.call判断
  • 如果基本类型,通过typeof来判断
        function getType(value) {
            // 判断value是否为null undefined
            if (value === null || value == undefined) {
                // 隐式转换 转为字符串
                return value + ""
            }
            // 判断是不是对象类型
            if (value instanceof Object) {
                // 再准确的判断类型
                let valueClass = Object.prototype.toString.call(value)
                // [object Array] 截取后面的类型
                let type = valueClass.split(" ")[1].split("]")[0].toLowerCase();
                // 返回类型
                return type
            }else {
                // 基本类型
                return typeof value
            }
        }

7、手写call方法

思路:

  • 先判断调用对象是否为函数
  • 判断上下文对象是否合法
  • 给上下文对象绑定方法
  • 调用上下文方法
        Function.prototype.myCall = function (context, ...args) {
            // 判断调用对象是否为函数
            if (typeof this !== 'function') {
                return
            }
            // 判断出入的上下文
            context = context || window
            // 创建唯一标识
            let key = Symbol('KEY')
            // 给上下文添加该方法的属性
            context[key] = this
            // 调用该方法
            let result = context[key](...args)
            // 删除该属性
            delete context[key]
            // 返回结果
            return result
        }
        let obj = {
            name: 'hwm'
        }
        function fn(a,b) {
            console.log(this);
            console.log(a,b);
        }
        fn.myCall(obj,1,2)

8、手写apply方法

思路:

  • 判断调用对象是否为函数
  • 判断上下文是否合法
  • 给上下文对象绑定方法
  • 调用上下文方法
        Function.prototype.myApply = function(context,args){
            // 判断调用对象是否为函数
            if(typeof this !== 'function'){
                return
            }
            // 判断上下文是否合法
            context = context || window
            // 唯一标识
            let key = Symbol('KEY')
            // 给上下文添加该属性
            context[key] = this
            // 调用该函数
            let result = context[key](...args)
            // 删除属性
            delete context[key]
            return result
        }
        let obj = {
            name: 'hwm'
        }
        function fn(a,b) {
            console.log(this);
            console.log(a,b);
        }
        fn.myApply(obj,[1,2])

9、手写bind方法

思路:

  • 判断调用对象是否为函数
  • 判断上下文对象是否合法
  • 保存调用对象
  • 返回一个函数
  • 在返回函数中给上下文对象绑定方法
  • 调用上下文方法
  • 删除该方法
        Function.prototype.mybind = function (context, ...args) {
            if (typeof this !== 'function') {
                return
            }
            // 保存函数
            let fn = this
            context = context || window
            return function () {
                let key = Symbol('KEY')
                context[key] = fn
                let result = context[key](...args)
                delete context[key]
                return result
            }
        }
        let obj = {
            name: 'hwm'
        }
        function fn(a, b) {
            console.log(this);
            console.log(a, b);
        }
        let a = fn.mybind(obj, 1, 2)
        a()

10、实现浅拷贝

如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址

Object.assign

注意:

  • 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
  • 因为null 和 undefined 不能转化为对象,所以第一个参数不能为null或 undefined,会报错。
let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);  
console.log(target);  // {a: 1, b: 2, c: 3}

扩展运算符

let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

数组方法

Array.prototype.slice

let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() === arr); //false

Array.prototype.concat

let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false

手写实现浅拷贝

思路:

  • 判断拷贝的对象是否合法
  • 根据拷贝对象的类型生成一个空对象
  • 遍历对象的属性,并给新对象添加
        function shallowCopy(object){
            // 判断传入的对象是否合法
            if(!object || typeof object !== 'object') return
            // 根据object类型生成一个同类型的空对象
            let newObj = Array.isArray(object) ? [] : {}
            // 遍历对象
            for(let key in object){
                // 判断属性是否存在对象上
                if(object.hasOwnProperty(key)){
                    // 只对第一次属性进行拷贝
                    newObj[key] = object[key]
                }
            }
            return newObj
        }
        let obj1 = {
            name:'hwm',
            color:[1,2,3]
        }
       let obj2 = shallowCopy(obj1)
       obj2.name = "obj2"
       obj2.color.push(4)
       console.log(obj1);
       console.log(obj2);

11、实现深拷贝

JSON.stringify()

let obj1 = {  a: 0,
              b: {
                 c: 0
                 }
            };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

函数库lodash的_.cloneDeep方法

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

手写实现深拷贝

思路:

  • 判断拷贝对象是否合法
  • 根据拷贝对象类型生成一个空对象
  • 遍历拷贝对象
  • 判断属性值是否为对象,如果是则递归该函数
        function deepCopy(object) {
            if (!object || typeof object !== 'object') return
            let newObj = Array.isArray(object) ? [] : {}

            for (let key in object) {
                if (object.hasOwnProperty(key)) {
                    newObj[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]
                }
            }
            return newObj
        }
        var obj1 = {
            a: 1,
            b: { f: { g: 1 } },
            c: [1, 2, 3]
        };
        let obj2 = deepCopy(obj1)
        obj2.b.f.g = 3
        obj2.c.push(4)
        console.log(obj1);
        console.log(obj2);

12、数组push方法

思路:

  • 遍历传入的参数
  • 以数组长度为下标赋值为每一项参数
        let arr = [1,2,3]
        Array.prototype._push = function () {
            for (let i = 0; i < arguments.length; i++) {
                this[this.length] = arguments[i]
            }
        }
        arr._push(4,5)

13、数组filter方法

思路:

  • 遍历数组每一项,将每一项都传入到回调函数中
  • 返回值为true,则放进新数组中
        Array.prototype._filter = function (fn) {
            // 判断fn是不是函数
            if (typeof fn !== 'function') {
                return
            }
            // 保存数组
            let self = this
            let res = []
            for (let i = 0, len = self.length; i < len; i++) {
                // 返回值结果为true则放进数组
                if (fn(self[i])) {
                    res.push(self[i])
                }
            }
            return res
        }
        let arr = [1, 2, 3, 4, 5]
        let a = arr._filter((item) => {
            if (item > 3) {
                return true
            }
        })

14、数组的map方法

思路:

  • 遍历数组每一项,将每一项都传入到回调函数中
  • 将返回值放进新数组中
        Array.prototype._map = function (fn) {
            if (typeof fn !== "function") {
                return
            }
            // 保存数组
            let self = this
            let res = []
            for(let i = 0; i < self.length; i++){
                // 将经过fn处理的结果放进数组
                res.push(fn(self[i]))
            }
            return res
        }
        let arr = [1,2,3]
        let a = arr._map((item)=>{
            return item += 1
        })

15、将js对象转换为树形结构

思路:

  • 判断传入的参数是否为数组
  • 遍历每一项,以map将id作为key元素本身作为value保存起来
  • 遍历每一项,获取每一项的父元素id
  • 判断父元素是否存在ma对象中,存在则判断是否存在child属性,不存在则添加属性,并将当前项添加进数组中
  • 如果父元素不存在map中,则将该元素添加进结果数组中
        let source = [{
            id: 1,
            pid: 0,
            name: 'body'
        }, {
            id: 2,
            pid: 1,
            name: 'title'
        }, {
            id: 3,
            pid: 2,
            name: 'div'
        }]
        function jsonTree(data) {
            //  返回结果
            let result = []
            //  判断参数是否为数组
            if (!Array.isArray(data)) {
                return result
            }
            // 使用map将数组存储起来
            let map = {}
            data.forEach(item => {
                map[item.id] = item
            })

            data.forEach(item => {
                // 获取到每一项的父元素
                let parent = map[item.pid]
                // 给父元素添加孩子属性
                // console.log(parent);
                if (parent) {
                    // 判断父元素是否有child属性
                    if (!parent.child) {
                        parent.child = []
                    }
                    // 将该item项添加到父元素的child属性中
                    parent.child.push(item)
                }else {
                    // 只有一个为根所以只有一个元素的父元素为undefined
                    result.push(item)
                }
            })
            return result
        }
         let tree = jsonTree(source)
  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值