zoukankan      html  css  js  c++  java
  • 【JS】528- 前端常见手写代码实现


    模拟call

    • 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 StringNumberBoolean

    • 为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值

    • 将函数作为传入的上下文(context)属性执行

    • 函数执行完成后删除该属性

    • 返回执行结果

    Function.prototype.myCall = function(context, ...args) {
        context =  (context ?? window) || new Object(context)
        const key = Symbol()
        context[key] = this
        const result = context[key](...args)
        delete context[key]
        return result
    }
    

    注: 代码实现使用了ES2020新特性Null判断符 ??, 详细参考阮一峰老师的ECMAScript 6 入门


    模拟apply

    • 前部分与call一样

    • 第二个参数可以不传,但类型必须为数组或者类数组

    Function.prototype.myApply = function(context) {
        context =  (context ?? window) || new Object(context)
        const key = Symbol()
        const args = arguments[1]
        context[key] = this
        let result
        if(args) {
            result = context[key](...args)
        } else {
            result = context[key]
        }
        delete context[key]
        return result
    }
    

    注:代码实现存在缺陷,当第二个参数为类数组时,未作判断(有兴趣可查阅一下如何判断类数组)


    模拟bind

    • 使用 call / apply 指定 this

    • 返回一个绑定函数

    • 当返回的绑定函数作为构造函数被new调用,绑定的上下文指向实例对象

    • 设置绑定函数的prototype 为原函数的prototype

    Function.prototype.myBind = function(context, ...args) {
        const fn = this
        const bindFn = function (...newFnArgs) {
            fn.call(
                this instanceof bindFn ? this : context,
                ...args, ...newFnArgs
            )
        }
        bindFn.prototype = Object.create(fn.prototype)
        return bindFn
    }
    

    模拟new

    • 创建一个新的空对象

    • this绑定到空对象

    • 使空对象的__proto__指向构造函数的原型(prototype)

    • 执行构造函数,为空对象添加属性

    • 判断构造函数的返回值是否为对象,如果是对象,就使用构造函数的返回值,否则返回创建的对象

    const createNew = (Con, ...args) => {
        const obj = {}
        Object.setPrototypeOf(obj, Con.prototype)
        let result = Con.apply(obj, args)
        return result instanceof Object ? result : obj
    }
    


    模拟instanceOf

    • 遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false

    const myInstanceOf = (left, right) => {
        let leftValue = left.__proto__
        let rightValue = right.prototype
        while(true) {
            if(leftValue === null) return false
            if(leftValue === rightValue) return true
            leftValue = leftValue.__proto__
        }
    }
    

    深拷贝(简单版)

    • 判断类型是否为原始类型,如果是,无需拷贝,直接返回

    • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回

    • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系

    • 对引用类型递归拷贝直到属性为原始类型

    const deepClone = (target, cache = new WeakMap()) => {
        if(target === null || typeof target !== 'object') {
            return target
        }
        if(cache.get(target)) {
            return target
        }
        const copy = Array.isArray(target) ? [] : {}
        cache.set(target, copy)
        Object.keys(target).forEach(key => copy[key] = deepClone(obj[key], cache))
        return copy
    }
    

    深拷贝(尤雨溪版)

    vuex源码

    • 原理与上一版类似

    function find(list, f) {
        return list.filter(f)[0]
    }
    
    
    function deepCopy(obj, cache = []) {
        // just return if obj is immutable value
        if (obj === null || typeof obj !== 'object') {
            return obj
        }
    
    
        // if obj is hit, it is in circular structure
        const hit = find(cache, c => c.original === obj)
        if (hit) {
            return hit.copy
        }
    
    
        const copy = Array.isArray(obj) ? [] : {}
        // put the copy into cache at first
        // because we want to refer it in recursive deepCopy
        cache.push({
            original: obj,
            copy
        })
        Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache))
    
    
        return copy
    }
    

    函数防抖

    • this继承自父级上下文,指向触发事件的目标元素

    • 事件被触发时,传入event对象

    • 传入leading参数,判断是否可以立即执行回调函数,不必要等到事件停止触发后才开始执行

    • 回调函数可以有返回值,需要返回执行结果

     const debounce = (fn, wait = 300, leading = true) => {
        let timerId, result
        return function(...args) {
            timerId && clearTimeout(timerId)
            if (leading) {
                if (!timerId) result = fn.apply(this, args)
                timerId = setTimeout(() => timerId = null, wait)
            } else {
                timerId = setTimeout(() => result = fn.apply(this, args), wait)
            }
            return result
        }
    }
    

    函数节流(定时器)

    const throttle = (fn, wait = 300) => {
        let timerId
        return function(...args) {
            if(!timerId) {
                timerId = setTimeout(() => {
                    timerId = null
                    return result = fn.apply(this, ...args)
                }, wait)
            }
        }
    }
    

    函数节流(时间戳)

    const throttle = (fn, wait = 300) => {
        let prev = 0
        let result
        return function(...args) {
            let now = +new Date()
            if(now - prev > wait) {
                prev = now
                return result = fn.apply(this, ...args)
            }
        }
    }
    
    函数节流实现方法区别
    方法使用时间戳使用定时器
    开始触发时立刻执行n秒后执行
    停止触发后不再执行事件继续执行一次事件


    数组去重

    const uniqBy = (arr, key) => {
        return [...new Map(arr.map(item) => [item[key], item])).values()]
    }
    
    
    const singers = [
        { id: 1, name: 'Leslie Cheung' },
        { id: 1, name: 'Leslie Cheung' },
        { id: 2, name: 'Eason Chan' },
    ]
    console.log(uniqBy(singers, 'id'))
    
    
    //  [
    //    { id: 1, name: 'Leslie Cheung' },
    //    { id: 2, name: 'Eason Chan' },
    //  ]
    

    原理是利用Map的键不可重复


    数组扁平化(技巧版)

    const flatten = (arr) => arr.toString().split(',').map(item => +item)
    

    数组扁平化

    const flatten = (arr, deep = 1) => {
      return arr.reduce((cur, next) => {
        return Array.isArray(next) && deep > 1 ?
          [...cur, ...flatten(next, deep - 1)] :
          [...cur, next]
      },[])
    }
    

    函数柯里化

    const currying = (fn) {
        _curry = (...args) => 
            args.length >= fn.length
            ? fn(...args)
            : (...newArgs) => _curry(...args, ...newArgs)
    }
    

    原理是利用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数


    发布订阅EventEmitter

    class EventEmitter {
        #subs = {}
        emit(event, ...args) {
            if (this.#subs[event] && this.#subs[event].length) {
                this.#subs[event].forEach(cb => cb(...args))
            }
        }
        on(event, cb) {
            (this.#subs[event] || (this.#subs[event] = [])).push(cb)
        }
        off(event, offCb) {
        if (offCb) {
            if (this.#subs[event] && this.#subs[event].length)
                this.#subs[event] = this.#subs[event].filter(cb => cb !== offCb)
          } else {
            this.#subs[event] = []
          }
        }
    }
    

    subsEventEmitter私有属性(最新特性参考阮一峰老师的ECMAScript 6 入门),通过on注册事件,off注销事件,emit触发事件


    寄生组合继承

      function Super(foo) {
        this.foo = foo
      }
      Super.prototype.printFoo = function() {
        console.log(this.foo)
      }
      function Sub(bar) {
        this.bar = bar
        Super.call(this)
      }
      Sub.prototype = Object.create(Super.prototype)
      Sub.prototype.constructor = Sub
    

    ES6版继承

     
     class Super {
        constructor(foo) {
          this.foo = foo
        }
        printFoo() {
          console.log(this.foo)
        }
      }
      class Sub extends Super {
        constructor(foo, bar) {
          super(foo)
          this.bar = bar
        }
      }
    

    ES5的继承,实质是先创造子类的实例对象,然后将再将父类的方法添加到this上。ES6的继承,先创造父类的实例对象(所以必须先调用super方法,然后再用子类的构造函数修改this

    源自:https://juejin.im/post/5e24590ef265da3e152d27bc

    声明:文章著作权归作者所有,如有侵权,请联系小编删除。

    原创系列推荐

    1. JavaScript 重温系列(22篇全)

    2. ECMAScript 重温系列(10篇全)

    3. JavaScript设计模式 重温系列(9篇全)

    4. 正则 / 框架 / 算法等 重温系列(16篇全)

    5. Webpack4 入门(上)|| Webpack4 入门(下)

    6. MobX 入门(上) ||  MobX 入门(下)

    7. 59篇原创系列汇总

    回复“加群”与大佬们一起交流学习~

    点这,与大家一起分享本文吧~

    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    Android之TabHost实现Tab切换
    银联支付SDK集成
    iOS 支付 [支付宝、银联、微信]
    MySQL数据库数据类型以及INT(M)的含义
    cherrypy
    使用PyMySQL操作mysql数据库
    面向新手的Web服务器搭建(一)——IIS的搭建
    SQLite3中自增主键相关知识总结,清零的方法、INTEGER PRIMARY KEY AUTOINCREMENT和rowid的使用
    FMDB-FMDatabaseQueue
    SQLite 数据类型
  • 原文地址:https://www.cnblogs.com/pingan8787/p/13069464.html