前端面试常见手写题

168 阅读4分钟

手写实现call

call()的作用是改变函数执行的this指向,需要传至少一个参数,第一个参数为指向的对象和单独给出的一个或多个参数来调用函数,注意: call()只会改变一次this指向

 let obj = {
            name: '张三',
            age: 18
        }//先定义一个要指向的对象
        
    function fn(q, w, e) {
        console.log(this)
        console.log(q, w, e)
        return 888
    }//定义一个函数来调用手写的myCall
     //call不能改变this的两种情况 1.箭头函数 2.bind后的新函数
    Function.prototype.myCall = function(target, ...arg) {
    //定义一个myCall挂在Function的原型上 让每个函数都可以调用
            // this --->  fn  现在this指向调用myCall的fn这个函数
            let fn = this; // 定义一个变量赋值为fn这个函数
            let a = Symbol('mycall')//定义一个唯一值
            target[a] = fn;//给要指向的对象新增一个属性,属性名为唯一值,属性值为fn这个函数
            let res = target[a](...arg) //target调用的这个函数,所以this是这个对象
            //res是fn执行结果
            delete target[a] //只改变一次this指向,执行完后删除这个属性
            return res //myCall的执行结果就是fn的执行结果
        }
        fn.myCall(obj,1,2,3)

手写实现bind

bind的作用: bind()  方法返回一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

 let obj = {
        name: '李四',
        age: '18'
    }
    Function.prototype.myBind = function(target, ...arg) {
        let fn = this;//定义一个变量赋值为调用myBind的函数
        return function(...arg2) {//返回一个新函数
            return fn.call(target, ...arg, ...arg2)//新函数中返回fn的执行结果,使用call改变this指向
           // 将两个函数形参放在一起,新函数的形参会放在myBind形参的后面
        }
    }

    function fn(...arg) {
        console.log(arguments);
        console.log(this);//此处打印obj这个对象
    }
    let res = fn.myBind(obj, 1, 2, 3)
    console.log(res( 4, 5, 6));

手写实现apply

和call的唯一区别就是 第二个参数是一个集合

   let obj = {
        name: 'leizai',
        sex: '1',
    }
    Function.prototype.myapply = function(target, ...arg) {
        let fn = this, //当前的this指向调用此方法的函数
            a = Symbol('myapply'); //定义一个唯一值 作为属性名
        target[a] = fn; //将唯一值a赋值为fn
        let res = target[a](...arg); //执行这个函数并传参
        delete target[a]; //调用完成删除这个属性
        return res; //返回函数的执行结果
    }

    function fn() {
        console.log(this);
        console.log(arguments);
    };
    let res = fn.myapply(obj, 1, 2, 3, 4, 5, 6);
    console.log(res);

手写实现函数防抖

函数的防抖主要应用在按钮的频繁点击以及input框的连续输入

防抖的作用: 防止用户多次点击触发多次请求,用户点击后。会有一段等待时间,在等待时间内,如果用户重复则重新定时,,并且只执行最后一次。

    const debounce = (fn, ...argu) => {
        let sign = null; //标记定时器
        return () => {
            clearTimeout(sign) //每次进来先清除定时器
            sign = setTimeout(() => {
                fn.apply(this, argu); //只执行最后一次
            }, 200)
        }
    }

手写函数节流

函数的节流主要应用于onscroll和touchmove等此类触发频率高的事件

节流的作用: 减低函数的触发次数,减少栈内存的占用,以免造成页面卡顿

const throttle = (fn, ...arg) => {
        let flag = true; //定义标记
        return () => {
            if (!flag) return; //边界判断 防止指定时间内再被执行
            flag = false; //将标记赋值为false,此时无法进入此函数
            setTimeout(() => {
                fn.apply(this, arg); //执行函数
                flag = true; //将flag重新赋值为true,以便执行下一次
            }, 200)

        }
    }

正则匹配身份证号码

let Id = 130432199811282111,
reg = /^1[\d]{16}[\d|x]$/;
let res = reg.test(id);
console.log(res);

正则解析URL

   let obj = {};
    let url = 'https://baidu.com?name=aa&&id=1',
        reg = /([^?&=]+)=([^?&=]+)/g;
    url.replace(reg, ($1, $2, $3) => {
    //$1 为整个正则捕获的内容  $2为第一个分组捕获的内容  $3为第二个分组捕获的内容
        obj[$2] = $3;
    });
    console.log(obj);

正则匹配邮箱

 let email = '3077251786@qq.com',
        reg = /^\d{5,11}\@qq\.com$/, //简易匹配qq邮箱
        reg1 = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; //完整匹配邮箱
    let res = reg.test(email);
    console.log(res);

