1、什么是数据劫持?
首先写一个简单vue 例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据劫持</title>
</head>
<body>
<div id="app">{{message}}</div>
<script src="vue.js"></script>
<script>
var vm = new Vue({
el:"#app",
data(){
return {
message:"测试数据"
}
}
})
</script>
</body>
</html>
将数据挂载到视图上很简单,使用{{}}模板即可,那么如果要进行二次渲染、再次渲染,该怎么做呢?
我们可以使用延迟函数 ,若要在1s之后修改数据,添加如下函数:
setTimeout(function(){
vm.message="123456789";
// vm["message"]="123456789"; //两种方式都可以修改,效果一样
},1000)
运行发现页面显示内容发生改变,说明改变数据的时候触发视图的变化
再次渲染的时候怎么知道数据的变化呢 ?-----数据劫持/数据观察
在vue2.0中使用Object.defineProperty()来实现vue数据劫持这一行为。
数据劫持:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
2、如何劫持:Object.defineProperty()在数组 对象中的使用
首先创建一个对象,并将name属性数据劫持:
let vm = {
name:"张三"
}
let vm = Object.defineProperty(vm,"name",{
get(){
console.log("get...")
return "张三"
},
set(newValue){
console.log("set...")
console.log("新值",newValue)
}
})
但是以上对象只有一个属性,若有多个属性或者嵌套多层时,就需要逐层遍历对象。
let obj = {
name:"王五",
age:22
}
Object.keys(vm).forEach(key=>{
let value = vm[key]
Object.defineProperty(vm,key,{
get(){
console.log("get....")
return value
},
set(newV){
console.log("set....")
if(newV!==value){
value= newV
}
}
})
})
若执行 console.log(vm.name)输出name值,则控制台输出结果为:
get...
王五
若修改name值,vm.name=“李四”,则控制台输出结果为:
set...
但是,若给对象新增属性 vm.sex=“23432”,删除属性delete vm.name都监测不到变化。
Object.defineProperty() 可以监测到属性的获取、修改,但是新增、删除监测不到
那么数组的情况是什么样子的呢?
let array = [1,2,3]
Object.keys(array).forEach(key=>{
let value = vm[key]
Object.defineProperty(vm,key,{
get(){
console.log("get....")
return value
},
set(newV){
console.log("set....")
if(newV!==value){
value= newV
}
}
})
})
array.push(4) // 监测不到
array[2] = 9 // 这个能正常输出 set...
修改其中某个索引的值,都会输出set…
但是若给数组加一个值,array.push(4) ,监测不到数组变化,还有pop等方法均监测不到数组变化。
若执行的方法修改了原数组, Object.defineProperty() 监测不到数组的变化,但是若该方法不修改原数组,返回一个新数组的时候, Object.defineProperty()就可以检测到数组的变化。
从上可以看出,使用Object.defineProperty() 对对象、数组的监测实现的不是很好,那么,就需要使用Proxy实现。
3、Proxy
在 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty() 来实现数据响应式。Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。可以说Proxy 是defineProperty的升级版。
Proxy 的使用:
var newVm = new Proxy(vm,{
get(target,key){
console.log("get.....");
console.log(target);
return target[key]
},
set(target,key,newV){
console.log("set.....");
if(target[key]!==newV){
target[key]=newV
}
}
})
proxy不需要对数组、对象进行比遍历,性能上比较好,而且可以完美的监听到任何方式的数据改变,唯一的缺陷就是浏览器的兼容性不好。
一道面试题
什么样的 a 可以满足 (a === 1 && a === 2 && a === 3) === true 呢?(注意是 3 个 =,也就是严格相等)???
let a = {
temp:1,
valueOf:function(){
return this.temp++
}
}
Object.defineProperty(window, 'temp', {
get () {
temp++
return temp
}
})
console.log(a === 1 && a === 2 && a === 3) // true
Vue 无法检测到对象属性的添加或删除
由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。例如,对于:
//第一种方式 Vue.set(object, propertyName, value)
Vue.set(vm.someObject, 'b', 2)
//第二种方式 vm.$set 实例方法
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新属性不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })