前端开发框架—Vue

本文围绕Vue.js展开,介绍其实现原理为数据劫持与发布 - 订阅模式。阐述生命周期各阶段及钩子函数顺序,对比Computed与Watch,讲解组件定义、通信方式,包括父子、兄弟组件传参。还提及插槽、代码优化方式、插件使用,最后对比MVC与MVVM开发模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、实现原理

数据劫持:Vue是采用数据劫持+发布-订阅者模式实现的,使用Object.defineProperty()来劫持各个属性的getter和setter,在数据变动时,通知给各个订阅者,触发相应的回调。

二、生命周期

beforeCreate:创建之前,此时还没有data与method
created: data与method有值,但还没有el(undefined)
beforeMount: 页面渲染之前,此时el有值,即挂载的dom元素
Mounted: 页面渲染之后,此时会用数据替换掉dom元素双括号内的变量
beforeUpdate: data改变,对应的组件重新渲染之前
Updated: 对应的组件重新渲染之后
beforeDestroy: 实例销毁之前,此时实例仍然可以使用
Destoryed: 实例销毁之后

<body>
    <div id="app">
        <p>{{message}}</p>
        <button @click="changeMsg">改变</button>
    </div>
</body>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world'
        },
        methods: {
            changeMsg () {
                this.message = 'goodbye world'
            }
        },
        beforeCreate() {
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeUpdate () {
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        updated() {
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el)
        }
    })
</script>

生命周期钩子函数的执行顺序?

  1. 首先是数据加载渲染到页面,包括初始化阶段和挂载阶段
    Vue实例化(new Vue())-> 父组件beforeCreate -> 父组件 created -> 父组件 beforeMount -> 子组件 beforeCreate -> 子组件 created -> 子组件 beforeMount -> 真实DOM挂载完毕 -> 子组件 mounted -> 父组件 mounted

  2. 其次当data里的数据发生了变化,进入更新阶段
    父组件 beforeUpdate -> 子组件 beforeUpdate -> 子组件 updated -> 父组件 updated -> 获取更新后的真实DOM

  3. 最后当$destroy()被调用进入销毁阶段
    父组件 beforeDestroy -> 子组件 beforeDestroy -> 实例销毁后 -> 子组件 destroyed -> 父组件 destroyed

三、Vue.nextTick(callback)

应用:

  1. 在数据改变后需要执行某些操作,而这些操作需要使用随数据改变而改变的DOM结构时,这些操作都需要放在nextTick回调函数中。
  2. 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。
  3. 通常ajax发送请求,放在created函数中
<div class="app">
<!--使用 ref 可以获取页面dom元素 this.$refs.msgDiv-->
  <div ref="msgDiv">{{msg}}</div>   
  <div v-if="msg1">Message got outside : {{msg1}}</div>
  <div v-if="msg2">Message got inside : {{msg2}}</div>
  <div v-if="msg3">Message got outside : {{msg3}}</div>
  <button @click="changeMsg">
    Change the Message
  </button>
</div>
<script>
new Vue({
  el: '.app',
  data: {
    msg: 'Hello Vue.',
    msg1: '',     // 空字符串为false
    msg2: '',
    msg3: ''
  },
  methods: {
    changeMsg() {
      this.msg = "Hello world."
      this.msg1 = this.$refs.msgDiv.innerHTML
      this.$nextTick(() => {
        this.msg2 = this.$refs.msgDiv.innerHTML
      })
      this.msg3 = this.$refs.msgDiv.innerHTML
    }
  }
})
</script>

四、Computed VS Watch

相同点:Computed与watch 都以Vue的依赖追踪机制为基础,当一个数据发生变化时,依赖这些数据的相关数据也会自动发生变化。
不同点:

  1. computed 计算属性。当它依赖的数据(可以是单个,也可以是多个)发生变化时,会重新调用get来计算,默认只有get, 如果需要也可以自己加上set;watch是监听器,可以监听某一数据,当数据发生变化时触发相应的操作。
  2. computed支持缓存,当其依赖的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值;watch不支持缓存,当对应属性发生变化的时候,响应执行。
  3. computed不支持异步,有异步操作时无法监听数据变化;watch支持异步操作。
  4. computed第一次加载时就监听;watch默认第一次加载时不监听。
