이 글에서는 vue2.x의 this 포인팅 문제에 대해 설명하고, 이것이 vue 인스턴스를 가리키는 이유를 소개합니다. 모두에게 도움이 되기를 바랍니다.
그룹 내의 코드 연습에서 이것이 왜 데이터, 메서드, props 및 계산의 값에 직접 호출될 수 있는지 언급했습니다. 그러자 모두가 추측을 했지만 명확한 대답은 없었습니다. 이 문제를 명확히 하려면 vue의 소스 코드를 이해한 후 이를 기록하는 기사를 작성하겠습니다.
일반적으로 vue 코드를 개발하면 거의 항상 이렇게 작성합니다
export default { data() { return { name: '彭鱼宴' } }, methods: { greet() { console.log(`hello, 我是${this.name}`) } } }
여기서 this.name이 데이터에 정의된 이름에 직접 액세스할 수 있거나, this.someFn이 정의된 메서드에 직접 액세스할 수 있는 이유는 무엇입니까? 이 질문을 염두에 두고 vue2.x의 소스 코드를 살펴보기 시작했습니다.
vuevue 소스코드의 소스코드 주소입니다. 먼저 vue 인스턴스의 생성자를 살펴보겠습니다. 생성자는 소스 코드 디렉토리인 /vue/src/core/instance/index.js에 있습니다. 모두 게시하고 살펴보세요. 생성자는 매우 간단합니다. if (!(this 인스턴스of Vue)){}
new
키워드가 생성자를 호출하는 데 사용되는지 여부를 결정합니다. 그렇지 않으면 경고가 발생합니다. . 여기 이
가 있습니다. Vue의 인스턴스를 나타냅니다. new
키워드를 정상적으로 사용한다면 _init
함수를 사용하면 됩니다. 아주 간단하죠?
if (!(this instanceof Vue)){}
判断是不是用了 new
关键词调用构造函数,没有则抛出warning,这里的this
指的是Vue的一个实例。如果正常使用了new
关键词,就走_init
函数,是不是很简单。
_init函数分析
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
_init函数有点长,做了很多事情,这里就不一一解读,和我们此次探索相关的内容应该在initState(vm)这个函数中,我们继续到initState这个函数里看看。
initState函数分析
let uid = 0 export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self 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') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
可以看出initState做了5件事情
我们先重点看看初始化methods做了什么
initMethods 初始化方法
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
initMethods主要是一些判断:
function initMethods (vm, methods) { var props = vm.$options.props; for (var key in methods) { { 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); } }
除去上述说的这些判断,最重要的就是在vue实例上定义了一遍methods里所有的方法,并且使用bind函数将函数的this指向Vue实例上,就是我们new Vue()的实例对象上。
这就解释了为啥this可以直接访问到methods里的方法。
initData 初始化数据
判断methods中定义的函数是不是函数,不是函数就抛warning; 判断methods中定义的函数名是否与props冲突,冲突抛warning; 判断methods中定义的函数名是否与已经定义在Vue实例上的函数相冲突,冲突的话就建议开发者用_或者$开头命名;
initdata做了哪些事情呢:
再看下proxy函数做了什么:
function initData (vm) { var data = vm.$options.data; data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; if (!isPlainObject(data)) { data = {}; 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 var keys = Object.keys(data); var props = vm.$options.props; var methods = vm.$options.methods; var i = keys.length; while (i--) { var key = keys[i]; { if (methods && hasOwn(methods, key)) { warn( ("Method \"" + key + "\" has already been defined as a data property."), vm ); } } if (props && hasOwn(props, key)) { warn( "The data property \"" + key + "\" is already declared as a prop. " + "Use prop default value instead.", vm ); } else if (!isReserved(key)) { proxy(vm, "_data", key); } } // observe data observe(data, true /* asRootData */); }
其实这里的Object.defineProperty
就是用来定义对象的
proxy
的用处就是使this.name
指向this._data.name
剩下的observe函数不在此次探讨范围,感兴趣的朋友可以自己去看看源码。
回到一开始抛出的问题做一个解答:
methods
里的方法通过 bind
指定了this
为 new Vue的实例(vm
),methods里的函数也都定义在vm
上了,所以可以直接通过this
直接访问到methods
里面的函数。
data
函数返回的数据对象也都存储在了new Vue的实例(vm
)上的_data
上了,访问 this.name
时实际访问的是Object.defineProperty
代理后的 this._data.name
_init 함수 분석
function noop (a, b, c) {} var sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop }; 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); }_init 함수는 좀 길고 할 일이 많아서 하나씩 설명하지는 않겠습니다. 이번에는 내용이 initState(vm) 함수에 있어야 합니다. initState가 5가지 일을 하는 것을 볼 수 있습니다🎜
Object.defineProperty
여기는프록시
의 목적은 this.name
이 this._data.name을 가리키도록 만드는 것입니다.
🎜🎜나머지 관찰 기능은 이 토론의 범위에 포함되지 않습니다. 관심 있는 친구는 소스 코드를 직접 확인해 볼 수 있습니다. 🎜🎜🎜요약🎜🎜🎜처음에 제기한 질문으로 돌아가서 답변해 주세요. 🎜메소드의 메소드 code> <code>bind
를 통해 this
는 새로운 Vue(vm
)의 인스턴스로 지정되고 메소드의 함수도 에 정의됩니다. >vm
이므로 this
를 통해 메서드
의 함수에 직접 액세스할 수 있습니다. 🎜🎜data
함수에서 반환된 데이터 개체는 새 Vue 인스턴스(vm
)의 _data
에도 저장됩니다. this.name
에 액세스하면 실제로 액세스되는 것은 Object.defineProperty
프록시 다음의 this._data.name
입니다. 🎜🎜🎜🎜 이 데이터 디자인 패턴의 장점과 단점에 대해서는 계속해서 살펴볼 수 있지만 결국 이는 이 토론의 일부가 아닙니다. 🎜🎜【관련 추천: 🎜vue.js 동영상 튜토리얼🎜】🎜위 내용은 vue2.x에서 이것의 포인팅 문제에 대해 이야기해 봅시다. 왜 이것이 vue 인스턴스를 가리키나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!