vue学习笔记

初识Vue

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初识Vue</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
    	<!--通过插值语法{{name}}取得data中的数据-->
        <h1>Hello,{{name}}</h1>
    </div>
    <script type="text/javascript">
        new Vue({			//创建Vue实例,参数是Vue对象的配置参数
            el:'#root',		//将Vue实例与#root元素绑定,这个Vue实例只能操作#root
            data:{			//数据
                name:"ikun"
            }
        })
    </script>
</body>
</html>

注意:容器和Vue实例要一一对应,不能一对多或多对一

vue的模板语法

插值语法

用于解析标签体中的内容
如:<h1>name:{{xxx}}</h1>,xxx中为js表达式,可以直接读取与其绑定的Vue实例中的data中的数据

指令语法

用于解析标签属性、标签体内容、绑定事件等,都是以v-xxx开头
如以可以操作标签数据的v-bind举例:<a v-bind:href="xxx">,或简写为<a :href="xxx">,xxx同样为js表达式,可以读取到Vue实例中的data中的内容

数据绑定

单向数据绑定v-bind,只能Vue实例——>页面元素

<input type="text" v-bind:value="name>"

<input type="text" :value="name>"

双向数据绑定v-model,可以Vue实例<——>页面元素

<input type="text" v-model:value="name>"

<input type="text" v-model="name>"
注意:v-model不是所有的标签都能使用,只能作用在表单类元素(有value属性)上的value属性上

Vue实例中el和data的两种写法

el方式1:

直接在Vue实例构造方法中指定容器

new Vue({			//创建Vue实例,参数是Vue对象的配置参数
    el:'#root',		//将Vue实例与#root元素绑定,这个Vue实例只能操作#root
})

el方式2:

通过$mount指定

const v = new Vue({			//创建Vue实例,参数是Vue对象的配置参数
})
v.$mount('#root')	//通过$mount与容器绑定

data方式1:

以对象的方式

new Vue({			//创建Vue实例,参数是Vue对象的配置参数
    el:'#root',		//将Vue实例与#root元素绑定,这个Vue实例只能操作#root
    data:{			//数据
        name:"ikun"
    }
})

data方式2:

以方法的方式

new Vue({			//创建Vue实例,参数是Vue对象的配置参数
    el:'#root',		//将Vue实例与#root元素绑定,这个Vue实例只能操作#root
    //data是由Vue管理的方法,由Vue自己调用,其中如果有this的话,其实就是Vue对象本身
    data(){
		return{
			name:'ikun';
		}
	}
})

注:vm实例中并不存在vm.data,比如在data中定义了attr属性,可以通过vm._data.attr访问Vue通过数据收集放到_data中的属性,或者直接用vm.attr通过数据代理访问_data中的属性

MVVM模型

在这里插入图片描述

数据代理

Object.defineProperty

为js对象动态绑定属性,如

let number = 18;
let person = {		//定义一个对象
    name:'ikun',
    sex:'男'
}
//通过defineProperty为对象动态绑定新的属性
Object.defineProperty(person,'age',{
	//当age属性被调用时执行get方法,将其返回值作为属性值,这里get方法是读取number变量的值
    get(){
        return number;
    },
    //当age属性被修改时执行set方法,这里set方法是修改number变量的值,后面如果再读取age时,再利用get读取number的值
    set(value){
        number = value;
    }
})

数据代理:通过一个对象代理对另一个对象中属性的操作,可以是读或者写,一个简单的数据代理如下:

let obj1 = {x:100}
let obj2 = {y:200}
//obj2中x属性由obj1的x属性进行代理
//如果读取obj2的x,读到的是obj1的x
//如果修改obj2的x,修改的是obj1的x
Object.defineProperty(obj2,'x',{
    get(){
        return obj1.x;
    },
    set(value){
        obj1.x = value;
    }
})

Vue中的数据收集和数据代理原理

  1. 数据收集:当在data中定义属性时,Vue对每个属性进行一定加工,生成每个属性对应的getter和setter,并将属性和方法收集到vm._data中
  2. 数据代理:利用defineProperty将vm._data中的属性直接代理到vm身上,当访问vm.name或vm.address时,才实际调用对应属性的defineProperty中的getter,而defineProperty中的getter又去vm._data中调用对应属性的getter,返回最新的属性值
    在这里插入图片描述

事件处理

事件的基本使用

  1. 使用v-on:xxx 或 @xxx绑定事件,xxx是具体的事件名

  2. 触发事件执行的回调方法配置在vue实例的methods对象中,最终会出现在vm身上,如@click="showInfo1()",等号右边引号中执行的方法,会在vm身上找;又比如点击按钮<button @click="numbers++">点我让a++</button>时,会去Vue实例上找number属性并让其++

  3. 在触发事件时若需要传参,则使用括号,如希望某个按钮的click事件触发时传递特定参数,则:@click=“methodName($event,param)”,然后在回调方法methodName中就能拿到事件对象event和参数param

v-on:click为按钮绑定点击事件

为#root元素中的按钮绑定点击事件,当点击时触发在Vue实例methods中定义的对应的事件方法

<body>
    <div id="root">
        <h1>Hello</h1>
        <!--不传参调用事件方法-->
        <button v-on:click="showInfo">点我</button>
        <!--传参调用事件方法-->
        <button @click="showInfo1($event,10)">点我</button>
    </div>
    <script type="text/javascript">
        new Vue({
            el:'#root',
            methods:{
            	//event是触发该方法时的事件对象,event.target可以得到触发该事件的页面元素
                showInfo(event){
                    alert("你好");
                },
                showInfo1(event,param){
                    alert("你好"+param);
                }
            }
        })

    </script>
</body>

其中 <button v-on:click="showInfo">点我</button>可以简写为 <button @click="showInfo">点我</button>

事件修饰符

当触发某些元素的事件后,希望对事件原本行为作出一定改变时,可以用事件修饰符,如在点击a标签后不希望其进行跳转:

<a href="www.baidu.com" @click.prevent="showInfo">提示</a>

这里click后面的prevent就是修饰符,用来阻止a标签的跳转事件

常用事件修饰符

  1. prevent:阻止默认事件
  2. stop:阻止事件冒泡,在下面的例子中,如果点击a,可以阻止div触发showInfo事件
<div @click="showInfo">
	<a href="www.baidu.com" @click.stop="showInfo">提示</a>
</div>
  1. once:事件只会被触发一次
<a href="www.baidu.com" @click.once="showInfo">提示</a>

键盘事件@keyup、@keydown

在input框中输入完数据后按下Enter触发回调

<input @keyup.enter="showInfo">

同理还有@keyup.space、@keyup.delete