var vm = new Vue({
        el: '#app',
        data: {
            firstName: 'Foo',
            lastName: 'Bar',
            fullName: 'Foo Bar'
        },
	    computed: {
      	  fullName: {
            	// getter
            	get: function () {
               		 return this.firstName + ' ' + this.lastName
           	 },
            	// setter
            	set: function (newValue) {
                		var names = newValue.split(' ')
                		this.firstName = names[0]
                		this.lastName = names[names.length - 1]
            	}

            }

        }
        watch: {
            firstName: function (newVal,oldVal) {
                this.fullName = newVal + ' ' + this.lastName
                console.log(newVal+'========='+oldVal)//Fooaaaa=========Foo
            },
            lastName: function (newVal,oldVal) {
                this.fullName = this.firstName + ' ' + newVal
                console.log(newVal+'========='+oldVal)//Baraaaa=========Bar
            }
        }
    })

五、组件

定义:组件的本质就是自定义标签。将一些公共的模块(比如头部、尾部、上传图片等)抽取出来,写成单独的组件,在需要的页面中直接引入即可,提高了代码的复用率。每一个.vue文件都可以视为一个组件。

  1. 全局组件
    所有Vue实例都可使用。
    语法:Vue.component(‘组件名’,{options}); 组件名使用驼峰命名法,如testName,
    标签使用test-name.
<div id="app">
	<test-name></test-name>
</div>
	
<!--定义外部模板-->
<template id='temp'>
	<div>   <!--如果模板中有多个标签定义,必须用父元素包裹-->
	<h1>名字:{{name}}</h1>
	<p @click="changeName()"> 改变名字 </p>
	</div>
</template>

<script>
// 注册全局组件
Vue.component('testName', {
    template:'#temp',     //template: '<h1>{{name}}</h1>'
    data() {               // data在组件中必须使用函数形式
		return {
		name:'小明'
		}
	},
	methods:{
		changeName(){     //注意函数写法:函数名(){函数体}
		console.log(111)
		this.name="小红";
		}
	}
})
	
// 创建根实例
new Vue({
  el: '#app'
})
</script>
  1. 局部组件
    只在当前vue实例有效,使用components属性
<div id="app">
	<demo></demo>
</div>
	
<!--定义外部模板-->
<template id='temp'>
	<div>   <!--如果模板中有多个标签定义,必须用父元素包裹-->
		<h1>名字:{{name}}</h1>
		<p @click="changeName()"> 改变名字 </p>
	</div>
</template>
<script>
new Vue({
  el: '#app',
  components:{
	  "demo":{
		  template:'#temp',
		  data(){
			  return { name : "小青" }
		  },
		  methods:{
		  	changeName(){
	  			this.name="小紫";
  			}
	  	  }
	  }
 }
})
</script>
  1. 父子组件
    组件嵌套组件。
    注意:父子组件之间的作用域完全独立,数据不互通
<div id="app">
	<parent-item></parent-item>
</div>
	
<!--父组件模板-->
	<template id='parent'>
		<div>   
			<h1>父组件:{{data}}</h1>
			<!--子组件必须在父组件模板中调用-->
			<son-item></son-item>
		</div>
	</template>
	
<script>
new Vue({
  el: '#app',
  components:{
	  "parentItem":{
		  template:'#parent',
		  data(){
			  return { data : "父组件数据" }
		  },
		  components:{
			  "sonItem":{
				  template:"<h2>子组件:{{data}}</h2>",
				  data(){
					  return { data : "子组件数据" }
				  }
			  }
		  }
	  } 
  }
})
</script>

六、如何进行父子组件之间的通信?

父组件通过props向子组件传递数据,子组件通过事件向父组件发送数据

  1. 父向子传递数据
    第一步:子模板内定义变量。 {{msg}}
    第二步:子组件添加props属性。[“msg”]
    第三步:父模板内,向子组件定义的变量传参。 v-bind:msg 简写 :msg
    v-bind 绑定元素属性,并为其传参
<div id="app">
	<parent-item></parent-item>
</div>
	
<!--父组件模板-->
	<template id='parent'>
	<div>   
	<h1>父组件:{{data}}</h1>
	<!— v-bind:msg1 简写形式 :msg1 ,msg1 msg2是传参,msg3是死数据-->
	<!—第3步:传参 -->
	<son-item :msg1="msg1" v-bind:msg2="msg2" msg3="数据3"></son-item>
	</div>
	</template>
	<template id='son'>
		<div>   
		<h2>子组件:{{data}}</h2>
		<p>{{msg1}}</p>   //第一步: 子模板内定义变量
		<p>{{msg2}}</p>
		<p>{{msg3}}</p>
		</div>
	</template>
	
