vue和react都实现了一套虚拟DOM,不直接操作DOM元素,只操作数据便可以重新渲染页面
使用传统jquery库操作dom都是整块整块的操作,虚拟dom通过js模拟dom结构,使用diff算法对比dom,局部更新dom,提高浏览重绘能力。
使用snabbdom实现vdom,关键API为:1、h函数创建节点;2、patch函数渲染dom
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.0/h.js"></script>
<script type="text/javascript">
var snabbdom = window.snabbdom
// 定义关键函数 patch
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
// 定义关键函数 h
var h = snabbdom.h
// 原始数据
var data = [
{
name: '张三',
age: '20',
address: '北京'
},
{
name: '李四',
age: '21',
address: '上海'
},
{
name: '王五',
age: '22',
address: '广州'
}
]
// 把表头也放在 data 中
data.unshift({
name: '姓名',
age: '年龄',
address: '地址'
})
var container = document.getElementById('container')
// 渲染函数
var vnode
function render(data) {
var newVnode = h('table', {
}, data.map(function (item) {
var tds = []
var i
for (i in item) {
if (item.hasOwnProperty(i)) {
//是否是自己创建的而不是原型里的
tds.push(h('td', {
}, item[i] + ''))
}
}
return h('tr', {
}, tds)
}))
if (vnode) {
// re-render
patch(vnode, newVnode)
} else {
// 初次渲染
patch(container, newVnode)
}
// 存储当前的 vnode 结果
vnode = newVnode
}
// 初次渲染
render(data)
var btnChange = document.getElementById('btn-change')
btnChange.addEventListener('click', function () {
data[1].age = 30
data[2].address = '深圳'
// re-render
render(data)
})
</script>
</body>
</html>
在vue中模块的引入是用es6的语法,import...from...
响应式:vue如何监听数据变化
通过object.defineProperty,接收三个参数(代理到的对象、数据的key,对象里面是get、set方法)来劫持各个属性的 setter / getter,在数据变动时发布消息给订阅者,触发相应的监听回调
数据对象定义的属性都是静态的,for循环的时候使用key就会命中闭包注意下面的处理方法
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>Object.defineProperty test</p>
<p>模拟</p>
<script type="text/javascript">
// var obj = {
// name: 'zhangsan',
// age: 25
// }
// console.log(obj)
// var obj = {}
// var _name = 'shangsan'
// Object.defineProperty(obj, 'name', {
// get: function () {
// console.log('get', _name) // 监听
// return _name
// },
// set: function (newVal) {
// console.log('set', newVal) // 监听
// _name = newVal
// }
// })
// var vm = new Vue({
// el: '#app',
// data: {
// name: 'zhangsan',
// age: 20
// }
// })
var vm = {
}
var data = {
name: 'zhangsan',
age: 20
}
var key, value
for (key in data) {
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get', data[key]) // 监听
return data[key]
},
set: function (newVal) {
console.log('set', newVal) // 监听
data[key] = newVal
}
})
})(key)
}
</script>
</body>
</html>
模版引擎及渲染:vue模版如何渲染成html
模版本质就是字符串、有逻辑,html是静态的没有逻辑,在vue源码搜索code.render 大约一万行的位置,在var code = ......;下面打印code.render,能看到模版的渲染
_c相当于vdom中的h函数、_v相当与vdom中的patch函数
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>to-do-list by vue</title>
<script src="./vue-2.5.13.js"></script>
</head>
<body>
<div id="app">
<div>
<input v-model="title">
<button v-on:click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">{
{
item}}</li>
</ul>
</div>
</div>
<script type="text/javascript">
// data 独立
var data = {
title: '',
list: []
}
// 初始化 Vue 实例
var vm = new Vue({
el: '#app',
data: data,
methods: {
add: function () {
this.list.push(this.title)
this.title = ''
}
}
})
/*
with(this){ // this 就是 vm
return _c(
'div',
{
attrs:{"id":"app"}
},
[
_c(
'div',
[
_c(
'input',
{
directives:[
{
name:"model",
rawName:"v-model",
value:(title),
expression:"title"
}
],
domProps:{
"value":(title)
},
on:{
"input":function($event){
if($event.target.composing)return;
title=$event.target.value
}
}
}
),
_v(" "),
_c(
'button',
{
on:{
"click":add
}
},
[_v("submit")]
)
]
),
_v(" "),
_c('div',
[
_c(
'ul',
_l((list),function(item){return _c('li',[_v(_s(item))])})
)
]
)
]
)
}
*/
</script>
</body>
</html>
数据、视图、vmodel他把数据和视图关联、区别于传统的mvc模式 View 和 ViewModel 之间通过双向绑定(data-binding)建立联系。与 MVC 不同的是,它没有 Controller 层,而是演变为 ViewModel。ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来
内置组件:router-view keep-active transition slot
Vue双向绑定的实现原理Vue使用的发布订阅模式,是点对点的绑定数据。Vue的data里,每个属性都有set和get属性,像下面的name值每次改变都必须经过set,其他方式是改变不了它的,相当于一个万能的监听器
var Coder = function () {
var that = this;
return {
get name() {
if (that.name) {
return that.name
}
return '你还没有取名'
},
set name(val) {
console.log('你把名字修成了' + val)
that.name = val
}
}
}
var isMe = new Coder()
console.log(isMe.name)
isMe.name = '神'
console.log(isMe.name)
console.log(isMe)
//你还没有取名
//你把名字修成了神
//神
//name: "神" get name: ƒ name() set name: ƒ name(val)__proto__: Object
在<template> 元素上使用 v-if 条件渲染分组,相当于小程序上的block
v-if vs v-show 的区别
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
vm.$set 实例方法替换数组、对象
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
用 v-on 指令监听 DOM 事件
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
}
动画&过渡
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
Vue 提供了 过渡模式
in-out:新元素先进行过渡,完成之后当前元素过渡离开。
out-in:当前元素先进行过渡,完成之后新元素过渡进入。
用 out-in 重写之前的开关按钮过渡:
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
指令v-el的作用,作为 Vue 实例的挂载目标
Vue中常用的生命周期钩子函数
beforecreate : 举个栗子:可以在这加个loading事件
created :在这结束loading,还做一些初始化,实现函数自执行
mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情
beforeDestroy: 你确认删除XX吗? destroyed :当前组件已被删除,清空相关内容
beforeCreate () {
console.log(this.$el, 'beforeCreate')
},
created () {
console.log(this.$el, 'created')
},
beforeMount () {
console.log(this.$el, 'beforeMount')
// 渲染dom 可以拿到$el的实例
},
mounted () {
console.log(this.$el, 'mounted')
},
beforeUpdate () {
console.log(this, 'beforeUpdate')
},
updated () {
console.log(this, 'updated')
},
activated () {
// 在组件章节讲解 keep-alive
console.log(this, 'activated')
},
deactivated () {
// 在组件章节讲解 keep-alive
console.log(this, 'deactivated')
},
beforeDestroy () {
console.log(this, 'beforeDestroy')
},
destroyed () {
console.log(this, 'destroyed')
},
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
DOM 渲染在 mounted 中就已经完成了
axios的特点有哪些
一、Axios 是一个基于 promise 的 HTTP 库,支持promise所有的API
二、它可以拦截请求和响应
三、它可以转换请求数据和响应数据,并对响应回来的内容自动转换成 JSON类型的数据
四、安全性更高,客户端支持防御 XSRF
Vuex的原理和使用方法
文章下面有详细的代码演示vuex的使用流程
所有组件的数据中心,做状态管理。
一个实例化的Vuex.Store由state, mutations和actions三个属性组成,
• state中保存着共有数据
• 改变state中的数据可以通过mutations方法,同步处理
• 如果要写异步的方法,需要写在actions中
keep-alive
当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行,在keep-alive激活会触发activated钩子函数
同样也存在一个问题就是被keep-alive包裹的组件我们请求获取的数据不会再重新渲染页面:
<keep-alive include="bookLists,bookLists">
<router-view>router-view>
keep-alive>
<keep-alive exclude="indexLists">
<router-view>router-view>
keep-alive>
include属性表示只有name属性为bookLists,bookLists的组件会被缓存,(注意是组件的名字,不是路由的名字)其它组件不会被缓存exclude属性表示除了name属性为indexLists的组件不会被缓存,其它组件都会被缓存
利用meta属性:
export default[
{
path:'/',
name:'home',
components:Home,
meta:{
keepAlive:true //需要被缓存的组件
},
{
path:'/book',
name:'book',
components:Book,
meta:{
keepAlive:false //不需要被缓存的组件
}
]
<keep-alive>
<router-view v-if="this.$route.meat.keepAlive"></router-view>
<!--这里是会被缓存的组件-->
</keep-alive>
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!-- 这里是会被缓存的视图组件 -->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 这里是不被缓存的视图组件 -->
</router-view>
被包裹在keep-alive中的组件的状态将会被保留,例如我们将某个列表类组件内容滑动到第100条位置,那么我们在切换到一个组件后再次切换回到该组件,该组件的位置状态依旧会保持在第100条列表处,产品可能会要求在每一次进入一个组件时页面的初始位置都是保持在顶部的,这里可以利用Vue中的滚动行为,但是前提是你是HTML5 history模式history.pushState
创建一个router实例的时候,可以提供一个scrollBehavior方法
v-model 是一个语法糖,可以拆解为 props: value 和 events: input
v-model语法糖如下面的代码
<input
:value="something"
@:input="something = $event.target.value">
实际应用:父子组件通信,一般都是单项的;通过v-model原理实现双向通信
一般都会有一个 currentValue 的内部 data,初始时从 value 获取一次值,当 value 修改时,也通过 watch 监听到及时更新;组件不会修改 value 的值,而是修改 currentValue
<template>
<div>
<button @click="increase(-1)">减 1</button>
<span style="color: red;padding: 6px">{
{
currentValue }}</span>
<button @click="increase(1)">加 1</button>
</div>
</template>
<script>
export default {
name: 'InputNumber',
props: {
value: {
type: Number
}
},
data () {
return {
currentValue: this.value
}
},
watch: {
value (val) {
this.currentValue = val;
}
},
methods: {
increase (val) {
this.currentValue += val;
this.$emit('input', this.currentValue);
}
}
}
</script>
上面的数字选择器组件可以有下面两种使用方式:
<template>
<InputNumber v-model="value" />
</template>
<script>
import InputNumber from '../components/input-number/input-number.vue';
export default {
components: {
InputNumber },
data () {
return {
value: 1
}
}
}
</script>
或
<template>
<InputNumber :value="value" @input="handleChange" />
</template>
<script>
import InputNumber from '../components/input-number/input-number.vue';
export default {
components: {
InputNumber },
data () {
return {
value: 1
}
},
methods: {
handleChange (val) {
this.value = val;
}
}
}
</script>
如果你不想用 value
和 input
这两个名字,从 Vue.js 2.2.0 版本开始,提供了一个 model
的选项,可以指定它们的名字,所以数字选择器组件也可以这样写
<template>
<div>
<button @click="increase(-1)">减 1</button>
<span style="color: red;padding: 6px">{
{
currentValue }}</span>
<button @click="increase(1)">加 1</button>
</div>
</template>
<script>
export default {
name: 'InputNumber',
props: {
number: {
type: Number
}
},
model: {
prop: 'number',
event: 'change'
},
data () {
return {
currentValue: this.number
}
},
watch: {
value (val) {
this.currentValue = val;
}
},
methods: {
increase (val) {
this.currentValue += val;
this.$emit('change', this.currentValue);
}
}
}
</script>
.sync 修饰符
<template>
<div>
<button @click="increase(-1)">减 1</button>
<span style="color: red;padding: 6px">{
{
value }}</span>
<button @click="increase(1)">加 1</button>
</div>
</template>
<script>
export default {
name: 'InputNumber',
props: {
value: {
type: Number
}
},
methods: {
increase (val) {
this.$emit('update:value', this.value + val);
}
}
}
</script>
<template>
<InputNumber :value.sync="value" />
</template>
<script>
import InputNumber from '../components/input-number/input-number.vue';
export default {
components: {
InputNumber },
data () {
return {
value: 1
}
}
}
</script>
===========v-model 在一个组件中只能有一个,但 .sync 可以设置很多个=============
vue 跳转方式
router-link-active类为路由激活是的状态,
在路由文件中添加redirect定义默认页面
router-link默认是a标签,我们通过tag指定为div .router-link-active这个class是组件自带的
注意下面的注释,如何定义动态路由以及获取传来的参数
<router-link :to="{name:'home', params: {id:1}}">
// params传参数 (类似post)
// 路由配置 path: "/home/:id" 或者 path: "/home:id"
// 不配置path ,第一次可请求,刷新页面id会消失
// html 取参 $route.params.id
// script 取参 this.$route.params.id
<router-link tag="div" class="tab-item" to="/recommend">
<span class="tab-link">推荐</span>
</router-link>
<router-link :to="{name:'home', query: {id:1}}">
// query传参数 (类似get,url后面会显示参数)
// query通过url传参,刷新页面还在
// html 取参 $route.query.id
// script 取参 this.$route.query.id
this.$router.push
跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面
this.$router.replace
跳转到指定url路径,但是history栈中不会有记录,
点击返回会跳转到上上个页面 (就是直接替换了当前页面)
this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数
路由全局的钩子
router.beforeEach((to, from, next) => {
--做数据的校验 验证页面需要用户登录才能访问--
console.log('before each invoked')
next()
})
router.beforeResolve((to, from, next) => {
console.log('before resolve invoked')
next()
})
router.afterEach((to, from) => {
console.log('after each invoked')
})
$route是路由对象里面主要包含路由的一些基本信息,$router是VueRouter的实例包含了一些路由的跳转方法,钩子函数
应用内部的钩子
export default {
metaInfo: {
title: 'The Todo App'
},
// 路由加载完成
beforeRouteEnter (to, from, next) {
console.log('todo before enter', this)
next(vm => {
console.log('after enter vm.id is ', vm.id)
})
},
// 如果此组件 是一个公共组件 反复进入(详情页)才会触发 路由更新
beforeRouteUpdate (to, from, next) {
console.log('todo update enter')
next()
},
// 如果用户的表单 信息修改没有保存 提示用户是否保存 再离开页面
beforeRouteLeave (to, from, next) {
console.log('todo leave enter')
next()
},
$set
export default {
data () {
return {
item: {
a: 1
}
}
},
methods: {
handler () {
this.$set(this.item, 'b', 2); // 是响应性的
}
}
}
还有一种小技巧,就是先 copy 一个数组,然后通过 index 修改后,再把原数组整个替换,比如:
handler () {
const data = [...this.items];
data[1] = 'x';
this.items = data;
}
计算属性computed
侦听属性watch
是否支持异步是它们最大的区别
watch: {
obj: {
handler(newVal, objVal) {
// if(newVal.obj || objVal.obj != objVal.obj){
// console.log(22); //控制台并没有打印 监测为对象的时候,newVal == oldVal
// }
console.log(33) //打印为33
},
deep:true
}
}
通过其他的数据算出一个新数据,而且它有一个好处就是,它把新的数据缓存下来了,当其他的依赖数据没有发生改变,它调用的是缓存的数据,这就极大的提高了我们程序的性能,而如果写在methods里,数据根本没有缓存的概念,所以每次都会重新计算
但大多数时候,我们只是用它默认的 get 方法,也就是平时的常规写法
事实上可以写为一个 Object,而非 Function,只是 Function 形式是我们默认使用它的 get 方法,当写为 Object 时,还能使用它的 set 方法
computed: {
fullName: {
get () {
return `${
this.firstName} ${
this.lastName}`;
},
set (val) {
const names = val.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
}
}
<custom-component @click.native="xxx">内容</custom-component>
v-model修饰符
事件修饰符
<p v-on="{click:clickfn,mousemove:mousemovefn}">p>
<p @click="fn1(),fn2()">点击p>
为了保证组件的独立性 和 可 复用性,data 是一个函数,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次,就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们
主要的区别是,actions 可以执行异步。actions 是调用 mutations 可以批量修改mutations,而 mutations 来修改 store。
父组件是通过 prop 把数据传递到子组件的,但是这个 prop 只能由父组件修改,子组件不能修改,否则会报错。子组件想修改时,只能通过 $emit
派发一个自定义事件,父组件接收到后,由父组件修改。
slot也是 一种单向流动
nextTick 是 Vue.js 提供的一个函数,并非浏览器内置。nextTick 函数接收一个回调函数 cb
<template>
<div>
<p v-if="show" ref="node">内容</p>
<button @click="handleShow">显示</button>
</div>
</template>
<script>
export default {
data () {
return {
show: false
}
},
methods: {
handleShow () {
this.show = true;
console.log(this.$refs.node); // undefined
this.$nextTick(() => {
console.log(this.$refs.node); // 内容
});
}
}
}
</script>