计算属性computed

原理:底层就是借助defineProperty的get和set方法实现的,当需要使用到computed中的属性时才调用其get方法,一般会在页面第一次读取时,和该计算属性所依赖的普通属性发生改变时才会被调用,计算属性在页面上调用时和普通属性一样,通过{{计算属性名}}获取

举例:

<div id="root">
	<!--调用Vue实例中fullName计算属性的get方法-->
    <span>{{fullName}}</span>
</div>
<script type="text/javascript">
    new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
        computed:{
            fullName:{
            	//get方法会在当前页面第一次读取到fullName时,或fullName中所依赖的普通属性发生改变时被调用
            	//使用data中的普通属性计算出的
                get(){
                    return this.firstName+this.lastName;
                },
                //当计算属性fullName被修改时调用
                set(value){
                    const arr = value.split("-");
                    this.firstName = arr[0];
                    this.lastName = arr[1];
                }
            }
        }
    })
</script>

计算属性的简写

一般计算属性不会被修改,所以当计算属性只有get方法时,可以将上面的代码简写为

<div id="root">
    <span>{{fullName}}</span>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
        computed:{
        	//简写为一个方法
            fullName(){
                return this.firstName + this.lastName;
            }
        }
    })
</script>

监视属性watch

可以用来监视Vue实例中的普通属性和计算属性的值的改变,并自动调用handler方法处理

实现方法一

直接在Vue配置对象中通过watch绑定监视属性,适合明确知道应该监视Vue实例中的哪些属性

const vm = new Vue({
    el:'#root',
    data:{
        isHot:true
    },
    watch:{
        isHot:{
            //handler是监视中自带的方法,在isHot改变时调用
            //newVal是改变后的值,oldVal是改变前的值
            handler(newVal,oldVal){
                console.log(newVal,oldVal);
            },
            //immediate默认为false,当设置为true时,页面一加载就会首先执行一次handler
            immediate:true
        }
    }
})

实现方法二

通过vm对象进行监视,适合监视Vue实例初始化时还未生成的属性

const vm = new Vue({
    el:'#root',
    data:{
        isHot:true
    }
})
vm.$watch('isHot',{
    //handler是监视中自带的方法,在isHot改变时调用
    //newVal是改变后的值,oldVal是改变前的值
    handler(newVal,oldVal){
        console.log(newVal,oldVal);
    },
    //immediate默认为false,当设置为true时,页面一加载就会首先执行一次handler
    immediate:true
})

深度监视

const vm = new Vue({
    el:'#root',
    data:{
        numbers:{
            a:1,
            b:1
        }
    },
    watch:{
        //若只想监视numbers属性中的a,而不想监视numbers的b时
        //需要用字符串形式表示多级结构的监视
        'numbers.a':{
            handler(newVal, oldVal) {
                console.log("numbers.a被改变了:"+newVal,oldVal);
            }
        },
        //若希望监视numbers属性中所有子属性,即只要numbers中的一个子属性被修改,就触发handler时,需要开启深度监视
        numbers: {
            //开启深度监视
            deep:true,
            //默认deep=false,若直接写下面的监视方法,除非numbers属性的地址改变,否则不会触发handler
            handler(newVal,oldVal){
                console.log("numbers被改变了:",newVal,oldVal);
            }
        }
    }
})

监视属性的简写

当需要监视的属性中只需要配置handler方法时,可以使用下面的方法简写

方法一的简写
const vm = new Vue({
    el:'#root',
    data:{
        isHot:true
    },
    watch:{
        //完整写法
        isHot:{
            //handler是监视中自带的方法,在isHot改变时调用
            //newVal是改变后的值,oldVal是改变前的值
            handler(newVal,oldVal){
                console.log(newVal,oldVal);
            }
        },
        //简写
        isHot(newVal,oldVal){
            console.log("天气被切换了"+newVal,oldVal)
        }
    }
})
方法二的简写
const vm = new Vue({
    el:'#root',
    data:{
        isHot:true
    }
})

//完整写法
vm.$watch('isHot',{
    //handler是监视中自带的方法,在isHot改变时调用
    //newVal是改变后的值,oldVal是改变前的值
    handler(newVal,oldVal){
        console.log("天气被切换了"+newVal,oldVal);
    }
})
//简写
vm.$watch('isHot',function (newVal,oldVal){
    console.log("天气被切换了"+newVal,oldVal);
})

Vue中普通方法与箭头方法

在Vue实例中写方法时,需要根据情况判断当前方法应该写成普通方法还是箭头方法,判断的依据就是要保证这个方法中的this要指向Vue实例

  1. 被Vue直接管理的方法,如methods中的方法,写成普通方法
  2. 不被Vue直接管理的方法,如setTimeout定时器中的回调方法、ajax中的回调方法写成箭头方法

以setTImeout的回调方法举例,正确写法:

<body>
    <div id="root">
        <button @click="test">test</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            methods:{
                test(){
                    setTimeout(()=>{
                        console.log(this);
                    },1000);
                }
            }
        })
    </script>
</body>

点击test按钮,控制台正确输出Vue实例,因为箭头方法中没有自带的this,当在其中使用this时就会去找外层的this,所以这里会找setTImeout的外层,也就是test中的this,而test是被Vue管理的方法,this自然就是Vue实例
在这里插入图片描述
若把setTimeout的回调方法写成普通方法时:

<body>
    <div id="root">
        <button @click="test">test</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            methods:{
                test(){
                    setTimeout(function(){
                        console.log(this);
                    },1000);
                }
            }
        })
    </script>
</body>

点击test按钮,控制台输出的是window,因为this所在的回调方法是被setTImeout管理,而setTimeout是js的方法,所以this找的是window
在这里插入图片描述

利用v-bind动态为元素绑定样式的几种情况

<body>
    <div id="root">
        <!--情况一:字符串写法,适合需要给元素的样式在一些固定的style中切换,但具体切换哪一个不确定的情况-->
        <div class="basic" :class="mood" @click="changeMood">TEST</div><br>
        <!--情况二:数组写法,适合需要给元素添加一些样式,添加样式时直接在arr数组中追加样式名即可-->
        <div class="basic" :class="arr">TEST</div><br>
        <!--情况三:对象写法,适合元素有哪些样式已经确定,但不确定每个样式到底是否生效的情况,当需要某个样式时,修改其在classObj中样式名为true,否则修改为false-->
        <div class="basic" :class="classObj">TEST</div><br>
   		<!--情况四:内联样式对象写法,可以通过Vue实例控制内联样式-->
        <div class="basic" :style="styleObj">TEST</div><br>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                mood:'normal',
                arr:['style1','style2'],
                classObj:{
                    style1:true,
                    style2:false
                },
                styleObj:{
                    fontSize: '40px',   //font-size转换为fontSize
                    color: 'red'
                }
            },
            methods:{
                //点击TEST按钮时随机从arr中取出一个样式加到div上
                changeMood(){
                    const arr = ['normal','happy','sad'];
                    let index = Math.floor(Math.random()*3);
                    this.mood = arr[index];
                }
            }
        })
    </script>
