Banana-FE / github-weekly

Learn something from Github ,Go !
MIT License
3 stars 0 forks source link

2020-Oct-Week2 #1

Open webfansplz opened 3 years ago

webfansplz commented 3 years ago

Share your knowledge and repository sources from Github . ♥️

2020/10/12 - 2020/10/18 ~

For Example :

Repository (仓库地址) : vitesse

Gain (收获) : vitesse 是一个 vite + vue3 起手式项目,我从中收获了:

Shared By (分享者) : webfansplz

rxyshww commented 3 years ago

Repository (仓库地址) :monitorjs_horse Gain (收获) : monitorjs_horse 是一款前端监控工具,主要包含下面几个方面信息监控: 1)前端异常监控; 2)页面性能监控; 3)设备信息采集; 我从中收获了: 1)ajax异常拦截方式

handleError(){
        if(!window.XMLHttpRequest){
            return;
        }
        // 将原生send方法缓存起来,然后重写
        let xhrSend = XMLHttpRequest.prototype.send;
        let _handleEvent = (event)=>{
            try {
                // ...
                // report 投递
            } catch (error) {
                console.log(error);
            }
        };
        XMLHttpRequest.prototype.send = function(){
            // error,load,abort分别代表监听请求失败、成功、终止
            if (this.addEventListener){
                this.addEventListener('error', _handleEvent);
                this.addEventListener('load', _handleEvent);
                this.addEventListener('abort', _handleEvent);
            } else {
                let tempStateChange = this.onreadystatechange;
                this.onreadystatechange = function(event){
                    tempStateChange.apply(this,arguments);
                    if (this.readyState === 4) {
                        _handleEvent(event);
                    }
                }
            }
            // 调用原生 send 方法,传入原有的参数
            return xhrSend.apply(this,arguments);
        }
    }

如果浏览器存在addEventListener方法,则优先使用,可以略去复杂的判断,无需重写onreadystatechange方法去做拦截,并且可以解决一些猥琐的bug。我们看下面的代码:

    let tempStateChange = this.onreadystatechange;
          this.onreadystatechange = function(event){
          tempStateChange && tempStateChange.apply(this, arguments);
          if (this.readyState === 4) {
                 _handleEvent(event);
          }
    }

在上报时,我们可能会使用cors跨域请求投递记录信息,当请求是一个复杂请求时,同一个请求实例会先发出一个opitons请求,用于得知服务端是否允许此次跨域请求行为。同一个实例会send两次,上面的代码也会执行两次,但是两次定义的tempStateChange都指向同一个this.onreadystatechange,在options请求完成以后,this.onreadystatechange会被置为null,第二次send时,发生报错。 解决方案为设置 request:Content-Type: text/plain;charset=UTF-8,并且不携带headers信息,使其成为简单请求,同时后端支持这种接收方式。

scorpioLh commented 3 years ago

Repository (仓库地址):https://github.com/ElemeFE/element/blob/dev/packages/backtop/src/main.vue Gain (收获) : backtop 是一款VUE的UI库ElementUI的返回顶部组件,使用easeInOutCubic函数 + 递归的方式巧妙的做到了“慢——快——慢”的滚动方式。 easeInOutCubic函数:

easeInOutCubic: function(pos){
  if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 3)
  return 0.5 * (Math.pow((pos - 2), 3) + 2)
}
<template>
  <transition name="el-fade-in">
    <div
      v-if="visible"
      :style="{
        'right': styleRight,
        'bottom': styleBottom
      }"
      class="el-backtop"
      @click.stop="handleClick"
    >
      <slot>
        <el-icon name="caret-top" />
      </slot>
    </div>
  </transition>
</template>

<script>
import throttle from 'throttle-debounce/throttle'

// 返回value的三次方
const cubic = value => Math.pow(value, 3)
// easeInOutCubic 给出的是一条先慢后快再慢的曲线图
const easeInOutCubic = value => value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2

