2020-05 技术汇总

2020/05/28 周四

#node package.json中版本前的 ~ 与 ^ 分别代表什么

来看看element ui的package.json,其中async-validator是 ~ 开头,而其他都是 ^ 开头,有什么区别呢?

"dependencies": {
  "async-validator": "~1.8.1",
  "babel-helper-vue-jsx-merge-props": "^2.0.0",
  "deepmerge": "^1.2.0",
  "normalize-wheel": "^1.0.1",
  "resize-observer-polyfill": "^1.5.0",
  "throttle-debounce": "^1.0.1"
},

版本格式 1.8.1 对应 major.minor.patch

  • major:表示版本有了一个大更改。
  • minor:表示增加了新的功能,并且可以向后兼容。
  • patch:表示修复了bug,并且可以向后兼容。

~:他会更新到当前minor version(也就是中间的那位数字)中最新的版本,也就是只变动patch到最新版本,它不会自动更新minor版本, 波浪符号是曾经npm安装时候的默认符号,现在已经变为了插入符号。

^:这个符号就显得非常的灵活了,他将会把当前库的版本更新到当前major version(也就是第一位数字)中最新的版本。也就是更新minor到最新版本,他不会自动更新major版本。

当我们使用最新的Node运行'npm instal --save xxx',的时候,会优先考虑使用插入符号(^)而不是波浪符号(~)了。

可以手动安装指定版本

# 安装最新版本
npm instlal xxx 
# 默认情况下,会安装最新版本npm包,等价于
npm install xxx@latest
# 安装指定版本
npm install xxx@[指定版本号]
# 安装未来版本,未正式发布的beta版本
npm install xxx@next 

参考:Node.js中package.json中库的版本号详解(^和~区别)(opens new window)

#vue element表单组件简单实现

先来写一个调用示例,把el-前缀换成z-,然后我们需要实现z-form, z-form-item, z-input组件





表单组件的封装:ZInput.vue

  • Input
    • 双向绑定:@input、:value 派发校验事件
    • 派发校验事件




  • FormItem ZFormItem.vue
    • 给Input预留插槽
    • slot 能够展示label和校验信息
    • 能够进行校验




  • Form ZForm.vue
    • 给FormItem留插槽
    • 设置数据和校验规则
    • 全局校验




这里的实现和element-ui的实现有什么区别呢?可以参考element源码 (opens new window),我这里简单说几个区别

  1. element使用的表单校验 async-validator是 1.x.x 版本,而上面的示例需要使用的版本 3.x.x版本
  2. Form组件里provide,上面只为form绑定了rules和props,element中指定绑定了当前this
  3. 事件的监听和触发,这里使用的是$parent来处理,element里面通过 this.dispatch事件来触发
  4. element form支持很多参数,这里只是简单的模拟,element里面会复杂很多。

完整demo参见 element form实现 - fedemo | github(opens new window)

#2020/05/27 周三

#tabs标签页组件的坑

当使用tabs组件,特别是同一个组件可能会打开多个tab时。需要注意

  1. 组件打开一次后,created已执行,再打开一个tab时,不会触发created或mounted,需要用watch监听prop传值的改变进行一些请求接口的初始化操作,如果组件还有子组件,也需要这样做,防止数据不刷新的问题
  2. 点击一个tab后,如果请求比较慢,再点击另一个tab,数据可能会乱,注意tab切换时,取消发出的请求
  3. 仔细检查同组件不同tab切换时的数据、操作相关的影响,需要做到互相独立,互不影响

#pinyin中文转拼音npm包在前端使用时的坑

在很早之前node项目中就使用过这个npm包。这次由于Element table组件排序时,无法按照首字母排序,就引入了这个包。由于是单页面应用,import进来是没问题的,chrome里面正常。

import pinyin from "pinyin";

console.log(
  pinyin("测试", {
    style: pinyin.STYLE_NORMAL, // 设置拼音风格
    heteronym: true
  }).join("") 
);
// ceshi

后面在IE11里出现了一个bug,就是页面路由不能正常加载,调了好久。最开始以为是路由层级的问题,调到怀疑人生,最后发现是 pinyin 这个包的问题,他在IE下无法正常加载,偶尔报错 "函数错误",导致整个页面执行失败,路由无法加载。所以在遇到难调试的问题时,先把error的报错全部解决再调, 已经遇到好几次这种情况了

