面试!你真的准备好了吗?|手写API系列梳理

"不畏惧,不讲究,未来的日子好好努力"——大家好!我是小芝麻😄

标题党,它又、又、又来了......

这篇文章是集合了小芝麻之前所有写过和没写过的API的汇总,为后面整体复习做准备,如果你也刚好需要,那就跟小芝麻一起来复习吧😄 ;

一、仿写系列

1、重写call

(proto => {
    function myCall(thisArg, ...args) {
        thisArg = thisArg == undefined ? window : thisArg;
        let type = typeof thisArg;
        if (!/^(object|function)$/.test(type)) {
            if (/^(symbol|bigint)$/) {
                thisArg = Object(thisArg);
            } else {
                thisArg = new thisArg.constructor(thisArg);
            }
        }
        let key = Symbol('key');
        thisArg[key] = this;
        let result = thisArg[key](...args);
        delete thisArg[key];
        return result;
    }
    proto.myCall = myCall;
})(Function.prototype)

2、重写apply

(proto => {
    function myApply(thisArg, args) {
        thisArg = thisArg == undefined ? window : thisArg;
        let type = typeof thisArg;
        if (!/^(object|function)$/.test(type)) {
            if (/^(symbol|bigint)$/) {
                thisArg = Object(thisArg);
            } else {
                thisArg = new thisArg.constructor(thisArg);
            }
        }
        let key = Symbol('key');
        thisArg[key] = this;
        let result = thisArg[key](...args);
        delete thisArg[key];
        return result;
    }
    proto.myApply = myApply;
})(Function.prototype)

3、重写bind

(proto => {
    function myBind(thisArg, ...args) {
        let _this = this;
        thisArg = thisArg == undefined ? window : thisArg;
        let type = typeof thisArg;
        if (!/^(object|function)$/.test(type)) {
            if (/^(symbol|bigint)$/) {
                thisArg = Object(thisArg);
            } else {
                thisArg = new thisArg.constructor(thisArg);
            }
        }
        return function an(...innerArgs) {
            _this.call(thisArg, ...args.concat(innerArgs));
        }
    }
    proto.myBind = myBind;
})(Function.prototype)

4、重写new

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}
//==========重写开始
function _new(Func, ...args) {
    let obj = Object.create(Func.prototype);
    let result = Func.call(obj, ...args);
    if (result !== null && /^(object|function)$/.test(typeof result)) return result;
    return obj;
}
//==========重写结束
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true 
二、工具方法系列

1、防抖

debounce:函数防抖不是完成某个事件就去执行某函数,而是在某个间隔时间内只执行一次,避免函数的过多执行

  • @params:
    • func:需要执行的函数
    • wait:设置的间隔时间
    • immediate:设置为ture时,调用触发于开始边界而不是结束边界
  • @return: 返回可被调用的函数
let debounce = function (func, wait, immediate) {
    //=>result用来存储函数执行返回的结果
    //=>timeout记录定时器
    let result,
        timeout = null;
    //=>返回可被执行的函数
    return function (...args) {
        //=>now记录的是事件触发的时候立即执行,还是需要等待间隔事件后执行
        let context = this,
            now = immediate && !timeout;
        //=>每一次设置新的定时器等待之前,都要先清空上一次设置的,确保间隔时间内只执行一次
        clearTimeout(timeout);
        //=>设置定时器:到达时间间隔后执行函数
        timeout = setTimeout(() => {
            timeout = null;
            if (!immediate) result = func.apply(context, args);
        }, wait);
        //=>如果是事件触发就执行,把函数执行即可
        if (now) result = func.apply(context, args);
        return result;
    };
};

2、节流

throttle:函数节流是为了缩减执行频率,当达到了一定的时间间隔就会执行一次

  • @params:
    • func:需要执行的函数
    • wait:设置的间隔时间
  • @return: 返回可被调用的函数
let throttle = function (func, wait) {
    let timeout = null,
        result = null,
        previous = 0; //=>上次执行时间点
    return function (...args) {
        let now = new Date,
            context = this;
        //=>remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间间隔
        let remaining = wait - (now - previous);
        if (remaining <= 0) {
            clearTimeout(timeout);
            previous = now;
            timeout = null;
            result = func.apply(context, args);
        } else if (!timeout) {
            timeout = setTimeout(() => {
                previous = new Date;
                timeout = null;
                result = func.apply(context, args);
            }, remaining);
        }
        return result;
    };
};