export default {
  name: 'ElBacktop',
  props: {
    visibilityHeight: {
      type: Number,
      default: 200
    },
    target: [String],
    right: {
      type: Number,
      default: 40
    },
    bottom: {
      type: Number,
      default: 40
    }
  },
  data() {
    return {
      el: null,
      container: null,
      visible: false
    }
  },
  computed: {
    styleBottom() {
      return `${this.bottom}px`
    },
    styleRight() {
      return `${this.right}px`
    }
  },
  mounted() {
    this.init()
    // throttle为节流函数
    this.throttledScrollHandler = throttle(300, this.onScroll)
    // 监听scroll数值,来决定是否展示该组件
    this.container.addEventListener('scroll', this.throttledScrollHandler)
  },
  beforeDestroy() {
    this.container.removeEventListener('scroll', this.throttledScrollHandler)
  },
  methods: {
    init() {
      // 如果指定了元素就使用指定元素作为容器,如果没有就将document作为容器
      this.container = document
      this.el = document.documentElement
      if (this.target) {
        this.el = document.querySelector(this.target)
        if (!this.el) {
          throw new Error(`target is not existed: ${this.target}`)
        }
        this.container = this.el
      }
    },
    onScroll() {
      const scrollTop = this.el.scrollTop
      this.visible = scrollTop >= this.visibilityHeight
    },
    handleClick(e) {
      this.scrollToTop()
      this.$emit('click', e)
    },
    scrollToTop() {
      const el = this.el
      const beginTime = Date.now()
      const beginValue = el.scrollTop
      /**
       * window.requestAnimationFrame(callback) 告诉浏览器——你希望执行一个动画,
       * 并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,
       * 该回调函数会在浏览器下一次重绘之前执行。函数定时器的16是因为16ms执行一次可以保证每秒60帧,让动画表现更流畅。
       * callback 会收到一个参数,这个 DOMHighResTimeStamp 类型的参数指示当前时间距离开始触发 requestAnimationFrame 的回调的时间。
       */
      const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16))
      /** 采用递归的方式控制页面滚动距离 */
      const frameFunc = () => {
        const progress = (Date.now() - beginTime) / 500
        if (progress < 1) {
          el.scrollTop = beginValue * (1 - easeInOutCubic(progress))
          rAF(frameFunc)
        } else { // 滚动到顶部
          el.scrollTop = 0
        }
      }
      rAF(frameFunc)
    }
  }
}
</script>
AzTea commented 3 years ago

Repository (仓库地址):https://github.com/ustbhuangyi/better-scroll/tree/master/packages/observe-dom Gain (收获) : MutationObserver接口提供了监视对DOM树所做更改的能力。

使用场景: 1.通知用户当前所在的页面所发生的一些变化。 2.通过使用一些很棒的 JavaScript 框架来根据 DOM 的变化来动态加载 JavaScript 模块。 3.可能当你在开发一个所见即所得编辑器的时候,使用 MutationObserver 接口来收集任意时间点上的更改,从而轻松地实现撤消/重做功能。

方法: disconnect():阻止 MutationObserver 实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。 observe():配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。 takeRecords():从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord对象的新Array中。

小细节:MutationObserver的回调是放在microtask中执行的。

observe-dom部分源码;

private handleMutationObserver() {
    if (typeof MutationObserver !== 'undefined') {
      let timer = 0
     //通过往构造函数 MutationObserver 中传入一个函数作为参数来初始化一个 MutationObserver 实例
      this.observer = new MutationObserver((mutations) => {
        this.mutationObserverHandler(mutations, timer)
      })
      this.startObserve(this.observer)
    } else {
      this.checkDOMUpdate()
    }
  }