IE下出现异常,console里是无法看到是哪个文件报错的,需要在F12里点击断点位置,选择遇到错误时停止,这样出现问题就会自动跳转到对应的位置

ie_jserror.png

#2020/05/20 周三

#sessionStorage新打开一个tab页就失效的问题

首页我们要知道3点:

  1. sessionStorage在浏览器的两个tab页之前是无法共享的,一个tab页中sessionStorage修改后,不能触发其他tab页的storage事件
  2. 当前tab页的localStorage修改是无法触发当前页的storage事件的,他会触发其他tab页的storage事件
  3. localStorage的共享,只发生在同源的地址里。非同源无法共享localStorage

怎么将就页面的sessionStorage传递到新开的tab页呢?

由于sessionStorage打开新tab页默认会丢失。那新开tab页的sessionStorage就是空的。我们可以判断,如果sessionStorage.length值为0,那么就是新开的页面。这时我们通过设置一个localStorage字段的值,触发之前打开页面的Storage事件,在这个事件里我们将当前页面的sessionStorage通过localStorage设置值,来触发新页面的Storage事件,把sessionStorage传递到新的页面

下面是部分核心代码,详细demo参见 github demo地址(opens new window)



参考:

  • HTML 拖放 API - Web API 接口参考 | MDN(opens new window)
  • dataTransfer.setData无效,drop不触发的问题(opens new window)
  • 火狐drop后会打开新tab的问题(opens new window)
  • js 拖动后,怎么保持原来的元素不消失,drop后拖动元素消失的问题(opens new window)
  • cloneNode | JS高程3笔记(opens new window)

扩展:

  • 从电脑拖放文件到浏览器,读取拖拽文件并上传 | JS高程3笔记(opens new window)
  • 非H5原生拖放实现拖放 | JS高程3笔记(opens new window)

#2020/05/17 周日

#a + 1 === a + 2 为true的情况

注意这里是全等,不是宽松相等时,隐式转换的问题。我现在了解的有两种情况:

// 1\. Infinity
var a = Infinity // Infinity是这个神奇的数,我试了下除了 * 0等于NAN外,其他情况基本都等于他自己
a + 1 === a + 2 // true

// 2\. Math.pow(2, 53) - 1 最大的安全整数
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) -1 // true
a = Number.MAX_SAFE_INTEGER
a + 1 === a + 2 // true

以上,当大于2的53次方-1时,就不安全了,结果会超出常规,ES2020引入了bigint来处理大于2的53次方-1的数据

// bigint类型的数与n结尾
a = BigInt(Number.MAX_SAFE_INTEGER) // 9007199254740991n
a + 1n // 9007199254740992n
a + 2n // 9007199254740993n

参考:ES2020 bigint数据类型,为什么要新增这个数据类型?(opens new window)

#2020/05/14 周四

#less使用mixin抽取公共代码,减少重复代码

由于没有系统的学习less,之前只用到less的嵌套写法,很少用变量,基本没用mixin模块化封装,这次尝试了下,发现还是不错的,下面来用封装一个基础的布局组件








common/base.less

.container-mixin() {
  .container {
    @headerHeight: 100px; /* 变量,顶部高度 */
    .top {
      height: @headerHeight;
      background: #999;
    }
    .main {
      display: flex;
      height: calc(100vh - @headerHeight);
      background-color: rgba(255, 0, 0, 0.2);
      .left {
        width: 20%;
        background-color: greenyellow;
      }
      .right {
        width: 80%;
        background-color: turquoise;
      }
    }
  }
}

公共方法封装的好处在于,下次如果相同的页面,就不需要再写一遍了,虽然用class也可以,但less的mixin会更加强大,灵活,他还可以传参数,我们在页面B引入时,可以对默认样式进行修改







上面的例子中使用 (reference) 是为了防止在不同的组件中引入导致公共代码多次打包问题

#2020/05/13 周三

#vue为什么建议永远不要把 v-if 和 v-for 同时用在同一个元素上

当Vue处理指令时,v-for 比 v-if 优先级高,来看个例子

  • {{ user.name }}

将进行如下计算,其实显示是正常的,但v-for会遍历所有的元素,哪怕我们只想通过v-if渲染出少部分元素,每次重新渲染的时候都会遍历整个列表

this.users.map(function (user) {
  if (user.isActive) {
    return user.name
  }
})

这种情况,建议使用 computed属性过滤需要显示的数组