<script>
new Vue({
  el: '#app',
  components:{
	  "parentItem":{
		  template:'#parent',
		  data(){
			  return { 
				  data : "父组件数据" ,
				  msg1:"数据11",
				  msg2:"数据22",
			  }
		  },
		  components:{
			  "sonItem":{
				  template:"#son",
				  data(){
					  return { data : "子组件数据" }
				  },
				  props:["msg1","msg2","msg3"]  
				//第二步:子组件中加入props属性
			  }
		  }
	  } 
  }
})
</script>
  1. 子向父传递数据
    第一步:子组件通过this.$emit(“事件名”,参数),触发事件。
    第二部: 父组件通过 v-on:事件名 =‘callback’, 监听事件,触发回调函数 (简写:@事件名)
<div id="app">
	<parent-item></parent-item>
</div>
	
<!--父组件模板-->
<template id='parent'>
<div>   
<h1>父组件:{{data}}</h1>                              // 3
<son-item :msg1="msg1" :msg2="msg2" :msg3="msg3" v-on:change="change" ref=”aaa”></son-item>
</div>
</template>
<template id='son'>
<div>   
	<h2>子组件:{{data}}</h2>
	<p>{{msg1}}</p>
	<p>{{msg2}}</p>
	<p>{{msg3}}</p>
	<p @click="changeData">改变父组件数据</p>  // 1
	</div>
</template>
	
<script>
new Vue({
  el: '#app',
  components:{
	  "parentItem":{
		  template:'#parent',
		  data(){
			  return { 
				  data : "父组件数据" ,
				  msg1:"数据11",
				  msg2:"数据22",
				  msg3:"数据33",
			  }
		  },
		  methods:{
                                // 4
			  change(newdata1,newdata2){
				  console.log(newdata1,newdata2);
				  this.data=newdata2;
				//this.$refs.aaa.data 访问子组件
			  }
		  },
		  components:{
			  "sonItem":{
				  template:"#son",
				  data(){
					  return { 
						  data : "子组件数据",
						  data2:"修改父组件数据2",
						  data3:"修改父组件数据3"
					  }
				  },
				  props:["msg1","msg2","msg3"],
				  methods:{
 					//2
					  changeData(){
						  console.log(this.data2);
			this.$emit("change",this.data2,this.data3);  
					  }
				  }
			  }
		  }
	  } 
  }
})
</script>

父组件可以通过refs访问子组件。
vue使用自己的本身的属性,前面加$

七、兄弟组件之间传参

  1. 事件总线

在main.js中定义$bus

 beforeCreate(){
        Vue.prototype.$bus = this    //安装全局事件总线,$bus 就是当前应用的vm
}

然后可以通过this. b u s . bus. bus.on()监听事件,This. b u s , bus, bus,emit() 发起一个事件
2. Vuex

Vuex是专为vue开发的一个状态管理模式,为了解决不同vue组件间的共享数据问题。
适用场景:多个视图行为要更改统一数据,同一数据要被多个视图使用,(中大型单页面应用)
状态 data 视图 template 操作 methods
单一状态树
State 存放公共数据的地方
Getter 从state中派生出一些状态(根据一些业务场景,对state进行处理)
Mutations 不能直接对state进行修改,必须通过提交mutations来进行修改,同步的
Actions 异步处理,通过分发操作触发mutations
Mudule 将store分割,减少代码臃肿

Ajax 如果需要,一般写在actions里面
兄弟组件之间有大量通信的,建议一定要用vuex

八、插槽

作用:在组件内部定义一些标签,这些标签将无法显示,如果要显示需要在组件模板内使用接受
类型:默认插槽,具名插槽,作用域插槽(带数据的插槽)

<div id="app">
	<runoob>
		<h2>插槽内容1</h2>
		<h2>插槽内容2</h2>
		<h2 slot='slot1'>具名插槽</h2>
	</runoob>
</div>
<template id='temp'>
	<div>
		<h1>父组件:{{data}}</h1>
		<!--<slot></slot> --> <!--默认插槽-->
		<slot><h2>插槽备胎</h2></slot> 
		<slot  name='slot1'></slot>
	</div>
</template>
<script>
new Vue({
  	el: '#app',
	components:{
		'runoob':{
			template:'#temp',
			data(){
				return {
					data:'父组件数据'
				}
			}
		}
	}
}) 
</script>

