手写系列:JavaScript基础总结

前言

本篇主要归纳自己写过的一些JavaScript代码,它包含了一些实现方式,如去重的方式,数组扁平化的方式,防抖节流、发布订阅等,又或者包含了一些源码,new、call、apply、bind、数组方法等,统一将这些作为一个系列。

一、实现系列

1.数组去重

1.1 set

由于set的特点:所有元素值都是独一无二的,因此能用来去重,也是最简单的方式

function removeRepeat(arr) {
    return [...new Set(arr)]
}

1.2 indexOf

通过indexOf查找数据返回第一个索引的方式,可以通过新建数组,无则push有则略过。

function removeRepeat(arr) {
    let res = [];
    for (let val of arr) {
        if (res.indexOf(val) === -1) {
            res.push(val)
        }
    }
    return res
}

1.3 indexOf+filter

利用filter的条件过滤,我们返回的是索引号等于indexOf值的元素,也就是只让每个元素出现的第一次加入

function removeRepeat(arr) {
    let res = arr.filter(function (val, index) {
        return arr.indexOf(val) === index
    })
    return res
}

1.4 sort+splice

将数组先进行排序,然后从第二个元素开始for循环,如果等于前一个元素,则将当前元素删除。注意删除元素后索引需要减一,因为删除后数组减小,本来下一位的元素的索引变成了当前的索引值。

function removeRepeat(arr) {
    arr.sort((a, b) => a - b);
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] === arr[i - 1]) {
            arr.splice(i, 1)
            i--;
        }
    }
    return arr
}

1.5 sort

同上种方法相比,这个不需要splice,但是需要创建一个新的数组,当然也可以结合filter

function removeRepeat(arr) {
    arr.sort((a, b) => a - b);
    let res = [arr[0]];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] != arr[i - 1]) {
            res.push(arr[i])
        }
    }
    return res
}

1.6 map

也可以借助map的映射关系来判断是否已存在,从而取唯一值

function removeRepeat(arr) {
    let map = new Map();
    let res = [];
    for (let val of arr) {
        if (!map.has(val)) {
            map.set(val, true)
            res.push(val)
        }
    }
    return res
}

1.7 includes

includes是一个可以判断某元素是否存在的数组方法,返回布尔值

function removeRepeat(arr) {
    let res = [];
    for (let val of arr) {
        if (!res.includes(val)) {
            res.push(val)
        }
    }
    return res
}

2.数组扁平化

就是将多维数组转为一维数组

2.1 flat

flat方法是Array直接可调用的扁平化方法,传入参数为扁平化的深度,默认为1,infinity为扁平任意深度

function delayering(arr) {
    return arr.flat(Infinity)
}

2.2 递归

递归,如果是数组继续调用自身,如果是值直接push

function delayering(arr) {
    let res = [];
    fn(arr);
    function fn(item) {
        if (Array.isArray(item)) {
            for (let val of item) {
                fn(val)
            }
        } else {
            res.push(item)
        }
    }
    return res
}

2.3 扩展符

借用对数组调用一次扩展符运算会扁平一次的特性

      Array.prototype.flat = function (deep) {
        let arr = this;
        let res = [];
        for (let item of arr) {
          if (Array.isArray(item) && deep > 0) {
            res.push(...item.flat(deep - 1));
          } else {
            res.push(item);
          }
        }
        return res;
      };
      let arr = [1, 2, 3, [4, 5, [6]]];
      console.log(arr.flat(1));

2.4 reduce+concat

利用reduce设置开始数组为空,判断当前数组是否为数组,为数组则再次调用,为值则进行拼接

function delayering(arr) {
    return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? delayering(cur) : cur)
    }, [])
}

2.5 正则

这是一种比较取巧的方式,通过正则匹配将所有[]转为空再进行转为数组,不过此时所有元素都是字符串形式,需要根据需求转类型

function delayering(arr) {
    let reg = /\[|\]/g;
    return arr.toString().replace(reg, '').split(',')
}

3.类数组转为数组

类数组类似数组,它有自己的length,但实际是对象。如arguments、querySelectorAll选择的dom等,这里以arguments为例将类数组转为数组

3.1 Array.form

Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

function getArr() {
    return Array.from(arguments)
}

3.2 扩展符

扩展符还可以将类数组转为数组

function getArr() {
    return [...arguments]
}

3.3 Array.prototype.slice.call

通过此方法让类数组对象拥有slice方法进行截取,返回数组

function getArr() {
    return Array.prototype.slice.call(arguments)
}