</body>

条件渲染

v-show

当元素上设置v-show=‘false’时,元素会使用display:none隐藏,页面上还是会有元素节点,只是不会显示

v-if

当元素上设置v-if=’false‘时,元素会直接从页面上删除而不是隐藏

v-else-if

配合v-if使用,逻辑和if-elseif一样,当判断到其中一个为true后,其他的else就不再做判断,直接略过

<h1 v-if="true">Hello1</h1>
<h1 v-else-if="true">Hello2</h1>
<h1 v-else-if="true">Hello3</h1>

v-else

配合v-if、v-else-if使用,当前面所有都判断为false后,v-else生效

<h1 v-if="false">Hello1</h1>
<h1 v-else-if="false">Hello2</h1>
<h1 v-else-if="false">Hello3</h1>
<h1 v-else>Hello4</h1>

v-if、v-else-if、v-else必须成组出现,中间不允许被其他标签打断

template标签

template标签可以配合v-if使用,如果为true展示template标签中的内容,并且使用template标签不会对页面产生结构影响,例如下面通过template和v-if直接控制三个h1标签的显示

<body>
    <div id="root">
        <button @click="n++">n++</button>
        <template v-if="n%2 === 0">
            <h1>java</h1>
            <h1>javascript</h1>
            <h1>spring</h1>
        </template>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                n:1
            }
        })
    </script>
</body>

列表渲染v-for

使用v-for循环生成li举例:

<body>
    <div id="root">
        <!--遍历数组方法一,用数组中每个对象的属性作为v-for的key-->
        <ul>
            <li v-for="person in persons" :key="person.id">
                {{person.id}}-{{person.name}}
            </li>
        </ul>
        <!--遍历数组方法二,参数接收数组中每个对象以及下标,用下标作为v-for的key-->
        <ul>
            <li v-for="(person,index) in persons" :key="index">
                {{person.id}}-{{person.name}}
            </li>
        </ul>
        <!--遍历对象-->
        <ul>
            <li v-for="(value,key) in car" :key="key">
                {{key}}-{{value}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'1',name:"ikun1"},
                    {id:'2',name:"ikun2"},
                    {id:'3',name:"ikun3"}
                ],
                car:{
                    name:'Audi A8',
                    price: '70w',
                    color: 'black'
                }
            }
        })
    </script>
</body>

在这里插入图片描述

v-for中使用index或数组元素唯一标识(id)作为key的区别

使用下列代码动态新增v-for需要遍历的数组元素,并且使用unshift让新元素插入到数组头部

<body>
    <div id="root">
        <!--遍历数组,参数接收数组中每个对象以及下标,用下标index作为v-for的key-->
        <ul>
        	<!--循环遍历persons数组生成li-->
            <li v-for="(person,index) in persons" :key="index">
                {{person.id}}-{{person.name}}
                <!--每个元素后面还有一个输入框-->
                <input type="text">
            </li>
        </ul>
        <!--当点击add按钮时向persons数组头部插入一个元素-->
        <button @click="add">add</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'1',name:"ikun1"},
                    {id:'2',name:"ikun2"},
                    {id:'3',name:"ikun3"}
                ]
            },
            methods:{
                add(){
                    const p = {id:'4',name:'ikun4'};
                    this.persons.unshift(p);
                }
            }
        })
    </script>
</body>

在每个li的input中输入一些内容:
在这里插入图片描述
当点击add后,发现之前输入框中输入的内容与对应的person对象不能对应,原因出在使用index作为v-for的key上,原理如下图
在这里插入图片描述

vue中v-for生成的元素的虚拟dom与真实dom

当页面第一次加载时,会先根据数组遍历所有元素,并在内存中生成对应的虚拟dom,虚拟dom生成完成后再将虚拟dom转换为真实dom在页面中进行展示,之后若v-for的数组发生变化,vue会通过diff算法判断是否需要生成新的真实dom在页面中展示,如果不需要则直接复用之前已经生成好的真实dom,提高效率,而diff算法中会利用v-for的key作为判断是否生成新真实dom的依据,

vue的diff算法

点击add按钮之前页面中v-for元素对应的虚拟dom如下图
在这里插入图片描述

当点击add按钮在persons数组头部添加新元素后,这时页面中v-for元素对应的虚拟dom如下图
在这里插入图片描述
这时vue会根据虚拟dom的key值为依据,进行逐一判断是否生成新的真实dom,这里以key为0的元素的判断过程举例:

  1. 找到之前新虚拟dom和老虚拟dom中key为0的元素
  2. 判断两个虚拟dom中第一个元素是否一致,这里老dom中为张三,而新dom中为老刘,不一致,所以生成一个新的真实dom,即老刘-30
  3. 判断两个虚拟dom中第二个元素是否一致,这里两个虚拟dom中第二个元素都是<input type="text">,所以一致 (即使此时页面中老真实dom的input中已经输入有内容,但是这里只比较虚拟dom,与真实dom无关),所以这里直接复用老真实dom中key为0的input,所以新页面中新生成的老刘后的文本框中有之前在老页面中输入的内容张三
  4. 后面的key=1、2、3的元素diff算法过程与上述类似,流程图如下
    在这里插入图片描述
    要解决上面的问题,就要自己手动指定每个v-for元素的key,这里以数组中每个元素的id属性作为key,代码如下
<body>
    <div id="root">
        <!--遍历数组,参数接收数组中每个对象以及下标,用id属性作为v-for的key-->
        <ul>
            <li v-for="(person,index) in persons" :key="person.id">
                {{person.id}}-{{person.name}}
                <input type="text">
            </li>
        </ul>
        <button @click="add">add</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'1',name:"ikun1"},
                    {id:'2',name:"ikun2"},
                    {id:'3',name:"ikun3"}
                ]
            },
            methods:{
                add(){
                    const p = {id:'4',name:'ikun4'};
                    this.persons.unshift(p);
                }
            }
        })
    </script>
</body>

