Js数据监听

今天带大家来实现一个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
}

在这里附上源码地址,大家可以认真看一下,其中原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容