大家都知道小程序其实和Vue的写法以及原理都存在很大的相同,但是里有watch监听可以监听data定义的数据,而小程序里并没有(其实小程序并不需要哈哈,大家可以想想。但是作为程序员总想凭什么没有!)。

**其实监听器的原理,就是将data中需监听的属性写在watch对象中,并给其提供一个方法,当被监听属性的值改变时,调用该方法。

所以很显然,我们需要用到Javascript中的Object.defineProperty()方法,来手动劫持对象的getter/setter 从而实现给对象赋值时(调用setter),执行watch对象中相对应的函数,达到监听效果。Object.defineProperty()不在这里详细介绍,还不会使用的童鞋速戳这里-> Object.defineProperty()介绍。Object.defineProperty()

1监听函数实现

function setWatcher(page) {
  let data = page.data;
  let watch = page.watch;
  Object.keys(watch).forEach(v => {
    let key = v.split('.'); // 将watch中的属性以'.'切分成数组
    
    let nowData = data; // 将data赋值给nowData
    for (let i = 0; i < key.length - 1; i++) { // 遍历key数组的元素,除了最后一个!
      nowData = nowData[key[i]]; // 将nowData指向它的key属性对象   
    }
    let lastKey = key[key.length - 1];
    
    // 假设key==='my.name',此时nowData===data['my']===data.my,lastKey==='name'
    let watchFun = watch[v].handler || watch[v]; // 兼容带handler和不带handler的两种写法
    let deep = watch[v].deep; // 若未设置deep,则为undefine
    observe(nowData, lastKey, watchFun, deep, page); // 监听nowData对象的lastKey
  })
};
function observe(obj, key, watchFun, deep, page) {
  var val = obj[key];
  // 判断deep是true 且 val不能为空 且 typeof val==='object'(数组内数值变化也需要深度监听)
  if (deep && val != null && typeof val === 'object') {
    Object.keys(val).forEach(childKey => { // 遍历val对象下的每一个key
      observe(val, childKey, watchFun, deep, page); // 递归调用监听函数
    })
  }
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    set: function (value) {
      // 用page对象调用,改变函数内this指向,以便this.data访问data内的属性值
      watchFun.call(page, value, val); // value是新值,val是旧值
      val = value;
    },
    get: function () {
      return val;
    }
  })
};

2.页面Page重写(实现无需引入)

// 保存原生构造函数
const OldPage = Page;
Page = function (conf) {
// 这里的conf就是Page函数的内容{data, onLoad, onShow, ...}
	 // 重写onLoad方法,用一个变量保存旧的onLoad函数
    let oldonLoad = conf.onLoad; // 同理
   conf.onLoad = function() {
      //执行onLoad的默认事件 do something
      // *********************************************
       // 此处不能写成oldonLoad(),否则没有this,this.setData等方法为undefined。这里的this在Page构造函数实例化的时候才会指定
      // 在Page构造函数实例化的时候,小程序会将当前的Page对象的原型链(__proto__)增加很多方法,例如setData。当前的obj没有setData
      oldonLoad.call(this)
    }
  return OldPage(conf); // 将原生的构造函数return出去保持页面函数正常执行
};

3完整代码实现

1.在utils文件夹创建Page.js

// Page.js
// 保存原生构造函数
const OldPage = Page;
function setWatcher(page) {
  let data = page.data;
  let watch = page.watch;
  Object.keys(watch).forEach(v => {
    let key = v.split('.'); // 将watch中的属性以'.'切分成数组
    
    let nowData = data; // 将data赋值给nowData
    for (let i = 0; i < key.length - 1; i++) { // 遍历key数组的元素,除了最后一个!
      nowData = nowData[key[i]]; // 将nowData指向它的key属性对象   
    }
    let lastKey = key[key.length - 1];
    
    // 假设key==='my.name',此时nowData===data['my']===data.my,lastKey==='name'
    let watchFun = watch[v].handler || watch[v]; // 兼容带handler和不带handler的两种写法
    let deep = watch[v].deep; // 若未设置deep,则为undefine
    observe(nowData, lastKey, watchFun, deep, page); // 监听nowData对象的lastKey
  })
};
function observe(obj, key, watchFun, deep, page) {
  var val = obj[key];
  // 判断deep是true 且 val不能为空 且 typeof val==='object'(数组内数值变化也需要深度监听)
  if (deep && val != null && typeof val === 'object') {
    Object.keys(val).forEach(childKey => { // 遍历val对象下的每一个key
      observe(val, childKey, watchFun, deep, page); // 递归调用监听函数
    })
  }
  var that = this;
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    set: function (value) {
      // 用page对象调用,改变函数内this指向,以便this.data访问data内的属性值
      watchFun.call(page, value, val); // value是新值,val是旧值
      val = value;
    },
    get: function () {
      return val;
    }
  })
};
// 还原上个页面的参数到 options, 并删除 options.params  
const extractParams = function (query = {}) {  
  const { params } = query  
  let options = { ...query }  
  if (params !== undefined) {  
    options = {  
      ...options,  
      ...JSON.parse(decodeURIComponent(params)),  
    }  
    delete options.params  
  }  

  return options  
}  
Page = function (conf) {
  if (conf.watch) {
    // 设置了watch监听
    let oldonLoad = conf.onLoad;
    conf.onLoad = function(options = {}) {
      setWatcher(this)
      oldonLoad && oldonLoad.call(this, extractParams(options))
    }
  } else {
  }
  return OldPage(conf);
};
module.exports = {
  Page
}



// 在app.js头部引入即可
let Page = require('./utils/page.js').Page

// 在index.js使用
Page({
  data: {
    watchData: "",
    my: {
      name: 'yeshen',
      age: 18,
      hobby: ['girls', 'games'],
      info: {
        tag: "年轻的少年",
        like: {
          food: "鱼"
        }
      }
    },
  },
  watch: {
    'my': {
      handler(newValue, oldValue) {
        console.log(newValue, oldValue, '-----监听到数据变化')
      },
      deep: true
    }
  },
  onLoad: function () {
    this.setData({
      'my.age': "20"
    })
    setTimeout(() => {
      this.data.my.info.like.food = "肉肉"
    }, 1000)
    
  },
})

4微信小程序代码片段

!!!戳这里 =>

Logo

前往低代码交流专区

更多推荐