computed: {
  activeUsers: function () {
    return this.users.filter(function (user) {
      return user.isActive
    })
  }
}

参考:避免-v-if-和-v-for-用在一起必要 | Vue.js(opens new window)

#vue项目文件以及文件夹命名规范问题

单文件组件文件名 要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。

vue官方风格指南没有建议文件夹的命名,找了个网上我比较认同的一种,文件或文件夹的命名遵循以下原则:

  • index.js 或者 index.vue,统一使用小写字母开头的(kebab-case)命名规范
  • 属于组件或类的,统一使用大写字母开头的(PascalCase)命名规范
  • 其他非组件或类的,统一使用小写字母开头的(kebab-case)命名规范

参考

  • 风格指南 | Vue.js(opens new window)
  • Vue项目中的文件/文件夹命名规范(opens new window)

#vue里简单的总线(bus)发布订阅模式实现

先来看看怎么调用

// main.js
import Bus from 'Bus.js'
Vue.prototype.$bus = new Bus()

// child1
this.$bus.$on('foo', handle)
// child2
this.$bus.$emit('foo')

来写Bus.js

class Bus {
  constructor() {
    this.callbacks = {}
  }
  $on(name, fn) {
    // 如果之前没有监听,就创建一个新的数组
    !this.callbacks[name] && (this.callbacks[name] = [])
    typeof fn === 'function' && this.callbacks[name].push(fn)
  }
  $emit(name, args) {
    if (this.callbacks[name]) {
      this.callbacks[name].forEach(cb => cb(args))
    }
  }
  $off(name, fn) {
    if (this.callbacks[name]) {
      // 如果没传fn, 移除所有,如果传了移除对应的函数,这里只做移除素有的
      this.callbacks[name] = undefined // 讲思路
    }
  } 
} 

export default Bus

#vue组件之间通信方式总结

父组件 => 子组件

  • props

    // child
    props: { msg: string }
    
    // parent
    
    
    
  • 引用refs

    // parent
    
    
    this.$refs.hw.xx
    
    

子组件 => 父组件

// child
this.$emit('add', 'val')

// parent


兄弟组件:通过共同的祖辈组件(root) 利用vue内置的发布订阅模式功能

// brother1
this.$parent.$on('foo', handle)
// brother2
this.$parent.$emit('foo')

祖先和后代之间

  • provide / inject 祖先给后代传值

    // 祖先组件
    provide() {
      return { foo: 'foo'}  // 要像data一样,用函数包裹
    }
    
    // 后代组件
    inject: ['foo']
    
    
  • dispatch:后代给祖先传值

    function dispatch(eventName, data) {
      let parent = this.$parent
      // 只要还存在父元素就继续往上找
      while (parent) {
        // 父元素用$emit触发
        parent.$emit(eventName, data)
        // 继续传给上一层父元素
        parent = parent.$parent
      }
    }
    
    

任意两个组件之间:事件总线(Bus)或vuex

// vue组件自身实现了发布订阅模式
// Bus.js
export default new Vue()

// A组件
import Bus from 'Bus'
Bus.$on('foo', handle)
// B组件
import Bus from 'Bus'
Bus.$emit('foo', 'val')

#2020/05/12 周二

#vue为什么要将插槽slot="aaa"的写法变更为v-slot:aaa

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除

具名插槽 主要用于当有多个插槽时,通过名字对不同的插槽进行区分

由于在父组件里使用子组件,会写上对应的插槽,这时插槽的作用域为当前的父组件,如果想在这里获取子组件的作用域,就需要作用域插槽

来看代码


A paragraph for the main content.

Here might be a page title {{slotProps.user}}

A paragraph for the main content.

v-slot将slot和slot-scope简写为一个属性,且v-slot更符合vue的语法规则

参考:

  • v-slot | vue rfcs(opens new window)
  • 插槽 | Vue.js(opens new window)

#vue中$attrs$listeners 的使用场景

之前的笔记有提过,如果A组件包含B组件,B组件包含C组件。C组件想要触发A组件的方法,可以在B组件上加 v-on="$listeners" 来实现。那他做了哪些操作呢?

我们知道template里面 {{}} 或者v-bind、v-on等于的 "" 里,直接会省去this,v-on="$listeners" 里面的值在.vue文件的 script 中,可以使用 this.$listeners 来获取