点击add前在页面输入内容
在这里插入图片描述
点击add按钮在persons数组头部添加元素后
在这里插入图片描述
在点击add前的老虚拟dom如下图
在这里插入图片描述
当点击add在数组头部添加元素后,新的虚拟dom如下图
在这里插入图片描述
因为key与每个数组元素的id属性绑定,所以即使在数组头部添加,也不会导致之前已有的v-for元素的key值发生改变,所以按照diff算法根据key进行对比时,自然也就不会出错,同时能够保证之前每个元素的真实dom都被复用,提高效率
在这里插入图片描述

v-for的key使用总结

当需要针对v-for遍历的数组中已有的元素的下标进行改变时,比如在数组头、数组中插入元素,就必须使用元素唯一属性作为key;若不会针对已有元素的下标进行修改,如只会在数组末尾追加元素,则可以使用index作为key,如果使用v-for时不显示地指明key的值时,默认就是以index作为key

列表过滤

在这里插入图片描述
js中过滤数组的方法:filter

//filter执行后会返回一个新数组,不会对原数组arr产生改变
const filteredArr = arr.filter((elem)=>{	//elem代表过滤时每次循环遍历到的数组中的元素
	//最后会根据返回值的true或false决定是否过滤当前元素,如果为true则放入过滤后的数组中,如果为false则不被放入过滤后新数组中
    return true;
})

方法一:使用watch实现

<body>
    <div id="root">
        <input v-model="keywords" type="text" placeholder="输入名字模糊查询">
        <ul>
        	<!--filPersons是普通属性-->
            <li v-for="(person,index) in filPersons" :key="person.id">
                {{person.id}}-{{person.name}}-{{person.sex}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                keywords:'',	//保存输入框中的内容
                persons:[
                    {id:'1',name:"马冬梅",sex:'女'},
                    {id:'2',name:"周冬雨",sex:'女'},
                    {id:'3',name:"周杰伦",sex:'男'},
                    {id:'4',name:"温兆伦",sex:'男'}
                ],
                filPersons:[]
            },
            watch:{
                keywords:{
                    //让监测到keywords变化后所执行的handler方法一上来就执行一次,因为一上来用户没有在input中输入内容,所以这里的newVal为空串,任何字符串都是包含空串的,所以这样就能在一开始就显示出所有的内容
                    immediate:true,
                    handler(newVal){
                        this.filPersons = this.persons.filter((person)=>{
                        	//当indexOf返回值不是-1时,说明包含当前输入的字符串,所以返回true
                            return person.name.indexOf(newVal) !== -1;
                        })
                    }
                }
            }
        })
    </script>
</body>

方法二:使用computed实现

<body>
    <div id="root">
        <input v-model="keywords" type="text" placeholder="输入名字模糊查询">
        <ul>
        	<!--filPersons是计算属性-->
            <li v-for="(person,index) in filPersons" :key="person.id">
                {{person.id}}-{{person.name}}-{{person.sex}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                keywords:'',	//保存输入框中的内容
                persons:[
                    {id:'1',name:"马冬梅",sex:'女'},
                    {id:'2',name:"周冬雨",sex:'女'},
                    {id:'3',name:"周杰伦",sex:'男'},
                    {id:'4',name:"温兆伦",sex:'男'}
                ]
            },
            computed:{
            	//filPersons计算属性在页面初始化,和其依赖的keywords发生改变时会被调用
                filPersons(){
                	//直接返回filter后的数组,作为计算属性
                    return this.persons.filter((person)=>{
                    	//当indexOf返回值不是-1时,说明包含当前输入的字符串,所以返回true
                        return person.name.indexOf(this.keywords) !== -1;
                    })
                }
            }
        })
    </script>
</body>

列表排序

默认顺序或点击原顺序:
在这里插入图片描述
点击升序
在这里插入图片描述
点击降序
在这里插入图片描述
js中针对数组的排序方法sort

//假设arr数组中的元素都是整型
arr.sort((pre,rear)=>{
	return pre - rear;	//升序
	//return rear - pre;	//降序
})

代码实现

<body>
    <div id="root">
        <input v-model="keywords" type="text" placeholder="输入名字模糊查询">
        <button @click="sortType = 2">编号升序</button>
        <button @click="sortType = 1">编号降序</button>
        <button @click="sortType = 0">原顺序</button>
        <ul>
            <li v-for="(person,index) in filPersons" :key="person.id">
                {{person.id}}-{{person.name}}-{{person.sex}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                keywords:'',
                persons:[
                    {id:'2',name:"马冬梅",sex:'女'},
                    {id:'1',name:"周冬雨",sex:'女'},
                    {id:'4',name:"周杰伦",sex:'男'},
                    {id:'3',name:"温兆伦",sex:'男'}
                ],
                sortType:0      //0 代表原顺序,1 代表降序,2 代表升序
            },
            computed:{
                //filPersons计算属性在页面初始化,和其依赖的keywords、sortType发生改变时会被调用
                filPersons(){
                    //首先进行过滤,当页面刚加载时,利用空串进行过滤
                    const filteredPersons = this.persons.filter((person)=>{
                        return person.name.indexOf(this.keywords) !== -1;
                    });
                    //当sortType不等于0时进行排序
                    if(this.sortType){
                        filteredPersons.sort((pre,rear)=>{
                            return this.sortType === 2 ? parseInt(pre.id) - parseInt(rear.id):parseInt(rear.id) - parseInt(pre.id);
                        })
                    }
                    return filteredPersons;
                }
            }
        })
    </script>
</body>

Vue更新数据

<body>
    <div id="root">
        <span>{{name}}</span>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                name:'ikun'
            }
        })
    </script>
</body>

根据Vue数据收集和数据代理原理可知,定义在data中的属性,会被Vue进行一番加工,为其生成getter和setter,并和属性一起放到Vue._data中,即数据收集,再利用defineProperty将Vue._data中的属性直接代理到Vue实例身上,即数据代理,所以可以直接通过vm.name拿到在data中定义的name属性

当页面中有以上代码时,只要修改Vue的name的值,页面就会刷新成更新后的值,其更新流程是:

name修改——>name的setter调用——>重新解析页面上用到name的模板(包含在name的setter中 )——>生成新的虚拟dom——>新旧虚拟dom对比——>更新页面

模拟Vue底层监视对象数据data改变的原理

下列代码模仿Vue的data动态刷新页面数据的原理

