vue



写在开头,学习一门技术最好沉浸在官网: vue官方文档

前端三要素

HTML(结构):超文本标记语言(Hyper Text Markup Language),决定网页的结构和内容

CSS(表现):层叠样式表(Cascading Style Sheets),设定网页的表现样式

JavaScript(行为):是一种弱类型脚本语言,其源代码不需经过编译,而是由浏览器解释运行,

用于控制网页的行为

前后端分离演变历史

MVC:

为了降低开发的复杂度,以后端为出发点,比如:Struts2、SpringMVC 等框架的使用,就是后端的 MVC

时代;

优点

MVC 是一个非常好的协作模式,能够有效降低代码的耦合度,从架构上能够让开发者明白代码应该写在

哪里。为了让 View 更纯粹,还可以使用 Thymeleaf、Freemarker 等模板引擎,使模板里无法写入 Java

代码,让前后端分工更加清晰。

缺点

前端开发重度依赖开发环境,开发效率低,这种架构下,前后端协作有两种模式:

第一种是前端写 DEMO,写好后,让后端去套模板。好处是 DEMO 可以本地开发,很高效。不足是还需要后端套模板,有可能套错,套完后还需要前端确定,来回沟通调整的成本比较大;

另一种协作模式是前端负责浏览器端的所有开发和服务器端的 View 层模板开发。好处是 UI 相关的代码都是前端去写就好,后端不用太关注,不足就是前端开发重度绑定后端环境,环境成为影响前端开发效率的重要因素。

前后端职责纠缠不清:模板引擎功能强大,依旧可以通过拿到的上下文变量来实现各种业务逻辑。

这样,只要前端弱势一点,往往就会被后端要求在模板层写出不少业务代码。还有一个很大的灰色地带是 Controller ,页面路由等功能本应该是前端最关注的,但却是由后端来实现。

Controller 本身与 Model 往往也会纠缠不清,看了让人咬牙的业务代码经常会出现在Controller 层。这些问题不能全归结于程序员的素养,否则 JSP 就够了。

对前端发挥的局限性:性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作,但由于后端框架限制,我们很难使用 CometBigPipe 等技术方案来优化性能。

基于 AJAX 带来的 SPA 时代

时间回到 2005 年 AJAX (Asynchronous JavaScript And XML,异步 JavaScript 和 XML,老技术新用法)

被正式提出并开始使用 CDN 作为静态资源存储,于是出现了 JavaScript 王者归来(在这之前 JS 都是用 来在网页上贴狗皮膏药广告的)的 SPA(Single Page Application)单页面应用时代。

这种模式下,前后端的分工非常清晰,前后端的关键协作点是 AJAX 接口。看起来是如此美妙,但回过头来看看的话,这与 JSP 时代区别不大。复杂度从服务端的 JSP 里移到了浏览器的 JavaScript,浏览器端变得很复杂。类似 Spring MVC,这个时***始出现浏览器端的分层架构MVC,MVP,MVVM

缺点

前后端接口的约定: 如果后端的接口一塌糊涂,如果后端的业务模型不够稳定,那么前端开发会很痛苦;不少团队也有类似尝试,通过接口规则、接口平台等方式来做。**有了和后端一起沉淀的接口规则 ,还可以用来模拟数据,使得前后端可以在约定接口后实现高效并行开发

前端开发的复杂度控制: SPA 应用大多以功能交互型为主,JavaScript 代码过十万行很正常。大量 JS 代码的组织,与 View 层的绑定等,都不是容易的事情。

前端为主的 MV* 时代

此处的 MV* 模式如下:

  • MVC(同步通信为主):Model、View、Controller

  • MVP(异步通信为主):Model、View、Presenter

  • MVVM(异步通信为主):Model、View、ViewModel

为了降低前端开发复杂度,涌现了大量的前端框架,比如: AngularJS 、 React 、 Vue.js 、

EmberJS 等,这些框架总的原则是先按类型分层,比如 Templates、Controllers、Models,然后再在层内做切分,如下图:

优点

前后端职责很清晰: 前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful。

前端开发的复杂度可控: 前端代码很重,但合理的分层,让前端代码能各司其职。这一块蛮有意思的,简单如模板特性的选择,就有很多很多讲究。并非越强大越好,限制什么,留下哪些自由, 代码应该如何组织,所有这一切设计,得花一本书的厚度去说明。

部署相对独立: 可以快速改进产品体验

缺点

代码不能复用。比如后端依旧需要对数据做各种校验,校验逻辑无法复用浏览器端的代码。如果可以复用,那么后端的数据校验可以相对简单化。全异步,对 SEO 不利。往往还需要服务端做同步渲染的降级方案。

性能并非最佳,特别是移动互联网环境下。

SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌 控。

NodeJS 带来的全栈时代

前端为主的 MV* 模式解决了很多很多问题,但如上所述,依旧存在不少不足之处。随着 NodeJS 的兴起,JavaScript 开始有能力运行在服务端。

