手写事件管理类(EventBus)-简易的观察者模式

因为小程序目前的基础库版本不支持全局的事件订阅和触发,只能自己写一个了,不然小程序里面没有vuex之类的东西用还真是不方便。

/* 简易的观察者实现
 * @Author: Liuxiaofan 
 * @Date: 2018-10-25 16:24:51 
 * @Last Modified by: Liuxiaofan
 * @Last Modified time: 2018-10-25 17:24:17
 */
class EventBus {
    constructor() {
        this.events = {}
    }
    getEvents() {
        return this.events || (this.events = {});
    }
    getListeners(eventName) {
        let events = this.getEvents();
        return events[eventName] || (events[eventName] = []);
    }
    on(eventName, fn, time) {
        time = typeof (time) == 'number' ? time : -1;
        time = time >= -1 ? time : -1;
        var listeners = this.getListeners(eventName);
        var listenerWrapper = {
            listener: fn,
            time: time,
        };
        listeners.push(listenerWrapper);
        return this;
    }
    off(eventName) {
        var events = this.getEvents();
        events[eventName] = [];
    }
    removeListener(eventName, listener) {
        var listeners = this.getListeners(eventName);
        for (var i = 0; i < listeners.length; i++) {
            if (listeners[i].listener == listener) {
                delete listeners[i];
            }
        }
    }
    trigger(eventName, args) {
        var listeners = this.getListeners(eventName);
        for (var i = 0; i < listeners.length; i++) {
            var listener = listeners[i];
            if (listener.time != -1) {
                listener.time--;
            }
            if (listener.time == 0) {
                this.removeListener(eventName, listener.listener);
            }

            listener.listener.apply(this, args || []);
        }
    }
    emit(eventName) {
        var args = Array.prototype.slice.call(arguments, 1);
        return this.trigger(eventName, args);
    }
}
export default EventBus

vuex 官方教程平民翻译版 – 核心概念

如果你看不懂vuex的官方教程,那就对了,因为他们写的不够平民化,而且用了很多术语来迷惑对手,来看看我翻译的平民版教程吧。

问:VUEX有什么用?
答:管理全局变量和相关的全局方法。当你需要子子通信的时候就知道了,如果一层一层的传值一定会把你烦死,所以你需要一个公用的全局对象去管理一些全局的变量和方法。
vuex就是eventbus(vue2.0之后改名叫store模式了)的升级版。jquery时代和backbone时代可以使用观察者模式实现,也有人使用nodejs里面提取的EventEmitter.js来实现“状态管理”,不过那时候还没有“状态管理”这么高大尚的词,我们都叫事件管理或者事件模型。

store 模式

这是一个精简版的vuex

// 定义全局对象 store
var store = {
    debug: true,
    state: {
        message: 'Hello!'
    },
    setMessageAction (newValue) { 
        if(this.debug) console.log(’setMess',newValue)
        this.state.message = newValue
    },
    clearMessageAction () {
         if(this.debug) console.log(’clearMess')
        this.state.message = ''
    }
}
// 组件里面声明好那些属性是私有的哪些是公共的
var vmA = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

var vmB = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

store内所有state值变化的操作方法都要放到store对象自身的方法里面去实现。然后再用一个log来记录每次操作。这样控制台里面就能看到所有与state变化的相关操作记录了。

基于上面的简单store模式,继续延伸约定。你不应该在各自的子组件里面直接修改store内的state里面的值。应该定义一个全局的公共对象来执行action来分发事件通知store去改变才行,这才是flux架构。
flux架构的好处是不仅能够管理store中state改变的记录。还能记录变更、保存状态快照、历史回滚/时间旅行。如果不理解这些概念就往下面看看vuex的思想吧

1. State

单一状态树 —— 只用1个对象来保存所有数据源,这样每个应用就只可以有1个store对象。相当于以前的全局对象。

1.1 在vue组件中获得vuex状态
其实跟读取data中的数据一样,使用computed方法,把store里面的属性返回出来

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

1.2 mapState 辅助函数
当需要读取store对象内多个属性的时候,如果写多个computed方法是不是太麻烦,而且代码冗余,mapState就是为了解决这个问题

computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })

// 计算属性的名称与 state 的子节点名称相同时
computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

// 对象展开运算符
computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}

2. Getter

store中的state只是保存固定的单一数据就相当于vue组件里的data属性一样。那么getter就相当于vue组件里的computed。
下面代码中的state换成data, getters换成computed是不是就理解了

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

2.1 调用方式