<body>
    <script type="text/javascript">
        //data模拟Vue实例中的data,针对其中数据的改变会引起页面数据的更新
        let data = {
            name:'ikun',
            age:18
        }
        //Observer对象的构造方法,通过Observer对象监视所有data中的属性
        function Observer(obj){
            //得到data对象的所有key,形成新数组
            const keys = Object.keys(obj);
            //遍历所有key
            keys.forEach((key)=>{
                //循环为data中所有属性在Observer对象上创建数据代理,这里的this就是Observer对象本身
                Object.defineProperty(this,key,{
                    //当访问Observer.name或Observer.age时,通过getter获取data上的属性
                    get(){
                        return obj[key];
                    },
                    //当通过Observer.name或Observer.age修改时,通过setter修改data上的属性,并且在其中会做针对页面模板的重新加载工作
                    set(val){
                        obj[key] = val;
                        //模拟刷新页面的操作
                        console.log("重新解析页面模板、生成虚拟dom、对比新旧dom、刷新页面");
                    }
                })
            })
        }
        //创建监视对象,传入data,用于监视data中的属性变化
        const obs = new Observer(data);
        //模拟一个vm实例对象,其中的_data用于保存obs对象
        let vm = {}
        //通过vm._data就能得到Observer监视对象,通过监视对象就能获取或修改data中的属性
        vm._data = obs;
    </script>
</body>

当在控制台通过vm._data修改数据时:
在这里插入图片描述

Vue.set追加响应式刷新属性

响应式刷新属性就是指拥有Vue生成的setter和getter方法的属性,当通过setter方法修改属性值时,Vue会自动触发页面模板的重新解析,实现数据的刷新
在这里插入图片描述
当点击按钮后
在这里插入图片描述
代码

<body>
    <div id="root">
        <button @click="addSex">添加性别属性</button>
        <h1>姓名:{{student.name}}</h1>
        <h1>年龄:{{student.age}}</h1>
        <!--当student.sex属性不存在时,不生成该节点-->
        <h1 v-if="student.sex">性别:{{student.sex}}</h1>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                student:{
                    name:'ikun',
                    age:18
                }
            },
            methods:{
                addSex(){
                    //方式一
                    Vue.set(this.student,'sex','男');
                    //方式二
                    //this.$set(this.student,'sex','男');
                }
            }
        })
    </script>
</body>

注意:set方法不能直接给vm实例或者vm的data添加属性,只能给data中的属性添加子属性,比如当addSex写成下面的方式就会报错

addSex(){
	//在vm实例的根数据data上添加属性
    Vue.set(this._data,'sex','男');
}
addSex(){
	//在vm实例上添加属性
    Vue.set(this,'sex','男');
}

报错不能在Vue实例或$data上直接添加属性
在这里插入图片描述
只能在绿色框中使用set添加响应式属性,而不能在红色箭头处添加
在这里插入图片描述

Vue.set也能针对数组属性实现响应式刷新

如下代码中的arr可以使用set实现修改的响应式刷新

<body>
    <div id="root">
        <h1 v-for="(a,index) in arr">{{a}}</h1>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                arr:['arr1','arr2','arr3']
            }
        })
    </script>
</body>

其中0代表想要修改的数组元素下标,'arr4’代表想要修改后的内容
在这里插入图片描述

实现Vue中数组的响应式刷新问题

注意:下面所有在控制台中执行的代码,如果要在Vue管理的方法中执行,需要把vm替换成this

<body>
    <div id="root">
        <h1>姓名:{{student.name}}</h1>
        <h1>年龄:{{student.age}}</h1>
        <h1 v-for="(hobby,index) in student.hobbies">爱好{{index}}:{{hobby}}</h1>
        <h1 v-for="(major,index) in student.majors">课程{{index}}:{{major.name}}</h1>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                student:{
                    name:'ikun',
                    age:18,
                    hobbies:['唱','跳','rap'],
                    majors:[
                        {name:'数据结构'},
                        {name:'高等数学'},
                        {name:'大学物理'}
                    ]
                }
            }
        })
    </script>
</body>
普通数组属性的响应式刷新问题

以上代码中,hobbies在vue中的结构如下,hobbies数组本身有getter和setter,但是hobbies中每个元素在箭头位置并没有getter和setter,所以修改hobbies数组整体可以触发hobbies实现响应式刷新,但单独修改其中下标为0、1、2的元素则不能实现响应式刷新
在这里插入图片描述

在Vue调试器中的结构如下,每个元素就是一个字符串
在这里插入图片描述

直接通过代码修改vm.student.hobbies[0]、vm.student.hobbies[1]、vm.student.hobbies[2]中元素的值,Vue并不能监测到其变化,也不会触发页面的刷新,比如
在这里插入图片描述
直接修改hobbies数组整体的内容,可以实现响应式刷新
在这里插入图片描述

对象数组属性的响应式刷新问题

majors数组的结构如下
在这里插入图片描述
在vue调试器中的结构如下,可以看到每个元素是一个对象
在这里插入图片描述

vm.student.majors[0].name、vm.student.majors[1].name、vm.student.majors[2].name三者内都分别有自己的getter和setter,所以当通过代码修改这三者的值时,Vue能监测到其变化,并刷新当前页面的模板,比如
在这里插入图片描述

普通数组属性实现响应式刷新的方法

以上面的hobbies属性数组举例,数组元素不是对象,所以没有对应的getter和setter,自然修改数组元素时也就不会被Vue监测到,若想要针对这类属性数组实现响应式刷新,需要使用Vue利用js数组操作api封装的方法进行普通数组的修改操作,假设有如下代码

<body>
    <div id="root">
        <h1 v-for="(a,index) in arr">{{a}}</h1>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                arr:['arr1','arr2','arr3']
            }
        })
    </script>
</body>

想要针对arr数组实现修改响应式刷新,需要使用下列方法:(当在Vue管理的方法内写下列方法时,把vm替换成this即可)

  1. 数组头插入:vm.arr.shift(elem)
  2. 数组头取出:vm.arr.unshift()
  3. 数组尾插入:vm.arr.push(elem)
  4. 数组尾取出:vm.arr.pop()
  5. 数组反转:vm.arr.reverse()
  6. 数组排序:vm.arr.sort()
  7. 数组元素替换:vm.arr.splice(index,1,elem),代表将下标为index的元素替换为elem元素,如
    在这里插入图片描述
    注:以上方法实际上并不是js数组原生的方法,vue针对这些方法进行了包装,除了完成了对数组的操作外,还触发对页面模板的重新加载等一系列响应式操作

除了使用这7个数组方法实现数组修改的响应式刷新外,还可以使用Vue.set实现,比如:
在这里插入图片描述
其中0代表想要修改的数组元素下标,'arr4’代表想要修改后的内容

Vue收集表单数据

在表单中输入下列数据
在这里插入图片描述
Vue调试器中收集的数据
在这里插入图片描述
代码