但是这与jsp有什么区别呢。

为什么要使用 Vue.js

  • 轻量级,体积小是一个重要指标。Vue.js 压缩后有只有 30kb (Angular 压缩后 56kb+,React 压缩后 44kb+)

  • 移动优先。更适合移动端,比如移动端的 Touch 事件

  • 易上手,学习曲线平稳,文档齐全

  • 吸取了 Angular( 模块化 )和 React( 虚拟 DOM )的长处,并拥有自己独特的功能,如: 计算属 性开源,社区活跃度高。

Vue.js属于MVVM架构中的viewmodel

Vue.js 的两大核心要素

数据驱动

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用

Object.defineProperty 把这些属性全部转为 getter/setter 。Object.defineProperty ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通

知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你

可能需要安装 vue-devtools 来获取更加友好的检查接口。

每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖

项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

组件化

  • 页面上每个独立的可交互的区域视为一个组件

  • 每个组件对应一个工程目录,组件所需的各种资源在这个目录下就近维护

  • 页面不过是组件的容器,组件可以嵌套自由组合(复用)形成完整的页面

试水(入门代码)

引用vue,可以直接使用官方的cdn:

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

也可以自己下载到本地,放到项目中:这里用hbuilder,直接给我弄好环境了。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	</head>
	<body>
		<div id="app">
			<p>{
  {msg}}</p>
		</div>
		<script type="text/javascript">
		  var app=new Vue({
				el:"#app",
				data:{
					msg:"ppl沙雕"
				}
				});
			
		</script>
	</body>
</html>

vue对象:

  • el:绑定的元素,实例是绑定id为app的元素,此元素中可以直接使用vue对象中data定义的变量名取到值
  • data: 数据,绑定元素内可用变量名访问
  • methods:元素内使用:v-on:事件 绑定事件属性名
  • 钩子函数

生命周期

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载 DOM、渲染→更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。通俗说就是 Vue 实例从创建到销毁的过程,就是生命周期。

在 Vue 的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册 JS 方法,可以让我们用自己注册的 JS 方法控制整个大局,在这些事件响应方法中的 this 直接指向的是 Vue 的实例。

作为扑街后端,这里我们不需要了解那么多,mounted:初始化函数。

beforeCreate

在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。

created

实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和

方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

beforeMount

在挂载开始之前被调用:相关的 render 函数首次被调用。

mounted

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。

beforeUpdate

数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,

这不会触发附加的重渲染过程。

updated

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情

况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不

被调用。

beforeDestroy实例销毁之前调用。在这一步,实例仍然完全可用。

destroyed

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件***会被移除,所

有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

语法使用

条件判断

v-if:相当于java的if

v-else-if:相当于java的else if

v-if:相当于java的else

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	</head>
	<body>
		<div id="app">
		<p v-if="grade<0">小老弟这分数不对啊</p>
		<p v-else-if="grade<60">小老弟不行啊</p>
		<p v-else-if="grade<80">小老弟很一般啊</p>
		<p v-else-if="grade<=100">小老弟可以啊</p>
		<p v-else>你这分数不对啊</p>
		</div>
		<script>
			var app=new Vue({
				el:"#app",
				data:{
					grade: 80
				}
			});
		</script>
	</body>
</html>

由于vue的数据在控制台不好看,这里我们搜索vue插件:vue devtools

在控制台分别修改为59,101,100

的,但是实例就懒得弄了。

列表遍历

v-for=“表达式”,类似增强for循环,表达式:item in items

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>遍历</title>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	</head>
	<body>
		<ol id="ol">
			<li v-for="one in pandas">{
  {one}}</li>
		</ol>
		<ul id="table">
					<li v-for="ap in pandasObj">{
  {ap.name}}---{
  {ap.age}}</li>
				</ul>
	</body>
	<script>
		var ol=new Vue({
			el:"#ol",
			data:{
				pandas:[
					'胖大海',
					'雪宝',
					'金虎'
				]
			}
		});
		var table=new Vue({
			el:"#table",
			data:{
				pandasObj:[
					{
						name:'胖大海',
						age:3
					},{
						name:'雪宝',
						age:2
					},{
						name:'金虎',
						age:4
					}
				
				]
			}
		});
	</script>
</html>

计算属性

计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 响应函数操作的数据 还没有发生改变,多次访问 cul 计算属性会立即返回之前的计算结果,而不必再次执行函数,而这里时间函数明显没有调用data中数据,所以它结果不会变。

这里我们做个对比:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>计算属性</title>
		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
	</head>
	<body>
		<div id="app">
			普通:{
  {pt()}}<br />
			计算:{
  {cul}}
		</div>
	</body>
	<script>
		var app=new Vue({
			el:"#app",
			data:{
				num:0
			},
			methods:{
				pt : function(){
					return new Date();
				}
			},
			computed:{
				cul:function(){
					return new Date();
				}
			}
		});
	</script>