private mutationObserverHandler(mutations: MutationRecord[], timer: number) {
    if (this.shouldNotRefresh()) {
      return
    }
    let immediateRefresh = false
    let deferredRefresh = false
    for (let i = 0; i < mutations.length; i++) {
      const mutation = mutations[i]
      if (mutation.type !== 'attributes') {
        immediateRefresh = true
        break
      } else {
        if (mutation.target !== this.scroll.scroller.content) {
          deferredRefresh = true
          break
        }
      }
    }

//observe -开始进行监听。接收两个参数-要观察的 DOM 节点以及一个配置对象
private startObserve(observer: MutationObserver) {
    const config = {
      attributes: true,
      childList: true,
      subtree: true,
    }
    observer.observe(this.scroll.scroller.content, config)
  }

//disconnect-停止监听变化。
private stopObserve() {
    this.stopObserver = true
    if (this.observer) {
      this.observer.disconnect()
    }
  }

private checkDOMUpdate() {
    const content = this.scroll.scroller.content
    let contentRect = getRect(content)
    let oldWidth = contentRect.width
    let oldHeight = contentRect.height

    const check = () => {
      if (this.stopObserver) {
        return
      }
      contentRect = getRect(content)
      let newWidth = contentRect.width
      let newHeight = contentRect.height

      if (oldWidth !== newWidth || oldHeight !== newHeight) {
        this.scroll.refresh()
      }
      oldWidth = newWidth
      oldHeight = newHeight

      next()
    }

    const next = () => {
      setTimeout(() => {
        check()
      }, 1000)
    }

    next()
  }

MutationObserver在Vue.nextTick中的使用

let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

在最新的Vue.nextTick源码中,MutationObserver已经变成了一个备胎,Promise变成了首选项。与其说Vue.nextTick想要MutationObserver来帮它真正监听DOM更改,不如说Vue.nextTick只是想要一个异步API,用来在当前的同步代码执行完毕后,执行Vue.nextTick想执行的异步回调,省去不必要的DOM修改,提升DOM Render效率。

Vue.nextTick和better-scroll都将setTimeout作为最后一个备选项。而setTimeout和Promise、MutationObserver都是异步回调, 它们的区别就是前者是macrotask,后两者是microtask,这两者在event loop中的区别相信大家都有了解。

如果说Vue.nextTick的本质是microtask,要是有优先级更高、浏览器兼容性更好的microtask:比如Promise,MutationObserver就被替换掉了。那better-scroll里的MutationObserver是不是也可以被更优的microtask替换掉呢?

webfansplz commented 3 years ago

Repository (仓库地址) : css-diff

Gain (收获) : css-diff 是一个对比 css 差异的库,使用它可以对比两个 css 文件的差异。收获见以下源码解析~

// a.css

.a {
  font-size: 16px;
  color: #fff;
}

// b.css

.a {
  font-size: 16px;
  color: #fff;
}

.b {
  font-weight: normal;
}
// index.js

require("css-diff")({
  files: ["./a.css", "./b.css"],
  omit: ["comment"],
  visual: true,
}).then(function (diff) {
  console.log(diff.visual); // 见下图
  console.log(diff.different); // ture
});

css-diff

源码解析

require("colors");

var cssParse = require("css-parse");
var Promise = require("bluebird");
var Diff = require("diff");
var Compiler = require("./lib/compiler.js");
var Path = require("path");

module.exports = function (options) {
  this.options = options;

  return (
    getContents
      .call(this, options.files)
      // 将两个css文件的cssom 传递给Diff.diffLines方法进行对比
      .spread(Diff.diffLines)
      // 生成差异
      .then(generateDiff)
      .catch(handleError)
  );
};

function handleError(e) {
  process.stderr.write(("Error: " + e.message + "\n").red.inverse);
  process.exit(1);
}
// 核心实现
function generateDiff(diff) {
  var different = false;
  var visual = diff.reduce(function (prev, part) {
    // 判断block是否有差异,很骚的双三元判断
    /**
     * 新增区域使用绿色渲染差异
     * 移除区域使用红色渲染差异
     * 灰色代表相同部分
     * 总结: 灰色表示相同的部分,绿色表示多出来的部分,红色表示少了的部分.
     */
    var color = part.added ? "green" : part.removed ? "red" : "grey";
    // 灰色,表示两个文件不存在差异
    if (color !== "grey") {
      different = true;
    }

    return prev + part.value[color] + "\n";
  }, "");

  return {
    different: different,
    visual: visual,
  };
}

function getContents(files) {
  var _this = this;

  if (files.length < 2) {
    return new Error("you must pass 2 file paths in");
  }

  return Promise.all(
    files.map(function (path) {
      return Compiler(Path.resolve(path)).then(function (css) {
        // 获取css字符串,使用cssParse将css字符串转换为CSSOM
        // css string -> cssom
        var rules = cssParse(css).stylesheet.rules; // 输出结果可见下图
        // 返回 序列化后的 过滤omit参数选项的cssom规则类型数组
        // 这里有个小技巧,JSON.stringify使用了space参数,用于在输出JSON字符串中插入空格以提高可读性。
        return JSON.stringify(
          rules.filter(function (rule) {
            return !~_this.options.omit.indexOf(rule.type);
          }),
          null,
          4
        );
      });
    })
  );
}

a.css,b.css cssom rules output

rules

思考

// 以下代码,你觉得输出的会是什么呢?

require("css-diff")({
  files: ["./b.css", "./a.css"],
  omit: ["comment"],
  visual: true,
}).then(function (diff) {
  console.log(diff.visual);
  console.log(diff.different);
});
BlingSu commented 3 years ago

Respository(仓库地址): commit-demo Gain (收获) : commit-demo 是一款Git提交规范,主要包含下面几个方面: Git提交格式

如何应用:

  1. 安装commitizen格式化git commit message的工具,他可以提供一种询问的方式去获取所需要提交的信息

    $ cnpm i commitizen -D
  2. package.json中的scripts添加如下,并且执行

"scripts": {
  "init": "commitizen init cz-conventional-changelog --save --save-exact"
}

执行成功完会在package.json中生成如下配置

// ...
"config": {
  "commitizen": {
    "path": "./node_modules/cz-conventional-changelog"
  }
}
  1. 只有git commit message的命令可以通过git-cz来实现,也可以配置命令行更加方便的操作,如下

    "scripts": {
    "commit": "git add . && git-cz && git push"
    }
  2. 如上已经用了commitizen来规范提交,但是在有些时候忘记了,确用git commit message来提交了怎么办?所以需要对提交信息进行校验,如果不符合要求的话,那么就拒绝提交,并且提示。通过commitlint来完成校验工作,husky来指定校验时机。

# 安装 commitlint 以及要遵守的规范
$ cnpm i -D @commitlint/cli @commitlint/config-conventional
// 在工程根目录中添加 commitlint.config.js 指定 commitlint的规范
module.exports = {
  extends: ['@commitlint/config-conventional']
}
# 安装 husky
$ cnpm i -D husky
// 在 package.json 中增加配置
"husky": {
  "hooks": {
    "pre-commit": "依赖于什么校验",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
} 

commit-msg是git提交时校验提交信息的钩子,当触发时便会使用commitlit来校验,可以做到统一的提交规范。pre-commit是提交前的钩子,此时提交文件时,便会自动修正并校验错误。即保证了代码风格统一,又能提高代码质量。

  1. 生成日志 通过conventional-changelog生成Change log,可以配置如下
"scripts": {
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -p"
}
  1. package.json 参考配置
{
  "name": "demo",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "commit": "git add . && git-cz && rm -rf ./CHANGELOG.md && npm run changelog && git push",
    "init": "commitizen init cz-conventional-changelog --save --save-exact",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -p",
    "lint": "vue-cli-service lint"
  },
  "devDependencies": {
    "commitizen": "^4.0.3",
    "conventional-changelog-cli": "^2.0.31",
    "cz-conventional-changelog": "^3.1.0",
    "@commitlint/cli": "^8.1.0",
    "@commitlint/config-conventional": "^8.1.0",
    "husky": "^4.2.3"
  },
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
}
diandiantong commented 3 years ago

vue-task-node 是一个基于Vue的任务节点图绘制插件插件 仓库地址:https://github.com/Liwengbin/vue-task-node 通过原生拖拽 draggable 进行基础功能提供,通过监听他的移动事件来进行位置确认

内生成的内容均可自定义外框样式已定以的配置进行区分

不足:此插件使用的连线采用 svg 画线,有部分参数存在兼容问题 没有碰撞检测,两个节点会有重叠现象 连线直间没有检测,线路会交叠

收获:较为系统的理解了拖拽规则及带配置交互规则 内使用了大量计算属性来限制样式的显示 理解了部分画线规则

从一开始通过 dragover 方发监听元素拖拽到目标时进行一次兼容检测位置:utils/firefoxCompatible 并通过 event.dataTransfer.setData() 设置拖拽节点信息 如果要设置图片信息可以把 setData 替换成 setDragImage

通过接收拖拽的节点信息来进行区分当前需要生成的是哪个对应的样式内容并生成对应节点

连线:通过监听固定区域内的鼠标移动轨迹来进行线路计算 监听方法 (mouseover、mouseout) 位置:src/lib/compons/tline 对应代码:

let _this = this.$refs.con
    _this.addEventListener('mouseover', function () {
      let wr = me.$refs.wrap
      wr.classList.add(me.conWrapHoverCls)
      me.$emit('on-mouse-over', wr, me.portData)
    })
    _this.addEventListener('mouseout', function () {
      let wr = me.$refs.wrap
      wr.classList.remove(me.conWrapHoverCls)
      me.$emit('on-mouse-out', wr, me.portData)
    })

在 utils/line 下定以了几种曲线计算(显示的曲线通过参数进行区分) 对应代码:

 /**
   * 计算二次贝塞尔曲线 Q线
   * @param Mxy 起点坐标
   * @param Txy 结束坐标
   * @returns {string} 'M xy Q xy xy Txy'
   */
  calculatedCurvePathQ (Mxy = {}, Txy = {}) {
    let mtx = (Txy.x - Mxy.x) / 4
    let mty = (Txy.y - Mxy.y) / 4
    if (mty < 0 && (mtx > 10 || mtx < -10)) {
      if (mty > -10 && mty < 10) {
        this.Q1xy = new XYObject(Mxy.x + 10, Mxy.y + 30)
      } else {
        this.Q1xy = new XYObject(Mxy.x + 10, Mxy.y + 4 * Math.abs(mty))
      }
      this.Q2xy = new XYObject(Mxy.x + 2 * mtx, Mxy.y + 2 * mty)
    } else {
      this.Q1xy = new XYObject(Mxy.x, Mxy.y + 2 * mty)
      this.Q2xy = new XYObject(Mxy.x + 2 * mtx, Mxy.y + 2 * mty)
    }

    let path = 'M' + Mxy.x.toFixed(1) + ' ' + Mxy.y.toFixed(1) + ' Q ' + this.Q1xy.x.toFixed(1) + ' ' + this.Q1xy.y.toFixed(1) + ', ' + this.Q2xy.x.toFixed(1) + ' ' + this.Q2xy.y.toFixed(1) + ' T ' + Txy.x.toFixed(1) + ' ' + Txy.y.toFixed(1)
    return path
  },
  /**
   * 计算折线 L线
   * @param Mxy 起点坐标
   * @param Txy 结束坐标
   * @returns {string} 'M xy Q xy xy Txy'
   */
  calculatedCurvePathL (Mxy = {}, Txy = {}) {
    let mtx = (Txy.x - Mxy.x) / 2
    let mty = (Txy.y - Mxy.y) / 2

    if (mty > 0) {
      this.L1xy = new XYObject(Mxy.x, Mxy.y + mty)
      this.L2xy = new XYObject(Txy.x, Mxy.y + mty)
      this.path = 'M' + Mxy.x.toFixed(1) + ' ' + Mxy.y.toFixed(1) + ' L ' + this.L1xy.x.toFixed(1) + ' ' + this.L1xy.y.toFixed(1) + ', ' + this.L2xy.x.toFixed(1) + ' ' + this.L2xy.y.toFixed(1) + ' T ' + Txy.x.toFixed(1) + ' ' + Txy.y.toFixed(1)
    } else {
      this.L1xy = new XYObject(Mxy.x, Mxy.y + 30)
      this.L2xy = new XYObject(Mxy.x + mtx, Mxy.y + 30)
      this.L3xy = new XYObject(Mxy.x + mtx, Txy.y - 30)
      this.L4xy = new XYObject(Txy.x, Txy.y - 30)
      this.path = 'M' + Mxy.x.toFixed(1) + ' ' + Mxy.y.toFixed(1) + ' L ' + this.L1xy.x.toFixed(1) + ' ' + this.L1xy.y.toFixed(1) + ', ' + this.L2xy.x.toFixed(1) + ' ' + this.L2xy.y.toFixed(1) + ', ' + this.L3xy.x.toFixed(1) + ' ' + this.L3xy.y.toFixed(1) + ', ' + this.L4xy.x.toFixed(1) + ' ' + this.L4xy.y.toFixed(1) + ' T ' + Txy.x.toFixed(1) + ' ' + Txy.y.toFixed(1)
    }
    return this.path
  },
  /**
   * 计算直线 ML线
   * @param Mxy 起点坐标
   * @param Txy 结束坐标
   * @returns {string} 'M xy Lxy'
   */
  calculatedCurvePathML (Mxy = {}, Txy = {}) {
    this.path = 'M' + Mxy.x.toFixed(1) + ' ' + Mxy.y.toFixed(1) + ' L ' + Txy.x.toFixed(1) + ' ' + Txy.y.toFixed(1)
    return this.path
  },
  /**
   * 获取曲线路径
   * @param Mxy 起点坐标
   * @param Txy 结束坐标
   * @returns {string} 'M xy Q xy xy Txy'
   */
  drawCurvePath (Mxy = {}, Txy = {}, type = 'Q', scaling) {
    let scalingMxy = {
      x: Mxy.x / scaling.ZoomX,
      y: Mxy.y / scaling.ZoomY
    }

    let scalingTxy = {
      x: Txy.x / scaling.ZoomX,
      y: Txy.y / scaling.ZoomY
    }
    if (type === 'Q') {
      return this.calculatedCurvePathQ(scalingMxy, scalingTxy)
    } else if (type === 'L') {
      return this.calculatedCurvePathL(scalingMxy, scalingTxy)
    } else if (type === 'ML') {
      return this.calculatedCurvePathML(scalingMxy, scalingTxy)
    }
  },

节点移出工作区检测:src/lib/components/node/node 对应代码:

getCheckX (X) { // 检查是否移出工作区
      let me = this
      let x = X
      if (x <= 0) {
        x = 0
        me.node.positionX = x
      } else if (x >= me.getBrowserHW().width) {
        x = me.getBrowserHW().width - me.width
        me.node.positionX = x
      }
      return x
    },
    getCheckY (Y) { // 检查是否移出工作区
      let me = this
      let y = Y
      if (y <= 0) {
        y = 0
        me.node.positionY = y
      } else if (y >= me.getBrowserHW().height) {
        y = me.getBrowserHW().height - me.height
        me.node.positionY = y
      }
      return y
    },

浏览器窗口大小变化时检测 位置:src/lib/components/workarea/workarea

添加、移动节点检测

updateTemp (val) {
      this.updateTem()
    },
    selectNodeMethod: function (event, node, ref) {
      this.$emit('on-select', event, node, ref)
    },
    dragStart: function (event, node) {
      this.$emit('on-drag-start', event, node)
    },
    dragGing: function (event) {
      this.$emit('on-drag-ging', event)
    },
    dragEnd: function (event, node) {
      this.$emit('on-drag-end', event, node)
    },
    addPath: function (event, start, end) {
      this.$emit('on-add-path', event, start, end)
    },
    mouseMenu: function (event, node) {
      this.$emit('on-mouse', event, node)
    }
  },
  updated: function () {
    this.updateTem()
  }