4.清除空格字符串

4.1 trim

trim可以清除字符串两旁的空格,不能清除中间的空格

function clearSpaces(str) {
    return str.trim()
}

4.2 正则

正则匹配进行替换

function clearSpaces(str) {
    return str.replace(/\s/g, '')
}

4.3 split+join

function clearSpaces(str) {
    return str.split(' ').join('')
}

5.判断回文字符串

回文字符串就是从前往后看和从后往前看都是相等的

5.1 reverse

转为数组调用reverse方法再转为字符串进行对比

function isPalindrome(str) {
    return str === str.split('').reverse().join('')
}

5.2 双指针

从两旁往中间移如果一直相等到结束,则为回文字符串

function isPalindrome(str) {
    let left = 0;
    let right = str.length - 1;
    while (left < right) {
        if (str[left] != str[right]) return false;
        left++;
        right--;
    }
    return true;
}

6.字符串转驼峰

6.1 split(’-’)

通过split按’-'分割然后将后续字符第一个大写进行拼接

function translateToHump(str) {
    let arr = str.split('-');
    for (let i = 1; i < arr.length; i++) {
        arr[i] = arr[i][0].toUpperCase() + arr[i].substring(1)
    }
    return arr.join('')
}

6.2 split+splice

将字符串直接分割成数组,遍历删除’-’,后一位大写,最后转字符串返回

function translateToHump(str) {
    let arr = str.split('');
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] === '-') {
            arr.splice(i, 1);
            arr[i] = arr[i].toUpperCase()
        }
    }
    return arr.join('')
}

6.3 正则匹配

通过正则匹配-和-后的首字母,然后将首字母大写返回

function translateToHump(str) {
    let reg = /\-(\w)/g;
    return str.replace(reg, function (_0, _1) {
        return _1.toUpperCase()
    })
}

7.驼峰转字符串

7.1 split+splice

转为数组,循环判断是否为大写,是则转为小写并且前面添加’-’

function translateToStr(hump) {
    let arr = hump.split('');
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] >= 'A' && arr[i] <= 'Z') {
            arr[i] = arr[i].toLowerCase()
            arr.splice(i, 0, '-')
        }
    }
    return arr.join('')
}

7.2 正则

正则匹配大写字母,然后加上’-'进行返回

function translateToStr(hump) {
    let reg = /([A-Z])/g
    return hump.replace(reg, function (_0, _1) {
        return '-' + _1.toLowerCase()
    })
}

8.闭包相关

8.1 防抖

在一定时间段内,将多次高频操作转为最后一次执行。如果相邻两次操作小于设置的时间,则会清除上次执行,调用当前执行。可设置immediate来满足是否需要第一次立即执行

        function debounce(fn, delay, immediate) {
            let timer = null;
            return function (...args) {
                if (immediate && !timer) {
                    fn.call(this, ...args)
                }
                timer && clearTimeout(timer)
                timer = setTimeout(() => {
                    fn.call(this, ...args)
                }, delay);
            }
        }

返回函数引用了timer,产生了闭包,作用在于能使高频操作时公用一个timer。

8.2 节流

在一定时间段内,将多次高频操作划分为等间隔段执行的低频操作,通过设置一个节流阀来控制是否可执行。

        function throttle(fn, delay) {
            let flag = true;
            return function (...args) {
                if (flag) {
                    flag = false;
                    setTimeout(() => {
                        flag = true;
                        fn.call(this, ...args)
                    }, delay);
                }
            }
        }

返回函数引用了flag,产生了闭包,作用在于能使高频操作时公用一个flag。

8.3 单例

单例只允许触发一次,之后再次触发会将之前的直接返回。
这里以创建一个dom元素为例

        function createInstance() {
            let div = document.createElement('div')
            div.innerHTML = 'hello world'
            document.body.appendChild(div)
            return div
        }
        //主要代码
        function getSingle(fn) {
            let instance = null;
            return function () {
                instance || (instance = fn());
            }
        }
        
        let funcSingle = getSingle(createInstance)
        btn.addEventListener('click', function () {
            funcSingle()
        })

中间为单例的主要代码,通过闭包使instance存活,后续调用由于已经存在instance也就不会去执行createInstance。

9.深浅拷贝

1.浅拷贝

浅拷贝创建一个新对象,对于基本类型直接拷贝,引用类型拷贝引用地址

        function shallowCopy(source) {
            if(typeof source!='object'||source===null){
                return source
            }
            cosnt target = {};
            for (let i in source) {
                target[i] = source[i]
            }
            return target
        }

