今天开始系统学习vue前端框架. 我是有前端基础的, 刚工作那会, 哪里分那么清楚啊, 前后端我都得做, 所以, css, js, jquery, bootstrap都会点, 还系统学过ext, 哈哈,是不是都不知道是啥, 没事, 都过时了. 现在开始, 学习最流行的Vue, 后端不会页面, 说不过去呀.....
言归正传, Ready, Go!
渐进式框架是说, vue可以作为应用的一部分嵌入.
比如:之前项目使用的是jquery开发的, 项目体量比较大, 现在知道vue使用上,效果上都更方便, 想要替换为vue, 可问题是之前的页面特别多,如果全部替换,工作量太大,那么没关系, vue允许你部分嵌入, 也就是说原来的页面依然使用jquery, 而后开发的页面使用Vuejs. vue可以作为一部分嵌入到项目中. 后面再逐渐替换.
vuejs的安装有三种方式,
<!-- 开发环境 -->
<script src= "https://cdn.jsdelivr.net/npm/vue/dist/vue.js></script>
<!-- 生产环境 -->
<script src= "https://cdn.jsdelivr.net/npm/vue/vue.js></script>
生产环境建议带上版本号, 避免因版本问题产生异常
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
开发环境
https://vuejs.org/js/vue.js
生产环境
https://vuejs.org/js/vue.min.js
我们学习程序, 经典代码helloworld. 这里说一下开发工具, 开发工具建议使用vscode, 因为里面有很多插件, 但是其他也不是不可以哈
我们在感受vue的时候, 为了简单, 方便的体验vue, 我们使用第二种方式(注: 后面详细研究还是会使用npm编译的方式), 下载vue.js, 并引入到项目中. 接下来开始操作.
<html>
<head>
<title>第一个vue程序</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">{{message}}</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello, 盛开的太阳!"
}
});
</script>
</body>
</html>
1 <html>
2
3 <head>
4 <title>第一个vue程序</title>
5 <script src="../js/vue.js"></script>
6 </head>
7
8 <body>
9 <div id="app">{{message}}</div>
10 <script>
11 const app = new Vue({
12 el: "#app",
13 data: {
14 message: "hello, 盛开的太阳!"
15 }
16 });
17 </script>
18 </body>
19 </html>
<html>
<head>
<title>第一个vue程序</title>
<script src="../js/vue.js"></script>
</head>
<body>
</body>
</html>
下面来看一个稍微复杂一点的例子---列表展示
先来看看效果
下面思考, 如果我们使用jquery会如何实现呢? 需要些一个for循环, 然后在里面定义n个li, 然后拼装数据. 很复杂. 然而, 使用vue完全不需要在js代码中拼装html元素的数据, 下面来看看怎么做
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>{{title}}</h1>
<ul>
<li v-for = "item in languages">{{item}}</li>
</ul>
</div>
<script>
const app = new Vue({
el: "#app",
data:{
title: "常见的后端编程语言有哪些?",
languages: ["python", "go", "java", "net", "php", "c++"]
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表页面</title>
<script src="../js/vue.js"></script>
</head>
<body>
</body>
</html>
计数器是一个小的综合案例, 通过这个案例来再次感受一下vue的强大. 我们先来看一下效果
分析: 这里有一个变量, 两个按钮. 点击+, 数字加1, 点击-, 数字减1. 下面我们就来实现这个功能
<button v-on:click="counter++"> + </button>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
当前数字: {{counter}}
<br>
<button v-on:click="add"> + </button>
<button v-on:click="sub"> - </button>
</div>
<script>
const app = new Vue({
el: "#app",
data:{
counter: 0
},
methods: {
add: function() {
console.info("add方法被执行")
this.counter ++;
},
sub: function () {
console.info("sub方法被执行")
this.counter --;
}
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
当前数字: {{counter}}
<br>
<button v-on:click="counter++"> + </button>
<button v-on:click="counter--"> - </button>
</div>
<script>
const app = new Vue({
el: "#app",
data:{
counter: 0
}
});
</script>
</body>
</html>
MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。这样便使得为应用构建UI非常的容易。
MVVM有助于将图形用户界面的开发与业务逻辑或后端逻辑(数据模型)的开发分离开来,这是通过置标语言或GUI代码实现的。MVVM的视图模型是一个值转换器,这意味着视图模型负责从模型中暴露(转换)数据对象,以便轻松管理和呈现对象。在这方面,视图模型比视图做得更多,并且处理大部分视图的显示逻辑。 视图模型可以实现中介者模式,组织对视图所支持的用例集的后端逻辑的访问。
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点
模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
就像在MVC和MVP模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。
视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器,也没有MVP模式的presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
下图不仅概括了MVVM模式(Model-View-ViewModel),还描述了在Vue.js中ViewModel是如何和View以及Model进行交互的。
ViewModel是Vue.js的核心,它是一个Vue实例。Vue实例是作用于某一个HTML元素上的,这个元素可以是HTML的body元素,也可以是指定了id的某个元素。
当创建了ViewModel后,双向绑定是如何达成的呢?
首先,我们将上图中的DOM Listeners和Data Bindings看作两个工具,它们是实现双向绑定的关键。 从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据; 从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。
拿第一个案例来说
<html>
<head>
<title>第一个vue程序</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">{{message}}</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello, 盛开的太阳!"
}
});
</script>
</body>
</html>
在这里, 定义了一个View, 定义了model, 创建了一个Vue实例(view-model), 它用于连接view和model
在创建Vue实例时,需要传入一个选项对象,选项对象可以包含数据、挂载元素、方法、模生命周期钩子等等。
在这个示例中,选项对象的el属性指向View,el: ‘#app’表示该Vue实例将挂载到<div id="app">...</div>
这个元素;data属性指向Model,data: { message: "hello, 盛开的太阳" 表示我们的Model是一个对象。
Vue.js有多种数据绑定的语法,最基础的形式是文本插值,使用一对大括号语法,在运行时{{ message }}会被数据对象的message属性替换,所以页面上会输出”hello, 盛开的太阳!”。
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
比如 created
钩子可以用来在一个实例被创建之后执行代码:
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted
、updated
和 destroyed
。生命周期钩子的 this
上下文指向调用它的 Vue 实例。
注意:
不要在选项 property 或回调上使用箭头函数,比如
created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。
因为箭头函数并没有 this,this 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致
Uncaught TypeError: Cannot read property of undefined 或
Uncaught TypeError: this.myMethod is not a function 之类的错误。
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
如上图, 常用的生命周期函数有: beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestory, destoryed, 这些钩子函数都是回调函数, 在vue生命周期执行过重,方便用户操作控制的入口
我们知道了vue的生命周期了, 接下来看看vue的源码, 对vue的生命周期加深理解
源码下载地址: https://github.com/vuejs/vue
我们选择一个release版本. 下载代码到本地
下载好以后, 打开项目, 我们来看看项目结构.
刚开始, 我们不熟悉, 那么先猜测一下, 哪个是主要文件, 经验告诉我们, src里面的才是主目录, 在src中和核心目录是core.
我们看到了index.js, 通常一个网站的入口是index.html, 而对应的js脚本就是index.js. 打开index.js
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
这里面有两句非常重要的话, 第一句
export default Vue
这句话表示export导出Vue, 我们new的就是这里导出的Vue. 我们看到index.js中没有主逻辑, 那主逻辑在哪里呢? 在第二句话里面:
import Vue from './instance/index'
导入了./instance/index中的文件. 我们来看看这个文件
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
首先, 我们看到定义了一个Vue对象, 在对象里面执行了很多操作, 初始化, 事件监听, 生命周期处理, 渲染等等. 这就是vue的整个流程. 我们进入到initMixin(Vue)初始化方法里面看一下
/* @flow */
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
......
初始化的时候又做了一系列的操作. 注意在方法创建之前有一个钩子函数callHook(vm, 'beforeCreate'), 方法创建之后, 有一个callHook(vm, 'created')函数, 这里可以和上面的生命周期图对比研究, 就能更加熟悉Vue的声明周期了