在离开当前组建时,移除对应自定义的监听内容

beforeUpdate: function () {
    if (!this.isDraw) {
      this.drawCurvePath()
    }
    this.isDraw = false
  },
  drawCurvePath () {
      if (this.portData.Mxy && this.portData.Txy) {
        this.lpath = Line.drawCurvePath(this.portData.Mxy, this.portData.Txy, this.portData.ptype, this.$store.getters.getViConfig.scaling)
      }
      return this.lpath
    },
wenfeihuazha commented 3 years ago

mitt 是一个200字节的一个工具库 以小而美著名

仓库地址 以 200b 的大小实现了 on,off,emit 这三个api 代码中有些技巧还是让人觉得耳目一新

源码

export type EventType = string | symbol;

// An event handler can take an optional event argument
// and should not return a value
export type Handler<T = any> = (event?: T) => void;
export type WildcardHandler = (type: EventType, event?: any) => void;

// An array of all currently registered event handlers for a type
export type EventHandlerList = Array<Handler>;
export type WildCardEventHandlerList = Array<WildcardHandler>;

// A map of event types and their corresponding event handlers.
export type EventHandlerMap = Map<EventType, EventHandlerList | WildCardEventHandlerList>;

export interface Emitter {
    all: EventHandlerMap;

    on<T = any>(type: EventType, handler: Handler<T>): void;
    on(type: '*', handler: WildcardHandler): void;