<body>
    <div id="root">
        <!--当点击按钮时触发demo方法,并且阻止form表单的默认事件,这里是阻止其刷新页面-->
        <form @submit.prevent="demo">
            <!--v-model.trim去掉用户输入的字符串前后的空格-->
            账号:<input type="text" v-model.trim="userInfo.account"><br><br>
            密码:<input type="password" v-model="userInfo.password"><br><br>
            <!--type="number"控制用户只能在输入框中输入数字,v-model.number表示会将用户输入的数字从字符串转换为数字类型-->
            年龄:<input type="number" v-model.number="userInfo.age"><br><br>
            性别:
            男<input type="radio" name="sex" value="man" v-model="userInfo.sex"><input type="radio" name="sex" value="woman" v-model="userInfo.sex"><br><br>
            爱好:
            学习<input type="checkbox" v-model="userInfo.hobbies" value="study">
            打游戏<input type="checkbox" v-model="userInfo.hobbies" value="game">
            吃饱<input type="checkbox" v-model="userInfo.hobbies" value="eat"><br><br>
            所属校区
            <select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
                <option value="wuhan">武汉</option>
            </select><br><br>
            其他信息:
            <!--v-model.lazy表示只有当前输入框失去焦点时,Vue才把数据收集到model中-->
            <textarea v-model.lazy="userInfo.other"></textarea><br><br>
            <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="https://www.baidu.com">用户协议</a>
            <button>提交</button>
        </form>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                userInfo:{
                    account:'',
                    password:'',
                    sex:'man',
                    hobbies:[],     //对应页面的爱好多选框,这里必须要初始化成数组类型才能收集到每个多选框的value值
                    city:'beijing',
                    other:'',
                    agree:false,
                    age:''
                }
            },
            methods:{
                demo(){
                    console.log(JSON.stringify(this.userInfo));
                }
            }
        })
    </script>
</body>

使用model收集表单数据时的注意:

  1. 收集radio时,要为radio设置value属性,收集到的就是radio的value
  2. 收集checkbox时,需要根据情况判断是否设置value属性
    1. 若只需要收集单个checkbox的勾选状态,则不用设置value,收集到的就是checkbox的checked状态,为true或者false
    2. 若需要收集多个勾选了的checkbox信息,则需要设置每个checkbox的value属性,并且与其对应的model在初始化时应该设置为数组形式,比如上面的hobbies:[]
  3. v-model的修饰符:
    1. lazy:当输入框失去焦点后再收集数据
    2. number:将输入的数字从字符串转为数字
    3. trim:将输入的字符串去掉首尾空格

过滤器

需求:把时间戳1685935819555转换为能看懂的日期格式,可以使用计算属性、methods和过滤器实现

<script type="text/javascript" src="../js/dayjs.min.js"></script>
<body>
    <div id="root">
        <h2>显示格式化后的时间</h2>
        <!--计算属性实现-->
        <h2>计算属性实现:{{fmtTime}}</h2>
        <!--methods实现-->
        <h2>methods实现:{{getFormattedTime()}}</h2>
        <!--过滤器实现-->
        <!--不传参使用默认的过滤器-->
        <h2>默认过滤器实现:{{time | timeFormatter}}</h2>
        <!--传参指定过滤器中的参数-->
        <h2>传参过滤器实现:{{time | timeFormatter('YYYY_MM_DD')}}</h2>
        <!--多个过滤器串联调用-->
        <h2>串联多个过滤器实现:{{time | timeFormatter('YYYY_MM_DD') | mySlice}}</h2>
    </div>
    <script type="text/javascript">
        //全局过滤器,所有模板都可以用
        Vue.filter('mySlice',function (value){
            return value.slice(0,4);
        })

        const vm = new Vue({
            el:'#root',
            data:{
                time:1685935819555,          //时间戳
                msg:'你好,ikun'
            },
            computed:{
                fmtTime(){
                    return dayjs(this.time).format('YYYY-MM-DD HH:hh:ss');
                }
            },
            methods:{
                getFormattedTime(){
                    return dayjs(this.time).format('YYYY-MM-DD HH:hh:ss');
                }
            },
            //局部过滤器,只有与当前Vue实例绑定的模板能用
            filters:{
                timeFormatter(timestamp,str='YYYY-MM-DD HH:hh:ss'){
                    return dayjs(timestamp).format(str);
                }
            }
        })
    </script>
</body>

效果:
在这里插入图片描述

Vue内置指令

  1. v-bind:单项数据绑定,数据改变——>页面更新,可简写为:xx,如<input type="text" :value="name>"
  2. v-model:双向数据绑定,数据改变<——>页面更新
  3. v-for:遍历数组、对象
  4. v-on:绑定事件的监听,可以简写为@,如 <button @click="showInfo">点我</button>
  5. v-if、v-else-if、v-else:条件渲染,控制节点是否存在
  6. v-show:条件渲染,控制节点是否展示

v-text

向其作用的节点中渲染文本内容,渲染时会替换掉整个节点中原有的内容,而插值语法{{xx}}可以拼接其他字符串

<body>
    <div id="root">
        <!--这里的div中再写其他字符也不会与msg拼接,而是直接将div中的内容替换为msg-->
        <div v-text="msg"></div>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                msg:'你好,ikun',
            }
        })
    </script>
</body>

v-html

作用和v-text类似,不过v-html可以解析html标签内容并进行渲染,注意不能将用户输入的内容放入v-html中渲染,因为会解析html内容,所以可能出现安全性问题

<body>
    <div id="root">
    	<!--如果msg中有html标签,会被正常解析成html元素-->
        <div v-html="msg"></div>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                msg:'<h1>你好,ikun</h1>>',
            }
        })
    </script>
</body>

在这里插入图片描述

v-cloak

配合css的标签属性选择器,可以控制页面处于阻塞状态时,不显示未被vue解析的页面元素,比如下面的例子中,vue.js文件通过访问某服务器获取,但服务器可能会延迟几秒才返回这个文件,导致{{name}}被加载到页面上,但由于vue.js还未返回,导致不能被正常解析,所以页面就会将{{name}}显示到页面上,这时就可以在这种html标签上使用v-cloak属性,v-cloak属性会在Vue解析当前元素时自动去掉,配合上方的属性选择器,就能控制这些元素在未被vue解析时,不显示在页面上

<head>
    <style>
        [v-cloak]{
            display: none;
        }
    </style>
</head>
<body>
    <div id="root">
        <h1 v-cloak>{{name}}</h1>
        <script type="text/javascript" src="http://xxx.com/vue.js"></script>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                name:'ikun'
            }
        })
    </script>
</body>

v-once

使用该属性的标签,其中只会动态读取一次vue中的data,当后续操作改变其中绑定的data数据时,也不会改变该标签中的内容

