使用Vue框架進行開發時,我們在option的data和methods中定義屬性和方法,在調用時直接使用 vm.attr 或 vm.func()的形式,而不是用vm.data.attr或vm.methods.func()的方式。
項目的git地址: https://github.com/xubaodian/SimuVue.git ,後續會持續更新,分析Vue的源碼,爭取實現一個乞丐版的Vue。
我們傳入Vue的options對象一般爲以下這種形式,
{
data: {
name: 'xxx'
},
mounted() {
//調用方法,沒有使用this.methods.getInfo();
this.getInfo();
},
methods: {
getInfo() {
//獲取屬性,沒有使用this.data.name
this.name = 'xxxx2314';
//操作等等....
}
},
computed: {
getName() {
return this.name;
}
},
watch: {
'name'(val, oldVal) {
//這是操作
}
}
}
在vue實例中,我們無論data還是method,都直接調用,這是因爲一下vue初始化時做了下面兩點操作:
1、給data中的屬性做了代理,所有訪問和設置vm[key]時,最終操作的是vm._data[key],而Vue在初始化時,會vm._data其實是options中data的引用。
2、methods中的所有方法都直接在vue實例重新定義了引用。
看下我的實現代碼,是對Vue源碼的精簡,如下:
//vue構造函數
class Vue {
constructor(options) {
//$options存儲構造選項
this.$options = options || {};
//data保存構造設置中的data,暫時忽略data爲函數的情況
let data = options.data;
this._data = data;
//初始化
this._init();
}
_init() {
//映射key
mapKeys(this);
//在vue實例上重新定義方法的引用
initMethods(this, this.$options.methods)
}
}
//重新定義方法的引用,注意修改調用函數時的上下文環境,這裏用bind,當然也可以用apply和call
function initMethods (vm, methods) {
for (const key in methods) {
vm[key] = typeof methods[key] !== 'function' ? noop : methods[key].bind(vm);
}
}
//重新定義data的get和set
function mapKeys(vm) {
let data = vm._data;
if (null !== data && typeof data === 'object') {
const keys = Object.keys(data);
let i = keys.length;
while (i-- >= 0) {
//所有屬性的操作就重新定向到了_data上
proxy(vm, `_data`, keys[i]);
}
}
}
//使用defineProperty重新定義get和set
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
//空函數,佔位用
function noop () {}
//使用
let options = {
data: {
name: 'xxx',
age: 18
},
methods: {
sayName() {
console.log(this.name);
}
}
}
let vm = new Vue(options);
vm.sayName();//控制檯打印了xxx,可以把代碼直接複製出去試一下
上面代碼就完成了屬性的重新映射和方法的引用重新定義。
看下vue中源碼,,如下,我做了註釋,應該比較好懂:
簡單說明一下,源碼中使用了flow作爲js代碼的靜態檢查工具,原理和typescript類似,所以代碼看起來會有些不同,不影響整體閱讀
//初始化,參數是vue實例
function initData (vm: Component) {
//獲取options中的
let data = vm.$options.data
//設置vm._data,判斷data是obj還是函數
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
//這是在開發環境打印的一些提示不用關心
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
//代理訪問,這就是爲何操作vm[key]被定位到vm._data[key]的原因
proxy(vm, `_data`, key)
}
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
//代理函數
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
//利用defineProperty設置對象的get和set,操作屬性時,target[key]會映射到target[sourceKey][key]
Object.defineProperty(target, key, sharedPropertyDefinition)
}
//方法映射
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
//這些都是開發環境的提示信息,可以忽略
if (process.env.NODE_ENV !== 'production') {
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
//關鍵在這,重新定義了引用
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
有疑問可以給我留言,或發郵件至[email protected],歡迎大家來討論