阅读目录javascript
在Vue中,组件是一个很强大的功能,组件能够扩展HTML元素,封装可重用的代码。好比在页面当中的某一个部分须要在多个场景中使用,那么咱们能够将其抽出为一个组件来进行复用。组件能够大大提升了代码的复用率。html
全部的Vue组件都是Vue的实列,所以它能够接受Vue中的全部的生命周期钩子。Vue又分为全局注册和局部注册两种方式来注册组件。全局组件它能够在任何(根)实列中使用的组件,局部组件只能在某一实列中使用的组件。vue
1.1 全局注册组件html5
全局组件可使用 Vue.component(tagName, option); 来注册组件。 tagName 是自定义的组件名称, option是组件的一些选项, 好比能够在option对象中添加以下一些选项:
1) template 表示组件模板。
2) methods 表示组件里的方法。
3) data 表示组件里的数据。java
在Vue中, 定义组件名的方式也是有规范的。定义组件名的方式有两种:jquery
1) 使用kebab-case webpack
Vue.component('my-component-name', {});
kebab-case 的含义是: "短横线分隔命名" 来定义一个组件, 所以咱们在使用该组件的时候, 就如这样使用: <my-component-name>web
2) 使用 PascalCasejson
Vue.component('MyComponentName', {});
PascalCase 的含义是: "首字母大写命名" 来定义一个组件, 咱们在使用该组件时,能够经过以下两种方式来使用:<my-component-name>或<MyComponentName>都是能够的。
那么通常的状况下,咱们习惯使用 第一种方式来使用组件的。api
下面以官方列子来举例如何注册一个全局组件,简单的代码以下:
<!DOCTYPE html> <html> <head> <title>vue组件测试</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <!-- 组件复用以下 --> <button-counter></button-counter><br/><br/> <button-counter></button-counter><br/><br/> <button-counter></button-counter> </div> <script type="text/javascript"> Vue.component('button-counter', { data: function() { return { count: 0 } }, template: '<button @click="count++">You clicked me {{ count }} times.</button>' }); new Vue({ el: '#app' }); </script> </body> </html>
如上调用了三次组件,对第一个按钮点击了一次,所以 count = 1了,第二个按钮点击2次,所以count=2了,对第三个按钮点击了3次,所以count=3了。以下图所示:
注意:当咱们定义 button-counter 组件的时候,data 必须为一个函数,不能是一个对象,好比不能是以下的对象:
data: { count: 0 };
由于每一个实列能够维护一份被返回对象的独立拷贝。若是是一个对象的话,那么它每次调用的是共享的实列,所以改变的时候会同时改变值。
官方文档是这么说的:当一个组件被定义时,data必须声明为返回一个初始数据对象的函数,由于组件可能被用来建立多个实列。若是data仍然是一个纯粹的对象,则全部的实列将共享引用同一个数据对象。经过提供data函数,每次建立一个新实列后,咱们可以调用data函数,从而返回初始数据的一个全新副本的数据对象。
2. 经过Vue.extend来注册
Vue.extend(options); Vue.extend 返回的是一个 "扩展实列构造器", 不是具体的组件实列, 它通常是经过 Vue.component 来生成组件。
简单的代码以下测试:
<!DOCTYPE html> <html> <head> <title>vue组件测试</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <!-- 组件复用以下 --> <button-counter></button-counter><br/><br/> <button-counter></button-counter><br/><br/> <button-counter></button-counter> </div> <!-- 全局组件在 id为app2下也能使用的 --> <div id="app2"> <button-counter></button-counter> </div> <script type="text/javascript"> var buttonCounter = Vue.extend({ name: 'button-counter', data: function() { return { count: 0 } }, template: '<button @click="count++">You clicked me {{ count }} times.</button>' }); /* Vue.component 是用来全局注册组件的方法, 其做用是将经过 Vue.extend 生成的扩展实列构造器注册为一个组件 */ Vue.component('button-counter', buttonCounter); // 初始化实列 new Vue({ el: '#app' }); new Vue({ el: '#app2' }); </script> </body> </html>
效果和上面也是同样的。如上咱们能够看到, 全局组件不只仅在 '#app' 域下可使用, 还能够在 '#app2' 域下也能使用。这就是全局组件和局部组件的区别, 局部组件咱们能够看以下的demo。
1.2 局部注册组件
<div id="app"> <child-component></child-component> </div> <script type="text/javascript"> var child = { template: "<h1>我是局部组件</h1>" }; new Vue({ el: '#app', components: { "child-component": child } }) </script>
在浏览器中会被渲染成html代码以下:
"<div id='app'><h1>我是局部组件</h1></div>";
如上代码是局部组件的一个列子, 局部组件只能在 id 为 'app' 域下才能使用, 在其余id 下是没法访问的。以下以下代码:
<div id="app"> <child-component></child-component> </div> <div id="app2"> <child-component></child-component> </div> <script type="text/javascript"> var child = { template: "<h1>我是局部组件</h1>" }; new Vue({ el: '#app', components: { "child-component": child } }); new Vue({ el: '#app2' }) </script>
如上代码, 咱们在 id 为 '#app2' 的域下使用 child-component 组件是使用不了的, 而且在控制台中会报错的。由于该组件是局部组件, 只能在 '#app' 域下才能使用。
1) props
在Vue中, 组件之间的通讯有父子组件、兄弟组件、祖前后代组件等之间通讯。
1. 父子组件通讯
父组件想把数据传递给子组件是经过 props 来传递的, 子组件想把数据传递给父组件是经过事件 emit 来触发的。
在vue中,子组件向父组件传递数据, 子组件使用 $emit 触发事件, 在父组件中咱们使用 v-on / @ 自定义事件进行监听便可。
咱们可使用以下图来解释他们是如何传递数据的, 以下图所示:
子组件的props选项可以接收来自父组件的数据。 咱们可使用一个比方说, 父子组件之间的数据传递至关于自上而下的下水管子, 只能从上往下流,不能逆流。这也正是Vue的设计理念之单向数据流。
而Props能够理解为管道与管道之间的一个衔接口。这样水才能往下流。
以下代码演示:
<!DOCTYPE html> <html> <head> <title>父子组件通讯</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <!-- 父组件把message值传递给子组件 --> <child-component :content="message"></child-component> </div> <script type="text/javascript"> var childComponent = Vue.extend({ template: '<div>{{ content }}</div>', // 使用props接收父组件传递过来的数据 props: { content: { type: String, default: 'I am is childComponent' } } }); new Vue({ el: '#app', data: { message: 'I am is parentComponent' }, components: { childComponent } }); </script> </body> </html>
在页面渲染出HTML以下:
<div id="app"> <div>I am is parentComponent</div> </div>
2) $emit
子组件想把数据传递给父组件的话, 那么能够经过事件触发的方式来传递数据, 父组件使用 v-on / @ 自定义事件进行监听便可。
以下基本代码演示:
<!DOCTYPE html> <html> <head> <title>父子组件通讯</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <child-component @event="eventFunc"></child-component> </div> <script type="text/javascript"> var childComponent = Vue.extend({ template: '<div @click="triggerClick">子组件使用事件把数据传递给父组件</div>', data() { return { content: '我是子组件把数据传递给父组件' } }, methods: { triggerClick() { this.$emit('event', this.content); } } }); new Vue({ el: '#app', components: { childComponent }, methods: { eventFunc(value) { console.log('打印出数据:' + value); } } }); </script> </body> </html>
如上代码, 在子组件childComponent中, 咱们定义了一个点击事件, 当咱们点击后会触发 triggerClick 这个函数, 该函数内部使用 $emit 来派发一个事件, 事件名为 "event", 而后在父组件中咱们使用 @ 或 v-on 来监听 'event' 这个事件名称, 在父组件中使用 @event="eventFunc"; 所以当咱们点击后, 会触发父组件中的 "eventFunc" 这个方法, 该方法有一个参数值, 就是子组件传递过来的数据。
3) 使用$ref实现通讯
1. 若是ref用在子组件上, 那么它指向的就是子组件的实列, 能够理解为对子组件的索引的引用, 咱们能够经过$ref就能够获取到在子组件定义的属性和方法。
2. 若是ref使用在普通的DOM元素上使用的话, 引用指向的就是DOM元素, 经过$ref就能够获取到该DOM的属性集合, 咱们轻松就能够获取到DOM元素。做用和jquery中的选择器是同样的。
那么咱们如何经过使用 $ref 来实现父子组件之间通讯呢? 咱们将上面使用 props 实现的功能, 下面咱们使用 $ref 来实现一下:
以下代码演示:
<!DOCTYPE html> <html> <head> <title>父子组件通讯</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <child-component ref="child"></child-component> </div> <script type="text/javascript"> var childComponent = Vue.extend({ template: '<div>{{message}}</div>', data() { return { message: '' } }, methods: { getMsg(value) { this.message = value; } } }); new Vue({ el: '#app', components: { childComponent }, mounted() { this.$refs.child.getMsg('父组件给子组件传递数据的'); } }); </script> </body> </html>
如上代码, 咱们子组件childComponent 默认 的 message值为 空字符串, 可是在父组件中的 mounted 生命周期中, 咱们使用 $refs 获取到 子组件的方法, 把值传递给子组件, 最后子组件就能获取值, 从新渲染页面了。 所以最后页面被渲染成为:<div id="app"><div>父组件给子组件传递数据的</div></div>; 如上咱们在父组件中调用子组件的方法,固然咱们也能够改变子组件的属性值也是能够实现的.
props 和 $refs 之间的区别:
props 着重与数据传递, 父组件向子组件传递数据, 可是他并不能调用子组件里面的属性和方法。
$refs 着重与索引,主要用于调用子组件里面的属性和方法。而且当ref使用在DOM元素的时候, 能起到选择器的做用, 咱们能够经过它能获取到DOM元素的节点, 能够对DOM元素进行操做等。
4) $attrs 和 $listeners 及 inheritAttrs
$attrs 是vue2.4+版本添加的内容, 目的是解决props 在组件 "隔代" 传值的问题的。
以下图所示:
如上图咱们能够看到, A组件与B组件是父子组件, A组件传递给B组件的数据可使用props传递数据, B组件做为A组件的子组件, 传递数据只须要经过事件 $emit 触发事件便可。 可是A组件它与C组件要如何通讯呢? 咱们能够经过以下方案解决:
1) 咱们首先会想到的是使用Vuex来对数据进行管理, 可是若是项目是很是小的话, 或者说全局状态比较少, 若是咱们使用Vuex来解决的话, 感受大材小用了。
2) 咱们能够把B组件当作中转站, 好比说咱们使用A组件把数据先传递给B组件, B组件拿到数据后在传递给C组件, 这虽然算是一种方案, 可是并很差, 好比说组件传递的数据很是多, 会致使代码的繁琐, 或致使代码之后更加的难以维护。
3) 自定义一个Vue中央数据总线, 可是这个方法适合组件跨级传递消息。
所以为了解决这个需求, 在vue2.4+ 的版本后, 引入了 $attrs 和 $listeners, 新增了 inheritAttrs选项。
inheritAttrs、attrs和listeners的使用场景: 组件之间传值, 特别对于祖孙组件有跨度的传值。
inheritAttrs 默认值为true, 意思是说会将父组件中除了props之外的属性会添加到子组件的根节点上。可是咱们的子组件仍然能够经过 $attrs 获取到 props 之外的属性。
上面的含义可能会有点理解不清晰, 咱们换句话说吧, 就是说咱们的父组件传了两个属性值给子组件,可是子组件只获取到其中一个属性了, 那么另一个属性会被当作子组件的根节点上的一个普通属性。
以下代码演示下:
<!DOCTYPE html> <html> <head> <title>父子组件通讯</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <father-component :foo="foo" :bar="bar" @event="reciveChildFunc" > </father-component> </div> <script type="text/javascript"> var str = ` <div> <div>attrs: {{$attrs}}</div> <div>foo: {{foo}}</div> </div> `; // 这是父组件 var fatherComponent = Vue.extend({ template: str, name: 'father-component', data() { return { } }, props: { foo: { type: String, default: '' } }, components: { }, }); // 这是祖先组件 new Vue({ el: '#app', components: { fatherComponent }, data() { return { foo: 'hello world', bar: 'kongzhi' } }, methods: { reciveChildFunc(value) { console.log('接收孙子组件的数据' + value); } } }); </script> </body> </html>
如上代码咱们定义了一个祖先组件和一个父组件, 在祖先组件里面, 咱们引用了父组件, 而且给父组件传递了两个属性数据, 分别为 'foo' 和 'bar'; 以下代码能够看获得:
<father-component :foo="foo" :bar="bar" @event="reciveChildFunc" > </father-component>
可是在咱们的父组件中只接收了一个属性为 'foo'; 'bar' 属性值并无接收, 所以该bar默认会把该属性放入到父组件的根元素上, 以下父组件接收祖先组件的代码以下:
var str = ` <div> <div>attrs: {{$attrs}}</div> <div>foo: {{foo}}</div> </div> `; // 这是父组件 var fatherComponent = Vue.extend({ template: str, name: 'father-component', data() { return { } }, props: { foo: { type: String, default: '' } }, components: { }, });
而后代码执行的结果以下所示:
如上效果能够看到, 咱们可使用 $attrs 来接收 祖先组件传递给父组件中未使用的数据。
同时bar参数默认会把属性放入到咱们父组件的根元素上当作一个普通属性, 以下图所示:
若是咱们不想让未使用的属性放入到父组件的根元素上当作普通属性的话, 咱们能够在父组件上把 inheritAttrs 设置为false便可。以下父组件代码添加 inheritAttrs: false
// 这是父组件 var fatherComponent = Vue.extend({ template: '.....', name: 'father-component', data() { return { } }, // 这是新增的代码 inheritAttrs: false, props: { foo: { type: String, default: '' } }, components: { }, });
效果以下图所示:
如上是祖先组件和父组件之间的交互, 如今咱们又来了一个子组件, 咱们如今要考虑的问题是咱们要如何让祖先组件的数据直接传递给子组件呢? 或者说咱们的子组件的数据如何能直接传递给祖先组件呢?
如上父组件咱们能够看到,咱们使用 $attrs 就能够拿到祖先组件的未使用的属性, 也就是 {"bar": 'kongzhi'} 这样的值, 若是咱们在父组件中把该 $attrs 传递给子组件的话, 那么子组件不就能够直接拿到 bar 的值了吗? 所以咱们在父组件中可使用 v-bind = "$attrs" 这样的就能够把数据传递给子组件了。以下代码演示:
<!DOCTYPE html> <html> <head> <title>父子组件通讯</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <father-component :foo="foo" :bar="bar" @event="reciveChildFunc" > </father-component> </div> <script type="text/javascript"> var str2 = `<div> <div>bar: {{bar}}</div> <button @click="childFunc">点击子节点</button> </div>`; // 这是子组件 var childComponent = Vue.extend({ name: 'child-component', template: str2, data() { return { } }, props: { bar: { type: String, default: '' } }, methods: { childFunc() { this.$emit('event', '32'); } } }); var str = ` <div> <div>attrs: {{$attrs}}</div> <div>foo: {{foo}}</div> <child-component v-bind="$attrs" v-on="$listeners"></child-component> </div> `; // 这是父组件 var fatherComponent = Vue.extend({ template: str, name: 'father-component', data() { return { } }, props: { foo: { type: String, default: '' } }, components: { childComponent }, }); // 这是祖先组件 new Vue({ el: '#app', components: { fatherComponent }, data() { return { foo: 'hello world', bar: 'kongzhi' } }, methods: { reciveChildFunc(value) { console.log('接收孙子组件的数据' + value); } } }); </script> </body> </html>
上面的执行结果以下所示:
如上咱们能够看到,在父组件里面,咱们传递数据给子组件, 咱们经过 v-bind="$atts" 这样的就能够把数据传递给孙子组件了, 一样孙子组件 emit的事件能够在中间组件中经过$listeners属性来传递。 在子组件里面咱们可使用 props 来接收参数 'bar' 的值了。一样在子组件咱们想把数据传递给 祖先组件的话, 咱们经过事件的方式:
@click="childFunc"; 而后在该函数代码里面执行: this.$emit('event', '32'); 和咱们之前父子组件传递数据没有什么区别, 无非就是在中间组件之间添加了 v-on="$listeners";
这样就能够实现祖先组件和孙子组件的数据交互了, 如上咱们在孙子组件点击 按钮后会调用 emit 触发事件; 如代码:this.$emit('event', '32'); 而后咱们在 祖先组件中经过 @event="reciveChildFunc" 来监听该事件了, 所以咱们在祖先组件中 编写reciveChildFunc函数来接收数据便可。
咱们下面能够看下中间组件(也就是父组件是如何使用 $listeners 和 $attrs的了)。 以下代码:
var str = ` <div> <div>attrs: {{$attrs}}</div> <div>foo: {{foo}}</div> <!-- 使用 v-bind="$attrs" 把数据传递给孙子组件, 经过v-on="$listeners"这样能够实现祖先组件和孙子组件数据交互 --> <child-component v-bind="$attrs" v-on="$listeners"></child-component> </div> `; // 这是父组件 var fatherComponent = Vue.extend({ template: str, name: 'father-component', data() { return { } }, props: { foo: { type: String, default: '' } }, components: { childComponent } });
5) 理解 provide 和 inject 用法
provide 和 inject 主要是为高阶插件/组件库提供用例, 在应用程序代码中并不推荐使用。
注意: 该两个属性是一块儿使用的。它容许一个祖先组件向其全部子孙后代组件注入一个依赖, 不论组件层次有多深。也就是说, 在咱们的项目当中会有不少不少组件,而且嵌套的很深的组件, 咱们的子孙组件想要获取到祖先组件更多的属性的话,那么要怎么办呢? 咱们总不可能经过父组件一级一级的往下传递吧, 那若是真这样作的话, 那么随着项目愈来愈大, 咱们的项目会愈来愈难以维护, 所以 provide 和 inject 出现了, 他们两个就是来解决这个事情的。
provide: 它是一个对象, 或者是一个返回对象的函数。里面它能够包含全部要传递给子孙组件的属性或属性值。
inject: 它能够是一个字符串数组或者是一个对象。属性值也能够是一个对象。
简单的来讲, 父组件经过provide来提供变量, 而后在子组件中能够经过 inject来注入变量。
下面咱们能够来看下一个简单的demo来理解下 provide/inject 的使用吧:
<!DOCTYPE html> <html> <head> <title>父子组件通讯</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <parent-component></parent-component> </div> <script type="text/javascript"> // 定义一个子组件 var childComponent = Vue.extend({ template: '<div>子组件获取祖先组件的age值为: {{age2}}</div>', inject: ['age'], data() { return { age2: this.age } } }); // 定义一个父组件 var str = `<div> <div>父组件获取name属性为: {{name2}}</div> <child-component></child-component> </div>`; var parentComponent = Vue.extend({ template: str, inject: ['name'], data() { return { name2: this.name } }, components: { childComponent } }); // 初始化祖先组件 new Vue({ el: '#app', components: { parentComponent }, // 祖先组件提供全部要传递的属性变量给子组件 provide: { name: 'kongzhi', age: 31, marriage: 'single' }, data() { return { } }, methods: { } }); </script> </body> </html>
如上代码运行效果以下所示:
如上咱们能够看获得, 咱们在祖先组件中 使用 provide 提供了全部须要传递给子组件甚至孙子组件的数据, 而后在咱们的祖先组件中调用了父组件, 而且没有经过props传递任何数据给父组件, 代码以下看获得:
<div id="app"> <parent-component></parent-component> </div>
而后咱们在父组件中使用 inject: ['name'] 这样的, 经过inject这个来注入name属性进来, 所以在咱们data中使用 this.name 就能够获取到咱们祖先组件中的属性了, 而后在父组件中使用该属性便可。
一样的道理在咱们的父组件中调用子组件, 也没有传递任何属性过去, 以下的代码能够看获得:
<child-component></child-component>
在咱们子组件中, 也同样能够经过 inject 来注入祖先组件的属性; 好比代码: inject: ['age'], 所以在页面上咱们同样经过 this.age 值就能够拿到祖先的属性值了。
6) 理解使用bus总线
bus总线可用于解决跨级和兄弟组件通讯的问题,它能够为一个简单的组件传递数据。咱们可使用一个空的Vue实列做为中央事件总线。
咱们能够封装Bus成Vue的一个方法; 以下代码:
const Bus = new Vue({ methods: { emit(event, ...args) { this.$emit(event, ...args); }, on(event, callback) { this.$on(event, callback); }, off(event, callback) { this.$off(event, callback); } } }); // 把该Bus放在原型上 Vue.prototype.$bus = Bus;
所以咱们能够把Bus抽离出来当作一个文件, 而后在入口文件引用进去便可; 假如咱们如今项目结构以下:
|--- app | |--- index | | |--- common | | | |--- bus.js | | |--- views | | | |--- c1.vue | | | |--- c2.vue | | | |--- index.vue | | |--- app.js | | |--- package.json | | |--- webpack.config.js | | |--- .babelrc
app/app.js 代码以下:
import Vue from 'vue/dist/vue.esm.js'; import Index from './views/index'; import Bus from './common/bus'; Vue.use(Bus); new Vue({ el: '#app', render: h => h(Index) });
app/index/common/bus.js 源码以下:
import Vue from 'vue/dist/vue.esm.js'; const install = function(Vue) { const Bus = new Vue({ methods: { emit(event, ...args) { this.$emit(event, ...args); }, on(event, callback) { this.$on(event, callback); }, off(event, callback) { this.$off(event, callback); } } }); // 注册给Vue对象的原型上的全局属性 Vue.prototype.$bus = Bus; }; export default install;
app/index/common/index.vue
<template> <div> <c1></c1> <c2></c2> </div> </template> <script> import c1 from './c1'; import c2 from './c2'; export default { components: { c1, c2 } } </script>
app/index/common/c1.vue
<template> <div> <div>{{msg}}</div> </div> </template> <script> export default { name: 'c1', data: function() { return { msg: 'I am kongzhi' } }, mounted() { this.$bus.$on('setMsg', function(c) { console.log(c); this.msg = c; }) } } </script>
app/index/common/c2.vue
<template> <button @click="sendEvent">Say Hi</button> </template> <script> export default { name: 'c2', methods: { sendEvent() { this.$bus.$emit('setMsg', '我是来测试Bus总线的'); } } } </script>
如上代码, 咱们把Bus代码抽离出来 封装到 app/index/common/bus.js 中, 而后在app/app.js 中入口文件中使用 Vue.use(Bus);
接着在 app/index/common/c2.vue c2组件中, 点击按钮 Say Hi 触发函数 sendEvent; 调用 this.$bus.$emit('setMsg', '我是来测试Bus总线的');事件, 而后在c1组件中使用
this.$bus.$on('setMsg', function(c) { console.log(c); } 来监听该事件. 如上代码咱们能够看到咱们 打印 console.log(c); 能打印新值出来。
注意: 使用Bus存在的问题是: 如上代码, 咱们在 this.$bus.$on 回调函数中 this.msg = c; 改变值后,视图并不会从新渲染更新,之因此会出现这种缘由: 是由于咱们接收的bus 是 $bus.on触发的。而不会从新渲染页面, 这也有多是Vue中官方的一个缺陷吧。
所以Bus总线会存在一些问题, 因此在Vue组件通讯的时候, 咱们能够综合考虑来使用哪一种方法来进行通讯。
三:在vue源码中注册组件是如何实现的呢?
上面已经介绍过, 全局注册组件有2种方式; 第一种方式是经过Vue.component 直接注册。第二种方式是经过Vue.extend来注册。
Vue.component 注册组件
好比以下代码:
<!DOCTYPE html> <html> <head> <title>vue组件测试</title> <meta charset="utf-8"> <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js"></script> </head> <body> <div id="app"> <!-- 组件调用以下 --> <button-counter></button-counter><br/><br/> </div> <script type="text/javascript"> Vue.component('button-counter', { data: function() { return { count: 0 } }, template: '<button @click="count++">You clicked me {{ count }} times.</button>' }); new Vue({ el: '#app' }); </script> </body> </html>
如上组件注册是经过 Vue.component来注册的, Vue注册组件初始化的时候, 首先会在 vue/src/core/global-api/index.js 初始化代码以下:
import { initAssetRegisters } from './assets'
initAssetRegisters(Vue);
所以会调用 vue/src/core/global-api/assets.js 代码以下:
/* @flow */ import { ASSET_TYPES } from 'shared/constants' import { isPlainObject, validateComponentName } from '../util/index' export function initAssetRegisters (Vue: GlobalAPI) { /** * Create asset registration methods. */ ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } this.options[type + 's'][id] = definition return definition } } }) }
如上代码中的 'shared/constants' 中的代码在 vue/src/shared/constants.js 代码以下:
export const SSR_ATTR = 'data-server-rendered' export const ASSET_TYPES = [ 'component', 'directive', 'filter' ] export const LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed', 'activated', 'deactivated', 'errorCaptured', 'serverPrefetch' ]
所以 ASSET_TYPES = ['component', 'directive', 'filter']; 而后上面代码遍历:
ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } this.options[type + 's'][id] = definition return definition } } });
从上面源码中咱们可知: Vue全局中挂载有 Vue['component'], Vue['directive'] 及 Vue['filter']; 有全局组件, 指令和过滤器。在Vue.component注册组件的时候, 咱们是以下调用的:
Vue.component('button-counter', { data: function() { return { count: 0 } }, template: '<button @click="count++">You clicked me {{ count }} times.</button>' });
所以在源码中咱们能够看到:
id = 'button-counter'; definition = { template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } } };
如上代码, 首先咱们判断若是 definition 未定义的话,就返回 this.options 中内的types 和id对应的值。this.options 有以下值:
this.options = { base: function(Vue), components: { KeepAlive: {}, Transition: {}, TransitionGroup: {} }, directives: { mode: {}, show: {} }, filters: { } };
如上咱们知道type的可取值分别为: 'component', 'directive', 'filter'; id为: 'button-counter'; 所以若是 definition 未定义的话, 就返回: return this.options[type + 's'][id]; 所以若是type为 'component' 的话, 那么就返回 this.options['components']['button-counter']; 从上面咱们的 this.options 的值可知; this.options['components'] 的值为:
this.options['components'] = { KeepAlive: {}, Transition: {}, TransitionGroup: {} };
所以若是 definition 值为未定义的话, 则返回 return this.options['components']['button-counter']; 的值为 undefined;
若是definition定义了的话, 若是不是正式环境的话, 就调用 validateComponentName(id); 方法, 该方法的做用是验证咱们组件名的合法性; 该方法代码以下:
// 验证组件名称的合法性 function validateComponentName (name) { if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'should conform to valid custom element name in html5 specification.' ); } if (isBuiltInTag(name) || config.isReservedTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component ' + 'id: ' + name ); } }
若是是component(组件)方法,而且definition是对象, 源码以下:
if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id = 'button-counter'; definition = this.options._base.extend(definition) }
咱们能够打印下 this.options._base 的值以下:
如上咱们能够看到 this.options._base.extend 就是指向了 Vue.extend(definition); 做用是将定义的对象转成了构造器。
Vue.extend 代码在 vue/src/core/global-api/extend.js中, 代码以下:
/* @param {extendOptions} Object extendOptions = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } } }; */ Vue.cid = 0; var cid = 1; Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const SuperId = Super.cid // 若是组件已经被缓存到extendOptions, 则直接取出组件 const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } /* 获取 extendOptions.name 所以 name = 'button-counter'; 若是有name属性值的话, 而且不是正式环境的话,验证下组件名称是否合法 */ const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } const Sub = function VueComponent (options) { this._init(options) } /* 将Vue原型上的方法挂载到 Sub.prototype 中。 所以Sub的实列会继承了Vue原型中的全部属性和方法。 */ Sub.prototype = Object.create(Super.prototype) // Sub原型从新指向Sub构造函数 Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub }
如上代码中会调用 mergeOptions 函数, 该函数的做用是: 用于合并对象, 将两个对象合并成为一个。如上代码:
Sub.options = mergeOptions(
Super.options,
extendOptions
);
如上函数代码,咱们能够看到 mergeOptions 有两个参数分别为: Super.options 和 extendOptions。他们的值能够看以下所示:
如上咱们能够看到, Super.options 和 extendOptions 值分别为以下:
Super.options = { _base: function Vue(options), components: {}, directives: {}, filters: {} }; extendOptions = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {} };
该mergeOptions函数的代码在 src/core/util/options.js 中, 基本代码以下:
/* 参数 parent, child, 及 vm的值分别为以下: parent = { _base: function Vue(options), components: {}, directives: {}, filters: {} }; child = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {} } vm: undefined */ export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // Apply extends and mixins on the child options, // but only if it is a raw options object that isn't // the result of another mergeOptions call. // Only merged options has the _base property. if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options }
如上代码, 首先该函数接收3个参数, 分别为: parent, child, vm ,值分别如上注释所示。第三个参数是可选的, 在这里第三个参数值为undefined; 第三个参数vm的做用是: 会根据vm参数是实列化合并仍是继承合并。从而会作不一样的操做。
首先源码从上往下执行, 会判断是不是正式环境, 若是不是正式环境, 会对组件名称进行合法性校验。以下基本代码:
export function validateComponentName (name: string) { if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'should conform to valid custom element name in html5 specification.' ) } if (isBuiltInTag(name) || config.isReservedTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component ' + 'id: ' + name ) } } function checkComponents (options: Object) { for (const key in options.components) { validateComponentName(key) } } if (process.env.NODE_ENV !== 'production') { checkComponents(child) }
接下来会判断传入的参数child是否为一个函数,若是是的话, 则获取它的options的值从新赋值给child。也就是说child的值能够是普通对象, 也能够是经过Vue.extend继承的子类构造函数或是Vue的构造函数。基本代码以下:
if (typeof child === 'function') { child = child.options }
接下来会执行以下三个函数:
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
它们的做用是使数据能规范化, 好比咱们以前的组件之间的传递数据中的props或inject, 它既能够是字符串数组, 也能够是对象。指令directives既能够是一个函数, 也能够是对象。在vue源码中对外提供了便捷, 可是在代码内部作了相应的处理。 所以该三个函数的做用是将数据转换成对象的形式。
normalizeProps 函数代码以下:
/* @param {options} Object options = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {} }; vm = undefined */ function normalizeProps (options: Object, vm: ?Component) { const props = options.props if (!props) return const res = {} let i, val, name if (Array.isArray(props)) { i = props.length while (i--) { val = props[i] if (typeof val === 'string') { name = camelize(val) res[name] = { type: null } } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.') } } } else if (isPlainObject(props)) { for (const key in props) { val = props[key] name = camelize(key) res[name] = isPlainObject(val) ? val : { type: val } } } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid value for option "props": expected an Array or an Object, ` + `but got ${toRawType(props)}.`, vm ) } options.props = res }
该函数的做用对组件传递的 props 数据进行处理。在这里咱们的props为undefined,所以会直接return, 可是咱们以前父子组件之间的数据传递使用到了props, 好比以下代码:
var childComponent = Vue.extend({ template: '<div>{{ content }}</div>', // 使用props接收父组件传递过来的数据 props: { content: { type: String, default: 'I am is childComponent' } } });
所以如上代码的第一行: const props = options.props; 所以props的值为以下:
props = { content: { type: String, default: 'I am is childComponent' } };
如上props也能够是数组的形式, 好比 props = ['x-content', 'name']; 这样的形式, 所以在代码内部分了2种状况进行判断, 第一种是处理数组的状况, 第二种是处理对象的状况。
首先是数组的状况, 以下代码:
export function cached<F: Function> (fn: F): F { const cache = Object.create(null) return (function cachedFn (str: string) { const hit = cache[str] return hit || (cache[str] = fn(str)) }: any) } const camelizeRE = /-(\w)/g; /* 该函数的做用是把组件中的 '-' 字符中的第一个字母转为大写形式。 好比以下代码: 'a-b'.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : ''); 最后打印出 'aB'; */ export const camelize = cached((str: string): string => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') }) const res = {} let i, val, name if (Array.isArray(props)) { i = props.length while (i--) { val = props[i] if (typeof val === 'string') { name = camelize(val) res[name] = { type: null } } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.') } } }
咱们能够假设props是数组, props = ['x-content', 'name']; 这样的值。 所以 i = props.length = 2; 所以就会进入while循环代码, 最后会转换成以下的形式:
res = { 'xContent': { type: null }, 'name': { type: null } };
同理若是咱们假设咱们的props是一个对象形式的话, 好比值为以下:
props: { 'x-content': String, 'name': Number };
所以会执行else语句代码; 代码以下所示:
const _toString = Object.prototype.toString; function isPlainObject (obj: any): boolean { return _toString.call(obj) === '[object Object]' } else if (isPlainObject(props)) { for (const key in props) { val = props[key] name = camelize(key) res[name] = isPlainObject(val) ? val : { type: val } } }
所以最后 res的值变为以下:
res = { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } } };
固然如上代码, 若是某一个key自己是一个对象的话, 就直接返回该对象, 好比 props 值以下:
props: { 'x-content': String, 'name': Number, 'kk': {'name': 'kongzhi11'} }
那么最后kk的键就不会进行转换, 最后返回的值res变为以下:
res = { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} };
所以最后咱们的child的值就变为以下值了:
child = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } } };
normalizeInject 函数, 该函数的做用同样是使数据可以规范化, 代码以下:
function normalizeInject (options: Object, vm: ?Component) { const inject = options.inject if (!inject) return const normalized = options.inject = {} if (Array.isArray(inject)) { for (let i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] } } } else if (isPlainObject(inject)) { for (const key in inject) { const val = inject[key] normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val } } } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid value for option "inject": expected an Array or an Object, ` + `but got ${toRawType(inject)}.`, vm ) } }
同理, options的值能够为对象或数组。options值为以下:
options = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, inject: ['name'], _Ctor: {} };
同理依次执行代码; const inject = options.inject = ['name'];
1: inject数组状况下:
inject是数组的话, 会进入if语句内, 代码以下所示:
var normalized = {}; if (Array.isArray(inject)) { for (let i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] } } }
所以最后 normalized 的值变为以下:
normalized = { 'name': { from: 'name' } };
所以child值继续变为以下值:
child = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'name': { form: 'name' } } };
2. inject为对象的状况下:
好比如今options的值为以下:
options = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, inject: { foo: { from: 'bar', default: 'foo' } }, _Ctor: {} };
如上inject配置中的 from表示在可用的注入内容中搜索用的 key,default固然就是默认值。默认是 'foo', 如今咱们把它重置为 'bar'. 所以就会执行else if语句代码,基本代码以下所示:
/** * Mix properties into target object. */ export function extend (to: Object, _from: ?Object): Object { for (const key in _from) { to[key] = _from[key] } return to } else if (isPlainObject(inject)) { for (const key in inject) { const val = inject[key] normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val } } }
由上可知; inject值为以下:
inject = { foo: { from: 'bar', default: 'foo' } };
如上代码, 使用for in 遍历 inject对象。执行代码 const val = inject['foo'] = { from: 'bar', default: 'foo' }; 能够看到val是一个对象。所以会调用 extend函数方法, 该方法在代码 vue/src/shared/util.js 中。
代码以下:
/* @param {to} to = { from: 'foo' } @param {_from} _form = { from: 'bar', default: 'foo' } */ export function extend (to: Object, _from: ?Object): Object { for (const key in _from) { to[key] = _from[key] } return to }
如上执行代码后, 所以最后 normalized 值变为以下:
normalized = { foo: { from: 'bar', default: 'foo' } };
所以咱们经过格式化 inject后,最后咱们的child的值变为以下数据了:
child = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'foo': { default: 'foo', from: 'bar' } } };
如今咱们继续执行 normalizeDirectives(child); 函数了。 该函数的代码在 vue/src/core/util/options.js中,代码以下:
/* * Normalize raw function directives into object format. * 遍历对象, 若是key值对应的是函数。则把他修改为对象的形式。 * 所以从下面的代码能够看出, 若是vue中只传递了函数的话, 就至关于这样的 {bind: func, unpdate: func} */ function normalizeDirectives (options: Object) { const dirs = options.directives if (dirs) { for (const key in dirs) { const def = dirs[key] if (typeof def === 'function') { dirs[key] = { bind: def, update: def } } } } }
如今咱们再回到 vue/src/core/util/options.js中 export function mergeOptions () 函数中接下来的代码:
export function mergeOptions ( parent: Object, child: Object, vm?: Component) { : Object { // ... 代码省略 if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options } }
从上面可知, 咱们的child的值为以下:
child = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'foo': { default: 'foo', from: 'bar' } } };
所以 child._base 为undefined, 只有合并过的选项才会有 child._base 的值。这里判断就是过滤掉已经合并过的对象。 所以会继续进入if语句代码判断是否有 child.extends 这个值,若是有该值, 会继续调用mergeOptions方法来对数据进行合并。最后会把结果赋值给parent。
继续执行代码 child.mixins, 若是有该值的话, 好比 mixins = [xxx, yyy]; 这样的,所以就会遍历该数组,递归调用mergeOptions函数,最后把结果仍是返回给parent。
接着继续执行代码;
// 定义一个空对象, 最后把结果返回给空对象 const options = {} let key /* 遍历parent, 而后调用下面的mergeField函数 parent的值为以下: parent = { _base: function Vue(options), components: {}, directives: {}, filters: {} }; 所以就会把components, directives, filters 等值看成key传递给mergeField函数。 */ for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } }
/* 该函数主要的做用是经过key获取到对应的合并策略函数, 而后执行合并, 而后把结果赋值给options[key下面的starts的值,在源码中 的初始化中已经定义了该值为以下: const strats = config.optionMergeStrategies; starts = { activated: func, beforeCreate: func, beforeDestroy: func, beforeMount: func, beforeUpdate: func, components: func, computed: func, created: func, data: func, deactivated: func, destroyed: func, directives: func, filters: func ...... }; 以下代码: const strat = strats[key] || defaultStrat; 就能获取到对应中的函数, 好比key为 'components', 所以 start = starts['components'] = function mergeAssets(){}; */ function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options
mergeAssets函数代码以下:
function mergeAssets ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): Object { const res = Object.create(parentVal || null) if (childVal) { process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) } else { return res } }
最后返回的options的值变为以下了:
options = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'foo': { default: 'foo', from: 'bar' } }, components: {}, directives: {}, filters: {} };
所以咱们再回到代码 vue/src/core/global-api/extend.js 代码中的Vue.extend函数,以下代码:
Vue.extend = function (extendOptions: Object): Function { //...... /* Sub.options的值 就是上面options的返回值 */ Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super; if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use; // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub }
所以 Sub.options值为以下:
Sub.options = { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'foo': { default: 'foo', from: 'bar' } }, components: {}, directives: {}, filters: {} };
所以执行代码:
if (Sub.options.props) { initProps(Sub) }
从上面的数据咱们能够知道 Sub.options.props 有该值的,所以会调用 initProps 函数。代码以下:
function initProps (Comp) { const props = Comp.options.props for (const key in props) { proxy(Comp.prototype, `_props`, key) } }
所以 const props = Comp.options.props;
即 props = { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }
使用for in 循环该props对象。最后调用 proxy 函数, 该函数的做用是使用 Object.defineProperty来监听对象属性值的变化。
该proxy函数代码以下所示:
该proxy函数代码在 vue/src/core/instance/state.js 中,代码以下所示:
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 } Object.defineProperty(target, key, sharedPropertyDefinition) }
继续执行代码以下:
if (Sub.options.computed) { initComputed(Sub) }
判断是否有computed选项, 若是有的话,就调用 initComputed(Sub); 该函数代码在 vue/src/core/instance/state.js; 该代码源码先不分析, 会有对应的章节分析的。最后代码一直到最后, 会返回Sub对象, 该对象值就变为以下了:
Sub = { cid: 1, component: func, directive: func, extend: func, extendOptions: { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'foo': { default: 'foo', from: 'bar' } } }, filter,func, mixin: func, options: { name: 'button-counter', template: '<button @click="count++">You clicked me {{ count }} times.</button>', data: function() { return { count: 0 } }, _Ctor: {}, props: { 'xContent': { type: function Number() { ... } }, 'name': { type: function String() { ... } }, 'kk': {'name': 'kongzhi11'} } }, inject: { 'foo': { default: 'foo', from: 'bar' } }, components: {}, directives: {}, filters: {}, components: button-counter: f VueComponent, _base: f Vue() ...... } };
注意:在代码中会有以下一句代码; 就是会把咱们的组件 'button-counter' 放到 Sub.options.components 组件中。
// enable recursive self-lookup if (name) { Sub.options.components[name] = Sub }
如上代码执行完成 及 返回完成后,咱们再回到 vue/src/core/global-api/assets.js 代码中看接下来的代码:
/* @flow */ import { ASSET_TYPES } from 'shared/constants' import { isPlainObject, validateComponentName } from '../util/index' export function initAssetRegisters (Vue: GlobalAPI) { /** * Create asset registration methods. */ ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } this.options[type + 's'][id] = definition return definition } } }) }
所以 最后代码: this.options[type + 's'][id] = definition;
this.options = { components: { KeepAlive: {}, Transition: {}, TransitionGroup: {}, button-counter: ƒ VueComponent(options){} }, directives: {}, filters: {}, base: f Vue(){} }; this.options[type + 's'][id] = this.options['components']['button-counter'] = f VueComponent(options);
最后咱们返回 definition 该Vue的实列。即definition的值为以下:
definition = ƒ VueComponent (options) { this._init(options); }
最后咱们就会调用 new Vue() 方法来渲染整个生命周期函数了,所以button-counter组件就会被注册上能够调用了。