MVVM模式即双向数据绑定,数据更新视图,视图影响数据。
在vue中通过数据劫持+发布订阅模式实现数据双向绑定,vue不兼容ie8一下的版本,其原因是使用了es5的Object.defineProperty()
Object.defineProperty()
Object.defineProperty()的作用是直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
1.用法
Object.defineProperty(obj, prop, descriptor)
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'myNmae'
})
2 参数
obj:必需。目标对象
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性,为对象类型
3 descriptor的属性
writable:是否可以被重写,true可以重写,false不能重写,默认为false。
enumerable:是否可以被枚举(使用for…in或Object.keys())。设置为true可以被枚举;设置为false,不能被枚举。默认为false。
value:值可以使任意类型的值,默认为undefined
configurable:是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。如
let person = {};
Object.defineProperty(person, 'name',
writable: true,
configurable: true,
enumerable: true,
value:'gjf'
});
4存取器描述(get和set)
当使用了getter或setter方法,不允许使用writable和value这两个属性,get和set可以在获取数据/设置数据的是时候进行一些处理
let person = {};
let n = 'gjf';
Object.defineProperty(person, 'name', {
configurable: true,
enumerable: true,
get() {
//当获取值的时候触发的函数
return n
},
set(val) {
//当设置值的时候触发的函数,设置的新值通过参数val拿到
n = val;
}
});
console.log(person.name) //'gjf'
person.name = 'newGjf'
console.log(person.name) //'newGif'
数据劫持
数据劫持使用Object.defineProperty定义所有的属性来实现
在vue中 ,可通过vm.$options来获取data属性以外的属性和方法
function Person(options = {}) {
this.$options = options // 将所有属性挂载到$options
var data = this_data = this.$options.data
observe(data)
}
给观察对象增加Object.defineProperty
ffunction Observe(data) {
for (let key in data) {
let val = data[key]
observe(val) // 递归子属性 使子属性同样具有get和set方法
Object.defineProperty(data, key, {
enumerable: true,
get() {
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal
observe(val)
}
})
}
}
上述方法可优化为
function Observe(data)
{
for (let key in data)
{
let val=data[key];
if(val!=null&&typeof val ==="object")
{
observe(val);
}
Object.defineProperty(data,key,{
enumerable:true,
get()
{
return val;
},
set(newval)
{
if(newval===val)
{
return;
}
val=newval;
if(val!=null&&typeof val==="object")
{
observe(val);
}
}
})
}
}
创建Observe实列
function observe (data) {
return new Observe(data)
}
使用数据劫持
let person = new Person({data:a:{a:1}})
console.log(persom.data.a) // {a: 1}
数据代理
在原构造函数Person上修改即可
function Person(options = {}) {
this.$options = options // 将所有属性挂载到$options
var data = this_data = this.$options.data
observe(data)
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key] // this.a = {a:1}
},
set(newVal) {
this._data[key] = newVal
}
})
}
}
let person = new Person({data:a:{a:1}})
console.log(persom.a) // {a: 1}
vue中不能新增不参在的属性,因为不存在的属性没有get和set方法,不能检测的数据变化
深度响应的原因: 每次赋予一个新对象时会给这个新对象增加数据劫持
替换{{}}中的内容
// 编译
function Compile(el, vm) {
// el 表示替换的范围
vm.$el = document.querySelector(el)
let fragment = document.createDocumentFragment()
while (child = vm.$el.firstChild) {
// 将appp中的内容移入到内存中
fragment.appendChild(child)
}
replace(fragment)
function replace(fragment) {
Array.from(fragment.childNodes).forEach(function (node) {
// 循环每一层
let text = node.textContent
let reg = /\{\{(.*)\}\}/
// 判断是否是文本节点
if (node.nodeType === 3 && reg.test(text)) {
console.log(RegExp.$1) //a.a b
let arrs = RegExp.$1.split('.') //[a,a] [b]
console.log(arrs,reg.exec(text),node,1,RegExp.$1)
let val = vm
arrs.forEach(function(k){
// 遍历[a,a]
val = val[k]
console.log(val, node.textContent)
})
node.textContent= text.replace(reg,val)
console.log(node.textContent,2)
}
if (node.childNodes) {
replace(node)
}
})
}
vm.$el.appendChild(fragment)
}
demo 实列
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>mvvm</title>
</head>
<body>
<div id="app">
<p>a的值{{a.a}}</p>
<div>b的值{{b}}</div>
</div>
</body>
<script src="mvvm.js">
</script>
<script>
let person = new Person({
el: "#app",
data: {
a: { a: '是a' },
b: '是b'
}
})
</script>
</html>
js部分
function Person(options = {}) {
this.$options = options // 将所有属性挂载到$options
var data = this._data = this.$options.data
observe(data)
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key] // this.a = {a:1}
},
set(newVal) {
this._data[key] = newVal
}
})
}
new Compile(options.el, this)
}
// 编译
function Compile(el, vm) {
// el 表示替换的范围
vm.$el = document.querySelector(el)
let fragment = document.createDocumentFragment()
while (child = vm.$el.firstChild) {
// 将appp中的内容移入到内存中
fragment.appendChild(child)
}
replace(fragment)
function replace(fragment) {
Array.from(fragment.childNodes).forEach(function (node) {
// 循环每一层
let text = node.textContent
let reg = /\{\{(.*)\}\}/
// 判断是否是文本节点
if (node.nodeType === 3 && reg.test(text)) {
console.log(RegExp.$1) //a.a b
let arrs = RegExp.$1.split('.') //[a,a] [b]
console.log(arrs,reg.exec(text),node,1,RegExp.$1)
let val = vm
arrs.forEach(function(k){
// 遍历[a,a]
val = val[k]
console.log(val, node.textContent)
})
node.textContent= text.replace(reg,val)
console.log(node.textContent,2)
}
if (node.childNodes) {
replace(node)
}
})
}
vm.$el.appendChild(fragment)
}
function Observe(data) {
for (let key in data) {
let val = data[key];
if (val != null && typeof val === "object") {
observe(val);
}
Object.defineProperty(data, key, {
enumerable: true,
get() {
return val;
},
set(newval) {
if (newval === val) {
return;
}
val = newval;
if (val != null && typeof val === "object") {
observe(val);
}
}
})
}
}
function observe(data) {
// if(typeof data =="object"){
// return
// }
return new Observe(data)
}
效果图