字节跳动面试官:请说一下vuex工作原理(重点就几行代码而已啦)

不知为何掘金的文章最近都流行以 "字节跳动面试官" 作为开头,不蹭一波都不好意思说逛过掘金了。23333

最近是真到了面试的季节,那么就说一下 Vuex 的源码吧。看完你会发现,Vue和Vuex的实现原理主要就那么几行代码。

Vue双向绑定

要说 Vuex 的双向绑定那么必须先从 Vue 的双向绑定开始

Vue 的双向绑定大部分文章都说的很详细,这里精简点说一下,因为重点还是讲 Vuex

从Vue的源码来看,Vue的双向绑定主要做了2件事

  1. 数据劫持
  2. 添加观察者

数据劫持实现:(源码精简)

// 老版本通过 Object.defineProperty 递归可以实现

// src/core/observer/index.js
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
      dep.depend()
      if (childOb) {
        childOb.dep.depend()
      }
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
    return value
  },
  set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    if (setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
  }
})

这里无非就是劫持了对象的get和set方法。在所代理的属性的get方法中,当dep.Target存在的时候会调用 dep.depend()

划重点:2行代码

  1. Object.defineProperty
  2. dep.depend()
// 最新版可以通过 Proxy 实现
Proxy(data, {
  get(target, key) {
    return target[key];
  },
  set(target, key, value) {
    let val = Reflect.set(target, key, value);
      _that.$dep[key].forEach(item => item.update());
    return val;
  }
})

从上面的代码看出,无非就劫持了对象的get和set方法。在数据劫持之外最重要的部分就是 DepWatcher,这其实是一个观察者模式。用最简单的代码实现以下 Vue 的观察者模式。

观察者模式实现:(源码精简)

    // 观察者
    class Dep {
        constructor() {
            this.subs = []
        }
        
        addSub(sub) {
            this.subs.push(sub)
        }
        
        depend() {
            if (Dep.target) { 
                Dep.target.addDep(this);
            }
        }
        
        notify() {
            this.subs.forEach(sub => sub.update())
        }
    }
    
    // 被观察者
    class Watcher {
        constructor(vm, expOrFn) {
            this.vm = vm;
            this.getter = expOrFn;
            this.value;
        }

        get() {
            Dep.target = this;
            
            var vm = this.vm;
            var value = this.getter.call(vm, vm);
            return value;
        }

        evaluate() {
            this.value = this.get();
        }

        addDep(dep) {
            dep.addSub(this);
        }
        
        update() {
            console.log('更新, value:', this.value)
        }
    }
    
    // 观察者实例
    var dep = new Dep();
    
    //  被观察者实例
    var watcher = new Watcher({x: 1}, (val) => val);
    watcher.evaluate();
    
    // 观察者监听被观察对象
    dep.depend()
    
    dep.notify()

划重点:3件事

  1. 通过 watcher.evaluate() 将自身实例赋值给 Dep.target
  2. 调用 dep.depend() 将dep实例将 watcher 实例 push 到 dep.subs中
  3. 通过数据劫持,在调用被劫持的对象的 set 方法时,调用 dep.subs 中所有的 watcher.update()

从此。双向绑定完成。

vuex插件

有了上文作为铺垫,我们就可以很轻松的来解释vuex的原理了。

Vuex仅仅是Vue的一个插件。Vuex只能使用在vue上,因为其高度依赖于Vue的双向绑定和插件系统。

Vuex的注入代码比较简单,调用了一下applyMixin方法,现在的版本其实就是调用了Vue.mixin,在所有组件的 beforeCreate生命周期注入了设置 this.$store这样一个对象。

// src/store.js
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
// src/mixins.js
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

划重点:1行代码 Vue.mixin

那么 Vuex.Store 是如何实现的呢?

// src/store.js
constructor (options = {}) {
  const {
    plugins = [],
    strict = false
  } = options

  // store internal state
  this._committing = false
  this._actions = Object.create(null)
  this._actionSubscribers = []
  this._mutations = Object.create(null)
  this._wrappedGetters = Object.create(null)
  this._modules = new ModuleCollection(options)
  this._modulesNamespaceMap = Object.create(null)
  this._subscribers = []
  this._watcherVM = new Vue()

  const store = this
  const { dispatch, commit } = this
  this.dispatch = function boundDispatch (type, payload) {
    return dispatch.call(store, type, payload)
}
  this.commit = function boundCommit (type, payload, options) {
    return commit.call(store, type, payload, options)
}

  // strict mode
  this.strict = strict

  const state = this._modules.root.state

  // init root module.

  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], this._modules.root)

  resetStoreVM(this, state)

  // apply plugins
  plugins.forEach(plugin => plugin(this))

}

划重点:其实上面的代码绝大部分都不需要关注的 - -。其实重点就是一行代码resetStoreVM(this, state)

那么 resetStoreVM 里面是什么呢?

// src/store.js
function resetStoreVM (store, state, hot) {
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
}

划重点:还是一行代码:new Vue。通过 Vue自己的双向绑定然后注入给

你是不是以为就这样结束了呢?NoNoNo,当你再Vue中通过 this 如果调用 store的数据呢?

// 当获取state时,返回以双向绑定的$$sate
var prototypeAccessors$1 = { state: { configurable: true } };

prototypeAccessors$1.state.get = function () {
  return this._vm._data.$$state
};

// 将state定义在原型中
Object.defineProperties( Store.prototype, prototypeAccessors$1 );

其实就是获取 this._vm._data.$$state 而已啦。

### 最后

欢迎关注公众号「前端进阶课」认真学前端,一起进阶。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容