$listeners 它是一个对象,包含了作用在当前组件上的所有监听器

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

$attr$listeners有什么用呢?他们在对input等表单元素的二次封装时非常有用

比如我要封装一个 zuo-input 组件,来对原生的input元素进行功能性增强,来看看zuo-input可能的使用场景



我们需要把 zuo-input 上的属性、方法直接绑定到内部的input元素上,你可以用props来传,但是如果有很多个属性呢?一个属性写一个props就太麻烦了,这时我们可以使用 $attrs

$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。

v-bind="$attrs" 他类似属性展开运算符,将父组件调用子组件时传入的属性展开(不包含props已接收的)、v-bind到当前的元素上。

v-on="$listeners" 也类似上面的行为,他会将父组件传递给子组件的事件 v-on 到当前的元素上

在 zuo-input 组件内部可以通过下面的方法直接绑定prop及事件

 

参考:

  • $attrs$listeners | Vue.js(opens new window)
  • 将原生事件绑定到组件 | Vue.js(opens new window)

#v-model与.sync的区别

一般父组件给子组件传值是单向的,对于非引用类型,子组件怎么修改父组件传给子组件prop对应的值呢?除了通过 root、Bus(发布,订阅)、状态管理(vuex)、额外定义一个方法外,还有两种方法:使用 v-model,或者为加.sync,来看下对比

先来看v-model







再来看.sync







两者的区别:

  • v-model主要用于表单输入的双向绑定,注重值的改变,.sync主要用于状态的切换
  • v-model事件及prop的名称,子组件接收时是可以通过model自定义的,.sync子组件接收到的值是固定的

参考:

  • .sync修饰符 | Vue.js(opens new window)
  • vue中v-model和.sync修饰符区别(opens new window)

#vue在自定义组件上使用v-model指令

在自定义组件上,使用v-model指令,默认会向子组件传递一个字段名为 value 的 prop 属性,以及绑定一个名为 input 的事件。在子组件里,可以用props来接收value字段,可以用 this.$emit('input') 来对父组件里value的值进行修改。







怎么修改v-model的默认行为呢?

model选项,允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。

export default {
  model: {
    prop: 'show',
    event: 'close'
  }
  props: {
    show: {
      type: String,
      required: true
    }
  },
  data() {
    return {}
  },
  methods: {
    modifyParentCompsValue() {
      this.$emit('close', '要设置的值')
    }
  }
}

参考:

  • 选项 model | Vue.js API(opens new window)
  • 自定义组件的 v-model | Vue.js(opens new window)

#2020/05/11 周一

#v-infinite-scroll 放到slot里或者用v-if控制时首次无法触发loadMore的问题

最新项目结构调整,发现一个问题,把 v-infinite-scroll 对应的元素放到 slot 里,首次无法触发loadMore, 不放到slot里面又是正常的,来看代码



带着这个问题,我看了下 v-infinite-scroll 的源码,在关键位置写了几个console,找到了其中的原因,来看看产生问题的地方

