目錄結構javascript
├── build --------------------------------- 構建相關的文件
├── dist ---------------------------------- 構建後文件的輸出目錄
├── examples ------------------------------ 存放使用Vue開發的的例子
├── flow ---------------------------------- 類型聲明,使用開源項目 [Flow](https://flowtype.org/)
├── package.json -------------------------- 項目依賴
├── test ---------------------------------- 包含全部測試文件
├── src ----------------------------------- 這個是咱們最應該關注的目錄,包含了源碼
│ ├──platforms --------------------------- 包含平臺相關的代碼
│ │ ├──web ----------------------------- 包含了不一樣構建的包的入口文件
│ │ | ├──entry-runtime.js ---------------- 運行時構建的入口,輸出 dist/vue.common.js 文件,不包含模板(template)到render函數的編譯器,因此不支持 `template` 選項,咱們使用vue默認導出的就是這個運行時的版本。你們使用的時候要注意
│ │ | ├── entry-runtime-with-compiler.js -- 獨立構建版本的入口,輸出 dist/vue.js,它包含模板(template)到render函數的編譯器
│ ├── compiler -------------------------- 編譯器代碼的存放目錄,將 template 編譯爲 render 函數
│ │ ├── parser ------------------------ 存放將模板字符串轉換成元素抽象語法樹的代碼
│ │ ├── codegen ----------------------- 存放從抽象語法樹(AST)生成render函數的代碼
│ │ ├── optimizer.js ------------------ 分析靜態樹,優化vdom渲染
│ ├── core ------------------------------ 存放通用的,平臺無關的代碼
│ │ ├── observer ---------------------- 反應系統,包含數據觀測的核心代碼
│ │ ├── vdom -------------------------- 包含虛擬DOM建立(creation)和打補丁(patching)的代碼
│ │ ├── instance ---------------------- 包含Vue構造函數設計相關的代碼
│ │ ├── global-api -------------------- 包含給Vue構造函數掛載全局方法(靜態方法)或屬性的代碼
│ │ ├── components -------------------- 包含抽象出來的通用組件
│ ├── server ---------------------------- 包含服務端渲染(server-side rendering)的相關代碼
│ ├── sfc ------------------------------- 包含單文件組件(.vue文件)的解析邏輯,用於vue-template-compiler包
│ ├── shared ---------------------------- 包含整個代碼庫通用的代碼
複製代碼
該章節是從打包文件vue.runtime.common.dev.js
中查看源碼內容。html
從打包文件中看vue源碼作了什麼:vue
1.package.json
文件java
裏的main選項說明了入口在dist/vue.runtime.common.js
:node
"main": "dist/vue.runtime.common.js",
複製代碼
dev環境中引用vue實際是引用該文件vue.runtime.common.dev.js
react
爲了抹平瀏覽器之間的差別,作了大量polyfill操做,例如ES6中的Setandroid
if (typeof Set !== 'undefined' && isNative(Set)) {
// use native Set when available.
_Set = Set;
} else {
// a non-standard Set polyfill that only works with primitive keys.
_Set = /*@__PURE__*/(function () {
function Set () {
this.set = Object.create(null);
}
Set.prototype.has = function has (key) {
return this.set[key] === true
};
Set.prototype.add = function add (key) {
this.set[key] = true;
};
Set.prototype.clear = function clear () {
this.set = Object.create(null);
};
return Set;
}());
}
...
複製代碼
vue中定義了大量通用函數,以下只是一部分,日常須要找一些通用函數也能夠在這裏找到例子ios
function isUndef (v) {
return v === undefined || v === null
}
function isDef (v) {
return v !== undefined && v !== null
}
function isTrue (v) {
return v === true
}
function isFalse (v) {
return v === false
}
複製代碼
var inBrowser = typeof window !== 'undefined';
var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
var isEdge = UA && UA.indexOf('edge/') > 0;
var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
var isPhantomJS = UA && /phantomjs/.test(UA);
var isFF = UA && UA.match(/firefox\/(\d+)/);
複製代碼
定義常量,資源類型,生命週期鉤子等:git
var SSR_ATTR = 'data-server-rendered';
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
];
複製代碼
vue加載模式分爲是否服務端渲染,從process.env.VUE_ENV
區分github
_init
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
複製代碼
注意:建立實例的時候,調用this._init(options)
纔是真正的開始。。。
initMixin(Vue); //初始化相關,beforeCreate,created鉤子在這裏體現
stateMixin(Vue); //狀態相關實例方法定義
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
//...
initGlobalAPI(Vue);
複製代碼
initMixin
函數做用:
_init
_init
方法:
Vue.prototype._init = function (options) {
//動態組件優化 initInternalComponent
//用proxy代理事件
...
//設置好各個實例方法,生命週期、事件、渲染
//注意鉤子觸發位置
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
//進入掛載流程
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
複製代碼
注意鉤子觸發位置:
先註冊生命週期,事件,initRender
建立節點
執行beforeCreate
鉤子
將選項註冊爲實例方法,實例屬性,用observe
將data裏的屬性註冊到觀察者模式
執行created
鉤子
進入掛載流程vm.$mount(vm.$options.el);
stateMixin
函數添加屬性相關實例屬性、實例方法
$data
屬性$props
屬性$set
方法$delete
方法$watch
方法:將屬性加入觀察者模式定義屬性相關的實例方法:
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
//觀察者模式的觸發器
Vue.prototype.$watch = function ( expOrFn, cb, options ) {
...
}
複製代碼
eventsMixin
函數1.添加事件相關實例函數:
$on
註冊事件$once
註冊只使用一次事件的方法$off
註銷事件方法$emit
觸發事件方法2.事件註冊,實質是在on指令用hook:event
的格式註冊到觀察者模式中;
var hookRE = /^hook:/;
Vue.prototype.$on = function (event, fn) {
//...省略數組式添加事件
(vm._events[event] || (vm._events[event] = [])).push(fn);
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
複製代碼
lifecycleMixin
函數生命週期混入,主要是更新,銷燬;create
,mount
是在init實例方法裏觸發。
1.添加實例函數
_update
更新(內部使用)$forceUpdate
$destroy
Vue.prototype.$forceUpdate = function () {
var vm = this;
if (vm._watcher) {
vm._watcher.update();
}
};
Vue.prototype.$destroy = function () {
}
複製代碼
renderMixin
函數1.添加渲染輔助實例函數
$nextTick
_render
渲染(內部使用)2.添加vm.VNode
屬性
3.給vnode.parent
賦值肯定組件的父子層級
initGlobalAPI
函數給Vue構造函數定義靜態方法、屬性:
function initGlobalAPI (Vue) {
// config
var configDef = {};
configDef.get = function () { return config; };
{
configDef.set = function () {
...
};
}
Object.defineProperty(Vue, 'config', configDef);
//工具方法
Vue.util = {
warn: warn,
extend: extend,
mergeOptions: mergeOptions,
defineReactive: defineReactive$$1
};
//設置屬性,刪除屬性,nextTick
Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
//添加對象到observe
Vue.observable = function (obj) {
observe(obj);
return obj
};
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);
});
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
initUse(Vue); //添加use方法
initMixin$1(Vue); //添加全局mixin方法
initExtend(Vue); //全局繼承
initAssetRegisters(Vue); //全局資源方法:components、directives、filters註冊方法
}
複製代碼
$mount
實例方法在initMixin函數中,_init
實例方法中用到的$mount
實例方法,是進入掛載模板的入口:
//進入掛載流程
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
複製代碼
// public mount method
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
複製代碼
最終找到函數mountComponent
function mountComponent ( vm, el, hydrating ) {
//1.檢查render選項、template選項、el選項看是否有可用的模板
//...
callHook(vm, 'beforeMount');
//...
//2.定義updateComponent方法
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
//3.vm註冊到觀察者模式中
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
}
複製代碼
因此,模板的渲染和更新是靠觀察者模式觸發的
Vue實例爲它的每個data都實現了getter/setter
方法,這是實現響應式的基礎。關於getter/setter
可查看MDN web docs。 簡單來講,就是在取值this.counter
的時候,能夠自定義一些操做,再返回counter的值;在修改值this.counter = 10
的時候,也能夠在設置值的時候自定義一些操做。initData(vm)
的實如今源碼中的instance/state.js
。
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
//getter中使用 收集依賴
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
//setter中使用 通知更新
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
複製代碼
Observer Class將每一個目標對象的鍵值(即data中的數據)轉換成getter/setter
形式,用於進行依賴收集和經過依賴通知更新。
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
複製代碼
繼續看walk()
方法,註釋中已說明walk()
作的是遍歷data對象中的每一設置的數據,將其轉爲setter/getter
。
/** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
複製代碼
那麼最終將對應數據轉爲getter/setter
的方法就是defineReactive()
方法。從方法命名上也容易知道該方法是定義爲可響應的,結合最開始的例子,這裏調用就是defineReactive(...)
如圖所示:
/** * Define a reactive property on an Object. */
function defineReactive$$1 ( obj, key, val, customSetter, shallow ) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var 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) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
複製代碼
var uid$2 = 0;
/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */
var Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher ) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
複製代碼
1.監聽器Watcher裏{對象,鍵值,回調函數}這樣一個數據結構的實例經過實例方法Watcher.prototype.addDep
添加到訂閱中心Dep,記錄在裏面的靜態屬性subs裏。
2.觸發器Observer負責在觸發getter/setter時候添加依賴depend/發送通知通知noticy
3.訂閱中心負責處理粗發器發過來的信息(添加依賴depend/發送通知通知noticy)循環調用靜態屬性subs裏的watcher實例,符合的實例會調用對應的回調函數
下圖來自官方:
這是觸發組件更新的圖例,省略了Dep部分
能夠是query函數能解析的字符串/HTMLElement 實例
提供一個在頁面上已存在的 DOM 元素做爲 Vue 實例的掛載目標。能夠是 CSS 選擇器,也能夠是一個 HTMLElement 實例。
在實例掛載以後,元素能夠用 vm.$el
訪問。
Vue 實例觀察的數據對象。Vue 實例代理了對其 data 對象屬性的訪問。
當前組件接收到的 props 對象。Vue 實例代理了對其 props 對象屬性的訪問。
Vue 實例使用的根 DOM 元素。
vue實例的屬性初始化後的配置集合
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
複製代碼
當前組件樹的根 Vue 實例。若是當前實例沒有父實例,此實例將會是其本身。
用來訪問被插槽分發的內容。每一個具名插槽 有其相應的屬性 (例如:v-slot:foo
中的內容將會在 vm.$slots.foo
中被找到)。default
屬性包括了全部沒有被包含在具名插槽中的節點,或 v-slot:default
的內容。
undefined
。$slots
如今都會做爲函數暴露在 $scopedSlots
中。若是你在使用渲染函數,不論當前插槽是否帶有做用域,咱們都推薦始終經過 $scopedSlots
訪問它們。這不單單使得在將來添加做用域變得簡單,也可讓你最終輕鬆遷移到全部插槽都是函數的 Vue 3。包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class
和 style
除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class
和 style
除外),而且能夠經過 v-bind="$attrs"
傳入內部組件——在建立高級別的組件時很是有用。
包含了父做用域中的 (不含 .native
修飾器的) v-on
事件監聽器。它能夠經過 v-on="$listeners"
傳入內部組件——在建立更高層次的組件時很是有用。