    off<T = any>(type: EventType, handler: Handler<T>): void;
    off(type: '*', handler: WildcardHandler): void;

    emit<T = any>(type: EventType, event?: T): void;
    emit(type: '*', event?: any): void;
}

/**
 * Mitt: Tiny (~200b) functional event emitter / pubsub.
 * @name mitt
 * @returns {Mitt}
 */
export default function mitt(all?: EventHandlerMap): Emitter {
    all = all || new Map();

    return {

        /**
         * A Map of event names to registered handler functions.
         */
        all,

        /**
         * Register an event handler for the given type.
         * @param {string|symbol} type Type of event to listen for, or `"*"` for all events
         * @param {Function} handler Function to call in response to given event
         * @memberOf mitt
         */
        on<T = any>(type: EventType, handler: Handler<T>) {
            const handlers = all.get(type);
            const added = handlers && handlers.push(handler);
            if (!added) {
                all.set(type, [handler]);
            }
        },

        /**
         * Remove an event handler for the given type.
         * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"`
         * @param {Function} handler Handler function to remove
         * @memberOf mitt
         */
        off<T = any>(type: EventType, handler: Handler<T>) {
            const handlers = all.get(type);
            if (handlers) {
                handlers.splice(handlers.indexOf(handler) >>> 0, 1);
            }
        },

        /**
         * Invoke all handlers for the given type.
         * If present, `"*"` handlers are invoked after type-matched handlers.
         *
         * Note: Manually firing "*" handlers is not supported.
         *
         * @param {string|symbol} type The event type to invoke
         * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
         * @memberOf mitt
         */
        emit<T = any>(type: EventType, evt: T) {
            ((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });
            ((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });
        }
    };
}

分析代码:

//接受要关闭的监听
off<T = any>(type: EventType, handler: Handler<T>) {
  const handlers = all.get(type);
  //判断是否存在后用 >>> 0 的操作来减少代码体积
  if (handlers) {
    //如果 查询的到位置 10 >>> 0 => 10 但是 -1 >>> 0 会获得一个巨大的正整数,让这一行代码做到删除需要的数据,如果查找不到不做处理
    handlers.splice(handlers.indexOf(handler) >>> 0, 1);
  }
},
               // on中分析的代码
               on<T = any>(type: EventType, handler: Handler<T>) {
            const handlers = all.get(type); //检索是否已存在监听对象
            const added = handlers && handlers.push(handler); //如果存在,直接push新的监听事件,
            if (!added) { //如果不存在 重新set
                all.set(type, [handler]);
            }
        },

这是一个比较出名的开源库 200b 的代码量不多,其实中间有不少这两年流行的操作,但是整个代码分析下来还是会有很多点是知道,但是没注意过的技巧组合.建议还是慢慢分析一遍