zoukankan      html  css  js  c++  java
  • 前端手写代码原理实现

    前言

    现在的前端门槛越来越高,不再是只会写写页面那么简单。模块化、自动化、跨端开发等逐渐成为要求,但是这些都需要建立在我们牢固的基础之上。不管框架和模式怎么变,把基础原理打牢才能快速适应市场的变化。下面介绍一些常用的源码实现:

    • call实现
    • bind实现
    • new实现
    • instanceof实现
    • Object.create实现
    • 深拷贝实现
    • 发布订阅模式

    call

    call用于改变函数this指向,并执行函数

    一般情况,谁调用函数,函数的this就指向谁。利用这一特点,将函数作为对象的属性,由对象进行调用,即可改变函数this指向,这种被称为隐式绑定。apply实现同理,只需改变入参形式。

    let obj = {
      name: 'JoJo'
    }
    function foo(){
      console.log(this.name)
    }
    obj.fn = foo
    obj.fn() // log: JoJo
    

    实现

    Function.prototype.mycall = function () {
      if(typeof this !== 'function'){
        throw 'caller must be a function'
      }
      let othis = arguments[0] || window
      othis._fn = this
      let arg = [...arguments].slice(1)
      let res = othis._fn(...arg)
      Reflect.deleteProperty(othis, '_fn') //删除_fn属性
      return res
    }
    

    使用

    let obj = {
      name: 'JoJo'
    }
    function foo(){
      console.log(this.name)
    }
    foo.mycall(obj) // JoJo
    

    bind

    bind用于改变函数this指向,并返回一个函数

    注意点:

    • 作为构造函数调用的this指向
    • 维护原型链
    Function.prototype.mybind = function (oThis) {
      if(typeof this != 'function'){
        throw 'caller must be a function'
      }
      let fThis = this
      //Array.prototype.slice.call 将类数组转为数组
      let arg = Array.prototype.slice.call(arguments,1)
      let NOP = function(){}
      let fBound = function(){
        let arg_ = Array.prototype.slice.call(arguments)
        // new 绑定等级高于显式绑定
        // 作为构造函数调用时,保留指向不做修改
        // 使用 instanceof 判断是否为构造函数调用
        return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
      }
      // 维护原型
      if(this.prototype){
        NOP.prototype = this.prototype
        fBound.prototype = new NOP()
      }
      return fBound
    }
    

    使用

    let obj = {
      msg: 'JoJo'
    }
    function foo(msg){
      console.log(msg + '' + this.msg)
    }
    let f = foo.mybind(obj)
    f('hello') // hello JoJo
    

    new

    new使用构造函数创建实例对象,为实例对象添加this属性和方法

    new的过程:

    1. 创建新对象
    2. 新对象__proto__指向构造函数原型
    3. 新对象添加属性方法(this指向)
    4. 返回this指向的新对象
    function new_(){
      let fn = Array.prototype.shift.call(arguments)
      if(typeof fn != 'function'){
        throw fn + ' is not a constructor'
      }
      let obj = {}
      obj.__proto__ = fn.prototype
      let res = fn.apply(obj, arguments)
      return typeof res === 'object' ? res : obj
    }
    

    instanceof

    instanceof 判断左边的原型是否存在于右边的原型链中。

    实现思路:逐层往上查找原型,如果最终的原型为null时,证明不存在原型链中,否则存在。

    function instanceof_(left, right){
      left = left.__proto__
      while(left !== right.prototype){
        left = left.__proto__ // 查找原型,再次while判断
        if(left === null){
          return false
        }
      }
      return true
    }
    

    Object.create

    Object.create创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,第二个可选参数为属性描述对象

    function objectCreate_(proto, propertiesObject = {}){
      if(typeof proto !== 'object' || typeof proto !== 'function' || proto !== null){
        throw('Object prototype may only be an Object or null:'+proto)
      }
      let res = {}
      res.__proto__ = proto
      Object.defineProperties(res, propertiesObject)
      return res
    }
    

    深拷贝

    深拷贝为对象创建一个相同的副本,两者的引用地址不相同。当你希望使用一个对象,但又不想修改原对象时,深拷贝是一个很好的选择。这里实现一个基础版本,只对对象和数组做深拷贝。

    实现思路:遍历对象,引用类型使用递归继续拷贝,基本类型直接赋值

    function deepClone(origin) {
      let toStr = Object.prototype.toString
      let isInvalid = toStr.call(origin) !== '[object Object]' && toStr.call(origin) !== '[object Array]'
      if (isInvalid) {
        return origin
      }
      let target = toStr.call(origin) === '[object Object]' ? {} : []
      for (const key in origin) {
        if (origin.hasOwnProperty(key)) {
          const item = origin[key];
          if (typeof item === 'object' && item !== null) {
            target[key] = deepClone(item)
          } else {
            target[key] = item
          }
        }
      }
      return target
    }
    

    发布订阅模式

    发布订阅模式在实际开发中可以实现模块间的完全解耦,模块只需要关注事件的注册和触发。

    发布订阅模式实现EventBus:

    class EventBus{
      constructor(){
        this.task = {}
      }
    
      on(name, cb){
        if(!this.task[name]){
          this.task[name] = []
        }
        typeof cb === 'function' && this.task[name].push(cb)
      }
    
      emit(name, ...arg){
        let taskQueue = this.task[name]
        if(taskQueue && taskQueue.length > 0){
          taskQueue.forEach(cb=>{
            cb(...arg)
          })
        }
      }
    
      off(name, cb){
        let taskQueue = this.task[name]
        if(taskQueue && taskQueue.length > 0){
          if(typeof cb === 'function'){
            let index = taskQueue.indexOf(cb)
            index != -1 && taskQueue.splice(index, 1)
          }
          if(typeof cb === 'undefined'){
            this.task[name].length = 0
          }
        }
      }
    
      once(name, cb){
        let callback = (...arg) => {
          this.off(name, callback)
          cb(...arg)
        }
        typeof cb === 'function' && this.on(name, callback)
      }
    }
    

    使用

    let bus = new EventBus()
    bus.on('add', function(a,b){
      console.log(a+b)
    })
    bus.emit('add', 10, 20) //30
    
  • 相关阅读:
    PAT (Advanced Level) Practice 1100 Mars Numbers (20分)
    PAT (Advanced Level) Practice 1107 Social Clusters (30分) (并查集)
    PAT (Advanced Level) Practice 1105 Spiral Matrix (25分)
    PAT (Advanced Level) Practice 1104 Sum of Number Segments (20分)
    PAT (Advanced Level) Practice 1111 Online Map (30分) (两次迪杰斯特拉混合)
    PAT (Advanced Level) Practice 1110 Complete Binary Tree (25分) (完全二叉树的判断+分享致命婴幼儿错误)
    PAT (Advanced Level) Practice 1109 Group Photo (25分)
    PAT (Advanced Level) Practice 1108 Finding Average (20分)
    P6225 [eJOI2019]异或橙子 树状数组 异或 位运算
    P4124 [CQOI2016]手机号码 数位DP
  • 原文地址:https://www.cnblogs.com/chanwahfung/p/12312253.html
Copyright © 2011-2022 走看看