3、深克隆

深克隆 VS 浅克隆|深比较 VS 浅比较|回调函数

方法一:

let clone = JSON.parse(JSON.stringify(obj));

方法二:

function _cloneDeep(obj) {
    if (obj === null) return null;
    if (typeof obj !== "object") return obj;
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) return new Date(obj);
    let cloneObj = new obj.constructor;
    for (let key in obj) {
        if (!obj.hasOwnProperty(key)) break;
        cloneObj[key] = _cloneDeep(obj[key]);
    }
    return cloneObj;
}

4、深合并

function _assignDeep(obj1, obj2) {
    let obj = _cloneDeep(obj1);
    for (let key in obj2) {
        if (!obj2.hasOwnProperty(key)) break;
        let v2 = obj2[key],
            v1 = obj[key];
        if ((v1 !== null && typeof v1 === "object") && (v2 !== null && typeof v2 === "object")) {
            obj[key] = _assignDeep(v1, v2);
            continue;
        }
        obj[key] = v2;
    }
    return obj;
}

5、深层次比较

  • 基本数据类型的值,基于 === 比较 即可
  • 函数:都转换为字符串再进行比较
  • 对象:
    • => 正则/日期:都转换为字符串再进行比较
    • => 普通对象/数组对象等:
      • 1)私有属性的个数
      • 2)分别遍历每个属性,看看属性值是否一致
function _is(val1, val2) {
    const type1 = val1 === null ? 'null' : typeof val1,
        type2 = val2 === null ? 'null' : typeof val2;
    // 函数
    if (type1 === "function" && type2 === "function") {
        return val1.toString() === val2.toString();
    }
    // 对象
    if (type1 === "object" && type2 === "object") {
        // 正则和日期
        const ct1 = val1.constructor,
            ct2 = val2.constructor;
        if ((ct1 === RegExp && ct2 === RegExp) || (ct1 === Date && ct2 === Date)) {
            return val1.toString() === val2.toString();
        }
        // 其它对象
        const keys1 = Object.keys(val1),
            keys2 = Object.keys(val2);
        if (keys1.length !== keys2.length) return false;
        for (let i = 0; i < keys1.length; i++) {
            let key1 = keys1[i],
                key2 = keys2[i];
            if (key1 !== key2) return false;
            let item1 = val1[key1],
                item2 = val2[key2];
            let flag = _is(item1, item2);
            if (!flag) return false;
        }
        return true;
    }
    // 其它
    return val1 === val2;
}

6、数据类型检测

function toType(obj) {
    let class2type = {};
    ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol"].forEach(
        item => {
            class2type["[object " + item + "]"] = item.toLowerCase();
        });
    if (obj == null) return obj + "";
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[class2type.toString.call(obj)] || "object" :
        typeof obj;
}

let obj = function () {};
console.log(toType(obj))

7、原型继承

继承这里详细可以看:JS中的多种继承方式

function Parent() {
    this.x = 100;
}

function Child() {
    this.y = 200;
}
//=> 让子类的原型等于父类的实例
Child.prototype = new Parent; //=>原型继承

Child.prototype.getY = function getY() {
    return this.y;
};

let c1 = new Child;
console.log(c1);

8、CALL继承

function Parent() {
    this.x = 100;
}
Parent.prototype.getX = function getX() {
    return this.x;
};

function Child() {
    // 在子类构造函数中,把父类当做普通方法执行(没有父类实例,父类原型上的那些东西也就和它没关系了)
    // this -> Child的实例c1
    Parent.call(this); // this.x=100 相当于强制给c1这个实例设置一个私有的属性x,属性值100,相当于让子类的实例继承了父类的私有的属性,并且也变为了子类私有的属性 “拷贝式”
    this.y = 200;
}
Child.prototype.getY = function getY() {
    return this.y;
};

let c1 = new Child;
console.log(c1);

9、寄生组合式继承

