在渲染时,会被替换为组件的内容。 常见的限制元素还有
、
、
。
提示: 如果使用的是字符串模板,是不受限制的,比如后面章节介绍的.vue
单文件用法等。
除了template
选项外,组件还可以像Vue实例那样使用其他的选项,比如data
、computed
、method
等。 但是在使用data
时,和实例稍有区别,data
必须是函数,然后将数据return
出去。 例如:
1 < div id ="app" >
2 < my-component > my-component >
3 div >
4
5 < script >
6 Vue.component( " my-component " , {
7 template: " {{message}}
" ,
8 data: function () {
9 return {
10 message: " 组件内容 "
11 };
12 }
13 });
14
15 var app = new Vue({
16 el: " #app "
17 });
18 script >
JavaScript对象是引用关系,所以如果return
出的对象引用了外部的一个对象,那这个对象就是共享的,任何一方修改都会同步。 比如下面的示例:
1 < div id ="app" >
2 < my-component > my-component >
3 < my-component > my-component >
4 < my-component > my-component >
5 div >
6
7 < script >
8 var data = {
9 counter: 0
10 };
11
12 Vue.component( " my-component " , {
13 template: " {{counter}} " ,
14 data: function () {
15 return data;
16 }
17 });
18
19 var app = new Vue({
20 el: " #app "
21 });
22 script >
组件使用了3次,但是点击任意一个
,3个按钮的数字都会加1。 那是因为组件的data
引用的是外部的对象,这肯定不是我们期望的效果。 所以给组件返回一个新的data
对象来独立,示例代码如下:
1 < div id ="app" >
2 < my-component > my-component >
3 < my-component > my-component >
4 < my-component > my-component >
5 div >
6
7 < script >
8 Vue.component( " my-component " , {
9 template: " {{counter}} " ,
10 data: function () {
11 return {
12 counter: 0
13 };
14 }
15 });
16
17 var app = new Vue({
18 el: " #app "
19 });
20 script >
这样,点击3个按钮就互不影响了,完全达到复用的目的。
7.2 使用props传递数据
7.2.1 基本用法
组件不仅仅是要把模板的内容进行复用,更重要的是组件间要进行通信。 通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作。 这个正向传递数据的过程就是通过props
来实现的。
在组件中,使用选项props
来声明需要从父级接收的数据。props
的值可以是两种,一种是字符串数组,一种是对象,本小节先介绍数组的用法。 比如我们构造一个数组,接收一个来自父级的数据message
,并把它在组件模板中渲染,示例代码如下:
1 < div id ="app" >
2 < my-component message ="来自父组件的数据" > my-component >
3 div >
4
5 < script >
6 Vue.component( " my-component " , {
7 props: [ " message " ],
8 template: " {{message}}
"
9 });
10
11 var app = new Vue({
12 el: " #app "
13 });
14 script >
渲染后的结果为:
1 < div id ="app" >
2 < div > 来自父组件的数据div >
3 div >
props
中声明的数据与组件data
函数return
的数据主要区别就是props
的来自父级,而data
中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template
及计算属性computed
和方法methods
中使用。 上例的数据message
就是通过props
从父级传递过来的,在组件的自定义标签上直接写该props
的名称,如果要传递多个数据,在props
数组中添加项即可。
由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名(camelCase)的props
名称要转为短横分隔命名(kebab-case)。例如:
1 < div id ="app" >
2 < my-component warning-text ="提示信息" > my-component >
3 div >
4
5 < script >
6 Vue.component( " my-component " , {
7 props: [ " warningText " ],
8 template: " {{warningText}}
"
9 });
10
11 var app = new Vue({
12 el: " #app "
13 });
14 script >
提示: 如果使用的是字符串模板,仍然可以忽略这些限制。
有时候,传递的数据并不是直接写死的,而是来自父级的动态数据,这是可以使用指令v-bind
来动态绑定props
的值,当父组件的数据变化时,也会传递给子组件。示例代码如下:
1 < div id ="app" >
2 < input type ="text" v-model ="parentMessage" >
3 < my-component :message ="parentMessage" > my-component >
4 div >
5
6 < script >
7 Vue.component( " my-component " , {
8 props: [ " message " ],
9 template: " {{message}}
"
10 });
11
12 var app = new Vue({
13 el: " #app " ,
14 data: {
15 parentMessage: ""
16 }
17 });
18 script >
这里用v-model
绑定了父级的数据parentMessage
。 当通过输入框任意输入时,子组件接收的props
(message
)也会实时响应,并更新组件模板。
提示: 注意,如果你要直接传递数字、布尔值、数组、对象,而且不使用v-bind
,传递的仅仅是字符串,尝试下面的示例来对比。
1 < div id ="app" >
2 < my-component message ="[1,2,3]" > my-component >
3 < my-component :message ="[1,2,3]" > my-component >
4 div >
5 < script >
6 Vue.component( " my-component " , {
7 props: [ " message " ],
8 template: " {{message.length}}
"
9 });
10 var app = new Vue({
11 el: " #app "
12 });
13 script >
同一个组件使用了两次,区别仅仅是第二个使用的是v-bind
。
渲染后的结果:第一个是7,第二个才是数组的长度3。
7.2.2 单向数据流
Vue 2.x与Vue 1.x比较大的一个改变就是,Vue 2.x通过props
传递数据是单向的了,也就是父组件数据变化时会传递给子组件,但是反过来不行。 而在Vue 1.x里提供了.sync
修饰符来支持双向绑定。 之所以这样设计,是尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态。
业务中经常用到两种需要改变prop
的情况,一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。 何种情况可以在组件data
内再声明一个数据,引用父组件的prop
,实例代码如下:
1 < div id ="app" >
2 < my-component :init-count ="1" > my-component >
3 div >
4
5 < script >
6 Vue.component( " my-component " , {
7 props: [ " initCount " ],
8 template: " {{count}}
" ,
9 data: function () {
10 return {
11 count: this .initCount
12 };
13 }
14 });
15
16 var app = new Vue({
17 el: " #app "
18 });
19 script >
组件中声明了数据count
,它在组件初始化时会获取来自父组件的initCount
,之后就与之无关了,只用维护count
,这样就可以避免直接操作initCount
。
另一种情况就是prop
作为需要转变的原始值传入。 这种情况用计算属性就可以了,示例代码如下:
1 < div id ="app" >
2 < my-component :width ="100" > my-component >
3 div >
4
5 < script >
6 Vue.component( " my-component " , {
7 props: [ " width " ],
8 template: " 组件内容
" ,
9 computed: {
10 style: function () {
11 return {
12 width: this .width + " px "
13 };
14 }
15 }
16 });
17
18 var app = new Vue({
19 el: " #app "
20 });
21 script >
因为用CSS传递宽度要带单位(px),但是每次都写太麻烦,而且数值计算一般是不带单位的,所以统一在组件内使用计算属性就可以了。
提示: 注意,在JavaScript中对象和数组是引用类型,指向同一个内存空间,所以props
是对象和数组时,在子组件内改变时会影响父组件的。
7.2.3 数据验证
我们上面所介绍的props
选项的值都是一个数组,一开始也介绍过,除了数组外,还可以是对象,当prop
需要验证时,就需要对象写法。
一般当你的组件需要提供给别人使用时,推荐都进行数据验证。 比如某个数据必须是数字类型,如果传入字符串,就会在控制台弹出警告。
一下是几个prop
的示例:
1 Vue.component("my-component", {
2 props: {
3 // 必须是数字类型
4 propA: Number,
5 // 必须是字符串或数字类型
6 propB: [String, Number],
7 // 布尔值,如果没有定义,默认值就是true
8 propC: {
9 type: Boolean,
10 default : true
11 },
12 // 数字,而且是必传
13 propD: {
14 type: Number,
15 required: true
16 },
17 // 如果是数组或对象,默认值必须是一个函数来返回
18 propE: {
19 type: Array,
20 default : function () {
21 rturn[];
22 }
23 },
24 // 自定义一个验证函数
25 propF: {
26 validator: function (value) {
27 return value > 10;
28 }
29 }
30 }
31 });
验证的type
类型可以是:
String
Number
Boolean
Object
Array
Function
type
也可以是一个自定义构造器,使用instanceof
检测。 当prop
验证失败时,在开发版本下会在控制台抛出一条警告。
7.3 组件通信
我们已经知道,从父组件向子组件通信,通过props
传递数据就可以了。 但Vue组件通信的场景不止有这一种,归纳起来,组件之间通信可以用图7-2表示。 
组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信。 本节将介绍组中组件之间通信的方法。
7.3.1 自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件。 我们在介绍指令 v-on 时有提到,v-on
除了监昕DOM事件外,还可以用于组件之间的自定义事件。
如果你了解过JavaScript的设计模式一一观察者模式,一定知道dispatchEvent
和addEventListener
这两个方法。 Vue组件也有与之类似的一套模式,子组件用$emit()
来触发事件,父组件用$on()
来监昕子组件的事件。
父组件也可以直接在子组件的自定义标签上使用v-on
来监昕子组件触发的自定义事件,示例代码如下:
1 < div id ="app" >
2 < p > 总数:{{total}}p >
3 < my-component @increase ="handleGetTotal" @reduce ="handleGetTotal" > my-component >
4 div >
5
6 < script >
7 Vue.component( " my-component " , {
8 template: " +1 -1
" ,
9 data: function () {
10 return {
11 counter: 0
12 }
13 },
14 methods: {
15 handleIncrease: function () {
16 this .counter ++ ;
17 this .$emit( " increase " , this .counter);
18 },
19 handleReduce: function () {
20 this .counter -- ;
21 this .$emit( " reduce " , this .counter);
22 }
23 }
24 });
25
26 var app = new Vue({
27 el: " #app " ,
28 data: {
29 total: 0
30 },
31 methods: {
32 handleGetTotal: function (total) {
33 this .total = total;
34 }
35 }
36 });
37 script >
上面示例中,子组件有两个按钮,分别实现加1和减1的效果,在改变组件的data
(counter
)后,通过$emit()
再把它传递给父组件,父组件用v-on:increase
和v-on:reduce
(示例使用的是语法糖)。$emit()
方法的第一个参数是自定义事件的名称,例如示例的increase
和reduce
后面的参数都是要传递的数据,可以不填或填写多个。
除了用v-on
在组件上监听自定义事件外,也可以监听DOM事件,这时可以使用.native
修饰符表示监听的是一个原生事件,监听的是该组件的根元素,实例代码如下:
1 < my-component v-on:click.native ="handleClick" > my-component >
7.3.2 使用v-model
Vue 2.x可以在自定义组件上使用v-model
指令,我们先来看一个示例:
1 < div id ="app" >
2 < p > 总数:{{total}}p >
3 < my-component v-model ="total" > my-component >
4 div >
5
6 < script >
7 Vue.component( " my-component " , {
8 template: " +1 " ,
9 data: function () {
10 return {
11 counter: 0
12 }
13 },
14 methods: {
15 handleClick: function () {
16 this .counter ++ ;
17 this .$emit( " input " , this .counter);
18 }
19 }
20 });
21 var app = new Vue({
22 el: " #app " ,
23 data: {
24 total: 0
25 }
26 });
27 script >
仍然是点击按钮加1的效果,不过这次组件$emit()
的事件名是特殊的input
,在使用组件的父级,并没有在
上使用@input='handler'
,而是直接用了v-model
绑定的一个数据total
。 这也可以称作是一个语法糖,因为上面的示例可以间接地用自定义事件来实现:
1 < div id ="app" >
2 < p > 总数:{{total}}p >
3 < my-component @input ="handleGetTotal" > my-component >
4 div >
5
6 < script >
7 Vue.component( " my-component " , {
8 template: " +1 " ,
9 data: function () {
10 return {
11 counter: 0
12 }
13 },
14 methods: {
15 handleClick: function () {
16 this .counter ++ ;
17 this .$emit( " input " , this .counter);
18 }
19 }
20 });
21
22 var app = new Vue({
23 el: " #app " ,
24 data: {
25 total: 0
26 },
27 methods: {
28 handleGetTotal: function (total) {
29 this .total = total;
30 }
31 }
32 });
33 script >
v-model
还可以用来创建自定义的表单输入组件,进行数据双向绑定,例如:
实现这样一个具有双向绑定的v-model
组件要满足下面两个要求:
接收一个value
属性。
在有新的value
时触发input
事件。
7.3.3 非父子组件通信
在实际业务中,除了父子组件通信外,还有很多非父子组件通信的场景,非父子组件一般有两种:兄弟组件和跨多级组件。 为了更加彻底地了解Vue.js 2.x中的通信方法,我们先来看一下在Vue.js 1.x中是如何实现的,这样便于我们了解Vue.js的设计思想。
在Vue.js 1.x中,除了$emit()
方法外,还提供了$dispatch()
和$broadcast()
这两个方法。$dispatch()
用于向上级派发事件,只要是它的父级(一般或多级以上),都可以在Vue实例的events
选项内接收,示例代码如下:
1
2 < div id ="app" >
3 {{message}}
4 < my-component > my-component >
5 div >
6
7 < script >
8 Vue.component( " my-component " , {
9 template: " 源发事件 " ,
10 methods: {
11 handleDispatch: function () {
12 this .$dispatch( " on-message " , " 来自内部组件的数据 " );
13 }
14 }
15 });
16
17 var app = new Vue({
18 el: " #app " ,
19 data: {
20 message: ""
21 },
22 methods: {
23 " on-message " : function (msg) {
24 this .message = msg;
25 }
26 }
27 });
28 script >
同理,$broadcast()
是由上级向下级广播事件的,用法完全一致,只是方向相反。
这两种方法一旦发出事件后,任何组件都是可以接收到的,就近原则,而且会在第一次接收到后停止冒泡,除非返回true
。
这两个方法虽然看起来很好用,但是在Vue.js 2.x中都废弃了,因为基于组件树结构的事件流方式让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱,并且不能解决兄弟组件通信的问题。
在Vue.js 2.x中,推荐使用一个空的Vue实例作为中央事件总线(bus),也就是一个中介。 为了更形象地了解它,我们举一个生活中的例子。
比如你需要租房子,你可能会找房产中介来等级你的需求,然后中介把你的信息发给满足要求的出租者,出租者再把报价和看房时间告诉中介,由中介再转达给你。 整个过程中,买家和卖家并没有任何交流,都是通过中间人来传话的。
或者你最近可能要换房了,你会找房产中介登记你的信息,订阅与你找房需求相关的资讯。 一旦有符合你的房子出现时,中介会通知你,并传达你房子的具体信息。
这两个例子中,你和出租者担任的就是两个跨级的组件,而房产中介就是这个中央事件总线(bus)。 比如下面的示例代码:
1 < div id ="app" >
2 {{message}}
3 < my-component-a > my-component-a >
4 div >
5
6 < script >
7 var bus = new Vue();
8
9 Vue.component( " my-component-a " , {
10 template: " 传递事件 " ,
11 methods: {
12 handleEvent: function () {
13 bus.$emit( " on-message " , " 来自组件 component-a 的内容 " );
14 }
15 }
16 });
17
18 var app = new Vue({
19 el: " #app " ,
20 data: {
21 message: ""
22 },
23 mounted: function () {
24 var _this = this ;
25 // 在实例初始化时,监听来自bus示例的事件
26 bus.$on( " on-message " , function (msg) {
27 _this.message = msg;
28 });
29 }
30 });
31 script >
首先创建了一个名为bus
的空Vue实例,里面没有任何内容;然后全局定义了组件component-a
;最后创建Vue实例app
。 在app
初始化时,也就是在生命周期mounted
钩子函数里监听了来自bus
的事件on-message
, 而在组件component-a
中,点击按钮会通过bus
把事件on-message
发出去, 此时app
就会接受到来自bus
的事件,进而在回调里完成自己的业务逻辑。
这种方法巧妙而清凉地实现了任何组件间的通信,包括父子、兄弟、跨级,而且Vue 1.x和Vue 2.x都适用。 如果深入使用,可以扩展bus
实例,给它添加data
、methods
、computed
等选项,这些都是可以公用的。 在业务中,尤其是协同开发时非常有用,因为经常需要共享一些通用的信息。 比如用户登录的昵称、性别、邮箱等,还有用户的授权token等,只需在初始化时让bus
获取一次,任何时间、任何组件就可以从中直接使用了,在单页面富应用(SPA)中会很实用,我们会在进阶篇中逐步介绍这些内容。
当你的项目比较大,有更多的小伙伴参与开发时,也可以你选择更好的状态管理解决方案vuex,在进阶篇里会详细介绍关于它的用法。
除了中央事件总线bus
外,还有两种方法可以实现组件间通信:父链和子组件索引。
父链
在子组件中,使用this.$parent
可以直接访问该组件的父实例或组件,父组件也可以通过this.$children
访问它所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。 示例代码如下:
1 < div id ="app" >
2 {{message}}
3 < my-component-a > my-component-a >
4 div >
5
6 < script >
7 Vue.component( " my-component-a " , {
8 template: " 通过父链直接修改数据 " ,
9 methods: {
10 handleEvent: function () {
11 // 访问到父链后,可以做任何操作,比如直接修改数据
12 this .$parent.message = " 来自组件 component-a 的内容 " ;
13 }
14 }
15 });
16
17 var app = new Vue({
18 el: " #app " ,
19 data: {
20 message: ""
21 }
22 });
23 script >
尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件紧耦合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只有组件自己能修改它的状态。 父子组件最好还是通过props
和$emit()
来通信。
子组件索引
当子组件较多时,通过this.$children
来一一遍历我们需要的一个组件实例是比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。Vue提供了子组件索引的方法,用特殊的属性ref
来为子组件指定一个索引名称。 示例代码如下:
1 < div id ="app" >
2 < button @click ="handleRef" > 通过ref获取子组件实例button >
3 < component-a ref ="comA" > component-a >
4 div >
5
6 < script >
7 Vue.component( " component-a " , {
8 template: " 子组件
" ,
9 data: function () {
10 return {
11 message: " 子组件内容 "
12 };
13 }
14 });
15
16 var app = new Vue({
17 el: " #app " ,
18 methods: {
19 handleRef: function () {
20 // 通过$refs来访问指定的实例
21 var msg = this .$refs.comA.message;
22 console.log(msg);
23 }
24 }
25 });
26 script >
在父组件模板中,子组件标签上使用ref
指定一个名称,并在父组件内通过this.$refs
来访问指定名称的子组件。
提示: \(refs只在组件渲染完成后才填充,并且它是非响应式的。 它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用\)refs。
与Vue 1.x不同的是,Vue 2.x将v-el
和v-ref
合并为了ref
,Vue会自动去判断是普通标签还是组件。 可以尝试补全下面的代码,分别打印出两个ref
看看都是什么:
1 < div id ="app" >
2 < p ref ="p" > 内容p >
3 < child-component ref ="child" > child-component >
4 div >
7.4 使用slot分发内容
7.4.1 什么是slot
我们先看一个比较常规的网站布局,如图7-3所示。 
这个网站由一级导航、二级导航、左侧列表、正文以及底部版权信息5个模块组成。 如果要将它们都组件化,这个结构可能会是:
1 < app >
2 < menu-main > menu-main >
3 < menu-sub > menu-sub >
4 < div class ="container" >
5 < menu-left > menu-left >
6 < container > container >
7 div >
8 < app-footer > app-footer >
9 app >
当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot
,这个过程叫做内容分发(transclusion)。 以
为例,它有两个特点:
组件不知道它的挂载点会有什么内容。挂载点的内容是由
的父组件决定的。
组件很可能有它自己的模板。
props
传递数据、events
触发事件和slot
内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成的。
7.4.2 作用域
正式介绍slot
前,需要先知道一个概念:编译的作用域。 比如父组件有如下模板:
1 < child-component >
2 {{message}}
3 child-component >
这里的message
就是一个slot
。 但是它绑定的是父组件的数据,而不是组件
的数据。
父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。 例如下面的代码示例:
1 < div id ="app" >
2 < child-component v-show ="showChild" > child-component >
3 div >
4
5 < script >
6 Vue.component( " child-component " , {
7 template: " 子组件
"
8 });
9 var app = new Vue({
10 el: " #app " ,
11 data: {
12 showChild: true
13 }
14 })
15 script >
这里的状态showChild
绑定的是父组件的数据,如果想在子组件上绑定,那应该是:
1 < div id ="app" >
2 < child-component > child-component >
3 div >
4
5 < script >
6 Vue.component( " child-component " , {
7 template: " 子组件
" ,
8 data: function () {
9 return {
10 showChild: true
11 };
12 }
13 });
14
15 var app = new Vue({
16 el: " #app "
17 })
18 script >
因此,slot
分发的内容,作用域是在父组件上的。
7.4.3 solt用法
单个Slot
在子组件内使用特殊的
元素就可以为这个子组件开启一个slot(插槽),在父组件模板里,插入子组件标签内的所有内容将替代子组件的
标签及它的内容。 示例代码如下:
1 < div id ="app" >
2 < child-component >
3 < p > 分发的内容p >
4 < p > 更多分发的内容p >
5 child-component >
6 div >
7
8 < template id ="template" >
9 < div >
10 < slot >
11 < p > 如果父组件没有插入内容,我将作为默认出现p >
12 slot >
13 div >
14 template >
15
16 < script >
17 Vue.component( " child-component " , {
18 template: " #template "
19 });
20 var app = new Vue({
21 el: " #app "
22 })
23 script >
子组件child-component
的模板内定义了一个
元素,并且用一个
作为默认的内容,在父组件没有使用slot
时,会渲染这段默认的文本;如果写入了slot
,那就回替换整个
。 所以上例渲染后的结果为:
1 < div id ="app" >
2 < div >
3 < p > 分发的内容p >
4 < p > 更多分发的内容p >
5 div >
6 div >
提示: 注意,子组件
内的备用内容,它的作用域是子组件本身。
具名Slot
给
元素指定一个name
后可以分发多个内容,具名Slot可以与单个Slot共存。 例如下面的示例:
1 < div id ="app" >
2 < child-component >
3 < h2 slot ="header" > 标题h2 >
4 < p > 正文内容p >
5 < p > 更多的正文内容p >
6 < div slot ="footer" > 底部信息div >
7 child-component >
8 div >
9
10 < template id ="template" >
11 < div class ="container" >
12 < div class ="header" >
13 < slot name ="header" > slot >
14 div >
15 < div class ="main" >
16 < slot > slot >
17 div >
18 < div class ="footer" >
19 < slot name ="footer" > slot >
20 div >
21 div >
22 template >
23
24 < script >
25 Vue.component( " child-component " , {
26 template: " #template "
27 });
28 var app = new Vue({
29 el: " #app "
30 })
31 script >
子组件内声明了3个
元素,其中在内的
没有使用
name
特性,它将作为默认slot出现,父组件没有使用slot特性的元素与内容都将会出现在这里。
如果没有指定默认的匿名slot,父组件内多余的内容片断都将被抛弃。
上例最终渲染后的结果为:
1 < div id ="app" >
2 < div class ="container" >
3 < div class ="header" >
4 < h2 > 标题h2 >
5 div >
6 < div class ="main" >
7 < p > 正文内容p >
8 < p > 更多的正文内容p >
9 div >
10 < div class ="footer" >
11 < div > 底部信息div >
12 div >
13 div >
14 div >
在组合使用组件时,内容分发API至关重要。
7.4.4 作用域插槽
作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染元素。 概念比较难理解,我们先看一个简单的示例来了解它的基本用法。 示例代码如下:
1 < div id ="app" >
2 < child-component >
3 < template scope ="props" >
4 < p > 来自父组件的内容p >
5 < p > {{props.msg}}p >
6 template >
7 child-component >
8 div >
9
10 < template id ="template" >
11 < div class ="container" >
12 < slot msg ="来自子组件的内容" > slot >
13 div >
14 template >
15
16 < script >
17 Vue.component( " child-component " , {
18 template: " #template "
19 });
20 var app = new Vue({
21 el: " #app "
22 })
23 script >
观察子组件的模板,在
元素上有一个类似props
传递数据给组件的写法msg="xxx"
,数据传到了插槽。 父组件中使用了
元素,而且拥有一个scope="props"
的特性,这里的props
只是一个临时变量,就像v-for="item in items"
里面的item
一样。template
内可以通过临时变量props
访问来自子组件插槽的数据msg
。
将上面的示例渲染后的最终结果为:
1 < div id ="app" >
2 < div class ="container" >
3 < p > 来自父组件的内容p >
4 < p > 来自子组件的内容p >
5 div >
6 div >
作用域插槽根据代表性的用力是列表组件,允许组件自定义应该如何渲染列表每一项。 示例代码如下:
1 < div id ="app" >
2 < my-list v-bind:books ="books" >
3 < template slot ="book" scope ="props" >
4 < li > {{props.bookName}}li >
5 template >
6 my-list >
7 div >
8
9 < template id ="template" >
10 < ul >
11 < slot name ="book" v-for ="book in books" v-bind:book-name ="book.name" >
12
13 slot >
14 ul >
15 template >
16
17 < script >
18 Vue.component( " my-list " , {
19 props: {
20 books: {
21 type: Array,
22 default : function () {
23 return {};
24 }
25 }
26 },
27 template: " #template "
28 });
29
30 var app = new Vue({
31 el: " #app " ,
32 data: {
33 books: [
34 {name: " 《Vue.js实战》 " },
35 {name: " 《JavaScript语言精粹》 " },
36 {name: " 《JavaScript高级程序设计》 " }
37 ]
38 }
39 });
40 script >
子组件my-list
接收一个来自父级的prop
数组books
,并且将它在name
为book
的slot
上使用v-for
指令循环,同时暴露一个变量bookName
。
如果你仔细揣摩上面的用法,你可能会产生这样的疑问:我直接在父组件用v-for
不就好了吗?为什么还要绕一步,在子组件里面循环呢? 的确,如果只是针对上面的示例,这样写是多此一举的。此例的用意主要是介绍作用域插槽的用法,并没有加入使用场景,而作用域插槽的适用场景就是既可以复用子组件的slot,又可以使slot内容不一致。 如果上例还在其他组件内使用,
的内容渲染权是由使用者掌握的,而数据却可以通过临时变量(比如props
)子组件内获取。
7.4.5 访问slot
在Vue.js 1.x中,想要获取某个slot是比较麻烦的,需要用v-el
间接获取。 而Vue.js 2.x提供了用来访问被slot分发的内容的方法$slots
,请看下面的示例:
1 < div id ="app" >
2 < child-component >
3 < h2 slot ="header" > 标题h2 >
4 < p > 正文内容p >
5 < p > 更多的正文内容p >
6 < div slot ="footer" > 底部信息div >
7 child-component >
8 div >
9
10 < template id ="template" >
11 < div class ="container" >
12 < div class ="header" >
13 < slot name ="header" > slot >
14 div >
15 < div class ="main" >
16 < slot > slot >
17 div >
18 < div class ="footer" >
19 < slot name ="footer" > slot >
20 div >
21 div >
22 template >
23
24 < script >
25 Vue.component( " child-component " , {
26 template: " #template " ,
27 mounted: function () {
28 var header = this .$slots.header;
29 var main = this .$slots.main;
30 var footer = this .$slots.footer;
31 console.log(footer);
32 console.log(footer[ 0 ].elm.innerHTML);
33 }
34 });
35
36 var app = new Vue({
37 el: " #app "
38 });
39 script >
通过$slots
可以访问某个具名slot,this.$slots.default
包括了所有没有被包含在具名slot中的节点。 尝试编写代码,查看两个console打印的内容。
$slots
在业务中几乎用不到,在用render
函数(进阶篇中将介绍)创建组件时会比较有用,但主要还是用于独立组件开发中。
7.5 组件高级用法
本节会介绍组件的一些高级用法,这些用法在实际业务中不是很常用,但会独立组件开发时可能会用到。 如果你感觉以上内容已经足骨完成你的业务开发了,可以跳过本节;如果你想继续探索Vue组件的奥秘,读完本节会对你有很大的启发。
7.5.1 递归组件
组件在它的模板内可以递归地调用自己,只要给组件设置name
的选项就可以了。 示例代码如下:
1 < div id ="app" >
2 < child-component v-bind:count ="1" > child-component >
3 div >
4
5 < template id ="template" >
6 < div class ="child" >
7 < child-component v-bind:count ="count+1" v-if ="count<3" > child-component >
8 div >
9 template >
10
11 < script >
12 Vue.component( " child-component " , {
13 name: " child-component " ,
14 props: {
15 count: {
16 type: Number,
17 default : 1
18 }
19 },
20 template: " #template "
21 });
22
23 var app = new Vue({
24 el: " #app "
25 });
26 script >
设置name
后,在组件模板内就可以递归使用了。 不过需要注意的是,必须给一个条件来限制递归数量,否则会抛出错误:max stack size exceeded
。
组件递归使用可以用来开发一些具有未知层级关系的阻力组件,比如级联选择器和树形控件等。 如图7-4和图7-5所示: 

