说道vue我们先来说一说MVVM,一句话总结,数据驱动视图。
那MVVM里面的第一个字母M就是model,数据。也就是vue中的data。第二个字母V就是View,视图,就是vue中的template。后面的VM比较抽象,可以理解为视图与数据的连接线,比如一些事件点击触发之类。
说到数据驱动视图,那数据怎样驱动视图呢?数据改变了页面怎样才会跟着变化呢?下面我们就用代码举例:
这里主要讲怎样实现数据响应式以至于改变数据触发视图更新,并不会具体讲解视图收到数据改变响应后怎样去更新。
在这里,就不提原始数据类型了。
首先我们写串基础代码:
const data = {
count: 1
}
function updateView() {
console.log('视图更新了')
}
data.count = 2
上面这串代码想要达到的目的就是data.count=2后,因为data的值发生了改变,然后给予数据驱动视图这个概念那么久应该触发updateView这个方法。但实际上,你在编辑器中运行一下会发现并没有什么反应。那么,我们就对上面的代码再完善一下:
const data = {
count: 1
}
function updateView() {
console.log('视图更新了')
}
function bind(target, key, value) {
// 实现响应式的核心
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
// 判断是否设置为了相同的值
if(newValue !== value) {
value = newValue
updateView()
}
}
})
}
function watch(target) {
// 监听对象属性 target就是具体的对象
// 做一下判断 如果传进来的数据不是对象或者说是null 就直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,使其每个属性变成响应式
for (let key in target) {
bind(target, key, target[key])
}
}
watch(data)
data.count = 2
然后再运行一下:
就可以看到视图更新打印出来了。
上面这只是最基础的情况,如果我修改一下data的值:
const data = {
count: 1
}
function updateView() {
console.log('视图更新了')
}
data.count = 2
上面这串代码想要达到的目的就是data.count=2后,因为data的值发生了改变,然后给予数据驱动视图这个概念那么久应该触发updateView这个方法。但实际上,你在编辑器中运行一下会发现并没有什么反应。那么,我们就对上面的代码再完善一下:
const data = {
count: 1,
info: {
name: 'zky'
}
}
function updateView() {
console.log('视图更新了')
}
function bind(target, key, value) {
// 实现响应式的核心
watch(value)
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
// 判断是否设置为了相同的值
if(newValue !== value) {
value = newValue
updateView()
}
}
})
}
function watch(target) {
// 监听对象属性 target就是具体的对象
// 做一下判断 如果传进来的数据不是对象或者说是null 就直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,使其每个属性变成响应式
for (let key in target) {
bind(target, key, target[key])
}
}
watch(data)
data.info.name = 'zzz'
这样把data改为了嵌套对象,就发现又没有视图更新的打印了。其实这里也很简单,就跟你遍历多层级数据一样,递归。
count: 1,
info: {
name: 'zky',
place: {
address: 'xx'
}
}
}
function updateView() {
console.log('视图更新了')
}
function bind(target, key, value) {
// 实现响应式的核心
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
// 判断是否设置为了相同的值
if(newValue !== value) {
value = newValue
updateView()
}
}
})
}
function watch(target) {
// 监听对象属性 target就是具体的对象
// 做一下判断 如果传进来的数据不是对象或者说是null 就直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,使其每个属性变成响应式
for (let key in target) {
bind(target, key, target[key])
}
}
watch(data)
data.info.name = 'zzz'
data.info.place.address = 'oo'
我们修改了两次data中的数据,那么就应该打印两次视图更新。
这样多层级的对象就完成了响应式。当然这样使用的时候也需要思考一些问题,如果这个初始对象层级很深,会不会有性能影响。
接下来就是数组实现响应式,首先我们还是用上面的例子看下会不会有打印出结果:
const data = {
count: 1
arr: [1,2,3]
}
function updateView() {
console.log('视图更新了')
}
function bind(target, key, value) {
// 实现响应式的核心
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
// 判断是否设置为了相同的值
if(newValue !== value) {
value = newValue
updateView()
}
}
})
}
function watch(target) {
// 监听对象属性 target就是具体的对象
// 做一下判断 如果传进来的数据不是对象或者说是null 就直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,使其每个属性变成响应式
for (let key in target) {
bind(target, key, target[key])
}
}
watch(data)
data.arr.push(4)
很明显,视图更新又没有触发。监听数组的话会用到一个叫重新定义数组原型的概念,请看代码:
const data = {
count: 1,
arr: [1,2,3]
}
function updateView() {
console.log('视图更新了')
}
const arrayPrototype = Array.prototype
// Object.create创建新对象,原型指向传入的对象,然后可以添加方法并且不会覆盖原型中的方法。
const arrProto = Object.create(arrayPrototype)
// 模拟部分数组中的方法
const methods = ['push', 'pop', 'shift', 'unshift'];
methods.forEach(method => {
// 重写上面列出的方法对应的函数
arrProto[method] = function() {
// 执行array原型中对应方法的操作 然后更新视图
arrayPrototype[method].call(this, ...arguments)
updateView()
}
})
function bind(target, key, value) {
// 实现响应式的核心
watch(value)
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
// 判断是否设置为了相同的值
if(newValue !== value) {
value = newValue
updateView()
}
}
})
}
function watch(target) {
// 监听对象属性 target就是具体的对象
// 做一下判断 如果传进来的数据不是对象或者说是null 就直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 判断是否为数组
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 遍历对象,使其每个属性变成响应式
for (let key in target) {
bind(target, key, target[key])
}
}
watch(data)
data.arr.push(5)
console.log(data.arr)
这样就大功告成了。