数据响应式即数据双向绑定,就是把Model绑定到view,当我们用Javascript代码更新model时,view就会自动更新,如果用户更新了view,model的数据也自动更新了,这种情况就是双向绑定
vue2.0原理
vue2.0实现数据响应式的原理就是利用了Object.defineProperty()这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的。
什么是defineProperty
defineProperty其实是定义对象的属性
defineProperty其实并不是核心的一个对象做数据双向绑定,而是去给对象做属性标签,只不过属性里的set和get实现了响应式
属性名 | 默认值 |
---|---|
vale | undefined |
get | undefined |
set | undefined |
writeable | false |
enumerable | false |
configurable | false |
查看自有属性
var a = {
b: 123,
c: 456
}
console.log(Object.getOwnPropertyDescriptor(a,'b'))
// 执行结果
{
configurable: true
enumerable: true
value: 123
writable: true
}
defineProperty get和set实现方式
var a = {
b: 123,
c: 456
}
var _value = a.b;
Object.defineProperty(a,'b',{
get: function(){
console.log('you get b')
return _value
},
set: function(newval){
console.log('this newvalue is' + newval)
_value = newval
}
})
console.log(Object.getOwnPropertyDescriptor(a,'b'))
a.b // 123
a.c = 567
a.c // 567
vue从改变一个数据到发生变化的过程
实现简单的对对象变化的监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
function vue() {
this.$data = {
a: 1
}
this.el = document.getElementById('app');
this._html = '';
this.observe(this.$data);
this.render();
}
vue.prototype.observe = function(obj) {
var value;
var self = this;
for (var key in obj) {
value = obj[key];
if (typeof value === 'object') {
this.observe(obj)
} else {
Object.defineProperty(this.$data, key, {
// get部分做依赖收集
get: function() {
return value
},
// set触发依赖更新视图
set: function(newvalue) {
value = newvalue;
self.render();
}
})
}
}
}
vue.prototype.render = function() {
this._html = 'i am ' + this.$data.a;
this.el.innerHTML = this._html;
}
var vm = new vue();
setTimeout(function() {
console.log('changes')
console.log(vm.$data);
vm.$data.a = 123
}, 2000)
</script>
</body>
</html>
实现简单的对数组变化的监听
var arraypro = Array.prototype;
var arrayob = Object.create(arraypro); // 深拷贝,断开引用
var arr = ['push', 'pop', 'shift'];
// 目标:arr里的方法,既能保持原有方法,又能触发更新
// 装饰者模式
arr.forEach(function(method, index) {
arrayob[method] = function() {
var ret = arraypro[method].apply(this, arguments);
return ret;
}
})
var arr = [];
arr._proto_ = arrayob;
arr.push(123);
arr // 查看
arr.push(345)
arr // 查看
什么是vue的依赖收集
new Dep()
- 收集依赖 -- 当前这个变量,有哪些地方依赖了的(dep.depend())
- 更新依赖部分(dep.notify)触发虚拟dom上的ast
什么是虚拟DOM
虚拟dom其实就是一个json对象
{
'component1': observer("ast"), // ast抽象语法术
'component2': observer("ast"), // ast抽象语法术
}
vue3原理
vue3.0原理采用ES6的Proxy对象来实现。Proxy对象用于定义基本操作的自定义行为
Proxy用于修改某些操作的默认行为,Proxy意思为“代理”,即在访问对象之前建立一道“拦截”,任何访问该对象的操作之前都会通过这道“拦截”
var ob = {
a: 1,
b: 2
}
// 使用时不可以再操作原对象了,所以直接对原对象进行重写
ob = new Proxy(ob, {
get: function(target,key,receive){
console.log(target,key)
return target[key]
},
set: function(target,key,newvalue,receive){
console.log(target,key,newvalue)
target[key] = newvalue;
}
})
为什么改用proxy
- defineProperty只能监听某个属性,不能对全对象监听
- 可以省去for in提升效率
- 可以监听数组,不用再去单独的对数组做特异性操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
function vue() {
this.$data = {
a: 1
}
this.el = document.getElementById('app');
this._html = '';
this.observe(this.$data);
this.render();
}
vue.prototype.observe = function(obj) {
var self = this;
this.$data = new Proxy(this.$data, {
get: function(target, key) {
return target[key]
},
set: function(target, key, newvalue) {
target[key] = newvalue;
self.render();
}
})
}
vue.prototype.render = function() {
this._html = 'i am ' + this.$data.a;
this.el.innerHTML = this._html;
}
var vm = new vue();
setTimeout(function() {
console.log('changes')
console.log(vm.$data);
vm.$data.a = 123
}, 2000)
</script>
</body>
</html>
我们还可以利用proxy做什么?
- 校验类型
function createValidator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
if (target.hasOwnProperty(key)) {
var validator = this._validator[key];
if (validator(value)) {
return Reflect.set(target, key, value, proxy);
} else {
throw Error('type error');
}
}
}
})
}
var personValidator = {
name(val) {
return typeof val === 'string'
},
age(val) {
return typeof val === 'number' && val > 18
}
}
class person {
constructor(name, age) {
this.name = name;
this.age = age;
return createValidator(this, personValidator);
}
}
var pj = new person('潘潘', 18)
- 真正的私有变量
function isProtected(key, action) {
if (key.slice(0, 1) === '_') { //规定:如果成员变量名以"_"开头,就视为私有的,抛出异常阻止操作
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
var yourObj = {
_a: 0,
b: 3
}
var myObj = new Proxy(yourObj, {
get: function(target, key) { //target为目标对象, key为成员变量
isProtected(key, 'get');
return target[key];
},
set: function(target, key, value) {
isProtected(key, 'set');
target[key] = value;
return true;
}
});
console.log(myObj._a)
使用proxy实现数据响应(具体案例)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h3 id="paragraph"></h3>
<input type="text" id="input">
</div>
<script>
// 获取段落的节点
const paragraph = document.getElementById('paragraph');
// 获取输入框节点
const input = document.getElementById('input');
// 需要代理的数据对象
const data = {
text: 'hello world'
}
const handler = {
// 监控data中的text属性变化
set: function(target, prop, value) {
// 更新值
target[prop] = value;
// 更新视图
paragraph.innerHTML = value;
input.value = value;
return true;
}
}
// 构造proxy对象
const myText = new Proxy(data, handler);
// 添加input监听事件
input.addEventListener('input', function(e) {
myText.text = e.target.value; // 更新myText的值
}, false)
// 初始化值
myText.text = data.text;
</script>
</body>
</html>
使用defineProperty实现数据响应式(具体案例)
- 实现一个整体的架构(包括MVVM类或者VUE类、Watcher类),这里用到一个订阅发布设计模式
- 然后实现MVVM中的由M到V,把模型里面的数据绑定到视图
- 最后实现V-M,当文本框输入文本的时候,由文本事件触发更新模型中的数据,同时也更新相对应的视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
input {
border: 1px solid #000;
}
</style>
<script>
// 发布者
class Vue {
constructor(options) {
this.$data = options.data; // 获取数据
this.$el = document.querySelector(options.el); // 获取元素对象
// 容器--保存订阅者信息,实例对象
this._directive = {};
// {myText: [订阅者1,订阅者2],myBox: [订阅者1,订阅者2]}
this.Observer(this.$data);
this.Complie(this.$el);
}
// 劫持数据
Observer(data) {
// 准备好数据澡盆
for (let key in data) {
this._directive[key] = [];
// 为什么要进行数据劫持?
let val = data[key];
let watcher = this._directive[key];
Object.defineProperty(this.$data, key, {
get: function() {
return val;
},
set: function(newVal) {
if (newVal !== val) {
val = newVal;
// element代表着订阅者的实例对象
watcher.forEach(element => {
element.update();
})
}
}
})
}
}
// 解析指令
// 为什么要解析指令--依赖收集--更新视图--订阅
Complie(el) {
let nodes = el.children; // 获取appdiv对象下所有的子对象
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
// 递归算法 继续往树形结构查找
if (node.children.length) {
this.Complie(node);
}
if (node.hasAttribute('v-text')) {
// 订阅
let attVal = node.getAttribute('v-text');
// push什么? -- 订阅者 -- 订阅者是谁?
this._directive[attVal].push(new Watcher(node, this, attVal, 'innerHTML'));
}
if (node.hasAttribute('v-model')) {
// 订阅
let attVal = node.getAttribute('v-model');
// push什么? -- 订阅者 -- 订阅者是谁?
this._directive[attVal].push(new Watcher(node, this, attVal, 'value'));
// this问题解决
// 方式一:
let self = this;
node.addEventListener('input', function() {
// 当文本框发生数据变化,更新模型
self.$data[attVal] = node.value;
})
// 方式二:
// node.addEventListener('input', () => {
// // 当文本框发生数据变化,更新模型
// this.$data[attVal] = node.value;
// })
// 方式三:
// node.addEventListener('input', function() {
// // 当文本框发生数据变化,更新模型
// this.$data[attVal] = node.value;
// }.bind(this))
}
}
}
}
// 订阅者 负责更新本身的状态
class Watcher { // 主要是功能 更新视图
constructor(el, vm, exp, attr) {
this.el = el;
this.vm = vm;
this.exp = exp;
this.attr = attr;
this.update();
}
update() {
// div.innerHTML = this.vue.$data['myText'];
// input[value] = this.vue.$data['myText'];
this.el[this.attr] = this.vm.$data[this.exp]
}
}
</script>
</head>
<body>
<div id="app">
<h1>数据响应式</h1>
<div>
<div v-text="myTest"></div>
<div v-text="myBox"></div>
<input type="text" v-model="myTest">
<input type="text" v-model="myBox">
</div>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
myTest: '晚上好',
myBox: '我是个盒子'
}
})
</script>
</body>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。