在我没接触vue之前我不着调this是啥压根就没有接触过,在我学过了vue之后我知道了this,那时候理解的this就是你要使用data中的属性或调用methods中的方法等其他东西都要用this去调用,那时候其实我还是不知道this是啥,后面慢慢的才知道,当然我知道应该就是八股文背出来的,通过今天读这个源码,让我理解的更加深刻了,原来还可以这么用。
一、vue的使用
看这一段代码我们能知道有个Vue的构造函数 ,然后我们使用new Vue创建了它的实例,并给它传了一个对象参数,里面有data和methods,那么在这个Vue构造函数做了什么才能让我使用this可以直接访问里面的属性或者方法呢?
//创建vue的实例 const vm = new Vue({ data: { desc: '为什么this能够直接访问data中的属性', }, methods: { sayName() { console.log(this.desc); } }, }); console.log(vm.name); console.log(vm.sayName());
二、Vue的构造函数
接收一个options参数
- 使用 instanceof 判断 this对象上是否出现了Vue的prototype,我们都知道this的指向是取决于谁调用
- this._init(options) 证明在这调用要么我们创建的实例上有_init方法要么方法在Vue的prototype上,但是我们可以看到实例上并没有_init方法 ,那么肯定在一个地方给Vue的prototype上加上了_init方法 继续往下看
function Vue(options) { if (!(this instanceof Vue) ) { warn('Vue是一个构造函数,应使用“new”关键字调用'); } this._init(options); } //Vue() //错误的调用方式 进入警告判断 此时this指向window 然后window的 window.__proto__的指向的Window构造函数的prototype
三 初始化initMixin(Vue)
在源码中会看到很多初始化的函数在这我们initMixin()
这个函数就是在Vue的原型上增加了_init方法,方法接收一个参数,然后定义了vm变量,在我看的时候就想看看这个函数的this指向谁,其实也不难函数挂在Vue构造函数的原型上,调用还是在构造函数里面使用this调用,构造函数的this指向Vue实例,根据this的指向规则 此时的vm就指向了Vue构造函数的实例。
使用this的访问规则如果实例上没有就去原型上找
然后执行 initState(vm)
initMixin(Vue) function initMixin(Vue) { //prototype上增加init方法 Vue.prototype._init = function (options) { var vm = this; //Vue实例 vm.age = 30 //代码进行了删减 initState(vm); } } //这里只是举例测试 const vm = new Vue({}) console.log(vm.age) //30
四 initState(vm)
这里就是对我们传入的data 或者methods进行不同的处理
//initState方法代码进行了删减 function initState(vm) { vm._watchers = []; var opts = vm.$options; //这里是我们在创建实例的时候传的参数 //如果传了methods 则去调用 if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } }
五 initMethods(vm, opts.methods)
如果有methods则取调用initMethods方法
前面主要是判断 methods中的值是不是函数,key有没有跟props冲突等
最后一段代码就是在vm的实例上增加方法vm[key]=methods[key],在读的时候我有这样一个以为为什么还要用bind改变this指向呢不本来就是写在vm实例上的方法吗 只能使用vm调用 那么方法的this不就指向vm吗?
/* vm:构造函数实例 methods:我们传的methods对象 */ function initMethods(vm, methods) { var props = vm.$options.props; //循环methods对象 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 ); } //判断 methods 中的每一项是不是和 props 冲突了,如果是,警告。 if (props && hasOwn(props, key)) { warn( ("Method "" + key + "" has already been defined as a prop."), vm ); } //判断 methods 中的每一项是不是已经在 new Vue实例 vm 上存在,而且是方法名是保留的 _ $ (在JS中一般指内部变量标识)开头,如果是警告。 if ((key in vm) && isReserved(key)) { warn( "Method "" + key + "" conflicts with an existing Vue instance method. " + "Avoid defining component methods that start with _ or $." ); } } //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了 vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm); } }
问了群里大佬之后原来这步操作时为了防止用户改变this指向,专门做了个例子
在这我有定义了对象a里面有个age属性和fn,fn我赋值vm实例上的sayHi,然后a.fn()调用很明显this的指向已经被改变了,使用bind之后则不会
const vm = new Vue({ methods: { sayHi() { console.log(this.age, 'hello-this') } } }); let a = { age: 15, fn: vm.sayHi } console.log(a.fn(), 'vm') //打印15
六 initData(vm)
data是如何做到的使用this可以直接访问的,其实原理都一样,
首先在vm实例上增加了_data,里面存的我们传入的data参数
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]; //判断key值有没有跟methods中的key重名 { if (methods && hasOwn(methods, key)) { warn( ("Method "" + key + "" has already been defined as a data property."), vm ); } } //判断key值有没有跟props中的key重名 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 */); }
七 proxy(vm, "_data", key)
get 和 set 方法 注意里面的this 指向vm实例对象,上面已经在vm实例对象上增加了_data 所有在获取或者设置属性值的时候 都是this._data[key] 也就是vm._data[key],
然后通过Object.defineProperty往实例对象上添加属性,所以当我们访问vm[key] 也就是 vm._data[key]
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); }
//创建vue构造函数 function Vue(options) { if (!(this instanceof Vue) ) { warn('Vue是一个构造函数,应使用“new”关键字调用'); } this._init(options); } //初始化 initMixin(Vue); function initMixin(Vue) { //prototype上增加init方法 Vue.prototype._init = function (options) { var vm = this; //Vue实例 let methods = options.methods initState(vm); } } //initState方法代码进行了删减 function initState(vm) { vm._watchers = []; var opts = vm.$options; //这里是我们在创建实例的时候传的参数 //如果传了methods 则去调用 if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } } /* vm:构造函数实例 methods:我们传的methods对象 */ function initMethods(vm, methods) { var props = vm.$options.props; //循环methods对象 for (var key in methods) { { //一些条件判断 } //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了 vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm); } } const vm = new Vue({ methods: { sayHi() { console.log('hello-this') } } }); vm.sayHi() //hello-this
总结
其实看明白了Methods是怎么做到直接用this可以直接访问的后面的都是差不多的,主要就是一个构造函数,然后创建一个实例,在实例上增加属性或者方法,这样我们就可以用实例对象直接访问了。原理就是那么简单。
总结给大家一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库