Vue自定义指令
Vue 前端工程师必须技能之一
vue基础使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 这里的变量就是vm 实例的属性 -->
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>性别:{{gender}}</p>
</div>
<script>
// vm 是 vue 的实例
const vm = new Vue({
el: '#app', // el 挂载
data: {
name: 'fqniu',
age: 18,
gender: '男'
}
})
console.log(vm)
/**
架构分层
* MVC
* Model 数据层
* View 视图层
* Controller 控制层 业务逻辑处理(M和V之间的连接器)
* 缺点:依赖复杂
* MVP
* Model 数据层
* View 视图层
* Presenter 可以理解为松散的控制器,其中包含了视图的 UI 业务逻辑,所有从视图发出的事件,
都会通过代理给 Presenter 进行处理;同时,Presenter 也通过视图暴露的接口
与其进行通信。
* MVVM
* Model 数据层
* View 视图层
* ViewModel 类似与MVP中的Presenter,唯一的区别是,它采用双向绑定:View的变动,
自动反映在 ViewModel,反之亦然
*/
</script>
</body>
</html>
架构模式
复杂的软件必须有清晰合理的架构,更容易开发、维护和测试。
MVC
MVC模式的意思是,软件可以分成三个部分。
- 模型(Model):数据处理
- 视图(View):数据展示
- 控制器(Controller):业务逻辑处理(M和V之间的连接器)
- View 传送指令到 Controller(用户发送指令)
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈
- 缺点:依赖复杂
- View 依赖Controller和Model
- Controller依赖View和Model
MVP
MVP 架构模式是 MVC的改良模式(改进Controller, 把Model和View完全隔离开)
- Model
- View
- Presenter 可以理解为松散的控制器,其中包含了视图的 UI 业务逻辑,所有从视图发出的事件,都会通过代理给 Presenter 进行处理;同时,Presenter 也通过视图暴露的接口与其进行通信。
MVVM
由MVP模式演变而来
- Model
- View
- ViewModel 类似与MVP中的Presenter,唯一的区别是,它采用双向绑定:View的变动,自动反映在 ViewModel,反之亦然
- 核心思想:关注Model的变化,让MVVM框架利用自己的机制去自动更新DOM,从而把开发者从操作DOM的繁琐中解脱出来!
响应式数据的原理
/**
响应式数据的原理:
* 响应式属性:能监听到修改操作,并更新视图 ==== 数据更新,页面跟着更新
* 如何设置一个属性为响应式属性(属性特性)
* 值属性:拥有值的属性
* configurable 可配置性(其他属性特性的总开关)
* enumerable 可枚举性
* writable 可写性
* value 属性的值
* 存储器属性:本身没有值,一般用于代理其他数据
* configurable
* enumerable
* get
* set
* 获取属性特性:
* Object.getOwnPropertyDescriptor(target,pro)
* Object.getOwnPropertyDescriptors(target)
* 设置属性特性:
* Object.defineProperty(target,prop,descriptor)
* Object.defineProperties(target,descriptor)
特别说明:
1. 传统方式添加的属性,所有属性特性默认为true
2. 通过Object.defineProperty()添加的属性,所有属性特性默认为false
*/
// 1、值属性
const data = {
name: 'fqniu',
age: 18
}
// 传统添加属性的方式
data.gender = '男'
// 2、通过Object.definePropery(target,prop,descriptor)
// target: 目标对象
// prop: 目标属性
// descriptor: 属性特性
// descriptor: 属性特性的方法:
// 1、writable 目的:控制data 里面某个属性不可更改 比如data.age
Object.defineProperty(data, 'age', {
writable: false
})
// 2、enumerable 目的:控制data某个属性不可枚举 比如data.password
Object.defineProperty(data, 'password', {
enumerable: false
})
// 循环遍历对象
for (let key in data) {
console.log('key=', key)
console.log('data=', data[key])
}
// key= name
// key= age
// key= gender
// 发现并没有打印出 password 属性
// 传统的添加属性方式
// data.job="front end" //job的所有属性特性为true
// console.log(data)
// 注意 password 属性的颜色,明显是淡色 而且__proto__也是淡色
// 而且淡色的属性是不会遍历出来的
// Object.defineProperty() 添加的属性
Object.defineProperty(data, 'job', {
value:'front end', //等效于data.job = "front end"
configurable:true
})
console.log(data)
// 特别说明:
// 1. 传统方式添加的属性,所有属性特性默认为true
// 2. 通过Object.defineProperty()添加的属性,所有属性特性默认为false
存储器属性
传统方法 添加存储器属性 和 Object.defineProperty() 添加存储器属性
// 存储器属性
const user={
name:'fqniu',
// gender:'女',
// 在传统对象中 添加存储器属性 gender
get gender(){
return '男'
},
set gender(newval){
console.log('gender.setter=',newval)
}
}
// console.log(user)
const newUser={
age:25
}
// 通过添加 Object.defineProperty() 添加 age 属性
Object.defineProperty(user,'age',{
get(){
console.log('getter')
return newUser.age
},
set(newValue){
console.log('setter=',newValue)
newUser.age=newValue
}
})
// console.log(user)
// user.age 读取时,获取getter 方法 打印getter
// user.age = 18 读取时,获取setter 方法 打印setter
注意:此时newUser里面的属性已被改变。
响应式属性的原理
// 响应式属性的原理:把值属性变为存储器属性 ( getter && setter)
const obj = {
name: 'fqniu',
age: 25
}
box.innerHTML = obj.name
const newObj = {}
// 把值属性变为存储器属性
for (let key in obj) {
Object.defineProperty(newObj, key, {
get() {
return obj[key]
},
set(newVal) {
box.innerHTML = newVal
obj[key] = newVal
}
})
}
console.log(obj)
console.log(newObj)
/*
通过 改变 newObj 这个对象里面的值 去改变 obj 里面的属性的值
这就是 vue 里面的实例 vm 内部做的事情 ,也就是viewmodel 来同时 改变 数据层 和 视图层
MVVM 中的 VM 检测 数据层 和 视图层 的改变
*/
// vue 响应式数据
const initData = {
name: 'fqniu',
age: 25,
score: {
eng: 92,
math: 96
}
}
const vm = new Vue({
el: '#app',
data: initData
})
console.log(vm)
// Vue.set(vm.score, 'chinese', 86);
vue2.x数组中哪些方法是响应式的(vue3.0已经解决)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in arr">{{item}}</li>
</ul>
<button @click="btnclick">按钮</button>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好,世界!',
arr: ['a', 'b', 'c', 'd']
},
methods: {
btnclick() {
// 数组中响应式方法有:
// 1、添加元素从最后一个
this.arr.push('111', '222');
// 2、添加元素从第一个
this.arr.unshift('aaa', 'bbb');
// 3、删除元素从最后一个
this.arr.pop('ccc', 'ddd');
// 4、删除元素从第一个
this.arr.shift('aaa', 'bbb');
// 5、splice作用:删除元素、插入元素、替换元素
// 删除元素:
this.arr.splice(1);//表示从第一个开始,后面的全部删除
this.arr.splice(1, 2);//表示从第一个开始,删除后面的两个
// 替换元素:
this.arr.splice(1, 4, 'e', 'f', 'g', 'h');//表示从第一个开始,替换后面4个值内容
// 插入元素:
this.arr.splice(1, 0, 'e', 'f', 'g', 'h');//表示从第一个开始,0代表不删除元素,后面是添加的元素
//6、sort 排序
this.arr.sort();
//7、reverse 反转
this.arr.reserve();
//------ 注意:通过索引值修改数组中的元素,不是响应式的,界面没有改变----------------
this.arr[0] = 'aaaa';
// 1、响应式可以修改数组中的元素:通过splice
this.arr.splice(0, 1, 'aaaa');
//2、 响应式可以修改数组中的元素:set(要修改的元素,索引值,修改后的值)
Vue.set(this.arr, 0, 'aaaa');
}
}
// 也就是上面说的,通过索引值修改数组中的元素,不是响应式的,界面没有改变
vm.arr[0] = 'e'; // 这种做不到响应式 数据更新,视图没有更新
// 或者通过vue的方法 把数组中的第一项改为 e
vm.$set(vm.arr, 0 , 'e');
// 或者 第 0 索引, 删除1 项 ,新增一项
vm.arr.splice(0, 1, 'e');
// ---- 数组的长度也是不能检测到变化 ------ 可以通过下面方式修改
vm.$delete(vm.arr, 1);
// 或者 使用splice
vm.arr.splice(2);
})
</script>
</body>
</html>
对象中的 delete 方法不是响应式的
// 该 方法做不到响应式
delete state.info.age
// 通过vue内部 删除对象中的某个属性
Vue.delete(state.info, 'age')
vue自定义指令(部分)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="../bootstrap/css/bootstrap.css">
</head>
<body>
<!--
v-for: 遍历数据 使用 v-for="item,idx in/of xxx"
v-on: 绑定事件 使用 v-on:事件 = "事件函数" / 简写 @
v-bind: 动态绑定数据 使用 v-bind:属性="变量" 或者 v-bind:属性="{ 变量:判断条件 }" 简写 :剩下: 即可
v-show: 是否显示 使用 v-show:"判断条件" true/false 条件成立显示 否则隐藏
v-if/v-else/v-else-if 使用 v-if:"判断条件" true/false 条件成立创建 否则销毁 用于兄弟同级之间
注意:v-show和v-if的区别???
v-show是显示和隐藏 而v-if是 创建和销毁 所以v-if指令存在游览器性能消耗。
建议如果只是显示和隐藏 , 用v-show即可
注意:v-for为什么不能和v-if连用?(其实不是不可以,会造成性能浪费)
因为v-for的优先级比v-if的高,会优先执行v-for,这样的话会先循环,再判断对性能浪费
所以如果使用的话,可以在v-for外面再套用一个标签 里面用v-if判断,先判断,再循环
如果外层不想多一个标签 可用template ,就不会多一个标签了
<template v-if="flag">
<div v-for="(item,idx) in xxxx" key="item.idx">{{ item }}</div>
</template>
或者:嵌套使用
<div v-for="(item,idx) in xxxx" key="item.idx">
<template v-if="idx % 2">xxx</template>
<template v-else>xxxx</template>
</div>
单向数据绑定:
1、{{ }} 只能写在标签里面 等效于v-text
2、v-text 把html 当文本处理
3、v-html 可以解析html
4、v-bind 给属性绑定数据 比如 v-bind:value =""
双向数据绑定
v-model v-model="数据" 一般用于表单元素 实现双向 视图层到数据层 / 数据层到视图层
双向数据绑定:能够让我们修改表单之后,不用操作DOM元素就可以获取表单元素的值。
单向数据绑定:能够让我们不必操作DOM元素,去渲染数据了。
-->
<div id="app">
<ul class="nav nav-tabs">
<li class="nav-item" v-for="(item,idx) in tablist" v-on:click="changeTab(idx)">
<a class="nav-link" href="#" v-bind:class="{active:tabflag===idx}">{{ item.name }}-{{ idx }}</a>
</li>
</ul>
<div class="container p-3">
<div v-for="(item,idx) in tablist" v-show="tabflag===idx">{{item.container}}</div>
</div>
<!-- 多种数据绑定的区别 -->
<p>{{ test }}</p>
<p v-text="test"></p>
<p v-html="test"></p>
<p v-bind:title="test">12</p>
</div>
<script src="../js/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
tablist: [{
name: 'fqniu',
container: '吃美食'
}, {
name: 'niuniu',
container: '学游泳'
}, {
name: '小牛牛',
container: '宅屋看书'
}, {
name: '牛先森',
container: '敲代码'
}],
tabflag: 0,
test: "吃东西"
},
methods: {
changeTab(idx) {
this.tabflag = idx
}
}
})
</script>
</body>
</html>
todolist
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="../bootstrap/css/bootstrap.css">
<script src="../js/vue.js"></script>
<style>
body,
html {
height: 100%;
background: #ccc;
}
#app {
width: 800px;
margin: 0 auto;
}
table {
background: #fff;
}
th{
width: 25%;
}
</style>
</head>
<body>
<div id="app">
<h1>todolist</h1>
<div class="input-group mb-3" style="width: 800px;">
<input type="text" class="form-control" placeholder="请输入内容" ref="search" @keyup.enter="addItem"
v-model="todoTarget">
<div class="input-group-append">
<button class="btn btn-success" type="button" v-on:click="addItem">添加</button>
</div>
</div>
<table class="table" style="width: 800px;">
<thead class="thead-dark">
<tr>
<th scope="col">序号</th>
<th scope="col">待办事项</th>
<th scope="col">是否完成</th>
<th scope="col">操作</th>
<!-- <th scope="col">添加时间</th> -->
</tr>
</thead>
<tbody>
<tr v-for="(item,idx) in todolistData">
<th scope="row" v-text="idx + 1"></th>
<td v-text="item.target"></td>
<td v-text="item.done ? '是' : '否'"></td>
<td>
<button class="btn btn-danger btn-sm" v-on:click="removeItem(item.id)">删除</button>
<button class="btn btn-success btn-sm" v-on:click="completeItem(item.id)">完成</button>
</td>
<!-- <td>{{ formatDate(item.addtime)}}</td> -->
</tr>
</tbody>
</table>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
todolistData: [
// {
// id: 1,
// target: '按时吃饭',
// done: false,
// addtime: Date.now()
// }, {
// id: 2,
// target: '早睡早起',
// done: false,
// addtime: Date.now()
// }
],
// 通过v-model双向绑定 获取v-model中的数据
todoTarget: ""
},
methods: {
// 添加
addItem() {
if (this.todoTarget.trim()) {
const data = {
id: this.todolistData.length + 1,
target: this.todoTarget,
done: false,
addtime: Date.now()
}
this.todolistData.unshift(data)
// 清空并自动获取焦点
this.todoTarget = ''
this.$refs.search.focus()
}
},
// 删除
removeItem(id) {
this.todolistData = this.todolistData.filter(item => item.id !== id)
},
// 完成
completeItem(id) {
this.todolistData = this.todolistData.map(item => {
if (item.id === id) {
item.done = true
}
return item
})
},
// // 日期
// formatDate() {
// let d = new Date();
// let year = d.getFullYear();
// let month = d.getMonth() + 1;
// let day = d.getDate();
// let hours = d.getHours();
// let min = d.getMinutes();
// let sec = d.getSeconds();
// hours = hours < 10 ? '0' + hours : hours
// min = min < 10 ? '0' + min : min
// sec = sec < 10 ? '0' + sec : sec
// return `${year}-${month}-${day} ${hours}:${min}:${sec}`
// }
}
})
</script>
</body>
</html>