vue2.0中实现数据劫持的方法:
Object.defineProperty()
var obj = {};
Object.defineProperty(obj, 'arr', {
configurable: true, // 默认false
enumerable: true, // 默认false
get: function(target, key) {
return Reflect.get(target, key);
},
set: function(target, key, val) {
Reflect.set(target, key, val)
console.log('数据更新')
}
})
configurable
当且仅当该属性的 configurable
键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为 false
。
enumerable
当且仅当该属性的 enumerable
键值为 true
时,该属性才会出现在对象的枚举属性中。
默认为 false
。
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined
。
writable
当且仅当该属性的 writable
键值为 true
时,属性的值,也就是上面的 value
,才能被改变。
默认为 false
。
这种方法可以监听到数据的变化,然后在set函数的的时候改变视图。但是这种方法有缺点,无法监听数组的某些变化:
obj.arr = [9,8] // 数据更新
obj.arr.push(3) // 没有打印
数组的元素增加并不能触发set函数,所以这种情况下是无法更新视图的,但是使用过vue的应该都知道,直接调用push方法可以触发视图的更新,这是因为vue内部对一些特定的方法进行了重写:
var arrProtoType = Array.prototype;
var newArrayProtoType = Object.create(arrProtoType);
var injected = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
injected.forEach(function(type) {
newArrayProtoType[type] = function(args) {
var args = Array.prototype.slice.call(args)
var result = arrProtoType[type].apply(this, args);
console.log('数组改变了');
return result;
}
})
var arr = [4,5]
arr.__proto__ = newArrayProtoType;
arr.push(8) // 数据更新
上面代码重写的Array原型上的一些方法,当调用push的时候,会调用新的push方法,这个方法会调用Array原型上的push方法,然后又做了些新操作,比如说更新视图。也可以理解数组数据的监听是Object.defineProperty和重写原型方法的结合来实现的。
如果我来修改数组的长度,上面的两种方法是都不能监听到的。
vue3.0中实现数据劫持的方法:
Proxy
var target = {};
var obj = new Proxy(target, {
get: function(target, key) {
return Reflect.get(target, key)
},
set: function(target, key, value) {
Reflect.set(target, key, value);
console.log('数据改变了')
}
})
obj.arr = [3,4];
obj.arr.push(6) // 不会打印
数组的只改变原数组方法依然监听不到改变,但是这种方式比 Object.defineProperty()性能要好,2.0的方法需要把所有的状态都设置为可以监听的属性,而3.0的方法不需要这一步,只需要把data传递进去就可以了。并且改变的新的对象,不是data。
下面是一种vue2.0的数据劫持,发布订阅模式:
class Vue {
constructor(options) {
// 获取到根元素
this.el = document.querySelector(options.el) // #root
// 获取到数据
this.data = options.data
// 收集数据和订阅者,每一个数据都有很多个订阅者 myText: [订阅者1, 订阅者2, 订阅者3]
this._derective = {}
// 调用函数
this.Observer(this.data)
this.Compile(this.el)
}
// 负责劫持数据
Observer(data) {
console.log(data)
for(let key in data) {
this._derective[key] = []
let value = data[key]
const watch = this._derective[key]
Object.defineProperty(data, key, {
get: () => value,
set: (newValue) => {
if(newValue !== value) {
value = newValue
watch.forEach(watcher => {
// watcher是订阅实例
watcher.update()
})
}
}
})
}
// 劫持数据 更新实图
}
// 负债解析指令 发布订阅者
Compile(el) {
console.log(el, 'el')
// 找到根元素下面所有的子节点
let nodesArr = el.children
console.log(nodesArr, 'nodesArr')
for(let i = 0, length = nodesArr.length; i < length; i++) {
// 如果每个字节点下面还有节点,需要再走一遍这个函数,这里用到了递归
if(nodesArr[i].children && nodesArr[i].children.length>0) {
// 这里用递归处理
this.Compile(nodesArr[i])
}
// node是当前元素 先查当前元素有没有指令属性,再取属性的值 然后发布订阅者
if(nodesArr[i].hasAttribute('v-text')) {
const atrributeValue = nodesArr[i].getAttribute('v-text')
// 发布订阅者 生成下面格式的数据,每一个订阅者 都是一个使用了该数据的元素。 收集所有的订阅者
// {myText: [订阅者1, 订阅者2, 订阅者3], myBox: [订阅者1, 订阅者2, 订阅者3]}
this._derective[atrributeValue].push(new Watcher(nodesArr[i], this, atrributeValue, 'innerHTML'))
}
if(nodesArr[i].hasAttribute('v-model')) {
const atrributeValue = nodesArr[i].getAttribute('v-model')
// let dataValue = this.data[atrributeValue]
nodesArr[i].addEventListener("input", () => {
this.data[atrributeValue] = nodesArr[i].value
})
this._derective[atrributeValue].push(new Watcher(nodesArr[i], this, atrributeValue, 'value'))
}
}
}
}
// 订阅者 主要作用是更新试图
class Watcher {
constructor(el, vm, value, op) {
this.el = el
this.vm = vm
this.value = value
this.op = op
this.update()
}
update() {
this.el[this.op] = this.vm.data[this.value]
}
}
new Vue({
el: '#root',
data:{
myText: "我是vue的数据双向绑定",
myBox: "我是vue的另外一个数据双向绑定"
}
})
有时候我们项目中可能又很多全局组件,都需要在main.js中进行全局注册,想这样:
如果量较大的话,就会让main.js看起来很臃肿。可以这样来做优化。
fali.vue
<template>
<div>
加载失败
</div>
</template>
<script>
export default {
}
</script>
<style lang="css" scoped>
</style>
loading.vue
<template>
<div>
数据加载中
</div>
</template>
<script>
export default {
}
</script>
<style lang="css" scoped>
</style>
index.js
import Loading from "./loading.vue"
import Fail from "./fail.vue"
export default {
install: (Vue) => {
Vue.component('loading', Loading)
Vue.component('fail', Fail)
}
}
只需要在main.js中引入,然后use,这样,所有的全局组件就都注册上去了,可以在任意地方使用。代码看起来简介了很多。
import Vue from 'vue'
import App from './app.vue'
import GlobalCom from "./common-components"
Vue.use(GlobalCom);
new Vue({
el: '#app',
render: h => h(App)
})
vue项目下运行vue ui可以打开vue 的可视化构建页面。类似下面:
组件的创建
组件注册
vue-html-loader解析vue文件中的template,并把外部的style统一归vue管理
vue-style-loader解析vue文件中的style
vue-loader 解析vue文件
vue-template-compiler编译器