Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue双向数据绑定主要有以下几个步骤:
模板解析 事件添加 数据劫持 双向绑定
模板解析
文本解析编译
<!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>
<script src="vue01.js"></script>
</head>
<body>
<div id="app">
{{str}}
<h1>{{str}}</h1>
</div>
</body>
<script>
// 创建一个Vue实例
new Vue({
el: "#app",
data: {
str: '前端小白学习中!'
}
});
</script>
</html>
<!-- 模板解析 事件添加 数据劫持 双向绑定 -->
<!-- 当我们没有写Vue的时候,浏览器是不能帮助我们解析{{}} 里面的文字的
目的: 让str 正常输出文字-->
<!-- 在data里面定义一些自己的文本数据,进行挂载以{{}}形式进行展示 -->
// 为了让str正常显示,我们要创建Vue这个类
class Vue {
// options 选择 在构造函数中传入参数 这个参数就是创建的Vue 传递过来的对象 ,所以我们通过el可以获取div这个DOM
constructor(options) {
console.log(options.el)
this.$el = document.querySelector(options.el)
this.$data = options.data //new Vue data 里面的数据
console.log('Html页面创建的Vue实例里面的data数据', this.$el)
// 第一步:成功获取模板
// 第二步:解析编译模板——【我们需要使用到一个方法】并且把DOM传进去
this.compile(this.$el)
}
// 将我们获取的DOM 元素传递进去,看一下里面的节点
compile (node) {
node.childNodes.forEach((item, index) => {
// 怎么获取文本节点:通过nodeType 案例中节点为3的是文本节点 1元素节点
console.log(item.nodeType) // 这里输出的是子节点,以及对应的下标
// 第三步:我们获取文本节点的目的是将它替换成为data中的数据
// 使用===进行判断
if (item.nodeType === 1) {
// 这里考虑到标签没有内容的情况获取单标签的情况
if (item.childNodes.length > 0) {
this.compile(item)
}
}
if (item.nodeType === 3) {
// 是文本节点 使用正则来匹配这个格式
let reg = /\{\{(.*?)\}\}/g
// 获取节点的文本内容
let text = item.textContent //{{str}}
item.textContent = text.replace(reg, (match, vmKey) => {
// vnKey 这里我们获取到了一个str,通过str 我们可以获取这边的值
// 因为对象作为参数已经进行了传递了
// 对 {{}}里面可能存在的空格进行处理
vmKey=vmKey.trim()
console.log("reg:", reg, "match:", match, "vmKey:", vmKey,)
return this.$data[vmKey]
// 按照正则的格式找到{{}}里面的文本,替换成为data里面的数据,也就是data中的字符串
// 再把返回的值赋值给item的textContent,就是item文本节点里的内容已经发生改变了
// 我们发现返回之后只有第一个节点进行了替换,因为h1是元素节点,这里只判断文本节点
// 在上面的第21行进行递归调用 之后就获取数据成功了
})
}
})
}
}
// 问题: Vue 创建类加构造函数的意义
// 当 new 一个对象的时候会自动调用这个对象的构造函数
// compile ——是什么??
// compile——【编译】是我们自己定义的一个方法 用来将我们获取的模板进行解析
// 问题3: forEach 的使用 里面的两个参数都是什么?
// 我们上面将node里面的节点进行了遍历,item 是node里面的每一个节点 index 是节点里面对应的下标
// <h1></h1> 是元素节点 我们是不需要动的
// 我们需要获取的是文本节点
// 怎么获取文本节点:通过nodeType
// 获取文本节点之后进行匹配 使用到了正则表达式 【对于个别关键字不是很了解】转义字符的作用
事件绑定
<!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>02Vues双向数据绑定的原理——事件绑定</title>
<script src="./vue02.js"></script>
</head>
<body>
<div id="app">
{{str}}
<h1>{{str}}</h1>
<button @click="cli">按钮</button>
</div>
</body>
<script>
// 创建一个Vue实例
new Vue({
el: "#app",
data: {
str: '前端小白学习中_双向数据绑定的原理——事件绑定'
},
// 在methods进行方法的调用
methods: {
cli () {
console.log('方法调用了')
}
}
});
</script>
</html>
<!-- 事件绑定:我们使用了点击事件之后需要对数据进行处理 之后才可以实现点击的功能
2. 我们的点击事件是写在元素标签的里面
2.1 我们需要找到元素节点 根据之前的判断元素类型为1就是元素节点
2.2 找到所有的元素节点之后对节点进行判断,看一下里面有没有包含点击事件
3.拿到方法名称之后通过名称拿到里面的整个方法,当我们点击的时候再让它进行执行就可以了
-->
<!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>02Vues双向数据绑定的原理——事件绑定</title>
<script src="./vue02.js"></script>
</head>
<body>
<div id="app">
{{str}}
<h1>{{str}}</h1>
<button @click="cli">按钮</button>
</div>
</body>
<script>
// 创建一个Vue实例
new Vue({
el: "#app",
data: {
str: '前端小白学习中_双向数据绑定的原理——事件绑定'
},
// 在methods进行方法的调用
methods: {
cli () {
console.log('方法调用了')
}
}
});
</script>
</html>
<!-- 事件绑定:我们使用了点击事件之后需要对数据进行处理 之后才可以实现点击的功能
2. 我们的点击事件是写在元素标签的里面
2.1 我们需要找到元素节点 根据之前的判断元素类型为1就是元素节点
2.2 找到所有的元素节点之后对节点进行判断,看一下里面有没有包含点击事件
3.拿到方法名称之后通过名称拿到里面的整个方法,当我们点击的时候再让它进行执行就可以了
-->
数据劫持
<!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>03Vue双向数据绑定的原理——数据劫持</title>
<script src="./vue03.js"></script>
</head>
<body>
<div>
<!--
核心:对数据进行拦截
获取设置属性的时候对属性进行拦截,再另外执行操作
-->
</body>
<script>
let obj = {
name: '沐舒',
age: 19,
// other 是一个子对象
other: {
like: '健身'
}
}
// 第一个参数:要操作的对象 第二个参数:哪一个属性 第三个参数:也是一个对象里面的get set 方法
Object.defineProperty(obj, 'name', {
// 这里defineProperty这个方法已经給name属性劫持掉了,把它控制住了,和原本的name属性没有关系了
// 后面再要获取值的时候就要触发get方法了,后面能获取到什么值就取决于get方法里面的return 返回值
// 企图获取属性的时候再来执行这个方法
get () {
return name //这里返回的是一个空的字符串,因为我们还没有对它进行设置
},
// 去设置修改属性的时候触发的
set (val) {
name = val // 这里在你设置值的时候,就已经把你输入的值赋值给name
}
})
</script>
</html>
<!--
这里属于编译解析——最后它对视图进行更新
这里的重点是:definedProperty 是Vue2里面实现数据劫持的方法
get: 不是用来获取属性值的,而是在企图获取属性值的时候要触发的方法
set: 设置修改属性的时候触发d
eg: 在控制台输入obj.name='jack' 触发了修改设置属性的这个行为,触发行为就会执行set
obj.name '沐舒'
这里必须要有设置的操作,设置成为什么不重要:重要的是进行触发
set(val){
name=val //set 方法将name设置成为amy 这样get就可以获取name为 沐舒
}
获取到的值就是修改之后的值吗??
在set 方法中传入一个参数 ,将参数设置为属性就可以了
set(val){
name=val 这个参数可以获取到当我们设置修改这个属性的时候输入的值
}
使用 definedProperty 将数据进行劫持之后,这个属性已经和原本设置的值没有关系了
后面需要进行值的获取的时候,
① 明确在get 方法里面给它一个返回值
get () {
return '小红'
},
虽然有set 方法进行设置但是获取的时候触发的还是get,当get方法中有具体的返回值的时候set方法中的值就不会返回
②
-->
// Vue03
// 为了让str正常显示,我们要创建Vue这个类
class Vue {
constructor(options) {
this.$el = document.querySelector(options.el)
this.$data = options.data
// ① 把传过来的参数的对象也存储一下 {el...data,methods}
this.$options = options
console.log(" this.$options", this.$options)
this.compile(this.$el)
}
compile (node) {
node.childNodes.forEach((item, index) => {
if (item.nodeType === 1) {
// 2. 我们的点击事件是写在元素标签的里面
// 2.1 我们需要找到元素节点 根据之前的判断元素类型为1就是元素节点
// 2.2 找到所有的元素节点之后对节点进行判断,看一下里面有没有包含点击事件
if (item.hasAttribute("@click")) {
// 通过getAttribute这个方法看一下里面的属性值是什么
let vmKey = item.getAttribute("@click")
console.log("vmKey", vmKey) // cli 可以拿到事件绑定的方法名称
vmKey = vmKey.trim() // 对vmKey 进行空格处理
// 3.拿到方法名称之后通过名称拿到里面的整个方法,当我们点击的时候再让它进行执行就可以了
item.addEventListener('click', () => {
console.log(this.$options.methods[vmKey])
// 4. 让方法执行 获取了里面的数据
this.$options.methods[vmKey]()
})
}
if (item.childNodes.length > 0) {
this.compile(item)
}
}
if (item.nodeType === 3) {
let reg = /\{\{(.*?)\}\}/g
let text = item.textContent //{{str}}
item.textContent = text.replace(reg, (match, vmKey) => {
vmKey = vmKey.trim()
return this.$data[vmKey]
})
}
})
}
}