官网教程:https://cn.vuejs.org/v2/guide/installation.html
[参考资料]http://mp.weixin.qq.com/s/6Mo5csEDVKMq4-v6Yi8ZPQ)
vue的主要作用是当数据发生改变时使用虚拟DOM来更改某一DOM节点,避免将整个页面渲染。
1.关于vue,像他是一种组件化的轻量级框架,它是基于什么前端设计模式的。
vue基于组件化的开发方式,用于构建用户界面的渐进式的js框架,自底层向上逐层应用,
Vue的双向绑定(你可以说这是vue最大的好处)
双向数据绑定是在单向的基础上给可输入元素(input、textare)添加change(input)事件,来动态修改model和view
实现数据绑定的方法:
发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)
MVVM
M - model,指的是模型,也就是数据,V - view,指的是视图,也就是页面展现的部分。通常,我们需要编写代码,将从服务器获取的数据进行“渲染”,展现到视图上。每当数据有变更时,我们会再次进行渲染,从而更新视图,使得视图与数据保持一致。
另一方面,页面也会通过用户的交互,产生状态、数据的变化,这个时候,我们则编写代码,将视图对数据的更新同步到数据,以致于同步到后台服务器。也就是不同的前端 MV* 框架对于这种 Model 和 View 间的数据同步有不同的处理。
VueJS 则使用 ES5 提供的 Object.defineProperty() 方法,监控对数据的操作,从而可以自动触发数据同步。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。
步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
采用数据劫持和发布者-订阅者结合的方式,来做数据绑定,核心是object.defineProperty(),劫持各个属性的getter和setter,在数据模型变化的时候,发布消息给订阅者(绑定了数据模型的DOM元素),触发相应的监听回调。
Object.defineProperty(obj, key, options)
直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。
vueJS采用 ES5 提供的 Object.defineProperty() 方法,监控对数据的操作,从而可以自动触发数据同步。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。
有三个参数,全部都是必填的。参数:
obj:目标对象
key:属性或者方法的名字
options:目标属性所拥有的特性
主要解释第三个参数 {
value:属性的值,
writable:布尔值,规定属性是否可重写, 在值为true的情况下,才能对该值进行重写修改。
configurable:总开关,以第一次设置为准,一旦是false,则其他属性不可设置,
enumerbale:决定属性是否可枚举 ,就是能不能被遍历出来。
get: 是一个函数,返回一个值,会在属性被调用的时候触发。
set: 是一个函数,接收一个新值,会在值被重写或修改的时候触发这个函数
}
因为如果没有明确的设置其他值,默认都是false。
访问器:(set\get)不能和writable/value同时设置 就是会冲突的意思,set和writable都是设置属性值的,所以会冲突 而get和value都是获取属性值的,所以也会冲突(可以先这么理解)
//判断是不是对象
function isObj(obj){
var type = Object.prototype.toString.call(obj);
return type ==='[object Object]'
}
//执行函数
function ObjFun(obj){
if(isObj(obj)){
new Objserver(obj);
}
}
function Observer(obj){
this.data = obj;
this.walk(obj);
}
//监听事件函数:
Observer.prototype.walk = function(obj){
for(var k in obj){
def(obj,k,obj[k])
}
}
function def(obj,k,val){
Object.defineProperty(obj,k,{
configurable:true,
enumerable:true,
get:function(){
console.log('get取值');
return val;
},
set:function(newVal){
if(val === newVal){
return;
}
val = newVal;
console.log('set设置值')
}
});
}
//测试:
var obj = {a:111,b:222};
objFun(obj);
console.log(obj.a)//get取值 222
obj.a = 333;//set设置值
console.log(obj)
装饰者模式
在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责
装饰器模式是一种结构型模式,它与对象的创建无关,主要考虑的是如何拓展对象的功能。也就是说,除了使用线性式(父-子-孙)继承方式之外,我们也可以为一个基础对象创建若干个装饰对象以拓展其功能。然后,由我们的程序自行选择不同的装饰器,并按不同的顺序使用它们。在不同的程序中我们可能会面临不同的需求,并从同样的装饰器集合中选择不同的子集。
观察者模式(发布-订阅模式)
观察者模式是一种行为型模式,主要用于处理不同对象
之间的交互通信问题。观察者模式中通常会包含两类对象。
一个或多个发布者对象:当有重要的事情发生时,会通知订阅者。
一个或多个订阅者对象:它们追随一个或多个发布者,监听它们的通知,并作出
相应的反应
总结: 1.指定一个发布者
2.给发布者添加缓存列表,存放回调函数,通知订阅者
3.发布信息时,发布者遍历缓存表,触发存放的回调函数
监听器Observer和订阅者Watcher
实现简单版Vue的过程,主要实现{{}}、v-model和事件指令的功能
- 监听器Observer
Observer是一个数据监听器,核心是前面一直谈论的Object.defineProperty(),
对所有属性监听,利用递归来遍历所有的属性值,对其进行Object.defineProperty()操作:
实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性
订阅者Watcher
Watcher将数据监听器和指令解析器连接起来,数据的属性变动时,执行指令绑定的相应回调函数,如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。
Watcher在初始化的时候要将自己添加进订阅者Dep中,如何做到:
已经知道监听器Observer是在get函数执行了添加订阅者Wather的操作的,
所以我们只要在订阅者Watcher初始化的时候触发对应的get函数,去执行添加订阅者操作即可,
那要如何触发get的函数:
只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了Object.defineProperty()进行数据监听。
注意:
我们只要在订阅者Watcher初始化的时候才需要添加订阅者,所以需要做一个判断操作,
因此可以在订阅器上做一下手脚:在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了。
创建一个watcher.js
指令解析器complie
指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher
Vue对生命周期的理解
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
创建前/后: 在beforeCreated阶段,vue实例的挂载元素 el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了, e l 和 数 据 对 象 d a t a 都 为 u n d e f i n e d , 还 未 初 始 化 。 在 c r e a t e d 阶 段 , v u e 实 例 的 数 据 对 象 d a t a 有 了 , el还没有。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
钩子函数
一个指令定义对象可以提供一下几个钩子函数:
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
钩子函数的参数(el、binding、vnode、oldVnode):
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:一个对象,包含以下属性:
name:指令名,不包括 v- 前缀。 value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。 oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。 arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。 modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
注意:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
vue的双向绑定
Vue的父子组件之间的传值
组件通信
父与子通信
发送: '<son myName='zhangsan'></son>'
接收到json组件:
Vue.component('son',{
props:['myName'],
template:`
<p>{{myName}}</p>
`
})
子与父通信
绑定:
methods:{
handleEvent:function(msg){}
}
<son @customEvent="handleEvent"></son>
触发:子组件内部:
this.$emit(‘customEvent’,100);
reference(引用、参考)
帮助在父组件中 得到子组件中的数据、方法。
指定ref属性 ‘<son ref="mySon"></son>’
根据ref得到子组件实例:
this.$refs.mySon
$parent
this.$parent得到父组件的实例
兄弟组件通信
var bus = new Vue();
接收方:
bus.$on(‘eventName’,function(msg){})
发送方:
bus.$emit(‘eventName’,123);
不相关组件之间传递数据
组件B $emit触发事件:
import Bus from './bus.js'
byBus:function(){
Bus.$emit('byBus',this.byBusData)
}
组件A $on接受事件传递数据
data(){
},
created(){
Bus.$on('byBus',(data)=>{
this.busData = data
})
},
Vue的路由关系
路由模块本质是建立起url和页面之间的映射关系
router路由器
route路由
routes路由数组(路由字典)
路由模块的基本使用
* 引入 vue.js vue-router.js
* 指定一个容器 ‘’
* 创建业务所需要用到的组件类‘var MYLogin = Vue.component()’
* 配置路由字典
const
myRoutes = [
{path:'',component:MyLogin},
{path:'/login',component:MyLogin}
];
new Vue({
router:myRouter
})
- 测试
修改地址栏中的路由地址,测试看加载的组件是否正确
注意事项:
1.先引入vue,再引入插件
2.一定要指定router-view
3.route路由 {path:'',component:}
routes:路由数组 []
router:路由器,按照指定的路由规则去访问对应的组件 new VueRouter
使用路由模块来实现页面跳转的方式
方法1: 直接修改地址栏
方法2: this.$router.push(‘路由地址’);
方法3: ‘’
完成参数传递
在页面之间跳转的时候,在有些场景下,需要同时指定参数
明确发送方和接收方
list --20--> detail
配置接收方的路由地址
/detail --》 /detail/:index
this.$route.params.index
发送
routerLink to="/detail/20"
this.$router.push('/detail/20')
路由嵌套
在一个路由中,path对应一个component,如果这个component需要根据不同的url再加载其他的component,称之为路由的嵌套
eg:A组件现在需要根据不同的url,加载B组件或者C组件
给A组件指定一个容器
<router-view></router-view>
配置路由词典
{
path:'/a',
component:A,
children:[
{path:'/b',component:B}
]
}
Vue的页面之间传值跳转
路由传值
在跳转页面时,在标签中用‘’标签
this.$router.push({
name: 'routePage',
query/params: {
routeParams: params
}
})
实用params去传值的时候,在页面刷新时,参数会消失,用query则不会有这个问题。
通过 parent, p a r e n t , chlidren等方法调取用层级关系的组件内的数据和方法
this.$parent.$data.id //获取父元素data中的id
this.$children.$data.id //获取父元素data中的id
用起来比较灵活,但是容易造成代码耦合性太强,导致维护困难
通过eventBus传递数据
使用前可以在全局定义一个eventBus
window.eventBus = new Vue();
在需要传递参数的组件中,定义一个emit发送需要传递的值,键名可以自己定义(可以为对象)
eventBus.$emit('eventBusName', id);
在需要接受参数的组件重,用on接受该值(或对象)
//val即为传递过来的值
eventBus.$on('eventBusName', function(val) {console.log(val)})
最后记住要在beforeDestroy()中关闭这个eventBus
eventBus.$off('eventBusName');
如何去引入组件
VUEX
vuex是一个专门为vue.js设计的集中式状态管理架构,集中存储和管理应用的所有组件状态。
vuex将组件公用数据抽离,在一个公共仓库管理,使得各个组件容易获取(getter)数据,也容易设置数据(setter)。
webpack
webpack搭建vue项目步骤
吗
webpack搭建vue项目步骤
webpack怎么部署 打包过程 优化
创建项目:
mkdir vue-demo
cd vue-demo
用npm init命令生成package.json文件
npm init
生成package,json文件
引入webpack
npm install webpack --save-dev
在项目中创建webpack.config.js文件
const path = require('path')
module.exports ={
entry:'./src/main.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:"demo.js"
}
}
vue的基本构造
1.vue组件的重要选项(属性)
new Vue({
data:{
a:1,
b:[]
},
methods:{
doSomething:function(){
console.log(this.a)
}
},
watch:{
'a':function(val,oldVal){
console.log(val,oldVal)
}
}
})
data:对象的数据,用对象a,b对HTML中的a,b进行双向绑定,
methods:对象的方法,包括的是方法,针对的是a
watch:进行的是对象监听,针对a进行新值和旧值的监听
2.模板指令(常用的几种)
作用:将HTML和vue连接起来,写在HTML中
- 数据渲染:v-text、v-html、{{}} (进行自动更新)
<p>{{ a }}</p>
<p v-text="a"></a>
<p v-html="a"></a>
new Vue({
data:{
a:1,
b:[]
}
})
控制模块隐藏:v-if、v-show
v-show用css中的display:none来隐藏元素
<p v-if="isShow"></p>
<p v-show="isShow"></p>
new Vue({
data:{
isShow: true
}
})
渲染循环列表 v-for
HTML中不需要定义多个列表,只需要传入多个数据,则V-for命令会直接对数据的选项列表进行渲染
<div id="app">
<ul>
<li v-for="val,key in fruits">
{{val}}=>{{key}}
</li>
</ul>
</div>
document.addEventListener('DOMContentLoaded',function(){
var vm = new Vue({
el:'#app',
data:{
fruits:['apple','banana','orange','pear']
}
});
},false);
事件绑定 v-on
第二行为简写形式,dothis是在methods中获取方法
<button v-on:click="doThis"></button>
<button @click="diThis"></button>
methods:{
doThis:function(someThing){
}
}
属性绑定 v-bind
第一条:对src属性赋值(字符串)
第二条:简写方式;对对象class进行绑定,isRead是对是否有class的一个判断(布尔值)
第三条:是一个数组,表示两个class,分别展现data给classA和classB所赋的值(字符串)
第四条:{classB: isB,classC: isC }中isB和IsC表示classB和classCss是否会展现
<img v-bind:src="imageSrc">
<div :class="{red:isRed}"></div>
<div :class="[classA,classB]"></div>
<div :class="[classA,{classB: isB,classC: isC }]"></div>
vue的简单尝试
1.第一个例子
渲染出多个li标签, 并用true 和false来设定标签的样式
<template>
<div id="app">
<h1 v-html="title"></h1>
<ul>
<li v-for="item in items" v-bind:class="{finished:item.isFinished}">
{{item.label}}
</li>
</ul>
</div>
</template>
<script>
export default {
data: function(){
return{
title: '<span>?</span>this is a todo list2',
items: [
{
label: 'coding',
isFinished: false
},
{
label:'walking',
isFinished: true
}
],
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.finished{
text-decoration: underline;
}
</style>
* v-html和 v-text的区别:v-html可以将<span>解析为一个标签;而v-text将<span>解析为字符串即效果为“<span>?</span>this is a todo list2”
* v-for:将li标签渲染成两个li;v-bind:给li标签添加一个带有下划线的属性,并且下划线的显示
2.第二个例子
显示一个输入框,将输入的值变为一个li列表
<div id="app">
<h1 v-html="title"></h1>
<input v-model="newItem" v-on:keyup.enter="addNew"/>
<ul>
<li v-for="item in items" v-bind:class="{finished:item.isFinished}" v-on:click="toggleFinish(item)">
{{item.label}}
</li>
</ul>
</div>
<script>
export default {
data: function(){
return{
title: '<span>?</span>this is a todo list2',
items: [
],
newItem:''
}
},
methods:{
toggleFinish: function (item) {
console.log(item.isFinished = !item.isFinished)
},
addNew: function () {
this.items.push({ //实现输入一个新值,在下方出现
label:this.newItem,
isFinished: false
});
this.newItem=''
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.finished{
text-decoration: underline;
}
</style>
- v-on:click 添加一个绑定事件method–>toggleFinished(item),实现点击一次下划线消失,再点击一次又出现
- v-model:表单控件双向绑定
- input–>v-on:methods–>addNew获取并打印出newItem的值
localstorage存储todolist
输入的内容在刷新后不会消失,并且获取到页面上
<div id="app">
<h1 v-html="title"></h1>
<input v-model="newItem" v-on:keyup.enter="addNew"/>
<ul>
<li v-for="item in items" v-bind:class="{finished:item.isFinished}" v-on:click="toggleFinish(item)">
{{item.label}}
</li>
</ul>
</div>
import Store from './store' //es6写法
console.log(Store);
export default {
data: function(){
return{
title: '<span>?</span>this is a todo list2',
items: Store.fetch(),
newItem:''
}
},
watch:{
items:{
handler:function(items){
Store.save(items)
},
deep:true
}
},
methods:{
toggleFinish: function (item) {
console.log(item.isFinished = !item.isFinished)
},
addNew: function () {
this.items.push({
label:this.newItem,
isFinished: false
});
this.newItem=''
}
}
}
store.js
const STORAGE_KEY='todos-vuejs';
export default {
fetch:function () {
return JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]')
},
save:function (items) {
window.localStorage.setItem(STORAGE_KEY,JSON.stringify(items))
}
}
* 在App.vue同级处新建一个store.js文件,作用是作为一个存储器,将输入的内容存储起来,防止刷新页面的时候丢失
* watch 对象:观察方法,handler 为深层赋值方法,给store传入items
划分组件
vue模板
1.html模板
2.字符串标签
template字符串
<script type="x-template" id="str">
<p>
我是一个标签
</p>
</script>
</head>
<body>
<div id="box">
</div>
</body>
document.addEventListener('DOMContentLoaded',function(){
var vm = new Vue({
el:'#box',
template:'#str'
}) ;
},false);
从demo图中可以看出,box中添加了script中的p标签,p标签替换了box中的内容
<body>
<template id="tem">
<p>我是template</p>
</template>
<div id="box">
</div>
</body>
这个demo中,template标签的p标签代替了div标签
3.render函数
<style type="text/css">
.bg{
background-color: yellow;
}
</style>
</head>
<body>
<div id="box">
</div>
document.addEventListener('DOMContentLoaded',function(){
var vm = new Vue({
el:'#box',
render(createrElement){
return createrElement(//调用一个函数
"ul",
{
class:{bg:true},//给UL添加dom属性和绑定事件等,相当于v-bind:class="{bg:true}"
style:{fontSize:"50px"},
attrs:{
abc:"Datura"
},
domProps:{
innerHTML:"<li>我是HTML</li>"//这个优先级比下面的createrElement高,此时下面效果无效
}
},
[
createrElement("li",1),
createrElement("li",2),
createrElement("li",3),
]
);
}
});
},false);
vue的数据同步
1.数据绑定
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
2.双向绑定
<div id="app">
{{message}}
<br>
<input type="text" placeholder="请输入" v-model="message"/>
</div>
document.addEventListener('DOMContentLoaded',function () {
//vm实例
var vm = new Vue({
el: '#app', //挂载元素
data:{
message:'hello,Datura!!!'
}
});
},false);
自定义指令
1.全局指令
可以让所有人使用
<div class="app">
<div v-color="colorStatus">我是一个普通div元素</div>
</div>
document.addEventListener('DOMContentLoaded',function (){
Vue.directive('color',function(el,binding){
console.log(el);
console.log(binding);
el.style.backgroundColor = 'lawngreen';
});
var vm = new Vue({
el:'.app',
data:{
colorStatus:true
}
});
},false);
2.局部指令
当前组件可用
<script type="text/javascript">
document.addEventListener('DOMContentLoded',function(){
var vm = new Vue({
el:'.app',
data:{
colorStatus:true
},
directives:{
'color':function(el,binding){
el.style.backgroundColor = 'green';
}
}
}) ;
},false);
</script>
</head>
<body>
<div class="app">
<div v-color="colorStatus">我是一个普通div元素</div>
</div>
</body>
- 没有运行成功,找不到原因。。。心塞
- 比较全局指令和局部指令,局部指令多有了一个directives,但是仅仅是把el和binding封装在里面