作用域插槽:带数据的插槽,子组件负责提供数据,父组件负责数据的显示格式。也是子组件向父组件传递数据的一种方法。
注意:使用作用域插槽,需要Vue版本在2.6以后。

<div id="app">
	<parent></parent>
</div>
<!--父组件-->
<template id='parentTemp'>
	<div>
	<h1>父组件:{{data}}</h1>
	<son>  <!—必须用template包围-->
		<template slot-scope="propName">
			<ul>
			<li v-for="color in propName.data">{{color}}</li>
			</ul>
		</template>
	</son>
	</div>
</template>
	<!—子组件模板-->
<template id='sonTemp'>
	<div>
		<h1>子组件:{{data}}</h1>
		<slot :data='arr'></slot>
	</div>
</template>
	
<script>
new Vue({
  	el: '#app',
	components:{
		'parent':{
			template:'#parentTemp',
			data(){
				return {
					data:'父组件数据'
				}
			},
			components:{
				'son':{
					template:'#sonTemp',
					data(){
						return {
							data:'子组件数据',
				arr:["red","blue","green","black"]
						}
					}
				}
			}
		}
	}
})
</script>

九、Vue代码优化方式

  1. 区分使用v-if 和v-show
    相同点:v-if和v-show都可用来控制这个控件显示或不显示
    区别:
    (1)v-if 多用于条件判断,每次一切换都会重新渲染,重新走一次vue生命周期。具有较高的切换代价;v-show 用于条件隐藏,它只是把控件的样式display:none, 不会增删dom元素,也不会重走生命周期,可以提高性能;
    (2)v-if更适合于带有权限的操作,渲染时判断权限数据,有则展示该功能,没有则删除;v-show更适合于日常使用,可以减少数据的渲染,减少不必要的操作
  2. 区别使用computed 和watch
  3. 使用Object.freeze() 冻结vue中不需要修改的属性,从而阻止数据劫持,减少组件初始化时间。
  4. 图片懒加载,当图片滚动到可视区域时再去加载
  5. 路由懒加载

十、插件

插件与组件的区别:
vue 插件通常是为了给vue增加全局功能,是对vue功能的增强或补充,比如 Vue-router;组件是为了构成业务模块的。将一些公共的模块抽取出来,封装成组件。

Vue-router主要用于在单页面应用中,每个视图的展示都需要配备一个特殊的URL来实现切换效果,页面的刷新,前进,后退都不会向服务器发送请求,都需要通过这个特殊的URL来实现
实现过程:
1.定义组件
2.将路径与组件匹配起来 【{path:’/home’,component:home},】创建routers对象
3.使用路由,借助于router-link、router-view>使用路由,router-link会默认渲染成a标签,router-view会根据当前的路径,动态渲染出不同的组件。页面切换其实就是router-view挂载的dom元素的切换
实现单页面应用路由有两种形式:hash和history,vue-router默认使用的是hash实现的
路由懒加载,就是将路由对应的组件打包成一个个代码块,在这个路由被访问的时候才进行加载
ES6实现路由懒加载: const Home=()=>impot(‘…/components/Home.vue’)

十一、MVC 与MVVM

  1. MVC
    Model 模型,是程序用于处理数据逻辑的部分,模型对象通常负责在数据库中存取数据
    View:视图,负责依据模型数据渲染页面
    Controller :控制器,是模型数据与视图之间通信的桥梁,负责从视图读取数据,控制用户输入,并向模型发送数据。同时负责将Model的数据用View显示出来.
    优点:耦合度低,重用性高,代码部署快,可维护性高
    缺点:比较适用于大中型项目。视图与模型虽然分离,但联系密切,妨碍了它们的独立重用。降低了视图对模型数据的访问,依据模型操作接口的不同,视图可能多次调用才能获得足够的显示数据
  2. MVVM
    Model 模型,数据部分
    View 视图,页面
    ViewModel 视图模型,是mvvm的核心,是model与view之间的桥梁。负责
    模型转化为视图,主要通过数据绑定来实现的;视图转化为模型,主要通过DOM事件监听来实现的。两种转化都实现,被称之为双向数据绑定。
    数据绑定:当model数据改变时,不需要手动操作DOM元素改变view的显示,view会自动更新。(典型例子:Vue)
    低耦合,可重用,可测试
  3. 比较
    MVVM主要解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度变慢,影响用户体验,vue数据驱动,通过数据来显示视图层而不是节点操作。数据操作比较多的场景,需要大量操作DOM元素时,采用MVVM的开发方式,会更加便捷。MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值