在实战篇里,我们会详细介绍级联选择器的实现。
7.5.2 内联模板
组件的模板一般都是在template
选项内自定义的,Vue提供了一个内联模板的功能,在使用组件时,给组件标签使用inline-template
特性,组件就会把它的内容当做模板,而不是把它当内容分发,这让模板更灵活。 示例代码如下:
1 < div id ="app" >
2 < child-component inline-template :data ="message" >
3 < div >
4 < h2 > 在父组件中定义了组件的模板h2 >
5 < p > {{data}}p >
6 < p > {{msg}}p >
7 div >
8 child-component >
9 div >
10
11 < script >
12 Vue.component( " child-component " , {
13 props: [ " data " ],
14 data: function () {
15 return {
16 msg: " 在子组件声明的数据 "
17 };
18 }
19 });
20
21 var app = new Vue({
22 el: " #app " ,
23 data: {
24 message: " 在父组件声明的数据 "
25 }
26 });
27 script >
渲染后的结果为:
1 < div id ="app" >
2 < div >
3 < h2 > 在父组件中定义了组件的模板h2 >
4 < p > 在父组件声明的数据p >
5 < p > 在子组件声明的数据p >
6 div >
7 div >
7.5.3 动态组件
Vue.js提供了一个特殊的元素
用来动态地挂载不同的组件,使用is
特性来选择要挂载的组件。 示例代码如下:
1 < div id ="app" >
2 < input type ="text" placeholder ="请输入组件名称: A/B/C" >
3 < button @click ="handleChangeView" > 确定切换button >
4 < component v-bind:is ="currentView" > component >
5 div >
6
7 < script >
8 var app = new Vue({
9 el: " #app " ,
10 components: {
11 comA: {
12 template: " 组件A
"
13 },
14 comB: {
15 template: " 组件B
"
16 },
17 comC: {
18 template: " 组件C
"
19 }
20 },
21 data: {
22 currentView: " comA "
23 },
24 methods: {
25 handleChangeView: function () {
26 var value = document.querySelector( " input " ).value;
27 this .currentView = " com " + value;
28 }
29 }
30 });
31 script >
动态地改变currentView
的值就可以动态挂载组件了。 也可以直接绑定在组件对象上:
1 < div id ="app" >
2 < component v-bind:is ="currentView" > component >
3 div >
4
5 < script >
6 var Home = {
7 template: " Welcome home!
"
8 };
9 var app = new Vue({
10 el: " #app " ,
11 data: {
12 currentView: Home
13 }
14 });
15 script >
7.5.4 异步组件
当你的工程足够大,使用的组件足够多时,是时候考虑下性能的问题了,因为一开始把所有的组件都加载是没必要的一笔开销。 好在Vue.js允许将组件定义为一个工厂函数,动态地解析组件。 Vue.js值在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。 例如下面的示例:
1 < div id ="app" >
2 < child-component > child-component >
3 div >
4
5 < script >
6 Vue.component( " child-component " , function (resolve, reject) {
7 window.setTimeout( function () {
8 resolve({
9 template: " 我是异步渲染的
"
10 });
11 }, 2000 );
12 });
13 var app = new Vue({
14 el: " #app "
15 });
16 script >
工厂函数接收一个resolve
回调,在收到从服务器下载的组件定义时调用。 也可以调用reject(reason)
指示加载失败。 这里setTimeout
只是为了演示异步,具体的下载逻辑可以自己决定,比如把组件配置写成一个对象配置,通过Ajax来请求,然后调用resolve
传入配置选项。
在进阶篇里,我们还会介绍主流的打包编译工具webpack和.vue单文件的用法,更优雅地实现异步组件(路由)。
7.6 其他
7.6.1 $nextTick
我们先来看这样一个场景: 有一个div,默认用v-if
将它隐藏,点击一个按钮后,改变v-if
的值,让它显示出来,同时拿到这个div的文本内容。 如果v-if
的值是false
,直接去获取div的内容是获取不到的,因为此时div还没有被创建出来。 那么应该在点击按钮后,改变v-if
的值为true
, div才会被创建,此时再去获取。 示例代码如下:
1 < div id ="app" >
2 < div id ="div" v-if ="showDiv" > 这是一段文本div >
3 < button v-on:click ="getText" > 获取div内容button >
4 div >
5
6 < script >
7 var app = new Vue({
8 el: " #app " ,
9 data: {
10 showDiv: false
11 },
12 methods: {
13 getText: function () {
14 this .showDiv = true ;
15 var text = document.getElementById( " div " ).innerHTML;
16 console.log(text);
17 }
18 }
19 });
20 script >
这段代码并不难理解,但是运行后在控制台会抛出一个错误:Cannot read property 'innerHTML' of null
。意思就是获取不到div元素。这里就涉及Vue一个重要的概念:异步更新队列。
Vue在观察到数据变化时并不是直接更新DOM,从而避免不必要的计算和DOM操作。 然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作。 所以如果你用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,这固然是一个很大的开销。
Vue会根据当前浏览器环境优先使用原生的Promise.then
和MutationObserver
,如果都不支持,就会采用setTimeout
代替。
知道了Vue异步更新DOM的原理,上面示例的报错也就不难理解了。 事实上,在执行this.showDiv=true
时,div仍然还是没有被创建出来,直到下一个Vue事件循环时,才开始创建。$nextTick
就是用来知道什么时候DOM更新完成的,所以上面的示例代码需要修改为:
1 < div id ="app" >
2 < div id ="div" v-if ="showDiv" > 这是一段文本div >
3 < button v-on:click ="getText" > 获取div内容button >
4 div >
5
6 < script >
7 var app = new Vue({
8 el: " #app " ,
9 data: {
10 showDiv: false
11 },
12 methods: {
13 getText: function () {
14 this .showDiv = true ;
15 this .$nextTick( function () {
16 var text = document.getElementById( " div " ).innerHTML;
17 console.log(text);
18 });
19 }
20 }
21 });
22 script >
这时再点击按钮,控制台就打印出div的内容“这时一段文本了”。
理论上,我们应该不用去主动操作DOM,因为Vue的核心思想就是数据驱动DOM,但在很多业务里,我们避免不了会使用一些第三方库,比如popper.js、swiper等,这些基于原生JavaScript的库都有创建和更新及销毁的完整生命周期,与Vue配合使用时,就要利用好$nextTick
。
7.6.2 X-Templates
如果你没有使用webpack、gulp等工具,试想一下你的组件template
的内容很冗长、复杂,如果都在JavaScript里拼接字符串,效率是很低的,因为不能像写HTML那样舒服。 Vue提供了另一种定义模板的方式,在
标签里使用text/x-template
类型,并且指定一个id
,将这个id
赋给template
。示例代码如下:
1 < div id ="app" >
2 < my-component > my-component >
3 < script type ="text/x-template" id ="my-component" >
4 < div > 这是组件的内容 < / div>
5 script >
6 div >
7
8 < script >
9 Vue.component( " my-component " , {
10 template: " #my-component "
11 });
12 var app = new Vue({
13 el: " #app "
14 });
15 script >
在
标签里,你可以愉快地编写HTML代码,不用考虑执行等问题。
很多刚接触Vue开发的新手会非常喜欢这个功能,因为用它,再加上组件知识,就可以很轻松地完成交互相对复杂的页面和应用了。如果再配合一些构建工具(gulp)组织好代码结构,开发一些中小型产品是没有问题的。 不过,Vue的初衷并不是滥用它,因为它将模板和组件的其他定义隔离了。 在进阶篇里,我们会介绍如何使用webpack来编译.vue的单文件,从而优雅地解决HTML书写的问题。
7.6.3 手动挂载实例
我们现在所创建的实例都是通过new Vue()
的形式创建出来的。 在一些非常特殊的情况下,我们需要动态地去创建Vue实例,Vue提供了Vue.extend
和$mount
两个方法来手动挂载一个实例。
Vue.extend
是基础Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象。
如果Vue实例在实例化时没有收到el
选项,它就处于“未挂载”状态,没有关联的DOM元素。 可以使用$mount()
手动地挂载一个未挂载的实例。 这个方法返回实例自身,因而可以链式调用其他实例方法。 示例代码如下:
1 < div id ="mount-div" > div >
2
3 < script >
4 var MyComponent = Vue.extend({
5 template: " Hello: {{name}}
" ,
6 data: function () {
7 return {
8 name: " Jack "
9 };
10 }
11 });
12
13 new MyComponent().$mount( " #mount-div " );
14 script >
运行后,id
为mount-div
的div元素会被替换为组件MyComponent
的template
的内容:
除了这种写法外,以下两种写法也是可以的:
1 new MyComponent().$mount("#mount-div");
2 // 同上
3 new MyComponent({
4 el: "#mount-div"
5 });
6 // 或者,在文档之外渲染并且随后挂载
7 var component = new MyComponent().$mount();
8 document.getElementById("mount-div").appendChild(component.$el);
手动挂载实例(组件)是一种比较极端的高级用法,在业务中几乎用不到,只是开发一些复杂的独立组件时可能会使用,所以只做了解就好。
7.7 实战:两个常用组件的开发
本节以组件知识为基础,整合指令、事件等前面两章的内容,开发两个业务中常用的组件,即数字输入框和标签页。
7.7.1 开发一个数字输入框组件
数字输入框时对普通输入框的扩展,用来快捷输入一个标准的数字。 如图7-6所示: 
数字输入框只能输入数字,而且有两个快捷按钮,可以直接减1或加1. 除此之外,还可以设置初始值、最大值、最小值,在数值改变时,触发一个自定义事件来通知父组件。
了解了基本需求后,我们先定义目录文件:
index.html 入口页
input-number.js 数字输入框组件
index.js 根实例
因为该示例是以交互功能为主,所以就不写CSS美化样式了。
首先写入基本的结构代码,初始化项目。
index.html:
1 DOCTYPE html >
2 < html lang ="zh" >
3 < head >
4 < meta charset ="UTF-8" >
5 < meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
6 < meta http-equiv ="X-UA-Compatible" content ="ie=edge" >
7 < title > 数字输入框组件title >
8 head >
9 < body >
10 < div id ="app" > div >
11
12 < script src ="vue.js" > script >
13 < script src ="input-number.js" > script >
14 < script src ="index.js" > script >
15 body >
16 html >
index.js:
1 var app = new Vue({
2 el: "#app"
3 });
input-number.js:
1 Vue.component("input-number", {
2 template: "\
3 \
4 \
5
",
6 props: {
7 max: {
8 type: Number,
9 default : Infinity
10 },
11 min: {
12 type: Number,
13 default : -Infinity
14 },
15 value: {
16 type: Number,
17 default : 0
18 }
19 }
20 });
该示例的主角是input-number.js
,所有的组件配置都在这里面定义。 现在template
里面定义了组件的根节点,因为是独立组件,所以应该对每个prop进行校验。 这里面根据需求有最大值、最小值、默认值(也就是绑定值)3个prop,max
和min
都是数字类型,默认值是正无限大和负无限大;value
也是数字类型,默认值是0。
接下来,我们先在父组件引入input-number
组件,并给它一个默认值5,最大值10,最小值0。
index.js:
1 var app = new Vue({
2 el: "#app",
3 data: {
4 value: 5
5 }
6 });
index.html:
1 < div id ="app" >
2 < input-number v-model ="value" :max ="10" :min ="0" > input-number >
3 div >
value
是一个关键的绑定至,所以用了v-model
,这样既优雅地实现了双向绑定,也让API看起来很合理。 大多数的表单类组件都应该有一个v-model
,比如输入框、单选框、多选框、下拉选择器等。
剩余的代码量就都聚焦到了input-number.js
上。
我们之前介绍过,Vue组件是单向数据流,所以无法从组件内部直接修改prop:value
的值。 解决办法也介绍过,就是给组件声明一个data
,默认引用value
的值,然后在组件内部维护这个data
:
1 Vue.component("input-number", {
2 // ...
3 data: function () {
4 return {
5 currentValue: this .value
6 };
7 }
8 });
这样只解决了初始化时引用父组件value
的问题。 但是如果从父组件修改了value
,input-number组件的currentValue
也要一起更新。 为了实现这个功能,我们需要用到一个新的概念,监听(watch)。
watch
选项用来监听某个prop或data的改变,当他们发生变化时,就会触发watch
配置的函数,从而完成我们的业务逻辑。 在本例中,我们要监听两个量:value
和currentValue
。 监听value
是要知晓从父组件修改了value
,监听currentValue
是为了当currentValue
改变时,更新value
。 相关代码如下:
1 Vue.component("input-number", {
2 // ...
3 data: function () {
4 return {
5 currentValue: this .value
6 };
7 },
8 watch: {
9 currentValue: function (val) {
10 this .$emit("input", val);
11 this .$emit("on-change", val);
12 },
13 value: function (val) {
14 this .updateValue(val);
15 }
16 },
17 methods: {
18 updateValue: function (val) {
19 if (val > this .max) {
20 val = this .max;
21 }
22
23 if (val < this .min) {
24 val = this .min;
25 }
26
27 this .currentValue = val;
28 }
29 },
30 mounted: function () {
31 this .updateValue(this .value);
32 }
33 });
从父组件传递过来的value
有可能是不符合当前条件的(大于max或小于min),所以在选项methods
里写了一个方法updateValue
,用来过滤出一个正确的currentValue
。
watch
监听的数据的回调函数有2个参数可用,第一个是新的智,第二个是旧的值,这里没有太复杂的逻辑,就只用了第一个参数。 再回调函数里,this
是指向当前组件实例的,所以可以直接调用this.updateValue()
,因为Vue代理了props
、data
、computed
及methods
。
监听currentValue
的回调里:
this.$emit("input", val)
是在使用v-model
时改变value
的;
this.$emit("on-change", val)
是触发自定义事件on-change
,用于告知父组件数字输入框的值有所改变(示例中没有使用该事件〉。
在生命周期mounted
钩子里也调用了updateValue()
方法,是因为第一次初始化时,也对value
进行了过滤。 这里也有另一种写法,在data
选项返回对象前进行过滤:
1 Vue.component("input-number", {
2 // ...
3 data: function () {
4 var val = this .value;
5
6 if (val > this .max) {
7 val = this .max;
8 }
9
10 if (val < this .min) {
11 val = this .min;
12 }
13
14 return {
15 currentValue: val
16 };
17 }
18 });
实现的效果是一样的。
最后剩余的就是补全模板template
,内容是一个输入框和两个按钮。 相关代码如下:
input绑定了数据currentValue
和原生的change事件,在句柄handleChange
函数中,判断了当前输入的是否是数字。 注意,这里绑定的currentValue
也是单向数据流,并没有用v-model
,所以在输入时,currentValue
的值并没有实时改变。 如果输入的不是数字(比如英文和汉字等〉,就将输入的内容重置为之前的currentValue。 如果输入的是符合要求的数字,就把输入的值赋给
currentValue`。
数字输入框组件的核心逻辑就是这些。 回顾一下我们设计一个通用组件的思路,首先,在写代码前一定要明确需求,然后规划好API。 一个Vue组件的API只来自props
、events
和slots
,确定好这3部分的命名、规则,剩下的逻辑即使第一版没有做好,后续也可以迭代完善。但是API如果没有设计好,后续再改对使用者成本就很大了。
完整的示例代码如下:
index.html:
1 DOCTYPE html >
2 < html lang ="zh" >
3 < head >
4 < meta charset ="UTF-8" >
5 < meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
6 < meta http-equiv ="X-UA-Compatible" content ="ie=edge" >
7 < title > 数字输入框组件title >
8 head >
9 < body >
10 < div id ="app" >
11 < input-number v-model ="value" :max ="10" :min ="0" > input-number >
12 div >
13
14 < script src ="vue.js" > script >
15 < script src ="input-number.js" > script >
16 < script src ="index.js" > script >
17 body >
18 html >
index.js:
1 var app = new Vue({
2 el: "#app",
3 data: {
4 value: 5
5 }
6 });
input-number.js:
1 function isValueNumber(value) {
2 return (/(^-?[0-9]+\.(1)\d+$)|(^-?[1-9][0-9]*$)|(^-?0{1})/.test(value + ""));
3 }
4
5 Vue.component("input-number", {
6 template: "\
7 \
8 \
9 - \
10 + \
11
",
12 props: {
13 max: {
14 type: Number,
15 default : Infinity
16 },
17 min: {
18 type: Number,
19 default : -Infinity
20 },
21 value: {
22 type: Number,
23 default : 0
24 }
25 },
26 data: function () {
27 return {
28 currentValue: this .value
29 };
30 },
31 watch: {
32 currentValue: function (val) {
33 this .$emit("input", val);
34 this .$emit("on-change", val);
35 },
36 value: function (val) {
37 this .updateValue(val);
38 }
39 },
40 methods: {
41 handleDown: function () {
42 if (this .currentValue <= this .min) {
43 return ;
44 }
45 this .currentValue -= 1;
46 },
47 handleUp: function () {
48 if (this .currentValue >= this .max) {
49 return ;
50 }
51 this .currentValue += 1;
52 },
53 handleChange: function (event) {
54 var val = event.target.value.trim();
55 var max = this .max;
56 var min = this .min;
57
58 if (isValueNumber(val)) {
59 val = Number(val);
60 this .currentValue = val;
61 if (val > max) {
62 this .currentValue = max;
63 } else if (val < min) {
64 this .currentValue = min;
65 }
66 } else {
67 event.target.value = this .currentValue;
68 }
69 },
70 updateValue: function (val) {
71 if (val > this .max) {
72 val = this .max;
73 }
74
75 if (val < this .min) {
76 val = this .min;
77 }
78
79 this .currentValue = val;
80 }
81 },
82 mounted: function () {
83 this .updateValue(this .value);
84 }
85 });
练习1: 在输入框聚焦时,增加对键盘上下键的支持,相当于加1和减1;
练习2: 增加一个控制步伐的prop:step,比如设置为10,点击加号按钮,一次增加10。
7.7.2 开发一个标签组件
本小节将开发一个比较有挑战的组件:标签页组件。 标签页(即选项卡切换组件)是网页和布局中经常用到的元素,常用于平级区域大块内容的收纳和展现。 如图7-7所示: 
根据上个示例的经验,我们先分析业务需求,制定出API,这样不至于一上来就无从下手。
每个标签页的主体内容肯定是由使用组件的父级控制的,所以这部分是一个slot,而且slot的数量决定了标签切换按钮的数量。 假设我们有3个标签页,点击每个标签按钮时,另外两个标签对应的slot应该被隐藏。 一般这个时候,比较容易想到的解决办法是,在slot里写3个div,在接收到切换通知时,显示和隐藏相关div。 这样设计没有问题,只不过提现不出组件的价值来,因为我们还是一些了一些与业务无关的业务逻辑,而这部分逻辑最好组件本身帮忙处理了,我们只用聚焦在slot内容本身,这才是我们业务最相关的。 这种情况下,我们在定义一个子组件panel,嵌套在标签页组件tabs里,我们的业务代码都放在panel的slot内,而3个panel组件作为整体成为tabs的slot。
由于tabs和panel两个组件是分离的,但是tabs组件上的标题应该由panel组件来定义,因为slot是卸载panel里,因此在组件初始化(及标签标题动态改变)时,tabs要从panel里获取标题,并保存起来,自己使用。
确定好了结构,我们先创建所需的文件:
index.html 入口页
style.css 样式表
tabs.js 标签页外层的组件 tabs
panel.js 标签页嵌套的组件 panel
先初始化各个文件:
index.html:
1 DOCTYPE html >
2 < html lang ="zh" >
3 < head >
4 < meta charset ="UTF-8" >
5 < meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
6 < meta http-equiv ="X-UA-Compatible" content ="ie=edge" >
7 < title > 标签页组件title >
8 < link rel ="stylesheet" type ="text/css" href ="style.css" >
9 head >
10 < body >
11 < div id ="app" > div >
12
13 < script src ="vue.js" > script >
14 < script src ="panel.js" > script >
15 < script src ="tabs.js" > script >
16 < script >
17 var app = new Vue({
18 el: " #app "
19 });
20 script >
21 body >
22 html >
tabs.js:
1 Vue.component("tabs", {
2 template: "\
3 \
4 \
5 \
6
\
7 \
8 \
9 \
10
\
11 "
12 });
panel.js
1 Vue.component("panel", {
2 name: "panel",
3 template: "\
4 \
5 \
6
"
7 });
panel需要控制标签页内容的显示与隐藏。 设置一个data:show
,并且用v-show
指令来控制元素:
1 Vue.component("panel", {
2 name: "panel",
3 template: "\
4 \
5 \
6
",
7 data: function () {
8 return {
9 show: true
10 };
11 }
12 });
当点击到这个panel对应的标签页标题按钮时,此panel的show
值设置为true
,否则应该是false
。 这步操作是在tabs组件完成的,我们稍后再介绍。
既然要单击对应的标签页标题按钮,那应该有一个唯一的值来标识这个panel,我们可以设置一个prop:name
让用户来设置,但它不是必须的,如果使用者不设置,可以默认从0开始自动设置,这不操作仍然是tabs执行的,因为panel本身并不知道自己是第几个。 除了name
,还需要标签页标题的prop:label
,tabs组件需要将它显示在标签页标题里。 这部分代码如下:
1 props: {
2 name: {
3 type: String
4 },
5 label: {
6 type: String,
7 default : ""
8 }
9 }
上面的prop:label
用户是可以动态调整的,所以在panel初始化以及label
更新时,都要通知父组件也更新,因为是独立组件,所以不能依赖像bus.js或vuex这样的状态管理办法,我们可以直接通过this.$parent
访问tabs组件的实例来调用它的方法更新标题,该方法暂定为updateNav
。 注意,在业务中尽可能不要使用$parent
来操作父链,这种方法适合于标签页这样的独立组件。 这部分代码如下:
1 methods: {
2 updateNav() {
3 this .$parent.updateNav();
4 }
5 },
6 watch: {
7 label() {
8 this .updateNav();
9 }
10 },
11 mounted() {
12 this .updateNav();
13 }
在生命周期mounted
,也就是panel初始化时,调用一遍tabs的updateNav
方法。 同时监听了prop:label
,在label
更新时,同样调用。
剩余任务就是完成tabs.js组件。
首先需要把panel组件设置的标题动态渲染出来,也就是当panel触发tabs的updateNav
方法时,更新标题内容。 我们先看一下这部分的代码:
1 Vue.component("tabs", {
2 // ...
3 data: function () {
4 return {
5 // 用于渲染tabs的标题
6 navList: []
7 };
8 },
9 methods: {
10 getTabs() {
11 // 通过遍历子组件,得到所有的panel组件
12 return this .$children.filter(function (item) {
13 return item.$options.name === "panel";
14 });
15 },
16 updateNav() {
17 this .navList = [];
18 // 设置对this的引用,在function回调里,this指向的并不是Vue实例
19 var _this = this ;
20 this .getTabs().forEach(function (panel, index) {
21 _this.navList.push({
22 label: panel.label,
23 name: panel.name || index
24 });
25 // 如果没有给panel设置name,默认设置它的索引
26 if (!panel.name) {
27 panel.name = index;
28 }
29 // 设置当前选中的tab的索引,在后面介绍
30 if (index === 0) {
31 if (!_this.currentValue) {
32 _this.currentValue = panel.name || index;
33 }
34 }
35 });
36 },
37 updateStatus() {
38 var tabs = this .getTabs();
39 var _this = this ;
40 // 显示当前选中的tab对应的panel组件,隐藏没有选中的
41 tabs.forEach(function (tab) {
42 return tab.show = tab.name === _this.currentValue;
43 });
44 }
45 }
46 });
getTabs
是一个公用的方法,使用this.$children
来拿到所有的panel组件实例。
需要注意的是,在methods
里使用了有function回调的方法时(例如遍历数组的方法forEach
),在回调内的this
不再执行当前的Vue实例,也就是tabs组件本身,所以要在外层设置一个_this=this
的局部变量来间接使用this
。 如果你熟悉ES2015,也可以直接使用箭头函数=>
,我们会在实战篇里介绍相关的用法。
遍历了每一个panel组件后,把它的label
和name
提取出来,构成一个Object并添加到数据navList
数组里,后面我们会在template
里用到它。
设置完navList
数组后,我们调用了updateStatus
方法,又将panel组件遍历了以便,不过这时是为了将当前选中的tab对应的panel组件内容显示出来,把没有选中的隐藏掉。 因为在上一步操作里,我们有可能需要设置currentValue
来标识当前选中项的name
(在用户没有设置value
时,才会自动设置),所以必须要遍历2次才可以。
拿到navList
后,就需要对它用v-for
指令把tab的标题渲染出来,并且判断每个tab当前的状态。 这部分代码如下:
1 Vue.component("tabs", {
2 template: "\
3 ",
11 props: {
12 // 这里的value是为了可以使用v-model
13 value: {
14 type: [String, Number]
15 }
16 },
17 data: function () {
18 return {
19 // 因为不能修改value,所以复制一份自己维护
20 currentValue: this .value,
21 navList: []
22 };
23 },
24 methods: {
25 tabCls: function (item) {
26 return [
27 "tabs-tab",
28 {
29 // 给当前选中的tab加一个class
30 "tabs-tab-active": item.name === this .currentValue
31 }
32 ];
33 },
34 // 点击tab标题时触发
35 handleChange: function (index) {
36 var nav = this .navList[index];
37 var name = nav.name;
38 // 改变当前选中的tab,并触发下面的watch
39 this .currentValue = name;
40 // 更新value
41 this .$emit("input", name);
42 // 触发一个自定义事件,供父级使用
43 this .$emit("on-click", name);
44 }
45 },
46 watch: {
47 value: function (val) {
48 this .currentValue = val;
49 },
50 currentValue: function () {
51 // 在当前选中的tab发生变化时,更新panel的显示状态
52 this .updateStatus();
53 }
54 }
55 });
在使用v-for
指令循环显示tab标题时,使用v-bind:class
指向了一个名为tabCls
的methods
来动态设置class
名称。 因为计算属性不能接收参数,无法知道当前tab是否是选中的,所以这里我们才用到methods。 不过要知道,methods是不缓存的,可以回顾关于计算属性的章节。
点击每个tab标题时,会触发handleChange
方法来改变当前选中tab的索引,也就是panel组件的name。 在watch选项里,我们监听了currentValue
,当其发生变化时,触发updateStatus
方法来更新panel组件的显示状态。
以上就是标签页组件的核心代码分解。 总结一下该示例的技术难点:
使用了组件嵌套的方式,将一系列panel组件作为tabs组件的slot;
tabs组件和panel组件通信上,使用了$parent
和$children
的方法访问父链和子链;
定义了prop:value
和data:currentValue
,使用$emit("input")
来实现v-model
的用法。
以下是标签页组件的完整代码:
index.html:
1 DOCTYPE html >
2 < html lang ="zh" >
3 < head >
4 < meta charset ="UTF-8" >
5 < meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
6 < meta http-equiv ="X-UA-Compatible" content ="ie=edge" >
7 < title > 标签页组件title >
8 < link rel ="stylesheet" type ="text/css" href ="style.css" >
9 head >
10 < body >
11 < div id ="app" >
12 < tabs v-model ="activeKey" >
13 < panel label ="标签一" name ="1" > 标签一的内容panel >
14 < panel label ="标签二" name ="2" > 标签二的内容panel >
15 < panel label ="标签三" name ="3" > 标签三的内容panel >
16 tabs >
17 div >
18
19 < script src ="vue.js" > script >
20 < script src ="panel.js" > script >
21 < script src ="tabs.js" > script >
22 < script >
23 var app = new Vue({
24 el: " #app " ,
25 data: {
26 activeKey: " 1 "
27 }
28 });
29 script >
30 body >
31 html >
panel.js:
1 Vue.component("panel", {
2 name: "panel",
3 template: "\
4 \
5 \
6
",
7 props: {
8 name: {
9 type: String
10 },
11 label: {
12 type: String,
13 default : ""
14 }
15 },
16 data: function () {
17 return {
18 show: true
19 };
20 },
21 methods: {
22 updateNav() {
23 this .$parent.updateNav();
24 }
25 },
26 watch: {
27 label() {
28 this .updateNav();
29 }
30 },
31 mounted() {
32 this .updateNav();
33 }
34 });
tabs.js:
1 Vue.component("tabs", {
2 template: "\
3 ",
11 props: {
12 value: {
13 type: [String, Number]
14 }
15 },
16 data: function () {
17 return {
18 currentValue: this .value,
19 navList: []
20 };
21 },
22 methods: {
23 tabCls: function (item) {
24 return [
25 "tabs-tab",
26 {
27 "tabs-tab-active": item.name === this .currentValue
28 }
29 ];
30 },
31 // 点击tab标题时触发
32 handleChange: function (index) {
33 var nav = this .navList[index];
34 var name = nav.name;
35 // 改变当前选中的tab,并触发下面的watch
36 this .currentValue = name;
37 // 更新value
38 this .$emit("input", name);
39 // 触发一个自定义事件,供父级使用
40 this .$emit("on-click", name);
41 },
42 getTabs() {
43 // 通过遍历子组件,得到所有的panel组件
44 return this .$children.filter(function (item) {
45 return item.$options.name === "panel";
46 });
47 },
48 updateNav() {
49 this .navList = [];
50 // 设置对this的引用,在function回调里,this指向的并不是Vue实例
51 var _this = this ;
52 this .getTabs().forEach(function (panel, index) {
53 _this.navList.push({
54 label: panel.label,
55 name: panel.name || index
56 });
57 // 如果没有给panel设置name,默认设置它的索引
58 if (!panel.name) {
59 panel.name = index;
60 }
61 // 设置当前选中的tab的索引,在后面介绍
62 if (index === 0) {
63 if (!_this.currentValue) {
64 _this.currentValue = panel.name || index;
65 }
66 }
67 });
68 },
69 updateStatus() {
70 var tabs = this .getTabs();
71 var _this = this ;
72 // 显示当前选中的tab对应的panel组件,隐藏没有选中的
73 tabs.forEach(function (tab) {
74 return tab.show = tab.name === _this.currentValue;
75 });
76 }
77 },
78 watch: {
79 value: function (val) {
80 this .currentValue = val;
81 },
82 currentValue: function () {
83 // 在当前选中的tab发生变化时,更新panel的显示状态
84 this .updateStatus();
85 }
86 }
87 });
style.css:
1 [v-cloak] {display :none ;}
2
3 .tabs {font-size :14px ; color :#657180 ;}
4 .tabs-bar:after {
5 content :"" ; display :block ;
6 width :100% ; height :1px ;
7 background :#D7DDE4 ; margin-top :-1px ;
8 }
9 .tabs-tab {
10 display :inline-block ; cursor :pointer ; position :relative ;
11 padding :4px 16px ; margin-right :6px ;
12 background :#FFF ; border :1px solid #D7DDE4 ;
13 }
14 .tabs-tab-active {
15 cursor :#3399FF ;
16 border-top :1px solid #3399FF ; border-bottom :1px solid #FFF ;
17 }
18 .tabs-tab-active:before {
19 content :"" ; display :block ; height :1px ; background :#3399FF ;
20 position :absolute ; top :0 ; left :0 ; right :0 ;
21 }
22 .tabs-content {padding :8px 0 ;}
练习1: 给panel组件新增一个prop:closable
的布尔值,来支持是否可以关闭这个panel,如果开启,在tabs的标签标题上会有一个关闭的按钮;
提示: 在初始化panel时,我们是在mounted里通知的。 关闭时,你会用到beforeDestroy。
练习2: 尝试在切换panel的显示与隐藏时,使用滑动的动画。提示:可以使用CSS3的transform:translateX
。
转载于:https://www.cnblogs.com/geeksss/p/10810698.html
你可能感兴趣的:(javascript,webpack,设计模式,ViewUI)
webpack性能优化策略
雅望天堂i
webpack 前端 node.js
1.代码分割(CodeSplitting)通过代码分割,可以将代码拆分成多个较小的文件,实现按需加载,减少首屏加载时间。使用SplitChunksPlugin将公共代码提取到单独的chunk中,避免重复打包。config.optimization.splitChunks({chunks:'all',cacheGroups:{//第三方组件libs:{name:'chunk-libs',test:/
JavaScript网页设计案例:打造交互式个人简历网站
程序媛小果
前端 javascript 开发语言 ecmascript
在当今数字化时代,个人简历不再局限于纸质文档,而是越来越多地以网页形式呈现。JavaScript作为一种强大的客户端脚本语言,为网页设计提供了无限可能,使得网页不仅仅是静态的信息展示,而是具有丰富交互性的平台。本文将通过一个案例,展示如何使用HTML、CSS和JavaScript来设计一个交互式的个人简历网站。1.项目概述本案例的目标是创建一个个人简历网站,它不仅展示个人信息、工作经历、教育背景和
ECMAScript与JavaScript:探索两者之间的联系与区别
程序媛小果
前端 ecmascript javascript 前端
在Web开发的早期,JavaScript成为了客户端脚本语言的代名词,而随着时间的推移,JavaScript已经发展成为一个功能强大的语言,它的影响力远远超出了浏览器的范畴。在这场语言演进的过程中,ECMAScript扮演了一个关键角色。本文将深入探讨ECMAScript与JavaScript之间的关系,以及它们之间的主要区别。1.什么是ECMAScript?ECMAScript是由欧洲计算机制造
23种设计模式-享元(Flyweight)设计模式
萨达大
软考中级-软件设计师 设计模式 享元模式 软考 软件设计师 C++ 行为型设计模式 JAVA
文章目录一.什么是享元设计模式?二.享元模式的特点三.享元模式的结构四.享元模式的优缺点五.享元模式的C++实现六.享元模式的JAVA实现七.代码解析八.总结类图:享元设计模式类图一.什么是享元设计模式? 享元(Flyweight)设计模式是一种结构型设计模式,通过共享对象来减少内存占用和对象创建开销。它通过将对象的可共享部分与不可共享部分分离,减少重复对象的数量,从而节省内存。 享元模式的核心思
设计模式 - 单例模式
one客
设计模式 设计模式 单例模式 c++
设计模式-单列模式单例模式(SingletonPattern)定义:单例模式(SingletonPattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。该模式通过控制实例的创建过程来避免多次创建同一个对象。单例模式的关键点:只有一个实例:确保某个类在整个系统中只有一个实例。全局访问点:提供一个静态方法来访问该实例,确保全局可以访问到这个唯一实例。单例模式的
null和undefined的区别
编程星空
JavaScript 前端 javascript 开发语言
null和undefined是JavaScript中两个特殊的值,它们都表示“无”或“空”,但在语义和使用场景上有明显区别。以下是它们的详细对比:1.定义undefined表示变量已声明但未赋值,或函数没有返回值时的默认返回值。是JavaScript引擎默认赋予的初始值。类型为undefined。null表示一个空对象指针,通常用于显式表示“无”或“空”。是开发者主动赋值的值。类型为object(
dreamweaver html语言,Dreamweaver网页设计与制作(HTML+CSS+JavaScript)
weixin_39979245
dreamweaver html语言
Dreamweaver网页设计与制作(HTML+CSS+JavaScript)编辑锁定讨论上传视频本词条缺少信息栏,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧!《Dreamweaver网页设计与制作(HTML+CSS+JavaScript)》是2014年清华大学出版社出版的图书。Dreamweaver网页设计与制作(HTML+CSS+JavaScript)图书详细信息编辑ISBN:978
html 5中css的含义,HTML 5+CSS+JavaScript网页设计与制作
律保阁-Michael
html 5中css的含义
HTML5+CSS+JavaScript网页设计与制作编辑锁定讨论上传视频《HTML5+CSS+JavaScript网页设计与制作》是2019年4月清华大学出版社出版的图书,作者是彭进香、张茂红、王玉娟、叶娟、孙秀娟、万幸、刘英。书名HTML5+CSS+JavaScript网页设计与制作作者彭进香张茂红王玉娟叶娟作者孙秀娟展开作者孙秀娟万幸刘英收起出版社清华大学出版社出版时间2019年4月定价48
html+css+javascript实用详解,HTML+CSS+JavaScript 课程标准
vvv666s
②学会运用HTML语言中的标记设置颜色、文本格式和列表;熟练掌握颜色值的配置和背景图案的设置方法,熟练掌握字符、链接颜色的设置方法;③掌握在网页中添加CSS、嵌入图像、声音、多媒体信息的方法;④熟练掌握表格的使用方法,学会利用表格设布局网页;掌握框架制作网页的方法,会使用框架设计网页;掌握制作表单的方法,会利用表单建立交互式页面;⑤掌握JavaScript语言的语法;⑥掌握在HTML语言代码中嵌入
JavaScript的魔法世界:巧妙之处与实战技巧
skyksksksksks
综合个人杂记 javascript 开发语言 html5 css 前端
一、从浏览器玩具到全栈利器的蜕变之路JavaScript诞生于1995年,原本只是网景公司为浏览器设计的"小脚本"。谁能想到这个曾被戏称为"玩具语言"的家伙,如今已蜕变成支撑现代Web开发的擎天柱?就像一只破茧成蝶的幼虫,JavaScript经历了ECMAScript标准的持续进化,在Node.js的加持下突破了浏览器的桎梏,实现了从客户端到服务端的华丽转身。V8引擎的涡轮增压让它跑得比猎豹还快,
前端开发入门指南:HTML、CSS和JavaScript基础知识
方向感超强的
javascript css html 前端
引言:大家好,我是一名简单的前端开发爱好者,对于网页设计和用户体验的追求让我深深着迷。在本篇文章中,我将带领大家探索前端开发的基础知识,涵盖HTML、CSS和JavaScript。如果你对这个领域感兴趣,或者想要了解如何开始学习前端开发,那么这篇文章将为你提供一个良好的起点。1.前端开发概述在我们深入了解前端开发的细节之前,让我们先了解一下前端开发的定义和作用。简而言之,前端开发涉及构建用户直接与
js如何直接下载文件流
涔溪
js javascript 前端 开发语言
在JavaScript中直接处理文件下载,尤其是在处理文件流的情况下,通常涉及到使用fetchAPI或者XMLHttpRequest来获取文件流,并通过创建一个临时的标签(锚点元素)触发下载。以下是使用fetchAPI的一个示例:fetch('你的文件URL',{method:'GET',headers:{//如果需要的话,可以在这里添加请求头}}).then(response=>response
部署前端项目2
augenstern416
前端
前端项目的部署是将开发完成的前端代码发布到服务器或云平台,使其能够通过互联网访问。以下是前端项目部署的常见步骤和工具:1.准备工作在部署之前,确保项目已经完成以下步骤:代码优化:压缩JavaScript、CSS和图片文件,减少文件体积。环境配置:区分开发环境和生产环境(如API地址、环境变量等)。测试:确保项目在本地测试通过,没有明显Bug。2.部署流程1.构建项目大多数前端项目(如React、V
对象的操作
augenstern416
javascript 开发语言 ecmascript
在前端开发中,JavaScript提供了许多内置对象和方法,用于处理数据、操作DOM、处理事件等。以下是一些常用对象及其方法和扩展技巧:1.Object对象Object是JavaScript中最基础的对象,几乎所有对象都继承自Object。常用方法Object.keys(obj):返回对象的所有可枚举属性的键名数组。constobj={a:1,b:2};console.log(Object.key
前端基础入门:HTML、CSS 和 JavaScript
阿绵
前端 前端 html css js
在现代网页开发中,前端技术扮演着至关重要的角色。无论是个人网站、企业官网,还是复杂的Web应用程序,前端开发的基础技术HTML、CSS和JavaScript都是每个开发者必须掌握的核心技能。本文将详细介绍这三者的基本概念及其应用一、HTML——网页的骨架HTML(HyperTextMarkupLanguage)是构建网页的基础语言。它是网页的结构和内容的标记语言,决定了网页上的文本、图像、表单等元
C++ 设计模式-外观模式
ox0080
# 北漂+滴滴出行 C++设计模式 VIP 激励 c++ 外观模式 开发语言
外观模式的定义外观模式是一种结构型设计模式,它通过提供一个简化的接口来隐藏系统的复杂性。外观模式的核心思想是:封装复杂子系统:将多个复杂的子系统或组件封装在一个统一的接口后面。提供简单接口:为客户端提供一个更简单、更易用的接口,而不需要客户端直接与复杂的子系统交互。外观模式就像一个“前台接待员”,客户端只需要与这个接待员打交道,而不需要了解后台复杂的运作机制。外观模式的核心思想简化接口外观模式通过
网页制作03-html,css,javascript初认识のhtml的图像设置
Ama_tor
网页制作专栏 html css 前端
一、图像格式网页中图像的格式有三种,Gif,Jpeg,PngGif:Graphicinterchangeformat图像交换格式,文件最多可使用256种颜色,最适合显示色调不连续或具有大面积单一颜色的图像,例如导航条、按钮、图标、徽标或其他具有统一色彩和色调的图像;还可以制作动态图像Jpeg:Giantphotographicexpectgroup,它是一种图像压缩格式,可包含数百万种颜色,不支持
JavaScript——操作浏览器窗口
yiqi_perss
JavaScript
学习内容:今天学习了alert提示框,提示框中的内容,就是alert后边小括号中的内容例如:alert('我要学JavaScript!');alert('我要学习!');学习总结:日常小总结例如:后面的分号;可以随便去掉,不影响运行效果。不能去掉小括号,否则会报错,不信你可以试试。必须是英文引号,否则会报错。课外扩展:历史渊源例如:ECMAScript是一种语言标准,而JavaScript是网景公
【设计模式精讲】结构型模式之装饰器模式
道友老李
设计模式精讲 设计模式 装饰器模式
文章目录第五章结构型模式5.3装饰器模式5.3.1装饰器模式介绍5.3.2装饰器模式原理5.3.3装饰器模式应用实例5.3.4装饰器模式总结个人主页:道友老李欢迎加入社区:道友老李的学习社区第五章结构型模式5.3装饰器模式5.3.1装饰器模式介绍装饰模式(decoratorpattern)的原始定义是:动态的给一个对象添加一些额外的职责.就扩展功能而言,装饰器模式提供了一种比使用子类更加灵活的替代
设计模式-模板方法实现
阿绵
设计模式 java 开发语言
文章目录模式结构模式特点示例代码输出结果关键点解析模式的优缺点使用场景总结模板方法模式(TemplateMethodPattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,而将某些步骤的实现延迟到子类中。通过这种方式,模板方法模式可以让子类在不改变算法结构的情况下,重新定义算法中的某些步骤模式结构模板方法模式的结构包括以下几个关键部分:抽象类(AbstractClass):定义算法的骨
Python性能优化:懒加载与其他高级技巧
车载testing
pytest数据驱动框架开发 python python 数据库 开发语言
Python性能优化:懒加载与其他高级技巧在软件开发中,我们经常会遇到一些需要大量资源或时间来初始化的对象。如果这些对象在程序的整个生命周期中只被使用一次或很少使用,那么在程序启动时就立即初始化它们将是一种资源浪费。什么是懒加载?懒加载是一种设计模式,它推迟了对象的初始化直到其被实际需要的时候。这种方式可以提高程序的启动速度,减少内存消耗,并在某些情况下提高性能。实现懒加载的步骤定义类和属性:首先
百度极速版APP 自动脚本 javascript代码
zaxjb123
dubbo
使用JavaScript编写针对百度极速版APP的自动化脚本通常涉及到使用WebView测试框架,比如Puppeteer或Selenium,这些工具允许你控制一个浏览器或WebView环境,从而与网页或APP中的Web内容进行交互。然而,对于原生APP(如百度极速版)的自动化测试,通常需要使用专门的移动应用自动化框架,如Appium。Appium支持多种编程语言,包括JavaScript。要使用J
JavaScript案例(简易ATM机)
fusheng_cn
前端 JavaScript javascript 前端
Documentvarnum=100;do{varoperate=prompt("请输入您需要的操作:\n1.存钱\n2.取钱\n3.显示余额\n4.退出");switch(parseInt(operate)){case1://存钱varmoney1=prompt("请输入您需要存入的钱数:");varsum1=num+parseInt(money1);alert("您的余额为:"+sum1);n
Day48(补)【AI思考】-设计模式三大类型统一区分与记忆指南
一个一定要撑住的学习者
# AI深度思考学习方法 设计模式
文章目录设计模式三大类型统一区分与记忆指南**一、创建型模式(对象如何生?)****二、结构型模式(对象如何组?)****三、行为型模式(对象如何动?)****1.行为型类模式(靠继承)****2.行为型对象模式(靠组合)****四、统一对比表****五、终极记忆技巧**设计模式三大类型统一区分与记忆指南让思想碎片重焕生机的灵魂:行为模式分三类,每一类又有好多不同的模式,特别是行为式模式,还要区分
正则表达式regex
GotoMeiben
正则表达式
工具网站:RegExr:Learn,Build,&TestRegEx正则表达式(RegularExpression,Regex)是一种强大的字符串匹配工具,广泛用于文本搜索、数据处理和输入验证等场景。无论是Python、Java、JavaScript还是Shell脚本,Regex都是不可或缺的技能。本文将深入介绍正则表达式的各种用法,包括:基本匹配(字母、数字)特殊符号^$\b量词{}*+?字符类
大厂面试真题-说说DDD中的防腐层以及它和四层架构的关系
鱼跃鹰飞
大厂真题 DDD 架构设计 1024程序员节 职场和发展 开发语言 面试
DDD(领域驱动设计)中的防腐层(Anti-CorruptionLayer,ACL)是一种设计模式,旨在解决不同子系统或限界上下文间由于领域模型或接口不兼容而带来的集成问题。以下是对DDD防腐层的详细介绍,以及它与四层架构的关系:一、DDD防腐层定义:防腐层是一种在不同应用间转换的机制,通过引入一层适配层来隔离和转换不同系统间的交互。作用:隔离领域模型:保护自身领域模型免受其他领域模型代码的侵害。
(01)ES6 教程——let与const、解构赋值、Symbol
欲游山河十万里
web框架学习 es6 前端 javascript
前言ES6,全称ECMAScript6.0,是JavaScript的下一个版本标准,2015.06发版。ES6主要是为了解决ES5的先天不足,比如JavaScript里并没有类的概念,但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6的部分特性和功能。ECMAScript的背景JavaScript是大家所了解的语言名称,但是这个语言名称是商标(O
同步&异步日志系统-设计模式
2401_82609762
设计模式
六大原则单⼀职责原则(SingleResponsibilityPrinciple)类的职责应该单⼀,⼀个⽅法只做⼀件事。职责划分清晰了,每次改动到最⼩单位的⽅法或类。使⽤建议:两个完全不⼀样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很⾼的函数、数据的封装⽤例:⽹络聊天:⽹络通信&聊天,应该分割成为⽹络通信类&聊天类开闭原则(OpenClosedPrinciple)对扩展开放,对修改封闭使⽤建
Electron学习
星空0107
electron javascript ecmascript
Electron的简介Electron基于chromium和Node.js,让我们可以使用Javascript,HTML,CSS构建跨平台的桌面应用程序,同时Electron兼容Mac,Window,和Linux,可以构建出三个平台的应用程序Electron的安装运行cmd,输入npminit,然后一直按enter换行即可输入cnpmielectron-S(如果电脑没有安装cnpm会报错,需要安装
electron学习笔记
weixin_46452138
electron 学习 javascript
electron个人学习笔记一、electron简单了解Electron是一个跨平台的、基于Web前端技术的桌面GUI应用程序开发框架。可以使用HTML、CSS来绘制界面和控制布局,使用JavaScript来控制用户行为和业务逻辑,使用Node.js来通信、处理音频视频等,几乎所有的Web前端技术和框架(jQuery、Vue、React、Angular等)都可以应用到桌面GUI开发中。二、开发前基
apache 安装linux windows
墙头上一根草
apache inux windows
linux安装Apache 有两种方式一种是手动安装通过二进制的文件进行安装,另外一种就是通过yum 安装,此中安装方式,需要物理机联网。以下分别介绍两种的安装方式
通过二进制文件安装Apache需要的软件有apr,apr-util,pcre
1,安装 apr 下载地址:htt
fill_parent、wrap_content和match_parent的区别
Cb123456
match_parent fill_parent
fill_parent、wrap_content和match_parent的区别:
1)fill_parent
设置一个构件的布局为fill_parent将强制性地使构件扩展,以填充布局单元内尽可能多的空间。这跟Windows控件的dockstyle属性大体一致。设置一个顶部布局或控件为fill_parent将强制性让它布满整个屏幕。
2) wrap_conte
网页自适应设计
天子之骄
html css 响应式设计 页面自适应
网页自适应设计
网页对浏览器窗口的自适应支持变得越来越重要了。自适应响应设计更是异常火爆。再加上移动端的崛起,更是如日中天。以前为了适应不同屏幕分布率和浏览器窗口的扩大和缩小,需要设计几套css样式,用js脚本判断窗口大小,选择加载。结构臃肿,加载负担较大。现笔者经过一定时间的学习,有所心得,故分享于此,加强交流,共同进步。同时希望对大家有所
[sql server] 分组取最大最小常用sql
一炮送你回车库
SQL Server
--分组取最大最小常用sql--测试环境if OBJECT_ID('tb') is not null drop table tb;gocreate table tb( col1 int, col2 int, Fcount int)insert into tbselect 11,20,1 union allselect 11,22,1 union allselect 1
ImageIO写图片输出到硬盘
3213213333332132
java image
package awt;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imagei
自己的String动态数组
宝剑锋梅花香
java 动态数组 数组
数组还是好说,学过一两门编程语言的就知道,需要注意的是数组声明时需要把大小给它定下来,比如声明一个字符串类型的数组:String str[]=new String[10]; 但是问题就来了,每次都是大小确定的数组,我需要数组大小不固定随时变化怎么办呢? 动态数组就这样应运而生,龙哥给我们讲的是自己用代码写动态数组,并非用的ArrayList 看看字符
pinyin4j工具类
darkranger
.net
pinyin4j工具类Java工具类 2010-04-24 00:47:00 阅读69 评论0 字号:大中小
引入pinyin4j-2.5.0.jar包:
pinyin4j是一个功能强悍的汉语拼音工具包,主要是从汉语获取各种格式和需求的拼音,功能强悍,下面看看如何使用pinyin4j。
本人以前用AscII编码提取工具,效果不理想,现在用pinyin4j简单实现了一个。功能还不是很完美,
StarUML学习笔记----基本概念
aijuans
UML建模
介绍StarUML的基本概念,这些都是有效运用StarUML?所需要的。包括对模型、视图、图、项目、单元、方法、框架、模型块及其差异以及UML轮廓。
模型、视与图(Model, View and Diagram)
&
Activiti最终总结
avords
Activiti id 工作流
1、流程定义ID:ProcessDefinitionId,当定义一个流程就会产生。
2、流程实例ID:ProcessInstanceId,当开始一个具体的流程时就会产生,也就是不同的流程实例ID可能有相同的流程定义ID。
3、TaskId,每一个userTask都会有一个Id这个是存在于流程实例上的。
4、TaskDefinitionKey和(ActivityImpl activityId
从省市区多重级联想到的,react和jquery的差别
bee1314
jquery UI react
在我们的前端项目里经常会用到级联的select,比如省市区这样。通常这种级联大多是动态的。比如先加载了省,点击省加载市,点击市加载区。然后数据通常ajax返回。如果没有数据则说明到了叶子节点。 针对这种场景,如果我们使用jquery来实现,要考虑很多的问题,数据部分,以及大量的dom操作。比如这个页面上显示了某个区,这时候我切换省,要把市重新初始化数据,然后区域的部分要从页面
Eclipse快捷键大全
bijian1013
java eclipse 快捷键
Ctrl+1 快速修复(最经典的快捷键,就不用多说了)Ctrl+D: 删除当前行 Ctrl+Alt+↓ 复制当前行到下一行(复制增加)Ctrl+Alt+↑ 复制当前行到上一行(复制增加)Alt+↓ 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了)Alt+↑ 当前行和上面一行交互位置(同上)Alt+← 前一个编辑的页面Alt+→ 下一个编辑的页面(当然是针对上面那条来说了)Alt+En
js 笔记 函数
征客丶
JavaScript
一、函数的使用
1.1、定义函数变量
var vName = funcation(params){
}
1.2、函数的调用
函数变量的调用: vName(params);
函数定义时自发调用:(function(params){})(params);
1.3、函数中变量赋值
var a = 'a';
var ff
【Scala四】分析Spark源代码总结的Scala语法二
bit1129
scala
1. Some操作
在下面的代码中,使用了Some操作:if (self.partitioner == Some(partitioner)),那么Some(partitioner)表示什么含义?首先partitioner是方法combineByKey传入的变量,
Some的文档说明:
/** Class `Some[A]` represents existin
java 匿名内部类
BlueSkator
java匿名内部类
组合优先于继承
Java的匿名类,就是提供了一个快捷方便的手段,令继承关系可以方便地变成组合关系
继承只有一个时候才能用,当你要求子类的实例可以替代父类实例的位置时才可以用继承。
在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类。
内部类不是很好理解,但说白了其实也就是一个类中还包含着另外一个类如同一个人是由大脑、肢体、器官等身体结果组成,而内部类相
盗版win装在MAC有害发热,苹果的东西不值得买,win应该不用
ljy325
游戏 apple windows XP OS
Mac mini 型号: MC270CH-A RMB:5,688
Apple 对windows的产品支持不好,有以下问题:
1.装完了xp,发现机身很热虽然没有运行任何程序!貌似显卡跑游戏发热一样,按照那样的发热量,那部机子损耗很大,使用寿命受到严重的影响!
2.反观安装了Mac os的展示机,发热量很小,运行了1天温度也没有那么高
&nbs
读《研磨设计模式》-代码笔记-生成器模式-Builder
bylijinnan
java 设计模式
声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/
/**
* 生成器模式的意图在于将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示(GoF)
* 个人理解:
* 构建一个复杂的对象,对于创建者(Builder)来说,一是要有数据来源(rawData),二是要返回构
JIRA与SVN插件安装
chenyu19891124
SVN jira
JIRA安装好后提交代码并要显示在JIRA上,这得需要用SVN的插件才能看见开发人员提交的代码。
1.下载svn与jira插件安装包,解压后在安装包(atlassian-jira-subversion-plugin-0.10.1)
2.解压出来的包里下的lib文件夹下的jar拷贝到(C:\Program Files\Atlassian\JIRA 4.3.4\atlassian-jira\WEB
常用数学思想方法
comsci
工作
对于搞工程和技术的朋友来讲,在工作中常常遇到一些实际问题,而采用常规的思维方式无法很好的解决这些问题,那么这个时候我们就需要用数学语言和数学工具,而使用数学工具的前提却是用数学思想的方法来描述问题。。下面转帖几种常用的数学思想方法,仅供学习和参考
函数思想
把某一数学问题用函数表示出来,并且利用函数探究这个问题的一般规律。这是最基本、最常用的数学方法
pl/sql集合类型
daizj
oracle 集合 type pl/sql
--集合类型
/*
单行单列的数据,使用标量变量
单行多列数据,使用记录
单列多行数据,使用集合(。。。)
*集合:类似于数组也就是。pl/sql集合类型包括索引表(pl/sql table)、嵌套表(Nested Table)、变长数组(VARRAY)等
*/
/*
--集合方法
&n
[Ofbiz]ofbiz初用
dinguangx
电商 ofbiz
从github下载最新的ofbiz(截止2015-7-13),从源码进行ofbiz的试用
1. 加载测试库
ofbiz内置derby,通过下面的命令初始化测试库
./ant load-demo (与load-seed有一些区别)
2. 启动内置tomcat
./ant start
或
./startofbiz.sh
或
java -jar ofbiz.jar
&
结构体中最后一个元素是长度为0的数组
dcj3sjt126com
c gcc
在Linux源代码中,有很多的结构体最后都定义了一个元素个数为0个的数组,如/usr/include/linux/if_pppox.h中有这样一个结构体: struct pppoe_tag { __u16 tag_type; __u16 tag_len; &n
Linux cp 实现强行覆盖
dcj3sjt126com
linux
发现在Fedora 10 /ubutun 里面用cp -fr src dest,即使加了-f也是不能强行覆盖的,这时怎么回事的呢?一两个文件还好说,就输几个yes吧,但是要是n多文件怎么办,那还不输死人呢?下面提供三种解决办法。 方法一
我们输入alias命令,看看系统给cp起了一个什么别名。
[root@localhost ~]# aliasalias cp=’cp -i’a
Memcached(一)、HelloWorld
frank1234
memcached
一、简介
高性能的架构离不开缓存,分布式缓存中的佼佼者当属memcached,它通过客户端将不同的key hash到不同的memcached服务器中,而获取的时候也到相同的服务器中获取,由于不需要做集群同步,也就省去了集群间同步的开销和延迟,所以它相对于ehcache等缓存来说能更好的支持分布式应用,具有更强的横向伸缩能力。
二、客户端
选择一个memcached客户端,我这里用的是memc
Search in Rotated Sorted Array II
hcx2013
search
Follow up for "Search in Rotated Sorted Array":What if duplicates are allowed?
Would this affect the run-time complexity? How and why?
Write a function to determine if a given ta
Spring4新特性——更好的Java泛型操作API
jinnianshilongnian
spring4 generic type
Spring4新特性——泛型限定式依赖注入
Spring4新特性——核心容器的其他改进
Spring4新特性——Web开发的增强
Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC
Spring4新特性——Groovy Bean定义DSL
Spring4新特性——更好的Java泛型操作API
Spring4新
CentOS安装JDK
liuxingguome
centos
1、行卸载原来的:
[root@localhost opt]# rpm -qa | grep java
tzdata-java-2014g-1.el6.noarch
java-1.7.0-openjdk-1.7.0.65-2.5.1.2.el6_5.x86_64
java-1.6.0-openjdk-1.6.0.0-11.1.13.4.el6.x86_64
[root@localhost
二分搜索专题2-在有序二维数组中搜索一个元素
OpenMind
二维数组 算法 二分搜索
1,设二维数组p的每行每列都按照下标递增的顺序递增。
用数学语言描述如下:p满足
(1),对任意的x1,x2,y,如果x1<x2,则p(x1,y)<p(x2,y);
(2),对任意的x,y1,y2, 如果y1<y2,则p(x,y1)<p(x,y2);
2,问题:
给定满足1的数组p和一个整数k,求是否存在x0,y0使得p(x0,y0)=k?
3,算法分析:
(
java 随机数 Math与Random
SaraWon
java Math Random
今天需要在程序中产生随机数,知道有两种方法可以使用,但是使用Math和Random的区别还不是特别清楚,看到一篇文章是关于的,觉得写的还挺不错的,原文地址是
http://www.oschina.net/question/157182_45274?sort=default&p=1#answers
产生1到10之间的随机数的两种实现方式:
//Math
Math.roun
oracle创建表空间
tugn
oracle
create temporary tablespace TXSJ_TEMP
tempfile 'E:\Oracle\oradata\TXSJ_TEMP.dbf'
size 32m
autoextend on
next 32m maxsize 2048m
extent m
使用Java8实现自己的个性化搜索引擎
yangshangchuan
java superword 搜索引擎 java8 全文检索
需要对249本软件著作实现句子级别全文检索,这些著作均为PDF文件,不使用现有的框架如lucene,自己实现的方法如下:
1、从PDF文件中提取文本,这里的重点是如何最大可能地还原文本。提取之后的文本,一个句子一行保存为文本文件。
2、将所有文本文件合并为一个单一的文本文件,这样,每一个句子就有一个唯一行号。
3、对每一行文本进行分词,建立倒排表,倒排表的格式为:词=包含该词的总行数N=行号