造轮子!


优秀的前端er一定要学会自己造轮子 ---- 柴可夫斯基

# 数据类型的判断

前置知识
(1) call() 允许为不同的对象分配和调用属于一个对象的函数/方法。

function typeOf(obj) {
    // 这里的call就是调用this本身
    // call与apply的区别在于call用逗号隔开,apply用数组隔开
    let res = Object.prototype.toString.call(obj).split(' ')[1]
    // 如果写 obj.toString的话,console.log()会得到[function: toString]
    // 如果写 obj.toString()的话,console.log()会得到a中的值
    res = res.substring(0,res.length - 1).toLowerCase()
    // 这里有两个作用,第一个是把最后的“]”给弄掉
    // 第二个作用是转化为小写字母
    return res
}

阶段代码

// Object.prototype.toString.call(obj) -> [object ...(可能是array/object/date)]
function typeOf(obj) {
    let res = Object.prototype.toString.call(obj)
    res = res.substring(0,res.length-1).toLowerCase()
    return res
}
console.log(typeOf([])) // 得到[object array]
console.log(typeOf({})) // 得到[object object]
console.log(typeOf(new Date)) // 得到[object date]

更简便的ANS写法

function typeOf(obj) {
    // 用slice代替两次分割的写法
    return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}

反思

Function.prototype.toString和Object.prototype.toString的关系:
调用一个方法的时候是顺着原型链往上找,由于Function是Object的子类型,Function也有toString方法
向上找的时候就会被Function.toString拦住,但实际要类型的话要Object.toString才能得的到

# 数组去重

前置知识

(1)set的值总是唯一的
(2)filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

// ES6写法
var unique = arr => [...new Set(arr)]
// 两个部分,首先这里把arr放进去构造了一个Set,然后就不重合了
// 然后咱们把他展开到数组里了
function unique(arr) {
    // 这里用indexOf方法标记位置,只有第一个出现的item会被留下,达到了去重的效果
    var res = arr.filter(function(item,index,array) {
        return arr.indexOf(item) === index
    })
    return res
}

# 数组扁平化

前置知识

(1) flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。「flat(idx)可以填入参数idx,idx为几表示去除几重嵌套」「同时,flat可以去除数组中的空元素」 (2) concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

[1,[2,[3]]].flat(2) // [1,2,3]

ES5 实现:递归

function flatten(arr){
    var result = []
    for(let i=0,len = arr.length;i<len;i++){
        if(Array.isArray(arr[i])){
            // 如果是数组就把你给消掉然后继续迭代
            result = result.concat(flatten(arr[i]))
        }
        else{
            // 不是数组是数就给你送进去
            result.push(arr[i])
        }
    }
}

ES6 实现:

// 这个就没那么多花里胡哨的东西了,就是直接...展开运算符直接展开,何必递归呢
function flatten(arr) {
    // some只要有一个是满足有数组的,那就直接为true,然后开始展开
    while(arr.some(item => Array.isArray(item))){
        // 语法: const arr3 = arr1.concat(arr2)
        arr = [].concat(...arr)
    }
    return arr
}

# 深浅拷贝

浅拷贝:只考虑对象类型

前置知识:

(1) hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

function shallowCopy(obj) {
    // 因为只考虑对象类型,所以如果不是对象类型,就请离开(
    if(typeof obj !== 'object' ) return
    // 创建一个新的对象
    let newObj = obj instanceof Array ? [] : {}
    for(key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = obj[key]
        }
    }
    return newObj
}

简单版本深拷贝:只考虑普通对象属性,不考虑内置的对象和函数。

function deepClone(obj) {
    // 因为只考虑对象类型,所以如果不是对象类型,就请离开(
    if(typeof obj !== 'object' ) return
    // 创建一个新的对象
    let newObj = obj instanceof Array ? [] : {}
    for(key in obj){
        // hasOwnProperty是只要在这个实例里面的属性,而不是在这个原型里的属性
        if(obj.hasOwnProperty(key)){
            // 递归处理,完全赋到具体的值,而不是一个对象直接复制过去
            newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj
}

# 手写apply && bind && call

手写apply

// apply是一个数组传参「args」
Function.prototype.myApply = function (context,args){
    // 这里context其实就相当于this
    // 处理容错
    context = (typeof context === 'object' ? context : window)
    args = args ? args : []
    // 给context新增一个独一无二的属性以免覆盖原有属性
    const key = Symbol()
    context[key] = this
    // 通过隐式绑定的方式调用函数
    const result = context[key](...args)
    // 删除添加的属性
    delete context[key]
    return result
}

手写call

Function.prototype.myCall = function(context,...args){
    context = ( typeof context === 'object' ? context : window )
    args = args ? args : []
    const key = Symbol()
    context[key] = this
    let result = context[key](...args)
    delete context
    return result
}

手写bind

Function.prototype.myBind = function(objThis,...params){
    const thisFn = this;//存储源函数以及上方的params(函数参数)
    // 对返回的函数参数 secondParams 二次传参
    let fToBind = function(...secondParams) {
        const isNew = this instanceof fToBind //this是否是fToBind的实例,也就是返回的fToBind是否通过new调用
        const context = isNew ? this : Object(objThis)// new调用就绑定到this上,否则就绑定到传入的objThis上
        return thisFn.call(context,...params,...secondParams);//用call调用源函数绑定this的指向并传递参数,返回执行结果
    };
    if(thisFn.prototype) {
        // 复制源函数的prototype给fToBind 一般情况下没有prototype,比如箭头函数。
        fToBind.prototype = Object.create(thisFn.prototype);
    }
    return fToBind
}