正则匹配手机号

   let phone = '15203204450';
    let reg = /^1[3-8]\d{9}$///简易版
        reg1 = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/;//完整版
    let res = reg.test(phone);
    console.log(res);

手写实现new

  function myNew(target, ...arg) {
        let obj = {}; //第一步先创建一个对象
        obj.__proto__ = target.prototype; //第二步将对象的隐式原型指向target的原型
        let res = target.call(obj, ...arg);//使用call改变this指向
        //如果函数执行结果是一个引用数据类型 则返回执行结果  否则返回obj这个对象
        return typeof res == 'object' ? res : obj;
    };

    function Person(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    };
    let p1 = new Person('小明', 15, 1);
    let p2 = myNew(Person, '小李', 16, 2);
    console.log(p1, p2);

手写实现instanceof

instranceof使用来检测一个构造函数的prototype(显式原型)是否在某个实例对象的原型链上,是四个检测数据类型的方法中的一种。

   let ary = [];
    function myIstanceof(example, type) {
        //存储example的隐式原型
        let value = example.__proto__;
        while (value && value !== type.prototype) {
            //证明value不是null;而且value不等于b的显式原型
            //下一步就是 看a.__proto__.__proto__
            value = value.__proto__; //通过隐式原型向上查找
        }
        return value == null ? false : true
    }
    console.log(myIstanceof(ary, Array));
    console.log(myIstanceof(ary, Number));
    console.log(myIstanceof(ary, Date));

手写实现create

Object.create执行返回一个空对象,对象的__proto__指向传递的参数;

    function myCreate(value) {
        let obj = {};
        // obj.__proto__ = value; //__proto__是非法属性 
        function Person() {};
        Person.prototype = value; //原型重写 内置类的原型不可更改
        let a = new Person;//利用new执行的原理 创造一个实例
        return a;
    }
    console.log(myCreate(Array.prototype));

手写实现JSONP

JSONP是跨域的解决方案之一,利用script标签的src属性不会被浏览器的同源策略限制的机制,实现跨域。

(function () {
        //工具方法 
        const toString = Object.prototype.toString;
        //检测是否为纯粹对象
        const isObject = function isObject(obj) {
            let proto, Ctor;
            //判断obj是否不存在 或者是否 非对象
            if (!obj || toString.call(obj) !== '[object Object]') return;
            //获取obj的原型
            proto = Object.getPrototypeOf(obj);
            if (!proto) return true;
            //获取obj原型上的 函数本身
            Ctor = proto.hasOwnProperty('constructor') && proto.constructor;
            return typeof Ctor === "function" && Ctor === Object;
        };
        //将参数转为传参指定格式
        const stringify = function stringify(obj) {
            let str = ``;
            Reflect.ownKeys(obj).forEach(key => {
                //获取每一项的属性值
                let value = obj[key];
                str += `&${key}=#{value}`;
                console.log(str);

            });
            //返回一个从索引1开始的字符串 删除开头的&
            return str.substring(1);
        };
        //核心方法
        const jsonp = function jsonp(url, options) {
            //格式检验 & 合并默认配置项
            if (typeof url !== 'string') throw new Error('url is not a string');
            if (!isObject(options)) options = {};
            //合并默认配置项
            let {
                pamas,
                jsonpName
            } = {
                pamas: '',
                jsonName: 'callback',
                ...options
            };
            /* 或者 Object.assign({
             params: null,
             jsonpName: "callback"
             }, options); */
            //返回peomise实例
            return new Promise((resolve, reject) => {
                //移除创建的全局函数和script标签
                const cler = function cler() {
                    delete window.jsonName;
                    document.removeChild(script);
                };
                //先创建全局函数 不能造成全局变量污染
                const name = `json${+new Date}`;
                window[name] = function (data) {
                    // 请求成功:会把创建的全局函数执行,DATA就是从服务器获取的结果
                    resolve(data);
                    cler();
                };
                //处理url
                if (isObject(pamas)) {
                    url += `${url.includes('?')?'&':"?"}${stringify(options)}`;
                };
                url += `${url.includes("?") ? "&" : "?"}${jsonpName}=${name}`;
                //创建script标签
                const script = document.createElement('script');
                script.src = url;
                //如果script请求失败返回失败态
                script.onerror = function () {
                    reject();
                    cler();
                };
                //将标签插入到页面内
                document.body.appendChild(script);
            })
        };
        /* 暴露API */
        if (typeof module === 'object' && typeof module.exports === 'object') module.exports = jsonp;
        if (typeof window !== 'undefined') window.jsonp = jsonp;
    })()