如何获取vue实例里的data,vue this.data
本文主要介绍vue2这种可以直接获取数据的方法。看完这篇文章,你会学到如何学习如何调试Vue2源代码,为什么data中的数据可以通过这个直接获取,为什么methods中的方法可以通过这个直接获取。有需要的朋友可以参考一下。
:
目录
1.例子:这样可以直接得到数据和方法。
2.准备环境调试源代码找出2.1 Vue构造函数2.2 _init初始化函数2.3 initState初始化状态
2.4 initMethods初始化方法2.4.1 bind返回一个函数,并将其修改为指向2.5 initData初始化数据2.5.1 getData获取数据2.5.2 proxy代理2.5.3 Object.defineProperty定义了2.6条中出现的一些函数,最后统一说明2.6.1 hasOwn是否是对象本身拥有的属性。2.6.2保留字符串和_的开头。3.最后,用60多行代码实现了简化版。4.总结。
1. 示例:this 能够直接获取到 data 和 methods
举例:
const vm=new Vue({
数据:{
姓名:“我是若川”,
},
方法:{
sayName(){
console . log(this . name);
}
},
});
console . log(VM . name);//我是若川。
console . log(VM . say name());//我是若川。
这可以输出我是若川的。好奇的人会想为什么这个可以直接访问。
那为什么这个. xxx可以获取data和methods方法中的数据呢?
我们构造自己的函数。怎样才能达到类似Vue的效果?
功能人员(选项){
}
const p=新人({
数据:{
姓名:“若川”
},
方法:{
sayName(){
console . log(this . name);
}
}
});
console . log(p . name);
//未定义
console . log(p . say name());
//未捕获的类型错误:p.sayName不是函数
如果是你,你会怎么实现?带着疑问,我们来调试Vue2源码学习。
2. 准备环境调试源码一探究竟
您可以在本地创建一个新文件夹examples和一个新文件index.html文件。
在body/body中添加以下js。
script src= https://UNP kg . com/vue @ 2 . 6 . 14/dist/vue . js /script
脚本
const vm=new Vue({
数据:{
姓名:“我是若川”,
},
方法:{
sayName(){
console . log(this . name);
}
},
});
console . log(VM . name);
console . log(VM . say name());
/脚本
再全局安装npm i -g http-server启动服务。
npm i -g http-server
cd示例
http-服务器。
//如果遇到的端口被占用,也可以指定端口。
http-server -p 8081。
这样就可以打开刚刚在http://localhost:8080/写的index.html页面了。
调试:在F12打开调试、源面板,在示例中const vm=new Vue({ break point。
页面刷新后,按F11进入函数,然后断点进入Vue构造函数。
2.1 Vue 构造函数
功能Vue(选项){
如果(!(这是Vue的例子)
) {
warn(Vue是一个构造函数,应该用 new 关键字调用);
}
这个。_init(选项);
}
//初始化
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
生命周期米辛(Vue);
render mixin(Vue);
值得一提的是:如果(!(Vue的this instanceof判断构造函数是否用new关键字调用。
一般来说,平时应该不会考虑写这个。
当然,如果看源代码库,也可以在自己的函数内部调用new。但是一般vue对于一个项目只需要new Vue()一次,所以没必要。
但是jQuery的源代码是内部新的,对于用户来说没有新的结构。
jQuery=函数(选择器,上下文){
//新建后返回对象
返回新的jQuery.fn.init(选择器,上下文);
};
因为jQuery经常被调用。
其实jQuery也可以是新的。和不用new的效果是一样的。
调试:继续这个。_init(选项);创建一个断点,然后按F11进入该函数。
2.2 _init 初始化函数
进入_init函数后,这个函数比较长,做了很多事情。我们猜测与数据和方法相关的实现在initState(vm)函数中。
//代码已被删除
函数initMixin (Vue) {
vue。原型。_ init=函数(选项){
var vm=this
//一个用户界面设计(User Interface Design的缩写)
虚拟机._ uid=uid $ 3;
//避免被观察到的标志
虚拟机._ isVue=true
//合并选项
如果(选项选项. isComponent) {
//优化内部组件实例化
//因为动态选项合并非常慢,而且
//内部组件选项需要特殊处理。
initInternalComponent(vm,options);
}否则{
虚拟机.$options=mergeOptions(
resolveConstructorOptions(VM。建造师),
选项 {},
伏特计
);
}
//暴露真实的自己
虚拟机._ self=vm
初始化生命周期(虚拟机);
初始化事件(虚拟机);
初始化渲染(虚拟机);
callHook(vm,“创建前”);
初始注入(VM);//在数据/属性之前解析注入
//初始化状态
initState(VM);
初始化提供(虚拟机);//解析后提供数据/属性
callHook(vm, created );
};
}
调试:接着我们在初始化状态(虚拟机)函数这里打算断点,按F8可以直接跳转到这个断点,然后按F11接着进入初始状态函数。
2.3 initState 初始化状态
从函数名来看,这个函数主要实现功能是:
初始化小道具
初始化方法
监测数据
初始化计算
初始化看
函数初始化状态(虚拟机){
虚拟机._ watchers=[];
var opts=vm .$选项
if (opts.props) { initProps(vm,opts。道具);}
//有传入方法,初始化方法
如果(opts。方法){初始化方法(VM,opts。方法);}
//有传入数据,初始化数据
if (opts.data) {
初始化数据(虚拟机);
}否则{
观察(虚拟机._data={},true/* as root data */);
}
如果(opts。计算){ init计算(VM,opts。已计算);}
if (opts.watch opts.watch!==nativeWatch) {
initWatch(vm,opts。观看);
}
}
我们重点来看初始化方法,之后再看初始化数据。
调试:在初始化方法这句打上断点,同时在初始化数据(虚拟机)处打上断点,看完初始化方法函数后,可以直接按F8回到初始化数据(虚拟机)函数。继续按F11,先进入初始化方法函数。
2.4 initMethods 初始化方法
函数initMethods(虚拟机,方法){
var props=vm .$选项.道具
对于(方法中的变量键){
{
if (typeof methods[key]!==函数){
警告(
组件定义中的方法\ 键\ 具有类型\ (方法类型[键]) \
您是否正确引用了该函数?,
伏特计
);
}
if (props hasOwn(props,key)) {
警告(
(“方法”键)已经被定义为一个属性。),
伏特计
);
}
如果((vm中的键)被保留(键)){
警告(
方法"键"与现有的某视频剪辑软件实例方法冲突
避免定义以_或$开头的组件方法。
);
}
}
vm[key]=方法类型[关键]!==函数?noop : bind(methods[key],VM);
}
}
初始化方法函数,主要有一些判断。
判断方法中的每一项是不是函数,如果不是警告。
判断方法中的每一项是不是和小道具冲突了,如果是,警告。
判断方法中的每一项是不是已经在新某视频剪辑软件实例伏特计上存在,而且是方法名是保留的_ $ (在射流研究…中一般指内部变量标识)开头,如果是警告。
除去这些判断,我们可以看出初始化方法函数其实就是遍历传入的方法对象,并且使用约束绑定函数的这指向为vm,也就是新某视频剪辑软件的实例对象。
这就是为什么我们可以通过this直接访问到methods里面的函数的原因。
我们可以把鼠标移上约束变量,按中高音键,可以看到函数定义的地方,这里是218行,点击跳转到这里看约束的实现。
2.4.1 bind 返回一个函数,修改 this 指向
函数polyfillBind (fn,ctx) {
功能边界Fn (a) {
var l=参数.长度
返回l
?l 1
?fn.apply(ctx,参数)
:fn.call(ctx,a)
:fn。呼叫(ctx)
}
边界._ length=fn .长度
返回边界【数学】函数
}
函数nativeBind (fn,ctx) {
返回绑定(ctx)
}
var bind=函数。原型。约束
?nativeBind
:聚合填充粘合剂
简单来说就是兼容了老版本不支持原生的约束函数。同时兼容写法,对参数多少做出了判断,使用呼叫和应用实现,据说是因为性能问题。
如果对于调用、应用、绑定的用法和实现不熟悉,能否模拟实现射流研究…的呼叫和应用方法
调试:看完了初始化方法函数,按F8回到上文提到的初始化数据(虚拟机)函数断点处。
2.5 initData 初始化 data
initData函数也是一些判断。主要做了如下事情:
先给_数据赋值,以备后用。
最终获取到的数据不是对象给出警告。
遍历数据,其中每一项:
如果和方法冲突了,报警告。
如果和小道具冲突了,报警告。
不是内部私有的保留属性,做一层代理,代理到_数据上。
最后监测数据,使之成为响应式的数据。
函数初始化数据(虚拟机){
定义变量数据=vm .$ options.data
数据=虚拟机. data=数据类型===函数
?获取数据(数据,虚拟机)
:data { };
如果(!isPlainObject(data)) {
data={ };
警告(
数据函数应该返回对象:\n
https://vuejs。org/v2/guide/组件。 html #数据必须是函数,
伏特计
);
}
//实例上的代理数据
var keys=object。密钥(数据);
var props=vm .$选项.道具
定义变量方法=vm .$ options.methods
var i=keys.length
while (i - ) {
var key=keys[I];
{
if (methods hasOwn(methods,key)) {
警告(
("方法"键"\"已被定义为数据属性。),
伏特计
);
}
}
if (props hasOwn(props,key)) {
警告(
数据属性"键"已被声明为属性。
请改用合适默认值。
伏特计
);
} else if(!isReserved(key)) {
proxy(vm, _data ,key);
}
}
//观察数据
observe(data,true/* as root data */);
}
2.5.1 getData 获取数据
是函数时调用函数,执行获取到对象。
函数获取数据(数据,虚拟机){
//#7573调用数据获取器时禁用资料执行防止集合
push target();
尝试{
返回数据。调用(虚拟机,虚拟机)
} catch (e) {
handleError(e,vm, data());
返回{}
}最后{
pop target();
}
}
2.5.2 proxy 代理
其实就是用对象。定义属性定义对象
这里用处是:this.xxx则是访问的这个. data.xxx。
/**
*不执行任何操作。
*删除参数以使流快乐,而不会留下无用的传输代码
*与.休息(https://流量。org/blog/2017/05/07/Strict-Function-Call-Arity/).
*/
函数noop (a,b,c) {}
var sharedPropertyDefinition={
可枚举:真,
可配置:真,
get: noop,
设置:noop
};
函数代理(目标,源密钥,密钥){
sharedpropertydefinition。get=函数代理getter(){
返回此[源密钥][密钥]
};
sharedpropertydefinition。set=函数代理设置器(val){
这个[源密钥][密钥]=val;
};
Object.defineProperty(目标、键、共享属性定义);
}
2.5.3 Object.defineProperty 定义对象属性
对象。定义属性算是一个非常重要的API。还有一个定义多个属性的API:对象。定义属性(对象,属性)(ES5)
对象。定义属性涉及到比较重要的知识点,面试也常考。
值——当试图获取属性时所返回的值。
可写——该属性是否可写。
可枚举——该属性在因为在循环中是否会被枚举。
可配置——该属性是否可被删除。
set() —该属性的更新操作所调用的函数。
get() —获取属性值时所调用的函数。
2.6 文中出现的一些函数,最后统一解释下
2.6.1 hasOwn 是否是对象本身拥有的属性
调试模式下,按中高音键,把鼠标移到方法名上,可以看到函数定义的地方。点击可以跳转。
/**
*检查对象是否具有该属性。
*/
var hasOwnProperty=object。原型。hasOwnProperty
函数hasOwn (obj,key) {
返回hasOwnProperty.call(obj,key)
}
hasOwn({ a: undefined }, a) //true
hasOwn({}, a) //false
hasOwn({}, hasOwnProperty) //false
hasOwn({}, toString) //false
//它是自己拥有的属性,而不是通过原型链向上搜索。
2.6.2 isReserved 是否是内部私有保留的字符串$ 和 _ 开头
/**
*检查字符串是否以$或_
*/
函数保留(字符串){
var c=(str )。charCodeAt(0);
return c===0x24 c===0x5F
}
is reserved( _ data );//真
is reserved($ options));//真
is reserved( data );//假
is reserved( options );//假
3. 最后用60余行代码实现简化版
函数noop (a,b,c) {}
var sharedPropertyDefinition={
可枚举:真,
可配置:真,
get: noop,
设置:noop
};
函数代理(目标,源密钥,密钥){
sharedpropertydefinition . get=function proxy getter(){
返回此[源密钥][密钥]
};
sharedpropertydefinition . set=function proxy setter(val){
this[source key][key]=val;
};
Object.defineProperty(target,key,shared property definition);
}
函数initData(虚拟机){
常数数据=虚拟机。_data=vm。$ options.data
const keys=object . keys(data);
var i=keys.length
while (i - ) {
var key=keys[I];
proxy(vm, _data ,key);
}
}
函数initMethods(vm,methods){
for(方法中的变量键){
vm[key]=方法类型[key]!==函数?noop : methods[key]。绑定(虚拟机);
}
}
功能人员(选项){
设vm=this
vm。$ options=options
var opts=vm。$ options
if(opts.data){
initData(虚拟机);
}
if(opts.methods){
initMethods(vm,opts.methods)
}
}
const p=新人({
数据:{
姓名:“若川”
},
方法:{
sayName(){
console . log(this . name);
}
}
});
console . log(p . name);
//实现之前:未定义
//若川
console . log(p . say name());
//实现前:uncaughttypeerror: p.say name不是函数
//若川
4. 总结
本文涉及到的基础知识主要有如下:
构造器
这一点
呼叫、绑定、应用
对象.定义属性
本文来源于回答一起看源代码的那群朋友的疑惑,通过详细描述如何调试Vue源代码来探究答案。解答文章开头提问:
之所以通过this直接访问方法中的函数,是因为方法中的方法通过bind将this指定为new Vue的一个实例(vm)。
之所以通过这个直接访问data中的数据,是因为data中的属性最终会存储在new Vue的实例(vm)上的_data对象中。this.xxx是在访问Object.defineProperty代理后访问的。
Vue的这种设计具有容易访问的优点。还有就是道具、方法、数据容易冲突的不便。
整篇文章不难,但强烈建议读者朋友自行调试。经过调试,你可能会发现原来的Vue源代码并没有你想象的那么难,你可以理解一部分。启发:当我们在工作中使用常用的技术和框架或库时,保持好奇心,多思考内部原理。能够知道为什么和为什么。你可以远远超越很多人。
你可能会想,为什么模板语法里可以省略这个关键字?事实上,内部模板是用。有余力的读者可以探讨一下这个原理。
这就是这篇关于源代码揭示Vue2为什么可以直接获取数据和方法的文章。更多关于Vue2这种直接获取数据的方法,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。