golang给前端返回空数组而非null_初,中级前端应该要掌握的手写代码实现 - Go语言中文社区

golang给前端返回空数组而非null_初,中级前端应该要掌握的手写代码实现


点击上方“前端教程”,选择“星标”

每天前端开发干货第一时间送达!

来自:非常记得你

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

过完年马上又要到金三银四面试季了,想必很多同学已经跃跃欲试,提前开始准备面试了,本文就列举了面试过程中一些常见的手写代码实现供参考。或许很多人会问,这些手写代码实现意义何在,社区已经有很多poly-fill或者函数库供选择,何必要自己费力去折腾呢?

我的理解是,在真实业务开发场景中,我们真的用不上这些自己写的方法,一个lodash库完全可以满足我们的需求,但此时你仅仅只是一个API Caller ,你经常使用到它,但对它实现原理却一无所知,哪怕它实现起来其实是非常简单。所以亲自动手写出它的实现过程,对理解其中原理是很有帮助的。

另外,不要觉得用ES6语法,或者最新的语法去实现ES5甚至是ES3的方法是件可笑的事情,相反,它更能体现出你对ES6语法的掌握程度以及对JS发展的关注度,在面试中说不定会成为你的一个亮点。

模拟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。

参考:js基础-面试官想知道你有多理解call,apply,bind?[不看后悔系列]

【进阶 6-2 期】深入高阶函数应用之柯里化 JavaScript专题之跟着 underscore 学节流 如何写出一个惊艳面试官的深拷贝? 头条面试官:你知道如何实现高性能版本的深拷贝嘛?

ee6d3948d3505a86808711b4b8c0dcca.png

好文章,我在看❤️
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_39676034/article/details/112098631
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-04-10 23:23:53
  • 阅读 ( 516 )
  • 分类:前端

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