vueJs原始碼解讀0-2
上篇文章已經對index.js中的基本呼叫情況做了說明,接下來的幾篇將對各個函式做仔細的分析,能力有限,文章中不足之處,希望大家能夠指正!
上篇中提到在instance/vue中使用了9個高階函式來構建(install)Vue建構函式(並不會呼叫該建構函式的進行初始化的過程),一切等在使用new Vue({….})的時候將一個全新的物件作為函式內this的值,返回該新物件作為結果(函式 呼叫中建構函式呼叫的方法)
function Vue (options) {
this._init(options)
}
建立函式中函式申明的建立的方法(涉及知識函式宣告的提升),this為函式方法呼叫的接收者,一般為建構函式呼叫的方式 new Vue()
initMixin(Vue)
Mixin-mix in( 混入加入) 可能是作者取這個名字的原因吧(只是妄加猜測,具體已作者本人意圖為準)
import initMixin from ‘./internal/init’ 會在internal/init中就會存在default的export接下來的分析將會從這個開始著手
逐行程式碼的分析如
let uid = 0
設定了uid只在當前的塊中有效,let具體可以在預熱解讀中有說明
export default function (Vue) {
.....
}
export default 匿名函式具體用法可以在預熱解讀中找到,接下來的也即是function中的內容,接收引數為Vue
Vue.prototype._init = function (options){
.....
}
Vue也即是傳入進來的引數(函式名),函式會自帶一個預設的prototype的屬性在新建立之前幾乎為空,當使用new建立Vue的例項的時候,會得到自動分配的原型物件,存在User的prototype例如我們使用 var vm=new Vue({})來初始化構造方法的時候(先查詢自身的屬性再去原型鏈中進行查詢)
對於javaScript的繼承機制基於原型鏈(ES5),javaScript的例項物件由建構函式與在例項件共享的原型物件組成,對於原型用的較多的1個建立的方法(m.prototype)與2個獲取原型的方法(obj.getPrototypeOf(m)和obj. _ proto
options = options || {}
判斷options是否為null,0,-0,undefined,false,”,NaN等情況(以上也即是js的7大假值),當options不為假則直接執行賦值,否則為{}。(涉及賦值運算子優先順序,||運算時當左邊為假才會執行右邊;左右options的不一樣);
this.$el = null
this.$parent = options.parent
this.$root = this.$parent? this.$parent.$root : this
this.$children = []
this.$refs = {} // child vm references
this.$els = {} // element references
this._watchers = [] // all watchers as an array
this._directives = [] // all directives
this例項化之後也即是Vue物件,未指定呼叫接收者為undefined;先來了解下基本的含義,在後面涉及到會仔細介紹:
$parent存在的話則為父例項; $root:當前元件樹的根 Vue 例項。如果當前例項沒有父例項為自身。$children 當前例項的直接子元件。
$refs:一個物件,包含註冊有 v-ref 的子元件。\$els物件中包含註冊有 v-el 的 DOM 元素。
// a uid
this._uid = uid++ //上文中定義的let uid
// a flag to avoid this being observed 設定標誌避免被檢測到
this._isVue = true
// events bookkeeping 事件統計
this._events = {} // registered callbacks
this._eventsCount = {} // for $broadcast optimization //$broadcast的優化
// fragment instance properties fragment例項屬性
this._isFragment = false
this._fragment = // @type {DocumentFragment}
this._fragmentStart = // @type {Text|Comment}
this._fragmentEnd = null // @type {Text|Comment}
// lifecycle state 生命週期狀態
this._isCompiled =
this._isDestroyed =
this._isReady =
this._isAttached =
this._isBeingDestroyed =
this._vForRemoving = false
this._unlinkFn = null
各個例項到底什麼意思,相信也很困惑,這裡只要稍微有印象即可在之後的分析與學習中會逐步解釋
// context:
// if this is a transcluded component, context
// will be the common parent vm of this instance
// and its host.
如果這是一個嵌入式的元件,上下文將是這個例項共有父例項(或宿主)
this._context = options._context || this.$parent
// scope:
// if this is inside an inline v-for, the scope
// will be the intermediate scope created for this
// repeat fragment. this is used for linking props
// and container directives.
如果這是在一個內聯的v-for,將由這個迴圈的片段產生中間的作用域範圍,被用在連結父元件的資料和指令容器
this._scope = options._scope
// fragment:
// if this instance is compiled inside a Fragment, it
// needs to reigster itself as a child of that fragment
// for attach/detach to work properly.
如果這個例項在某個片段裡已經編譯,需要在該片段上進行註冊,利於attach或detach的正常工作
this._frag = options._frag
if (this._frag) {
this._frag.children.push(this)
}
// push self into parent / transclusion host
如果存在父例項則將其建立雙方的連結
if (this.$parent) {
this.$parent.$children.push(this)
}
// merge options.
合併options,含有一個mergeOptions的函式
options = this.$options = mergeOptions(
this.constructor.options,
options,
this
)
import { mergeOptions } from ‘../../util/index’
export * from './lang'
export * from './env'
export * from './dom'
export * from './options' //options
export * from './component'
export * from './debug'
export { defineReactive } from '../observer/index'
export * 也即是將所有的標記過的均匯出
在options.js中可以看到
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
* 主要用於在例項化與繼承
* @param {Object} parent
* @param {Object} child
* @param {Vue} [vm] - if vm is present, indicates this is
* an instantiation merge.
*
options = this.$options = mergeOptions(
this.constructor.options,
options,
this
)
*/
export function mergeOptions (parent, child, vm) {
}
下面均是該函式內的程式碼片段
guardComponents(child)
guardProps(child)
function guardComponents (options){
...
}
對於guardComponents主要用作options中的元件構造,下文的程式碼為guardComponents中的程式碼
var vm = new Vue({
el: '...',
data:{},
components: {
'a':{},
'b':{}
}
})
if (options.components) {
......
}
如果在options中存在components的存在,則會進行下部分的程式碼
var components = options.components =
guardArrayAssets(options.components)
賦值語句從右至左,使用guardArrayAssets函式將陣列形式的轉化為鍵值對的形式
guardArrayAssets:
function guardArrayAssets (assets) {
//assets 也即是傳遞過來的options.components
//1.components:{'s':{},'d':{}}
//2.componets:[{'name':'...','id':'...'}]
//3.?
if (isArray(assets)) {
var res = {}
var i = assets.length
var asset
//陣列迴圈取值組成鍵值對的形式 key值由id決定
while (i--) {
asset = assets[i]
var id = typeof asset === 'function'
? ((asset.options && asset.options.name) || asset.id)
: (asset.name || asset.id)
//id異常情況
if (!id) {
process.env.NODE_ENV !== 'production' && warn(
'Array-syntax assets must provide a "name" or "id" field.'
)
} else {
//規整為key-value的形式
res[id] = asset
}
}
return res
}
return assets
}
可以看出有3種方式填寫的option.components,主要目的是規整為字典的形式便於後面的直接呼叫
下面回到guardComponents
var components = options.components =
guardArrayAssets(options.components)
var ids = Object.keys(components)
這裡用到了一個Object.keys方法,獲取規整後的components的鍵值陣列
The Object.keys() method returns an array of a given object’s own enumerable properties, in the same order as that provided by a for…in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
返回一個列舉所有物件屬性的陣列,類似於for-in 列舉(並不保證按物件的順序輸各個屬性 ,不可預測的順序unpredicted order)
接下來飄逸與自然的for迴圈如下:
for (var i = 0, l = ids.length; i < l; i++) {
var key = ids[i]
if (commonTagRE.test(key) || reservedTagRE.test(key)) {
process.env.NODE_ENV !== 'production' && warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + key
)
continue
}
// record a all lowercase <-> kebab-case mapping for
// possible custom element case error warning
if (process.env.NODE_ENV !== 'production') {
map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key)
}
def = components[key]
if (isPlainObject(def)) {
components[key] = Vue.extend(def)
}
}
其中commonTagRE與reservedTagRE為options.js中匯入的兩個屬性
import { commonTagRE, reservedTagRE } from './component'
export const commonTagRE = /^(div|p|span|img|a|b|i|br|ul|ol|li|h1|h2|h3|h4|h5|h6|code|pre|table|th|td|tr|form|label|input|select|option|nav|article|section|header|footer)$/i
export const reservedTagRE = /^(slot|partial|component)$/i
const為es6中的關鍵字,表示不可以修改常量只在當前模組中有效,想要在其他模組中引用也即是利用前面提到的export命令,不會提升,必須先申明後使用
變數的提升:某一作用域範圍內
console.info(v) ==> var v
var v=’tev’ console.info(v)
v=’tev’
正則表示式中:
/i (忽略大小寫)
/g (全文查找出現的所有匹配字元)
/m (多行查詢)
/gi(全文查詢、忽略大小寫)
/ig(全文查詢、忽略大小寫)
鍵值中
process.env.NODE_ENV !== 'production' && warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + key
)
不要使用保留的slot,partial,component與Html的標籤作為鍵值
def = components[key]
if (isPlainObject(def)) {
components[key] = Vue.extend(def)
}
使用vue.extend定義元件,如下例子將更好解釋
components:{
'my-component':{
template:'<div>A custom component!</div>'
}
},
html頁面中使用<\my-component><\/\my-component>等同於
components:[{
// 'id':'my-component',
'name':'my-component',
'template':'<div>A custom component!</div>'
}
],
等同於:
var MyComponent = Vue.extend({
template: '<div>A custom component!</div>'
})
Vue.component('my-component', MyComponent)
上面的程式碼中,這裡涉及到兩個isplainObject與Vue.extend,x下面將對其進分析
if (isPlainObject(def)) {
components[key] = Vue.extend(def)
}
import {
isArray,
isPlainObject,
} from './lang'
/**
* Strict object type check. Only returns true
* for plain JavaScript objects.
*
* @param {*} obj
* @return {Boolean}
*/
//使用toString()方法判斷型別,可以表面toString對null的判斷方法,如下圖所示
var toString = Object.prototype.toString
var OBJECT_STRING = '[object Object]'
export function isPlainObject (obj) {
return toString.call(obj) === OBJECT_STRING
}
/**
* Array type check.
*
* @param {*} obj
* @return {Boolean}
*/
//也即是呼叫Array方法中的isArray方法
export const isArray = Array.isArray
Vue.extend在global-api.js中在接下來的中會分析
感覺跑偏了很遠這樣流水式的分析要知道自己要回到哪個地方
mergeOptions
export function mergeOptions (parent, child, vm) {
//在Options之前將options:components與props定義好
guardComponents(child)
guardProps(child)
....
}
Vue.prototype._init
Vue.prototype._init = function (options){
...
options = this.$options = mergeOptions(
this.constructor.options,
options,
this
)
}
props的定義
A list/hash of attributes that are exposed to accept data from the parent component(從父元件中獲得資料). It has a simple Array-based syntax (陣列形式)and an alternative Object-based(物件形式) syntax that allows advanced configurations such as type checking, custom validation and default values(物件形式用於高階的設定如 型別檢查,自定義驗證,預設值等).
guardProps(child)將所有的props規格化為基於物件的格式(雖然支援陣列與物件的兩種形式),child也即是為init中傳入的options
props: ['size', 'myMessage']
props: [{'name':'size'},{'name':'myMessage'}],
props: {
// 只檢測型別
size: Number,
// 檢測型別 + 其它驗證
name: {
type: String,
required: true,
// 雙向繫結
twoWay: true
}
}
function guardProps (options) {
var props = options.props
var i, val
if (isArray(props)) { //為陣列型別
options.props = {}
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
//為String型別的時候將其值設定為空 'size':null
options.props[val] = null
} else if (val.name) {
//取val.name
options.props[val.name] = val
}
}
} else if (isPlainObject(props)) {
var keys = Object.keys(props)
i = keys.length
while (i--) {
val = props[keys[i]]
if (typeof val === 'function') { //{ 初始為Object型別 {} }
props[keys[i]] = { type: val }
}
}
}
}