function Parent() {
    this.x = 100;
}
Parent.prototype.getX = function getX() {
    return this.x;
};
function Child() {
    Parent.call(this);
    this.y = 200;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.getY = function getY() {
    return this.y;
};

let c1 = new Child;
console.log(c1);

10、格式化时间字符串

String.prototype.formatTime = function formatTime(template) {
    // 1.根据操作的时间字符串获取年月日小时分钟秒等信息
    let arr = this.match(/\d+/g).map(item => {
        return item.length < 2 ? '0' + item : item;
    });

    // 2.解析格式化的模板,找到对应的时间,替换模板中的内容
    template = template || '{0}年{1}月{2}日 {3}时{4}分{5}秒';
    return template.replace(/\{(\d+)\}/g, (_, group) => {
        return arr[group] || "00";
    });
};

11、处理URL参数

JS中URL参数处理的三种方法

方法一:replace处理

(proto => {
    function queryURLParams() {
        let obj = {};
        this.replace(/([^?=&#]+)=([^?=&#]+)/g, (_, key, value) => obj[key] = value);
        this.replace(/#([^?=&#]+)/g, (_, hash) => obj['HASH'] = hash);
        return obj;
    }
    proto.queryURLParams = queryURLParams;
})(String.prototype);

console.log('http://www.xxxxxxx.cn/?lx=1&name=JS&from=baidu#video'.queryURLParams());

方法二:利用A标签内置方法

function queryURLParams(url) {
    // 1.创建A标签(A元素对象)来获取到问号参数和哈希值
    let link = document.createElement('a');
    link.href = url;
    let askText = link.search.substr(1),
        polText = link.hash.substr(1),
        obj = {};
    // 2.向对象中进行存储
    polText ? obj['HASH'] = polText : null;
    if (askText) {
        let arr = askText.split(/(?:&|=)/g); //=>同时按照两个字符来拆分:["lx", "1", "name", "JS", "from", "baidu"]
        for (let i = 0; i < arr.length; i += 2) {
            // console.log(arr[i], arr[i + 1]); 属性名和属性值
            obj[arr[i]] = arr[i + 1];
        }
    }
    return obj;
}
let result = queryURLParams('http://www.xxxxxxx.cn/?lx=1&name=JS&from=baidu#video');
console.log(result);

/* <a href="http://www.xxxxxxx.cn/?lx=1&name=JS&from=baidu#video" id="link">*/

12、实现千分符

String.prototype.millimeter = function millimeter() {
    return this.replace(/\d{1,3}(?=(\d{3})+$)/g, value => {
        return value + ',';
    });
};

13、字符串中出现最多的字符

let str = "hello";
let ary = [...new Set(str.split(''))];
let max = 0;
let code = '';
for (let i = 0; i < ary.length; i++) {
    let reg = new RegExp(ary[i], 'g');
    let val = str.match(reg).length;  
    if (val > max) {
        max = val;
        code = ary[i];
    } else if (val === max) {
        code = `${code}、${ary[i]}`;
    }
}
console.log(`出现次数最多的字符是:${code},次数为:${max}`); 

该方法引自:掘金作者:李开心呀【建议收藏】一份超简洁的前端总结 ;(PS:据说是一位很漂亮的小姐姐哟🌹 )

14、数组去重

JS中数组去重的三种方法

方法一:双for循环

for (let i = 0; i < arr.length - 1; i++) {
    let item = arr[i];
    for (let j = i + 1; j < arr.length; j++) {
        if (item === arr[j]) {
            arr[j] = arr[arr.length - 1];
            arr.length--;
            j--;
        }
    }
}
console.log(arr);

方法二:对象键值对的方式

let arr = [1, 2, 3, 1, 1, 4, 2, 3];
let obj = {};
for (let i = 0; i < arr.length; i++) {
    let item = arr[i];
    if (obj[item] !== undefined) {
        arr[i] = arr[arr.length - 1];
        arr.length--;
        i--;
        continue;
    }
    obj[item] = item;
}
console.log(arr);

方法三:ES6利用Set方式

/* ES6中没有提供现成的去重办法,但是提供了一些去重的方式 :Set数据结构*/
let obj = { y: 200 };
let arr = [obj, 1, 2, 3, 1, obj, 1, 4, 2, 3, '3', { x: 100 }, { x: 100 }];
arr = Array.from(new Set(arr));
console.log(arr);

15、数组排序

方法一:冒泡排序

// 交换位置函数
function swap(arr, i, j) {
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
    return arr;
}
Array.prototype.bubble = function bubble() {
    // 外层循环I控制比较的轮数
    for (let i = 0; i < this.length - 1; i++) {
        // 里层循环控制每一轮比较的次数J
        for (let j = 0; j < this.length - 1 - i; j++) {
            if (this[j] > this[j + 1]) {
                // 当前项大于后一项,交换位置
                swap(this,j,j+1);
            }
        }
    }
    return this;
}
let ary = [12, 8, 24, 16, 1];
ary.bubble();
console.log(ary);

方法二:选择排序

Array.prototype.select = function select() {
    for (let j = 0; j < this.length - 1; j++) {
        let min = j,
            temp = null;
        // 找到比当前项还小的这一项索引
        for (let i = min + 1; i < this.length; i++) {
            if (this[i] < this[min]) {
                min = i;
            }
        }
        // 让最小的项和当前首位交换位置
        swap(this,min,j);
    }
    return this;
};
let ary = [12, 8, 24, 16, 1];
ary.select();
console.log(ary);

方法三:插入排序

Array.prototype.insert = function insert() {
    // 1.准备一个新数组,用来存储抓到手里的牌,开始先抓一张牌进来
    let handle = [];
    handle.push(this[0]);

    // 2.从第二项开始依次抓牌,一直到把台面上的牌抓光
    for (let i = 1; i < this.length; i++) {
        // A是新抓的牌
        let A = this[i];
        // 和HANDDLE手里的牌依次比较(从后向前比)
        for (let j = handle.length - 1; j >= 0; j--) {
            // 每一次要比较的手里的牌
            let B = handle[j];
            // 如果当前新牌A比要比较的牌B大了,把A放到B的后面
            if (A > B) {
                handle.splice(j + 1, 0, A);
                break;
            }
            // 已经比到第一项,我们把新牌放到手中最前面即可
            if (j === 0) {
                handle.unshift(A);
            }
        }
    }
    return handle;
}
let ary = [12, 8, 24, 16, 1];
ary.insert();
console.log(ary);

方法四:快速排序

Array.prototype.quick = function quick() {
    // 4.结束递归(当数组中小于等于一项,则不用处理)
    if (this.length <= 1) {
        return this;
    }
    // 1.找到数组的中间项,在原有的数组中把它移除
    let middleIndex = Math.floor(this.length / 2);
    let middleValue = this.splice(middleIndex, 1)[0];
    // 2.准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,反之放到右边数组中
    let aryLeft = [],
        aryRight = [];
    for (let i = 0; i < this.length; i++) {
        let item = this[i];
        item < middleValue ? aryLeft.push(item) : aryRight.push(item);
    }
    // 3.递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止(最后让左边+中间+右边拼接成为最后的结果)
    return quick(aryLeft).concat(middleValue, quick(aryRight));
}
let ary = [12, 8, 15, 16, 1, 24];
ary.quick();
console.log(ary);

方法四:希尔排序

Array.prototype.shell = function shell() {
    let gap = Math.floor(this.length / 2);
    while (gap >= 1) {
        for (let i = gap; i < this.length; i++) {
            while (i - gap >= 0 && this[i] < this[i - gap]) {
                swap(this, i, i - gap);
                i = i - gap;
            }
        }
        gap = Math.floor(gap / 2);
    }
};
let arr = [58, 23, 67, 36, 40, 46, 35, 28, 20, 10];
arr.shell();
console.log(arr);

16、数组中的最大值/最小值

获取数组中最大值/最小值的三种基础方法

方法一:基于sort

ary.sort(function (a, b) {
    return a - b;
});
let min = ary[0];
let max = ary[ary.length - 1];
console.log(min, max);

方法二:利用Math.min/max

let min = Math.min(...ary);
console.log(min);
//==========================
let min = Math.min.apply(null,ary);
console.log(min);

方法三:假设法

let max = ary[0];
for (let i = 1; i < ary.length; i++) {
    let item = ary[i];
    item > max ? max = item : null;
}
//=> for 循环 也可以改写为 forEach
ary.forEach(item => {
    item > max ? max = item : null;
});
console.log(max); 

17、获取元素到BODY的偏移量

function offset(element) {
    let parent = element.offsetParent,
        top = element.offsetTop,
        left = element.offsetLeft;
    while (parent) {
        if (!/MSIE 8/.test(navigator.userAgent)) {
            left += parent.clientLeft;
            top += parent.clientTop;
        }
        left += parent.offsetLeft;
        top += parent.offsetTop;
        parent = parent.offsetParent;
    }
    return {
        top,
        left
    };
}

18、获取和设置CSS样式

/* 获取CSS样式 */
function getCss(element, attr) {
    let value = window.getComputedStyle(element)[attr],
        reg = /^\d+(px|rem|em)?$/i;
    if (reg.test(value)) {
        value = parseFloat(value);
    }
    return value;
}
/* 设置CSS样式:单个设置 */
function setCss(element, attr, value) {
    if (attr === "opacity") {
        element['style']['opacity'] = value;
        element['style']['filter'] = `alpha(opacity=${value*100})`;
        return;
    }
    let reg = /^(width|height|margin|padding)?(top|left|bottom|right)?$/i;
    if (reg.test(attr)) {

        if (!isNaN(value)) {
            value += 'px';
        }
    }
    element['style'][attr] = value;
}
/* 设置CSS样式:对象形式设置 */
function setGroupCss(element, options) {
    for (let key in options) {
        if (!options.hasOwnProperty(key)) break;
        setCss(element, key, options[key]);
    }
}

function css(element) {
    let len = arguments.length,
        attr = arguments[1],
        value = arguments[2];
    if (len >= 3) {
        // 单一设置样式
        setCss(element, attr, value);
        return;
    }
    if (attr !== null && typeof attr === "object") {
        // 批量设置
        setGroupCss(element, attr);
        return;
    }
    // 获取样式
    return getCss(element, attr);
}

今天的内容就先到这里吧,本来还想把梳理的部分源码也放进来,但是考虑到一堆代码本来就够枯燥乏味的了,篇幅在很大的话,恐怕小芝麻自己都看不进去 😓 ;

同时也欢迎大家提供新的思路,和一些常用的方法,小芝麻感激不尽🙏 😄

原文链接:juejin.im

上一篇:dimport
下一篇:一步一步实现手写promise

相关推荐

  • 🔥手写大厂前端知识点源码系列(上)

    如今前端攻城狮的要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。

    6 个月前
  • 🔥前端面试大厂手写源码系列(上)

    如今前端攻城狮的要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。

    6 个月前
  • 🔥0202年了,几个基础的手写函数总得会吧

    这几天看到一个大三大佬面试字节跳动的蚊子,突然觉得自己太辣鸡了···校招的题我一半多都不会啊···赶紧潜下心来学习学习提(an)高(wei)自己,边翻掘金边谷歌,简单实现了几个常用函数···(借鉴了太...

    6 个月前
  • 高阶组件 + New Context API = ?

    1. 前言 继上次小试牛刀尝到高价组件的甜头之后,现已深陷其中无法自拔。。。那么这次又会带来什么呢?今天,我们就来看看【高阶组件】和【New Context API】能擦出什么火花! 2. New C...

    2 年前
  • 面试高频JS考查点手写实现

    原文链接 考查 this call、apply Function.prototype._call = function(ctx = window, ...args) { const fnKey...

    6 个月前
  • 面试题|手写JSON解析器

    这周的 Cassidoo 的每周简讯有这么一个面试题:: 写一个函数,这个函数接收一个正确的 JSON 字符串并将其转化为一个对象(或字典,映射等,这取决于你选择的语言)。

    7 个月前
  • 面试题里的那些各种手写

    最近准备初级前端面试,发现有很多手写实现什么的,例如什么手写实现bind,promise。手写ajax,手写一些算法。 翻阅了很多书籍和博客。 这里做一个总结改进,算是对我后面大概为期一个月找工作的准...

    1 年前
  • 面试常谈之手写new、call、apply和bind

    new function myNew(){ //创建一个空对象 let obj = new Object(); //获取构造函数 let Constructor = [...

    1 年前
  • 面试官:请手写一个webpack4.0配置

    确认过眼神,你还是没有准备秋招的人?时间仓促。自京东6月8号开启管培生的招聘,就意味着秋招的开始。然而你还在等着秋天的到来?今年形势应该更为严峻,随着各大技术(vue,webpack,react,微信...

    2 年前
  • 面试官再问call、apply、bind。来来来、我给你手写一个!

    哈喽,大家好!我是前端Up主。一个有代码洁癖的前端攻城狮( 哈哈,生活很邋遢(* ̄︶ ̄) ) 相信不少小伙伴在面试中,都会被问到怎么改变this的指向呢,然后你说:call、apply、bind。

    22 天前

官方社区

扫码加入 JavaScript 社区