// 通过属性访问
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
// 通过方法访问
// 通过让 getter 返回一个函数,来实现给 getter 传参
getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2)

2.2 mapGetters 辅助函数
跟mapState类似

 computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }

3. Mutation

3.1 mutation更改store中state属性值的唯一方法,把mutation换成setStates是不是就好理解了

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

但是mutation毕竟不是setState,根据约定,你不可以直接调用mutation内声明的方法,因为mutation只负责注册事件。那么如何触发它定义的事件呢?这样:

store.commit('increment’) //把commit换成trigger是不是更好理解

3.2 提交载荷(Payload)
触发事件方法的参数你就说参数,非要起个难理解的名词来显示自己很高大上?

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
// 注意payload参数要用对象的形式,因为人家这么约定的
store.commit('increment', {
  amount: 10
})
// 另外一种写法
store.commit({
  type: 'increment',
  amount: 10
})

3.3 Mutation 必须是同步函数,里面不要出现异步操作,因为异步操作你就不知道每个方法执行的顺序了。

3.4 在子组件中提交Mutation
你可以直接在方法里面使用this.$store.commit(‘xxx’)或者使用mapMutations辅助函数,跟上面那两个辅助函数类似,这样写:

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

4. Action

上面说到Mutation必须要使用同步函数,那么Action就是来解决你想使用异步函数的这个事情。先看一下Action 怎么写:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) { // 注意context这个参数有点不一样
      context.commit('increment')
    }
  }
})

乍一看context是不是就等于store,最后介绍Moudles的时候你就知道区别了。实践中一般都是使用ES2015的参数解构方式简化代码:

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

4.1 分发Action

store.dispatch('increment')

乍一看是不是跟mutations没什么区别,实际上真就没什么区别,atciont其实就是专门为了异步操作来设计的。如果你有异步的方法就写在action里面,如果是同步的就写在mutations里面,就这么简单,别想复杂了。

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

官网那个购物车示例如果看不懂,看一下这个平民版:

actions: {
  checkout ({ commit, state }, products) {
    const id = state.id
    const savedCartItems = state.cart.items
    $ajax.postProduct({
        data:{
            id,
            products
        }
     }).then(res =>{
        commit(types.CHECKOUT_REQUEST)
     }).catch(res =>{
        commit(types.CHECKOUT_FAILURE, savedCartItems)
     })
  }
}

5. Module

我们知道一个应用只允许使用一个store,这样做有个问题就是,当业务越来越复杂的时候,这个对象里面就会有N多的属性和方法,所以我们要把store拆成一个个的小份,然后根据业务去分类,以便维护。
Module其实就是为了让你的代码看起来更整洁的,如果业务没那么复杂不用也可以。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

5.1 模块的局部状态
注意参数mutation和getter方法里面的第一个参数是模块的局部状体对象

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

根节点状态用context.rootState表示:

const moduleA = {
  // …
 getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  },
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

JS进行base64编码

现在后端API流行使用HTTP Basic Authentication来发送用户名和密码,但是要求必须把用户名和密码进行base64编码才行。
postman等工具可以点一下按钮就帮你编好。PHP提供了base64_encode()方法。JS以前通过专门的base64.js插件来实现。
不过ie10之后的版本源生JS已经支持base64编码,方法就是window.btoa 和 window.atob。
如果你的项目是移动端项目就可以大胆的使用btoa和atob这两个方法了,没有兼容性问题。
可以像这样简单的封装一个方法:

 function makeBaseStr (user, password) {
      // atob() //base64解密方法
      var token = user + ':' + password
      var hash = btoa(token)
      return 'Basic ' + hash
    }

解决MAC系统下使用Navicat连接MAMP的MySql报错

mac系统的Navicat如果连接MAMP的MySql跟连接系统默认的MySql有一点区别。需要设置一下套接字地址,不然会报错。
1. 首页进入MAMP的主页在页面的MySql面板下找到 Socket /Applications/MAMP/tmp/mysql/mysql.sock 这行,把MySql的Socket地址复制下来。
2. 在Navicat编辑连接的窗口中选择“高级”选项卡,在使用套接字文件的地方把连接粘贴上去即可。

理解JS中的迭代器和生成器

一、迭代器

在解释什么是迭代器之前先看一下下面的这段代码

var arr = [1,2,3]
for (var i = 0, len - arr.length; i < len; i++){
    console.log(arr[i])
}

这是一个非常常见的for循环,不过它存在一个问题。就是如果嵌套的过多的话,就特别容易出问题,难以维护,还有大量的重复性代码。
就像Promise为了解决回调地域一样,迭代器就是要解决这些嵌套的for循环,让代码更简洁和更容易维护。