浅拷贝还有Object.assign,会在后续的源码系列

2.深拷贝

深拷贝创建一个新对象,对于基本类型直接拷贝,引用类型则创建新空间拷贝数据。通过weakmap保存缓存过的数据信息,可以解决对象循环引用的问题

        function deepCopy(source, map = new WeakMap()) {
            if (map.has(source)) return map.get(source)
            const target = Array.isArray(source) ? [] : {}
            map.set(source, target)
            for (let i in source) {
                if (typeof source[i] === 'object' && source[i] != null) {
                    target[i] = deepCopy(source[i], map)
                } else {
                    target[i] = source[i]
                }
            }
            return target
        }

10.发布订阅

通过声明一个Sub类,保存一个公共事件池,声明订阅subscibe、取消订阅unsubscribe、发布publish、一次订阅once方法

  class Sub {
    constructor() {
        this.subsribers = {}
    }
    subsribe(type, fn) {
        if (!this.subsribers[type]) {
            this.subsribers[type] = []
        }
        this.subsribers[type].push(fn)
    }
    unsubsribe(type, fn) {
        if (!this.subsribers[type]) return;
        this.subsribers[type] = this.subsribers[type].filter(function (item) {
            return item !== fn
        })
    }
    publish(type, ...args) {
        if (!this.subsribers[type]) return;
        this.subsribers[type].forEach(function (fn) {
            fn.call(this, ...args)
        })
    }
    once(type, fn) {
        let _this = this

        function func() {
            fn()
            _this.unsubsribe(type, func)
        }
        this.subsribe(type, func)
    }
}   

2.源码系列

1.数组方法

关于数组的高阶方法,传入两个参数,第一个为函数回调,内部包含了数组单个值item、索引index、数组本身array,第二个参数为this指向

1.1 forEach

用于对数组每一个元素进行一次调用,没有返回值

Array.prototype.myForEach = function (callback, thisArg) {
    if (typeof callback != 'function') {
        throw new TypeError('sorry,callback is not a function')
    }
    let len = this.length;
    let k = 0;
    while (k < len) {
        callback.call(thisArg, this[k], k, this)
        k++;
    }
}

1.2 filter

用于过滤数组,返回符合条件的元素数组,返回值为一个新数组

Array.prototype.myFilter = function (callback, thisArg) {
    if (typeof callback != 'function') {
        throw new TypeError('Sorry,callback is not a function')
    }
    let resArr = [];
    for (let k = 0; k < this.length; k++) {
        if (callback.call(thisArg, this[k], k, this)) {
            resArr.push(this[k])
        }
    }
    return resArr
}

1.3 map

用于处理数组的各项返回,返回一个新数组

Array.prototype.myMap = function (callback, thisArg) {
    if (typeof callback != 'function') {
        throw new TypeError('Sorry,callback is not a function')
    }
    let resArr = [];
    for (let k = 0; k < this.length; k++) {
        resArr[k] = callback.call(thisArg, this[k], k, this)
    }
    return resArr
}

1.4 some

用于判断数组是否存在符合条件的元素,存在则返回true,否则返回false,不改变原数组

Array.prototype.mySome = function (callback, thisArg) {
    if (typeof callback != 'function') {
        throw new TypeError('Sorry,callback is not a function')
    }
    for (let k = 0; k < this.length; k++) {
        if (callback.call(thisArg, this[k], k, this)) {
            return true
        }
    }
    return false
}

1.5 every

用于判断数组所有元素是否满足条件,都满足返回true,一项不满足返回false

Array.prototype.myEvery = function (callback, thisArg) {
    if (typeof callback != 'function') {
        throw new TypeError('Sorry,callback is not a function')
    }
    for (let k = 0; k < this.length; k++) {
        if (!callback.call(thisArg, this[k], k, this)) {
            return false;
        }
    }
    return true;
}

1.6 reduce

传入两个参数,第一个为回调函数,包含了当前元素的上一个值和当前元素值,第二个参数为默认上一个元素值。如果第二个参数没有设置则令第一个元素为上一个值,并改变循环的起始位置

Array.prototype.myReduce = function (callback, initValue) {
    if (typeof callback != 'function') {
        throw new TypeError('Sorry,callback is not a function')
    }
    let start = 0;
    let preValue = initValue
    if (preValue === undefined) {
        preValue = this[0];
        start = 1;
    }
    for (let i = start; i < this.length; i++) {
        preValue = callback(preValue, this[i])
    }
    return preValue
}

