今天带大家来实现一个js的数据监听模型,我们会按照下面的步骤来实现。
1 Object.keys()
Object.keys()方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用(for in)循环遍历该对象时返回的顺序一致 (顺序一致不包括数字属性)(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。
let obj = {
name:'dailu',
age:123,
sex:"female"
}
console.log(Object.keys(obj)); //["name", "age", "sex"]
var arr = ["a","b","c"];
console.log(Object.keys(arr)); //["0", "1", "2"]
// 类数组对象
var obj1 = {0:"a",1:"b",2:"c"}
console.log(Object.keys(obj1)); //["0", "1", "2"]
2 Object.defineProperty()
使用Object.defineProperty()方法可以更方便的监听一个对象的变化。当我们的视图和数据任何一方发生变化的时候,我们希望能够通知对方也更新,这就是所谓的数据双向绑定。
var o = {
a: 0
}
Object.defineProperty(o, "b", {
get: function () {
return this.a + 1;
},
set: function (value) {
this.a = value / 2;
}
});
console.log(o.a); // "0"
console.log(o.b); // "1"
// 更新o.a
o.a = 5;
console.log(o.a); // "5"
console.log(o.b); // "6"
// 更新o.b
o.b = 10;
console.log(o.a); // "5"
console.log(o.b); // "6"
数据属性有4个描述行为的特性
- 1 [[configurable]]:表示能否能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
- 2 [[Enumerable]]:当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中
- 3 [[Writable]]:表示能否修改属性的值
定义多个属性Object.defineProperties()
var book ={}
Object.defineProperties(book,{
_year:{
configurable:true,
enumerable:true,
writable:true,
value:2004
},
edition:{
configurable:true,
enumerable:true,
writable:true,
value:1
},
year:{
enumerable:true,
get:function (){
return this._year
},
set:function (val){
if(val>2004){
this.edition += val-2004
}
}
}
})
console.log(book._year); //2004
book._year = 2005
console.log(book.edition);// 1
console.log(book.year); // 2005
console.log(book._year); // 2005
book.year = 2005
console.log(book.edition); //2
console.log(book.year);//2005
Vue源码core/util/lang.jsS中定义了这样一个方法:
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
3 Object.getOwnPropertyDescriptor( )
Object.getOwnPropertyDescriptor() 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
语法:Object.getOwnPropertyDescriptor(obj, prop)
var o = {
a: 0
}
Object.defineProperty(o, "b", {
get: function () {
return this.a + 1;
},
set: function (value) {
this.a = value / 2;
}
});
var des = Object.getOwnPropertyDescriptor(o,'b');
console.log(des);
console.log(des.get);
4 数据监测
/ /观察者构造函数
function Observer (value) {
this.value = value
this.walk(value)
}
// 递归调用,为对象绑定getter/setter
Observer.prototype.walk = function (obj) {
var keys = Object.keys(obj)
for (var i = 0, l = keys.length; i < l; i++) {
this.convert(keys[i], obj[keys[i]])
}
}
// 将属性转换为getter/setter
Observer.prototype.convert = function (key, val) {
defineReactive(this.value, key, val)
}
我们可以想到在 defineReactive方法里面写defineproperty最好
// 定义对象属性的getter/setter
function defineReactive (obj, key, val) {
var property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 保存对象属性预先定义的getter/setter
var getter = property && property.get
var setter = property && property.set
var childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
//取决于该对象是否有设置get方法
var value = getter ? getter.call(obj) : val
console.log("访问:"+key)
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 对新值进行监听
childOb = observe(newVal)
console.log('更新:' + key + ' = ' + newVal)
}
})
}
// 创建数据观察者实例
function observe (value) {
// 当值不存在或者不是对象类型时,不需要继续深入监听
if (!value || typeof value !== 'object') {
return
}
return new Observer(value)
}
我们下面来个粒子看一下
let data = {
user: {
name: 'zhaomenghuan',
age: '24'
},
address: {
city: 'beijing'
}
}
observe(data)
console.log(data.user.name)
// 访问:user
// 访问:name
data.user.name = 'ZHAO MENGHUAN'
// 访问:user
// 更新:name = ZHAO MENGHUAN
关于数组变化的监听的给出下面一段代码供大家参考
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
// 数组的变异方法
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
// 缓存数组原始方法
var original = arrayProto[method]
def(arrayMethods, method, function mutator () {
var i = arguments.length
var args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
console.log('数组变动')
return original.apply(this, args)
})
})
let skills = ['JavaScript', 'Node.js', 'html5']
// 原型指针指向具有变异方法的数组对象
skills.__proto__ = arrayMethods
skills.push('java')
// 数组变动
skills.pop()
5 自制数据监听
Dep.js
function Dep(){
this.subs = [] //存放订阅者的数组
}
Dep.target = null;//定义一个全局变量,用来判断是否是watcher调用了getter
var s = Dep.prototype;
// 把订阅者都存到数组里面
s.addSub = function (sub){
this.subs.push(sub)
}
// 订阅者想订阅的事件,注册到事件中心
s.notify = function (){
// 一旦调用了set就触发notify,然后遍历每个观察者,并触发他们相应的update方法
this.subs.forEach(function (sub){
sub.update();
/*
sub.update():
调度中心统一调度订阅者注册到调度中心的处理代码
*/
})
}
s.depend = function (){
Dep.target.addDep(this) //这个this指向Dep实例dep
}
一个watcher来负责对数据变动作出改变
// Watcher的实例就是订阅者
function Watcher(vm,exp,cb){
this.cb = cb;
this.vm = vm;
this.exp = exp
this.value = this.get()//更新前的值
}
var w = Watcher.prototype;
// 订阅者的更新方法
w.update = function (){
var value = this.get() //这里是更新后的值
if(value!==this.value){
this.value = value //用新值覆盖旧值
console.log(value);
this.cb.call(this.vm,value)
}
}
// 通过Watcher的实例调用了getter
w.get = function (){
Dep.target = this//表明是watcher调用了getter
// 遍历data的所有属性(深层遍历)
return this.vm.data[this.exp] //这里会调用get方法
}
w.addDep = function (dep){
dep.addSub(this) //this值watcher
}
在这里附上源码地址,大家可以认真看一下,其中原理。