目录
跳转链接 => Vue _ 教程版 02 指令
跳转链接 => Vue _ 教程版 03
跳转链接 => Vue _ 教程版 04 组件
跳转链接 => Vue _ 教程版 05
目标
- 在网页中实例化 Vue 对象
- 知道 Vue 数据绑定原理
- 熟练使用 插值表示式
一、Vue 基础
1.1、介绍
官网:Vue.js
Vue ( 读音 /vjuː/,类似于 view ) , Vue.js 是一套构建用户 界面 的 渐进式框架 。Vue 采用自底向上增量开发的设计。Vue 的核心库 只关注 视图层 ,它不仅易于上手,还便于与第三方库 或 既有项目整合。另一方面,当与 现代化的工具链 以及各种 支持类库 结合使用时,Vue 也完全能够为复杂的 单页应用程序提供驱动。( spa 单页面应用,所有的显示都在一个页面当中)
渐进式:一步一步,不是说你必须一次把所有的东西都用上
自底向上设计:是一种设计程序的过程和方法,就是先编写出基础程序段,然后再逐步扩大规模、补充和升级某些功能,实际上是一种自底向上构造程序的过程。
Vue 从设计角度来讲,虽然能够涵盖这张图上所有的东西,但是你并不需要一上手就把所有东西全用上,都是可选的。声明式渲染 和 组件系统 是 Vue 的 核心库 所包含内容,而 路由、状态管理、构建工具 都有专门 解决方案 。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。
1.2、声明式渲染和组件化
- 声明式渲染
Vue.js 的核心是一个允许采用简洁的 模板语法 来 声明式的将数据渲染进 DOM 的系统
- 组件化应用构建
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。几乎任意类型的应用界面都可以抽象为一个 组件树 。
1.3、MVVM 模式
MVVM 是 Model-View-ViewModel 的简写。它本质上就是 MVC 的 改进版 。MVVM 就是将其中的View 的 状态 和 行为 抽象化 ,让我们将 视图 UI 和 业务逻辑 分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。
Vue 使用 MVVM 响应式编程模型 ,避免直接操作 DOM , 降低 DOM 操作的 复杂性 。
MVVM:页面输入改变数据,数据改变 影响 页面数据 展示与渲染
M(model):普通的 javascript 数据对象
V(view):前端展示 页面
VM(ViewModel):用于双向绑定数据与页面,对于我们的课程来说,就是 Vue 的实例
二、Vue 基础使用
2.1、传统 Dom 操作
使用 js 对 html 页面结构中的指定的区域输出数据
传统 dom 操作数据显示 :
<div>
<h3 id="app">abcd</h3>
<input type="text" id="msg">
</div>
<script>
var data = {
username: '张三'
}
document.querySelector('#app').innerHTML = data.username
// document.querySelector('#app').textContent = data.username
// 查看当前dom的类型 1元素 3文本
// console.log(document.querySelector('#app').nodeType)
// console.log(document.querySelector('#app').firstChild.nodeType)
document.querySelector('#msg').value = data.username
document.querySelector('#msg').addEventListener('input', function () {
// 让使用数据源的标签更新视图
document.querySelector('#app').innerHTML = this.value.trim()
// 更新数据源
data.username = this.value.trim()
})
</script>
2.2、使用 Vue 实现
在 html 页面中使用 Vue 需要完成如下步骤即可
下载 Vue.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue _ 数据双向绑定</title>
</head>
<body>
<!-- 下面这是能在界面看见的部分就是 视图 部分 -->
<!-- 被 Vue 管理 -->
<div id="app">
<!-- {{ 双花括号之间不能有空格 , 需要紧挨在一起 }} -->
<h1> {{message}} </h1>
<h2>{{name}}</h2>
</div>
<!-- 不被 Vue 管理 -->
<div>{{message}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
/*
Vue 最大的特点 : 数据双向绑定
这个双向绑定指的是谁和谁绑定的呢?? => 数据 和 视图
( 操作 数据 , DOM 也发生了变化 , input 输入框把 数据 告诉 app.message 这个属性
(由 Vue 的实例 , 将上面的这一个 h1 进行了修改) )
1. DOM 将数据传递给 Vue 实例
2. Vue 实例的属性会传递给 DOM
*/
// let(变量) / const(常量)
// 编程范式 : 声明式编程 ( 当我这个实例去帮我管理这个 div 的时候 ,
//你只需要告诉我这里写的什么东西就可以了 , 你只需要声明我这里需要显示什么东西就可以了 ,
至于它内部如何处理的 , 无需知晓 )
// 它的好处 : 可以真正做到数据和我们的界面完全分离 , 以后并不需要再通过代码去创建元素了
// 创建 Vue 实例
const app = new Vue({
// vue 实例的配置项:
// element , vue 实例控制这一块 dom
el: '#app', // 用于挂载要管理的元素
// vue 实例的属性
// data 内部的属性和属性值就是 vue 实例的属性和属性值
// 下面 data 这一部分就是 数据
data: { // 定义数据
message: "hello Vue",
name: '小灰狼'
}
});
console.log(app.message);
// 响应式: 就是当数据发生改变的时候, 界面会自动发生一些响应, 会跟着自动更改
// 原始 js 的做法 : ( 编程范式 : 命令式编程 )
// =>( 就是一步一步告诉你怎么做, 需要你每一步都指定的非常清除 , 他才知道怎么做 )
// 1. 创建 div 元素 , 设置 id 属性
// 2. 定义一个变量叫 message
// 3. 将 message 变量放在前面的 div 元素中显示
// 4. 修改 message 的数据: 今天下雨天气 !
// 5. 将修改后的数据再次替换到 div 元素
</script>
</body>
</html>
2.3、vue devtools 工具安装
通过 Chrome 中的谷歌插件商店安装 Vue Devtools 工具,此工具帮助我们进行 vue 数据调试所用,一定要安装。
https://chrome.google.com/webstore?utm_source=chrome-ntp-icon
- 在 VsCode 中安装插件
2.4、Vue 实现数据绑定的原理
当把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的 属性,使用 Object.defineProperty ( Vue2.x 版本) , Vue3.x 中使用了 Proxy 类 把这些属性全部转为 getter / setter ( 数据劫持 ) 。在 属性被访问和修改时 通知变化 。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
vue 实现数据响应式
<body>
<h3 v-html="msg"></h3>
<input type="text" v-model="msg">
<script>
// 初始化数据 -- vue优化,扁平化数据
let data = {
msg: 'hello',
user: { id: 100 }
}
// 劫持或代理
// 观察者
observer(data)
// 编译 模板
compileTmeplate(data)
function compileTmeplate(target) {
/* document.querySelectorAll('[v-model]').forEach(node => {
console.log(node)
}) */
document.querySelectorAll('*').forEach(node => {
[...node.attributes].forEach(attr => {
if (/^v-/.test(attr.name)) { // 双向绑定所要用到的属性
if (attr.name === 'v-model') {
node.value = target[attr.nodeValue]
// 表单项,事件和数据绑定
node.addEventListener('input', function () {
// 更新数据
target[attr.nodeValue] = this.value.trim()
})
} else {
node.innerHTML = target[attr.nodeValue]
}
}
})
})
}
function observer(target) {
if (Object.prototype.toString.call(target) != '[object Object]') return;
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 代理
function defineReactive(target, key, value) {
observer(value)
Object.defineProperty(target, key, {
get() {
console.log('get')
return value
},
set(val) {
console.log('set')
if (val != value) {
document.querySelectorAll('[v-html]').forEach(node => {
node.innerHTML = val
})
document.querySelectorAll('[v-model]').forEach(node => {
node.value = val
})
value = val
}
}
})
}
</script>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue实现数据绑定的原理</title>
</head>
<body>
<div id="root">
<h3 v-text="title"></h3>
<hr>
<input type="text" v-model='title'>
</div>
<script>
// 数据劫持
let data = {
title: '我是一个标题',
};
// 观察数据
observe(data)
// 给input绑定事件
document.querySelector('[v-model]').addEventListener('input', function () {
let key = this.getAttribute('v-model')
data[key] = this.value.trim()
})
function observe(target) {
if (!isObject(target)) return;
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 数据劫持
function defineReactive(target, key, value) {
Object.defineProperty(target, key, {
get() {
console.log('get')
return value
},
set(v) {
if (v != value) {
value = v
console.log('set')
// 更新视图
updateView(value, key)
}
}
})
}
function updateView(value, key) {
document.querySelectorAll('[v-text]').forEach(node => {
let attrValue = node.getAttribute('v-text')
if (key === attrValue) {
if (node.nodeName === 'INPUT') {
node.value = value;
} else {
node.innerHTML = value;
}
}
})
}
function isObject(target) {
// return target !== null && typeof target === 'object' && !(target instanceof Array)
// return Object.prototype.toString.call(target) === '[object Object]'
return ({}).toString.call(target) === '[object Object]'
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据劫持</title>
</head>
<body>
<div id="app">
<h3 v-text="msg"></h3>
<h3 v-text="age"></h3>
<hr>
<input type="text" v-model="msg">
</div>
<script>
var data = {
msg: '你好mvvm',
age: 20,
user: { id: 1 },
user_id: 1,
user_name: 'aa'
}
// let target = { id: 100 }
// 冻结对象 对象就没有办法修改了
// target = Object.freeze(target)
// target.id = 200
// let target = {}
// 数据劫持
observe(data)
// 模板编译
compileRender(data)
function observe(target) {
// 只劫持json对象
if (Object.prototype.toString.call(target) != '[object Object]') return;
// 遍历
for (let key in target) {
defineRactive(target, key, target[key])
}
}
function defineRactive(target, key, value) {
observe(value)
/* if (Object.prototype.toString.call(value) == '[object Object]') {
observe(value)
return;
} */
// 劫持当前的对象
// defineProperty 它只能对对象中的属性进行劫持,不能劫持数组
Object.defineProperty(target, key, {
// 获取器
get() {
console.log('get')
return value
},
// 修改器
set(newV) {
if (newV != value) {
console.log('set')
value = newV
// 通知模板编译一下
compileRender(target)
}
}
});
}
function compileRender(target) {
// 模板编译
// 文本
document.querySelectorAll(`[v-text]`).forEach(node => {
// 要去数据源中查找的数据key
let key = node.getAttribute('v-text')
let value = target[key] || 0
node.innerHTML = value
})
// 输入框
document.querySelectorAll(`[v-model]`).forEach(node => {
let key = node.getAttribute('v-model')
let value = target[key] || 0
node.value = value
// 绑定一个事件
node.addEventListener('input', function () {
target[key] = this.value.trim()
})
})
}
</script>
</body>
</html>
相关知识点 :
let target = { id: 100 }
冻结对象 , 对象就没有办法修改了
target = Object.freeze( target )
target.id = 200
Dom 操作在内存中完成
=> 文档碎片 --> 在内存在存储,不会在界面中渲染,通过 appendChild 渲染到视图中
语法 : document.createDocumentFragment( )作用 : 可以承载节点 , 当把文档碎片插入页面时 , 文档碎片不进入页面 , 只有文档碎片里面承载的内容进入页面 ( 往内存里写入东西比往页面里写入东西会更快 )
( 1 ) 数据劫持-跳转链接 => 数据劫持 _ 简述版
( 2 ) 观察者模式-跳转链接 => JavaScript _ 设计模式
自己来实现一个 _ MVVM :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实现一个 _ MVVM</title>
<script src="./js/myVue.js"></script>
</head>
<body>
<div id="app">
<h3>{{ title }}</h3>
<input type="text" v-model='title'>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
title: '你好MVVM'
}
})
</script>
</body>
</html>
class Vue {
constructor(options) {
// 挂载点的 Dom
this.$el = document.querySelector(options.el)
// 数据
this.$data = options.data
// 数据劫持
observe(this.$data)
// 模板 编译
compileTemplate(this.$el, this)
}
}
// 观察仓库
class Dep {
constructor() {
// 观察者队列
this.subscribes = []
}
// 添加观察者
addSub(watcher) {
this.subscribes.push(watcher)
}
// 通知更新
notify() {
this.subscribes.forEach(watcher => watcher.update())
}
}
// 观察者
class Watcher {
constructor(vm, key, node) {
// 仓库对象的静态属性来保存当前 watcher 实例对象
Dep.target = this;
this.$vm = vm;
this.$key = key;
this.$node = node;
// 触发数据劫持中的 get 方法,此时 Dep.target 的值为当前对象
this.getValue();
// 设置为 null ,为下次 new Watcher 准备
Dep.target = null;
}
getValue() {
// 触发 get
this.$value = this.$vm.$data[this.$key]
}
update() {
// 获取一下最新数据
this.getValue();
if (this.$node.nodeType === 1) {
if (this.$node.nodeName == 'INPUT') {
this.$node.value = this.$value
} else {
this.$node.innerHTML = this.$value
}
} else if (this.$node.nodeType === 3) {
this.$node.textContent = this.$value
}
}
}
// 模板编译
function compileTemplate(el, vm) {
// Dom 操作在内存中完成
// 文档碎片 --> 在内存在存储,不会在界面中渲染,通过 appendChild 渲染到视图中
let fragment = document.createDocumentFragment()
let childNode;
while (childNode = el.firstChild) {
// 渲染模板
compileRender(childNode, vm)
// 把得到的 Dom 对象放到 fragment 中,此时 Dom 会删除
fragment.appendChild(childNode)
}
// 处理完成后,放到视图中
el.appendChild(fragment)
}
// 编译视图显示
function compileRender(node, vm) {
// 判断当前节点的类型 1元素,3文本
if (node.nodeType === 1) {
// 得到元素所有的属性集合
// [...node.attributes].forEach(attrObj => {
// console.log(attrObj.name, attrObj.value);
[...node.attributes].forEach(({ name, value }) => {
// 只关心 v- 开头属性
if (/^v-/.test(name)) {
if (name === 'v-model') { // 针对于input输入框
node.value = vm.$data[value]
// 观察者
new Watcher(vm, value, node)
// 绑定事件
node.addEventListener('input', function () {
vm.$data[value] = this.value.trim()
});
} else {
// 观察者
new Watcher(vm, value, node)
node.innerHTML = vm.$data[value]
}
}
});
// 问一下有没有子元素了
node.childNodes.forEach(child => compileRender(child, vm))
} else if (node.nodeType === 3) { // 文本节点
// 内容
let cnt = node.textContent
// 匹配只有 {{}} 才进行处理
let preg = /\{\{\s*(\w+)\s*\}\}/
// 替换
cnt = cnt.replace(preg, (a0, a1) => {
// 观察者
new Watcher(vm, a1, node)
return vm.$data[a1]
})
node.textContent = cnt
}
}
// ------------------- 数据劫持
// 监听数据源
function observe(target) {
// 只劫持 json 对象
if (Object.prototype.toString.call(target) != '[object Object]') return;
// 遍历
for (let key in target) {
defineRactive(target, key, target[key])
}
}
// 实现劫持
function defineRactive(target, key, value) {
observe(value)
let dep = new Dep()
Object.defineProperty(target, key, {
// 获取器
get() {
if (Dep.target) {
// 添加了观察者到通知队列中
dep.addSub(Dep.target)
}
return value
},
// 修改器
set(newV) {
if (newV != value) {
value = newV
dep.notify()
}
}
});
}
图例 :
跳转链接 => Vue _ 教程版 02 指令
跳转链接 => Vue _ 教程版 03
跳转链接 => Vue _ 教程版 04 组件
跳转链接 => Vue _ 教程版 05