2.this指向相关

2.1 call

call方法第一个参数为this指向的对象,后续参数为列表形式,立即执行返回结果。

Function.prototype.myCall = function (context, ...args) {
    if (context === null || context === undefined) {
        context = window;
    } else {
        context = Object(context)
    }
    let fn = Symbol()
    context[fn] = this;
    let res = context[fn](...args);
    delete context[fn];
    return res
}

过程:

  1. 判断如果第一个参数为undefined或null,则使它默认为window
  2. symbol创建唯一属性fn,其等于当前函数,并加入目标对象
  3. 函数在目标对象中执行获得结果
  4. 删除函数返回结果

2.2 apply

apply方法和call类似,区别就是第二个参数为数组形式

Function.prototype.myApply = function (context, args) {
    if (context === null || context === undefined) {
        context = window;
    } else {
        context = Object(context)
    }
    let fn = Symbol();
    context[fn] = this;
    let res = context[fn](args);
    delete context[fn];
    return res;
}

实现和call类似

2.3 bind

bind与call、apply不同的是它会返回一个新函数,并不会立即执行。它的第二个参数也是参数列表的形式。

Function.prototype.myBind = function (context, ...args) {
    let _this = this;

    function fn() {}
    let res = function (...rest) {
        return _this.apply(this instanceof res ? this : context, [...args, ...rest])
    }
    fn.prototype = this.prototype
    res.prototype = new fn()
    return res
}

注意:

  1. 由于new的优先级高于bind,所以需要判断当前的返回函数是不是作为构造函数在使用,如果是的话它的this不变指向原来的实例,如果不是则为传入的第一个参数
  2. 当前调用bind的函数有些属性方法是存在原型上的,所以需要把原型也赋给返回的函数,但是为了不造成两个原型之间直接性的影响,引入中间函数fn。

3.原型、原型链相关

3.1 new

通过new可以创建一个构造函数的实例,其内部过程如下

  1. 创建一个新对象
  2. 使新对象的__proto__指向构造函数的prototype
  3. 将构造函数的this指向新对象并运行
  4. 判断3运行的结果是否是对象,是对象代表了构造函数有返回的对象,那就取这个对象,如果不是,则取新对象。
function myNew(fn, ...args) {
    let newObj = {}
    newObj.__proto__ = fn.prototype;
    let res = fn.call(newObj, ...args)
    return (res && typeof res === 'object') ? res : newObj
}

3.2 instanceof

通过instanceof可以用来判断一个构造函数的原型在不在一个实例对象的原型链上,本质就是通过原型链的查找,查找到则返回true,查找到原型链顶端null了则说明未找到返回false

function myInstanceOf(left, right) {
    if (typeof left != 'object' || left == null) return false
    while (true) {
        if (left === null) return false;
        if (left === right.prototype) return true;
        left = left.__proto__
    }
}

3.3 Object.create

可用于创造一个新对象,这个新对象的原型prototype是传入的prototype

const myCreate = function (prototype) {
    if (typeof prototype != 'object') {
        throw new TypeError('Object prototype may only be an Object or null')
    }
    if (prototype === null) return {}

    function fn() {}
    fn.prototype = prototype;
    return new fn()
}

4.其他

4.1 Object.assign

Object.assign用于对象合并,有两个或多个参数,target、source。第一个target为合并目标对象,后续source为合并到target的对象。如果target为空对象,则可以用来做拷贝,它是浅拷贝。

  1. 同名属性后续会覆盖
  2. 可传入多个source对象
  3. 如果传入不是对象会转为对象
function myAssign() {
    let args = Array.prototype.slice.call(arguments);
    if (args.length === 0) throw new TypeError('Cannot convert undefined or null to object')
    let target = args[0]
    if (args.length === 1) return target
    let sourceArr = args.slice(1)
    for (let i = 0; i < sourceArr.length; i++) {
        for (let key in sourceArr[i]) {
            if (sourceArr[i].hasOwnProperty(key)) {
                target[key] = sourceArr[i][key]
            }
        }
    }
    return target
}

4.2 Object.is

Object.i是建立在===号之上,完善了一些特殊情况用来对比是否全等的方法

完善了

  1. +0===-0为true,应该为false
  2. NaN===NaN为false,应该为true
function myIs(left, right) {
    if (left === right) {
        return left !== 0 || right !== 0 || 1 / left === 1 / right
    } else {
        return left !== left && right !== right
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值