// InfiniteScroll 部分源码
// github地址:https://github.com/ElemeFE/vue-infinite-scroll/blob/master/src/directive.js
var InfiniteScroll = {
  bind: function bind(el, binding, vnode) {
    el[ctx] = {
      el: el,
      vm: vnode.context,
      expression: binding.value
    };
    var args = arguments;
    console.log('bindfunc before mouted', el[ctx].vm, el[ctx])
    el[ctx].vm.$on('hook:mounted', function () {
      console.log('hook:mounted')
      el[ctx].vm.$nextTick(function () {
        if (isAttached(el)) {
          doBind.call(el[ctx], args);
        }

正常情况下,页面一加载,InfiniteScroll 会开始初始化,执行其bind函数。bind函数里加了一个监听,当接收到当前组件的 hook:mounted 事件,也就是mounted事件时,开始做真正的绑定,执行doBind方法。

那么问题来了,正常情况下,在组件mounted之前,InfiniteScroll会完成初始化,这样就可以接收到页面的mounted消息,然后执行真正的相关事件绑定。

假如我们把 v-infinite-scroll 写在slot里,当前页面组件mounted过后,InfiniteScroll才执行初始化,初始化时监听mounted再执行doBind,而页面已经mounted过了,所以会无法触发loadMore,同理,v-if 控制时,如果为false,可能会有InfiniteScroll没初始化之前,页面就已经mounted的情况。

怎么解决这个问题呢?记住 v-infinite-scroll 必须放在一个单独的单文件组件里,不要放到某个组件的slot里。且不要用v-if控制,使用v-show,这样就不会有问题了。

测试demo,参见: v-infinite-scroll 测试demo(opens new window)

#2020/05/08 周五

#动态组件怎么动态绑定一个或多个v-bind属性

最近有封装一个tabs标签页组件,引入组件,可以将页面进行tab化。

原先的页面作为子组件放到tabs组件里,由于标签页跳转页面时有需要打开新的标签页。所以tabs组件里会包含多个页面组件。

为了避免像el-tabs那样,每次引入tabs组件都需要自己写v-if的逻辑来切换tab显示。我把这一步封装到了自定义tabs组件内部,内部使用动态组件component、is来切换组件显示。

为了页面tab化时最好不要改动,我需要根据不同的组件,动态v-bind不同的组件名。但问题是动态v-bind属性局限性很强,由于动态属性还包含修饰符,所以只能是单个的变量,不能是 tabs[curTabIndex].prop 这种写法,且这种方法只能传入一个参数,如果tab页组件需要传入多个参数,那怎么办?我暂时直接用options传入一个对象,在需要tab化的组件里转换一下才行。

如果需要更好的处理,可能需要写render函数了。



另外如果在动态组件里加了 keep-alive 也是有坑的,因为假如可以打开多个详情标签页,那多个标签页是同一个组件,只是不同的值在切换,如果加了keep-alive那每次打开的都是同一个详情页,我们可以使用watch监听下options值的变动,值改动时,触发页面数据跟着改变,也就是tabs页对于需要打开多个相同组件,不同内容的tab,是做不到keep-alive的,除非自己写缓存逻辑,-_-

组件封装的目的很简单,就是封装变化、减少代码量。易用性、可扩展、可维护性之间要寻找一个平衡。看哪些是必须要提供的功能,在这个前提下尽量精简,精简到不能继续封装为止。另外我们在使用这个组件时,需要做的工作尽可能少,代码尽量优雅。核心问题还是提高效率,增加代码结构化。

在大话设计模式的书里,有讲到,产品可能会频繁的变动、新增功能。我们要考虑到页面可能发生的各种变化,尽量在发生变动时,不用怎么改代码,这也是设计模式的核心理念:封装变化。这样才能写出更健壮的代码。

#多层级组件,父组件怎么将事件传递给孙组件?

来看一个例子,假设A组件包含组件B,B组件又包含组件C,我们知道,在B组件里 this.$emit('open-tab') 会执行其父组件A里面对应的方法,但如果B的子组件C,也想触发A组件的事件,那要怎么做呢?





这就要用到 v-on="$listeners",在B组件上加上这个属性,可以将A组件上v-on绑定的事件(不含 .native 修饰器的)传递到其子组件,对创建高层级组件非常有用。



同理怎么将A组件的props值传递到C组件?可以通过加 v-bind="$attrs" 来实现

vm.$listeners API — Vue.js(opens new window)

#2020/05/03 周日

#uni动态修改导航栏按钮文案

先来看导航栏按钮配置,导航栏右侧有一个按钮 "编辑"

{
  "path": "pages/cart/cart",
  "style": {
    "navigationBarTitleText": "标题",
    "app-plus": {
      "autoBackButton": false,
      "titleNView": {
        // 这里没有用搜索栏
        // "searchInput": {
        //     "align": "center",
        //     "backgroundColor": "#eee",
        //     "borderRadius": "5px", // 只能用px作单位
        //     "placeholder": "请输入内容",
        //     "placeholderColor": "#ccc"
        // },
        "buttons": [{
            "color": "#222222",
            "colorPressed": "#eee",
            "float": "right",
            "fontSize": "14px",
            "width": "45px",
            "text": "编辑" // 字体图标\u 开头,加上字体图标unicode后面四位
        }]
      }
    }
  }
}

对应的js

export default {
  // 导航栏右侧按钮  编辑 => 完成
  // 点击编辑或完成,会触发该函数
  onNavigationBarButtonTap(e) {
    let isApp = !!this.$mp.page.$getAppWebview
    if (isApp) {
      // 如果是app场景
      this.changeNavButtonText()
    } else {
      // 如果是H5
      let btnEle = document.querySelectorAll('.uni-page-head-btn i')[1]
      let curText = btnEle.textContent
      btnEle.textContent = curText === '完成' ? '编辑' : '完成'
      this.isEdit = curText === '编辑'
    }
  },
  methods: {
     // 修改导航栏标题
    changeNavButtonText(text) {
      let webview = this.$mp.page.$getAppWebview()
      let tn = webview.getStyle().titleNView;
      let curText = webview.getStyle().titleNView.buttons[0].text

      webview.setTitleNViewButtonStyle(0, {  
        text: curText === '完成' ? '编辑' : '完成'
      }); 
      this.isEdit = curText === '编辑' 
      // 用于真机调试时 log
      // uni.showToast({
      //   title: curText + '/' + this.isEdit + '/' + uni.getSystemInfoSync().platform,
      //   icon: 'none',
      //   image: '',
      //   duration: 1500,
      //   mask: false,
      // })
    }
  }
}

#uni复制功能只支持app、小程序,怎么兼容H5

当H5时引导用户自己选择后copy,如果是app调用uni的api

copy() {
  // #ifdef H5
  prompt('复制失败。请选中下列微信号,手动复制', this.copyInfo)
  // #endif

  // #ifdef APP-PLUS
  uni.setClipboardData({
    data: this.contact,
    success: function () {
      uni.showToast({
        title: '复制成功',
        icon: 'none',
        image: '',
        duration: 1500,
        mask: false,
      })
    }
  });
  // #endif
}

#2020/05/01 周五

#怎么看chrome浏览器更新记录及内容

最近发现办公电脑的chrome浏览器console里不支持 ?? 运算符,而我自己的电脑就可以,对比了下版本,我的是最新的81版本,而办公电脑还是71的版本,于是我就想看看chrome每次版本的更新记录,这个貌似要翻墙,我用了一个开源的chrome访问助手,找到了对应的位置

Web Updates (2020) | Google Developers"(opens new window)

这里有介绍每次chrome的更新记录,按月份来,以4月的 Chrome 81来讲 New in Chrome 81 (opens new window)介绍了对应的改动,比如

  • I've got an update on the adjusted Chrome release schedule.
  • App Icon Badging graduates from its origin trial.
  • Hit testing for augmented reality is now available in the browser.(WebXR hit testing)
  • Web NFC starts its origin trial.
  • And more.

感觉发现了新大陆,web还可以操作NFC... 对于了解一些新的技术,是很有必要看看这些的,顺便练习下英语

#为什么要写单元测试

昨天04/30日发版,持续到到今天凌晨2点左右,测试发现有个bug:时间区间组件DatePicker前面一个时间没有显示,而这里应该显示最近一周的时间区间,现在只显示了后面一个时间。

但测试环境测试、UAT测试都是过了的,怎么突然就有问题了呢?于是看同事的代码定位问题,发现根据当前时间计算最近一周的日期逻辑有问题,只是简单粗暴的把 day 减了 6 天,之前一直是4月中下旬,大于6,所以没有出问题,这次正好是5月1号, 1 - 6 就是 -5 了, 时间拼接为 2020-05-0-5,这就导致有bug了。还好今天是5月1号,不然测不出这个bug就会导致后面生成环境的bug了。

从发现问题到定位问提、解决问题,大概用了5-10分钟左右。最后用当前时间戳 - 6 * 24 * 3600 * 1000 来解决。

我们一般在写程序时,很难发现自己逻辑上的bug,假设我们这里有写单元测试,考虑了很多种情况,那就可以避免这个问题。但现实是,我们目前大部分人都没有这个习惯,只要测试过了,就基本以为没问题了。但对于那种测试都测试不出来的在特定时间才会触发的bug,真的很可能造成线上bug,完全依赖于个人写代码时的逻辑严谨性。

怎么让自己写的代码更严谨,出问题的几率更小呢?那就是写单元测试。这样我们会考虑更多的特殊场景、而不是靠人肉测试。

#WebSocket的使用场景

WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。之前在工作中基本没用到过,今天偶然看到一个网站,他里面列出了WebSocket的几种使用场景,如下:

  • 在线多人点菜
  • 远程画版同步
  • 在线选座
  • 游戏 (只要涉及到多人对战、协同的就需要用到)
  • 扫码登录/支付
  • IM 聊天

而且还可以在线体验,还不错,体验地址:https://www.goeasy.io/cn/demos/demos.html

你可能感兴趣的:(2020-05 技术汇总)