<body>
    <div id="root">
        <h1 v-once>count初值为:{{count}}</h1>
        <h1>count现在的值为:{{count}}</h1>
        <button @click="count++">点我count++</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                count:1
            }
        })
    </script>
</body>

在这里插入图片描述

v-pre

如果标签中有v-pre属性,当前标签就不会被vue进行解析,其中的内容直接放到页面上,可以利用其跳过不需要被vue解析的标签的识别,提高页面的加载速度

自定义Vue指令(方法写法)

<body>
    <div id="root">
        <h2>当前count的值:<span v-text="count"></span></h2>
        <!--使用自定义的v-bigi指令,实现数据的放大-->
        <h2>放大10倍后count的值:<span v-big="count"></span></h2>
        <button @click="count++">点我count+1</button>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                count:1
            },
            /**directives中自定义指令的调用时机:
             * 1. 页面刚加载,指令与参数绑定时
             * 2. 页面中需要被vue解析的元素发生改变时,比如data中的值被改变
             */
            directives:{
                /**写成方法形式,适合比较简单的逻辑
                 * element是使用该指令的页面真实dom元素
                 * binding是指使用时传递的参数与该指令的绑定对象,其中value为使用该指令时传入的值
                 */
                big(element,binding){
                    element.innerText = binding.value*10;
                }
            }
        })
    </script>
</body>

在这里插入图片描述

自定义Vue指令(对象写法)

需求:下面的输入框在页面一加载完成就自动获取输入光标,并且其中的值为count的值
在这里插入图片描述

<body>
    <div id="root">
        <h2>当前count的值:<span v-text="count"></span></h2>
        <h2>放大10倍后count的值:<span v-big="count"></span></h2>
        <button @click="count++">点我count+1</button>
        <hr/>
        <!--使用自定义fbind指令-->
        <input v-fbind="count" />
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el:'#root',
            data:{
                count:1
            },
            /**directives中自定义指令的调用时机:
             * 1. 页面刚加载,指令与参数绑定时
             * 2. 页面中需要被vue解析的元素发生改变时,比如data中的值被改变
             */
            directives:{
                /**将自定义big指令写成方法形式,适合比较简单的逻辑
                 * element是使用该指令的页面真实dom元素
                 * binding是指使用时传递的参数与该指令的绑定对象,其中value为使用该指令时传入的值
                 */
                big(element,binding){
                    element.innerText = binding.value*10;
                },
                /**
                 * 将自定义fbind指令写成对象形式,适合调用时间要求严格的逻辑
                 * fbind指令的功能:当页面加载完成后自动获取其输入光标,其中的内容为count的值
                 */
                fbind:{
                    /**
                     *bind方法在自定义指令和参数被绑定时执行
                     */
                    bind(element,binding){
                        element.value = binding.value;
                    },
                    /**
                     * inserted方法在当前element元素被放入页面后才执行
                     */
                    inserted(element,binding){
                        element.focus();
                    },
                    /**
                     * update方法在vue的data中的值被修改,vue重新解析页面的模板时调用
                     */
                    update(element,binding){
                        element.value = binding.value;
                    }
                }
            }
        })
    </script>
</body>

带有自定义Vue指令的标签在页面的解析流程:

  1. 先将自定义指令与传递的参数绑定,调用自定义指令的bind方法
  2. 再将当前带有自定义指令的标签放入页面中,调用自定义指令的inserted方法
  3. 当vue重新解析页面模板时,调用自定义指令的update方法

所以当使用方法写法自定义vue指令时,实际上就是只定义了对象写法中的bind和update方法,并且两个方法的执行内容一致,而没有定义inserted方法,使用自定义指令的对象写法更完整,能够分别定义三个阶段所执行的不同内容

注意:

  1. 如果自定义指令由多个单词组成,则多个单词应该用-分割,并且在directive中定义时,指令名应该写在引号中
  2. directive中所有方法中的this都是Window对象,不是vm实例
  3. 以上的directive的定义方法是局部方法,不能在其他vue实例中使用,要想多个实例通用,需要类似filter,定义全局自定义指令,比如将上面的fbind变成全局
//对象写法
Vue.directive('fbind',{
    bind(element,binding){
        element.value = binding.value;
    },
    inserted(element,binding){
        element.focus();
    },
    update(element,binding){
        element.value = binding.value;
    }
})

//方法写法
Vue.directive('fbind',function(element,binding){
    element.value = binding.value;
})

Vue生命周期及其钩子方法

生命周期方法中的this都指向当前vm实例或组件
请添加图片描述
上图左侧代表Vue中的钩子方法

组件

  1. 使用Vue.extend()创建组件
  2. 组件中的data必须写成方法形式,这样在不同页面引入同一组件并修改组件中data的内容时,不同页面之间才不会相互影响
  3. vm实例使用components配置项对组件进行局部注册,或者使用Vue.component注册全局组件
<body>
    <div id="root" style="background-color: aqua">
        <!--使用局部注册school组件-->
        <school></school>
        <hr/>
        <!--使用全局注册student组件-->
        <student></student>
    </div>

    <div id="root2"  style="background-color: chartreuse">
        <!--使用全局注册student组件-->
        <student></student>
    </div>
    <script type="text/javascript">
        //创建school组件
        const school = Vue.extend({
            template:`
              <div>
                  <h1>学校名称:{{schoolName}}</h1>
                  <h1>学校地址:{{address}}</h1>
                  <button @click="showName">点我提示学校名</button>
              </div>
            `,
            data: function (){
                return{
                    schoolName:"test",
                    address:"成都"
                }
            },
            methods:{
                showName: function (){
                    alert(this.schoolName);
                }
            }
        })
        //创建student组件
        const student = Vue.extend({
            template:`
              <div>
                  <h1>学生姓名:{{studentName}}</h1>
                  <h1>学生年龄:{{age}}</h1>
              </div>
            `,
            data: function (){
                return{
                    studentName:"ikun",
                    age:18
                }
            }
        })
        //注册student组件到全局(全局注册),参数1为注册后的组件名,参数2为组件定义名
        Vue.component('student', student);
        //注册school组件到vm1实例(局部注册)
        const vm1 = new Vue({
            el: '#root',
            components:{
                school
            }
        })
        //vm2不注册组件
        const vm2 = new Vue({
            el: '#root2'
        })
    </script>
</body>

效果:
在这里插入图片描述

组件使用注意

  1. 当注册到vm上的组件的组件名如果由多个单词组成,多个单词小写,且用-连接,如
const vm1 = new Vue({
    el: '#root',
    components:{
    	//school是组件定义时的名字,my-school是组件注册后在#root内使用时的名字
        'my-school': school
    }
})
  1. 组件定义时可以使用name配置项指定使用该组件后,在Vue开发者工具中显示的标签名字,如果没有指定name配置项,则默认为注册到vm实例上时指定的名字,如