</html>

普通方法调用两次,数据都不一样,而计算属性,计算值两次都一样。

监听属性

这里使用官方的说法。

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。

<div id="demo">{
  { fullName }}</div>
		<script>var vm = new Vue({
		  el: '#demo',
		  data: {
		    firstName: 'Foo',
		    lastName: 'Bar',
		    fullName: 'Foo Bar'
		  },
		  watch: {
		    firstName: function (val) {
		      this.fullName = val + ' ' + this.lastName
		    },
		    lastName: function (val) {
		      this.fullName = this.firstName + ' ' + val
		    }
		  }
		})</script>

在控制台修改firstName或lastName,fullName的值都会变。当值改变时,属性同名的监听方法就会被调用,效果类似于计算属性:

<div id="demo">{
  { fullName }}</div>
		<script>
		var vm = new Vue({
		  el: '#demo',
		  data: {
		    firstName: 'Foo',
		    lastName: 'Bar'
		  },
		  computed: {
		    fullName: function () {
		      return this.firstName + ' ' + this.lastName
		    }
		  }
		})
		</script>

简洁了很多。

双向数据绑定

使用v-model表单数据绑定vue对象中data的属性,data的属性又有其他引用

<input v-model="text" type="text" /><br />
			<p>{
  {text}}</p>
		</div>
		<script type="text/javascript">
		  Vue.component("luoweifei",{
				props: ['url','netname'],
				template: "<li><a :href='url'>{
  {netname}}</a></li>"
			});
		  var app=new Vue({
				el:"#app",
				data:{
					text:""
				}
				});
			
		</script>

取值

标签外取值:

<div id="app">
			<p>{
  {msg}}</p>
		</div>
		<script type="text/javascript">
		  var app=new Vue({
				el:"#app",
				data:{
					msg:"ppl沙雕"
				}
				});
			
		</script>

属性取值

<div id="app">
			<a :href="url">{
  {msg}}</a>
		</div>
		<script type="text/javascript">
		  var app=new Vue({
				el:"#app",
				data:{
					msg:"我的git",
					url:"http://lwfqw.git"
				}
				});
			
		</script>

:href等同于v-bind:href

可以绑定vue中的data到属性中。

路由

设置路由,每个路由有下列属性:

name:取的名字

path:路由路径

component: 页面模块(vue文件)

props: true (允许通过属性传值)

children:子路由,该页面中的router link标签path对应的路由

路由映射

vue中js和vue文件都可以作为模块被导入。导入的vue文件可以作为路由对象中的component的值与path属性唯一映射路由。

import Vue from "vue";
import Router from "vue-router";
import Login from "../components/Login";
import Main from "../components/main";
import HelloWorld from "../components/HelloWorld";
import userinfo from '../components/userinfo';
import userlist from '../components/user/list';
import jingduShow from '../components/jindu/show';
import jdadd from '../components/jindu/jdadd';

Vue.use(Router);

export default new Router({
  //路由模式,history模式没有#,hash模式有#,默认
  mode: 'history',
  routes: [
    //登录
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    //首页
    {
      path: '/hello',
      name: 'hello',
      component: HelloWorld,
      children: [
        {
          path: '/userinfo/:id',
          name: 'userinfo',
          component: userinfo,
          props: true
        },{
          path:'/userlist',
          name: 'userlist',
          component: userlist
        },{
          path:'/show',
          name:'jingdushow',
          component:jingduShow
        },{
          path:'/jdadd',
          name:'jdadd',
          component:jdadd
        }
      ]
    },
    {
      path: '/main',
      name: 'Main',
      component: Main
    },
    //重定向到main
    {
      path: '/goMain',
      redirect: '/main'
    }

  ]
})

跳转页面

跳转同级: 通过this.$router.push("/hello");跳转到同级的/hello;

页面某部位显示子路由页面:<router-link to="/userlist">用户列表</router-link>显示页面(/userlist对应路由映射vue文件)到<router -view />中;注意:使用router-link,to属性对应的路由必须为主体页面(router-link标签所在页面)的子路由。

携带值挑战页面或路由

路径携参:

路由中: {
path: ‘/userinfo/:id’,
name: ‘userinfo’,
component: userinfo,
props: true
}

:id就是占位

目标路径页面获取值jsthis.$route.params.id,html{ {$router.params.id}}

props携参,修改上面代码为

使用params

路由中: {
path: ‘/userinfo/:id’, //:id占位
name: ‘userinfo’,
component: userinfo,
props: true
}

:id就是占位

目标路径页面获取值jsthis.$route.params.id,html{ {$router.params.id}}

使用query

不需要占位

路由中: {
path: ‘/userinfo’,
name: ‘userinfo’,
component: userinfo,
props: true
}

目标路径页面获取值jsthis.$route.query.id,html{ {$router.query.id}}