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+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    Begin Example with Override Encoded SOAP XML Serialization
    State Machine Terminology
    How to: Specify an Alternate Element Name for an XML Stream
    How to: Publish Metadata for a WCF Service.(What is the Metadata Exchange Endpoint purpose.)
    Beginning Guide With Controlling XML Serialization Using Attributes(XmlSerializaiton of Array)
    Workflow 4.0 Hosting Extensions
    What can we do in the CacheMetaData Method of Activity
    How and Why to use the System.servicemodel.MessageParameterAttribute in WCF
    How to: Begin Sample with Serialization and Deserialization an Object
    A Test WCF Service without anything of config.
  • 原文地址:https://www.cnblogs.com/pingan8787/p/13069464.html
Copyright © 2011-2022 走看看