<script type="text/javascript">
    //创建school组件
    const school = Vue.extend({
        name:'mySchool',
        //。。。
    })
    //注册school组件到vm1实例(局部注册)
    const vm1 = new Vue({
        el: '#root',
        components:{
            'your-school': school
        }
    })
</script>

在开发者工具中显示的组件名:
在这里插入图片描述

  1. 定义组件时,可以省略Vue.extend,直接传入组件配置对象,如
const school = {
    name:'mySchool',
    template:`
      <div>
          <h1>学校名称:{{schoolName}}</h1>
          <h1>学校地址:{{address}}</h1>
          <button @click="showName">点我提示学校名</button>
      </div>
    `,
    data: function (){
        return{
            schoolName:"test",
            address:"成都"
        }
    }
}

原理:在将组件进行注册时,vue如果发现传入的school是配置对象,在底层还是会自动调用Vue.extend创建组件

组件的嵌套

在实际开发中,vm只注册一个app组件,app组件没有自己数据,专门用于管理其他所有子组件,页面上实际产生效果的子组件都到app中进行注册,结构如下
在这里插入图片描述

<body>
    <div id="root"></div>
    <script type="text/javascript">
        //创建student组件
        const student = Vue.extend({
            template:`
              <div>
                  <h1>学生姓名:{{studentName}}</h1>
                  <h1>学生年龄:{{age}}</h1>
              </div>
            `,
            data: function (){
                return{
                    studentName:"ikun",
                    age:18
                }
            }
        })
        //创建school组件
        const school = Vue.extend({
            template:`
              <div>
                  <h1>学校名称:{{schoolName}}</h1>
                  <h1>学校地址:{{address}}</h1>
                  <!--使用子组件-->
                  <student></student>
              </div>
            `,
            data: function (){
                return{
                    schoolName:"test",
                    address:"成都"
                }
            },
            //将student组件注册到school组件中
            components: {
                student
            }
        })
        //创建hello组件
        const hello = Vue.extend({
            template:`<h1>{{msg}}</h1>`,
            data: function (){
                return{
                    msg:"Welcome!"
                }
            }
        })
        //创建app组件
        const app = Vue.extend({
            //使用hello和school组件
            template:`
              <div>
                <hello></hello>
                <school></school>
              </div>
            `,
            components:{
                school,
                hello
            }
        })

        //注册school组件到vm1实例(局部注册)
        const vm1 = new Vue({
            el: '#root',
            template:`<app></app>`,
            //vm上注册app组件
            components:{
                app
            }
        })
    </script>
</body>

组件的本质

在这里插入图片描述

  1. 通过console.log打印,可以看到定义的Vue组件就是一个名为VueComponent的构造方法,其实这个构造方法是通过Vue.extend所生成的
  2. 因为创建的组件其实是构造方法,所以当注册完组件并且通过标签名使用该组件,比如上面的<school></school>,Vue就会根据组件的构造方法生成组件的实例对象,也就是执行一次组件对应的VueComponent构造方法
  3. 当多次使用Vue.extend创建多个不同的组件时,每个组件都是不同的VueComponent构造方法
  4. 组件中data方法、methods中的方法、watch中的方法、computed中的方法,他们的this均指向该组件自己,即VueComponent实例对象
  5. vm实例和vc(VueComponent)实例并不完全一致,如vm中可以配置el,而vc不行;vm中的data可以写成对象或方法形式,但vc的data只能写成方法形式,vc有的功能vm都有,但vm有的功能vc不一定有

Vue和VueComponent的关系

隐式原型链规则:js实例对象的隐式原型属性(obj.__proto)会指向自己缔造者(指创建js实例对象对应的构造方法)的原型对象(constructor.prototype)
在这里插入图片描述
由于有了以上的黄色箭头指向,所以VueComponent.prototype.__proto===Vue.prototype(VueComponent用调用Vue.extend创建组件时的返回值代替)
在这里插入图片描述
所以在Vue原型对象上的属性或方法(即Vue.protetype.xxx = ??),在vc实例上可以直接获取(即在vc组件实例中:this.xxx)

单文件组件

单文件组件是指每个组件都是一个单独的vue文件,所有组件最后都作为子组件注册到App.vue中,由App组件进行统一管理,然后在main.js中注册App组件,并将其绑定到html页面的指定元素上,结构如下
在这里插入图片描述
School.vue

<template>
  <div class="demo">
      <h1>学校名称:{{schoolName}}</h1>
      <h1>学校地址:{{address}}</h1>
  </div>
</template>

<script>
  export default {
    name:"School",
    data: function (){
      return{
        schoolName:"test",
        address:"成都"
      }
    }
  }
</script>

<style>
.demo{
  background-color: orange;
}
</style>

Student.vue

<template>
  <div>
      <h1>学生姓名:{{name}}</h1>
      <h1>学生年龄:{{age}}</h1>
  </div>
</template>

<script>
  export default {
    name:"Student",
    data: function (){
      return{
        name:"ikun",
        age:"18"
      }
    }
  }
</script>

App.vue:所有组件都注册到App中

<template>
  <div>
    <School></School>
    <Student></Student>
  </div>
</template>

<script>
    import School from './School.vue'
    import Student from './Student.vue'
    export default {
        name:'App',
        components:{
            School,
            Student
        }
    }
</script>

main.js:绑定html页面元素,并注册App组件

import App from './App.vue'
new Vue({
    el: '#root',
    components:{App},
})

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <div id="root">
            <App></App>
        </div>
        <script type="text/javascript" src="../js/vue.js"></script>
        <script type="text/javascript" src="./main.js"></script>
    </body>
</html>

脚手架

安装脚手架

  1. 配置npm淘宝镜像:npm config set registry https://registry.npm.taobao.org
  2. 全局安装脚手架:npm install -g @vue/cli
  3. 切换到工作目录后,创建脚手架项目:vue create xxxx
  4. 启动项目:npm run serve

脚手架文件分析

在这里插入图片描述

  1. babel.config.js:将es6语法转换为ess5
  2. package.json:npm项目配置文件,定义项目的版本、可以使用的命令等
  3. package-lock.json:包版本管理文件,控制项目中所依赖的包的版本
  4. src文件夹:
    1. main.js:项目入口文件,引入App组件,创建Vue实例vm,把App绑定到html页面中id为app的元素中
    2. App.vue:引入components文件夹下的组件
    3. compoonents文件夹:放Vue组件的文件
  5. public文件夹:保存index.html页面
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值