vue data 属性中的 this 指向问题
本文最后更新于:2021年6月21日 晚上
场景
之前在封装 table 组件 Vue 表格封装 BasicTableVue 的时候遇到的问题,在 data
属性中无法使用 this.**
调用 methods
中的函数。
例如下面的代码
class BasicTableData {
constructor({
user = {
name: "rx",
age: 17,
},
} = {}) {
this.user = user;
}
}
class Table extends Vue {
constructor({ data, methods, mounted, computed }) {
super({
data: _.merge(new BasicTableData(), data),
methods,
mounted,
computed,
});
}
}
const table = new Table({
data: {
user: {
birthday: new Date(),
birthdayFormatter: this.calcTime,
},
},
methods: {
calcTime(time) {
return time.toISOString();
},
},
});
// 将输出 undefined
console.log(table.user.birthdayFormatter);
吾辈尝试了一下原生的 vuejs,发现这样的 data 仍然不能用。
解决
后来在官方文档找到了 这里,data 如果是一个对象或者箭头函数时,不会绑定 this
,仅当 data
是一个普通函数(使用 function
声明)时,才会被绑定 this
。
那么,知道了原因,解决方案就很简单了。
- 如果需要使用在
data
中使用this
调用methods
中的函数,则data
必须声明为普通函数 - 如果需要默认
data
defaultData
,则Table
可以将合并后的data
声明为函数,并将defaultData
与data
(使用Table
创建实例时传入的)的返回值合并
修改后的代码如下
class BasicTableData {
constructor({
user = {
name: "rx",
age: 17,
},
} = {}) {
this.user = user;
}
}
class Table extends Vue {
constructor({ data, methods, mounted, computed }) {
super({
// 关键是这里将 data 声明为普通函数
data() {
// 此处为了简洁使用 lodash 的深度合并
return _.merge(
new BasicTableData(),
// 此处判断 data 是否为函数,是的话就绑定 this 计算结果
typeof data === "function" ? data.call(this) : data
);
},
methods,
mounted,
computed,
});
}
}
const table = new Table({
data: function () {
return {
user: {
birthday: new Date(),
birthdayFormatter: this.calcTime,
},
};
},
methods: {
calcTime(time) {
return time.toISOString();
},
},
});
// 打印的结果是
// ƒ calcTime(time) {
// return time.toISOString()
// }
console.log(table.user.birthdayFormatter);
思考
现在问题解决了,那么,为什么 vuejs
就能够在传入 data
函数时就能调用 methods
中的函数了呢?吾辈稍微 debug 进入源码看了一下
创建
Table
进入构造函数因为继承了 Vue,所以进入 Vue 的构造函数中
因为当前实例属于 Vue,所以进入
_init
进行初始化跳转到
initState(vm);
处,该函数将对 data 属性进行初始化(至于为什么是 state 可能是因为最初就是模仿 react 写的?)进入到
initState()
,跳转到initData(vm);
处进入到
initData()
函数,看到了判断逻辑var data = vm.$options.data; data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
注意看,这里的 vue 内部判断了 data 是否为函数,如果是就去
getData(data, vm)
进入
getData()
函数看看,发现了关键代码return data.call(vm, vm);
是的,data 调用时使用
call
绑定this
为 vm,而此时vm.calcTime
已经有值了。那么,
vm.calcTime
是什么时候被初始化的呢?
其实也在initState
函数中,可以看到,vue 的初始化顺序是props
: 外部传递的属性methods
: 组件的函数data
: 组件的属性computed
: 计算属性watch
: 监听函数
总结
相比于 react,vue 做了更多的 黑魔法 呢!就像 this 指向问题,react 是交由用户自行解决的,而 vue 则在后面偷偷的为函数绑定 this 为 vue 实例本身。