特征:迭代器函数定义了一个next()方法,这个方法执行后会返回一个对象,拥有value和done两个属性。为了更好的理解迭代器的运行方式,我们可以自己手写一个迭代器函数,代码如下:

function createIterator (items){
    var i = 0
    return {
        next: function () {
            var done = (i >= items.length)
            var value = !done ? items[i++] : undefined
            return {
                done,
                value
            }
        }
    }
}
var iterator = createIterator([1,2,3])
console.log(iterator.next()) // {value:1, done:false}
console.log(iterator.next()) // {value:2, done:false}

我们定义了一个函数,这个函数每次执行它的next方法的时候都会返回它下次执行的时候的返回值和是否到头了。

二、生成器

如果上面的迭代器理解了,那么我们就来了解一下生成器。
定义:一句话描述生成器就是“一个能够返回一个迭代器函数的函数”。
特征:生成器函数用*function来表示,内部可以使用yield关键字。
现在把上面迭代器的那段代码现在换成生成器的方式来写:

function *createIterator(){
    yield 1
    yield 2
    yield 3
}
let iterator = createIterator()

console.log(iterator.next().value) //1
console.log(iterator.next().value) //2
console.log(iterator.next().value) //3

这样返回的结果和上面的代码一样,不过这些值都是写死的,所以我们改一下,让它能够接受参数。代码如下:

function *createIterator(items){
    for (let i = 0; i< items.length; i++){
        yield items[i]
    }
}
let iterator = createIterator([1,2,3])

console.log(iterator.next()); // { value:1, done: false }
console.log(iterator.next()); // { value:2, done: false }
console.log(iterator.next()); // { value:3, done: false }
console.log(iterator.next()); // { value:undefined, done: true }

好了,看到这里就算是基本理解了,至于一些使用的细节,比如代码格式什么的,去查文档吧。使用场景呢,需要自己去实践中练习。本文追求简单,高效的让你理解什么是迭代器和生成器,所以不能写的太详细和啰嗦,好的文章就是要“干”一点。

小程序项目采坑记录之利用try catch跟踪问题

最近忙了半年的小程序终于上线了,第一天上线就暴露出来很多问题,面对每日几万人的使用量,n种不同的终端设备,测试部门测不出来一些问题也是情有可原。
其中比较典型的一个问题就是,小程序的管理后台会报警提示我一个页面进来的参数JSON格式不正确,但是这个问题出现的几率十分不规律。1万个人里面可能也就2-3个人会出现吧。而且测试部的同事根本就不能复现出来这个bug。
因为告警只提示你摸个页面会出现JSON错误,而没有具体的Log。
困扰了很久之后,我终于想出来了一个办法,利用try chatch把报错信息捕捉到,然后把JSON对象打印到页面上面。就这样用了一个很简单的小办法就解决了这个难题,现在把这个技巧分享出来,代码如下:

 onLoad(options) {
        let that = this
        let params = {}
        let p = decodeURIComponent(options.params || '')
        try {
            params = JSON.parse(p || '{}')
        }
        catch (exception) {
            wx.showModal({
                title: '数据错误',
                content: '错误代码:' + p,
                showCancel: false,
            })
            console.log(exception)
            return
        }
        ......

通过VUE项目的proxyTable配置实现本地跨域

1. 首先我们找一个用来实验的请求地址

(如果下面这个地址访问不了,那就是距离这篇文章发表的时间过得太久导致连接已经失效。你可以随便到某个网站找一条get请求的地址):

https://shared-https.ydstatic.com/gouwuex/ext/script/load_url_s.txt

这是一个get请求,可以通过浏览器的地址栏直接访问,返回的结果是

{"src":"https://shared-https.ydstatic.com/gouwuex/ext/script/extension_3_1.js?v=1513057776750" }

Read More

Mac下使用Sequel Pro连接MySql失败

Mac下使用MySql的dmg安装包安装后,使用Sequel Pro连接会报一个错误,如下:

Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(/usr/local/lib/plugin/caching_sha2_password.so, 2): image not found

原因:

就是在链接数据库时不能加载‘caching_sha2_password’这个插件,也就是不能对身份验证。

解决方案:

  1. 打开系统偏好设置,找到mysql,点击Initialize Database。
  2. 输入你的新密码,记住这个密码,用于后期链接数据库的登陆使用。
  3. 选择‘Use legacy password‘。
  4. 重启mysql服务。