(持续更新 )HarmonyOS系统中的JS开发框架 4

接上文

第一部分

export function Subject(target) {
  const subject = this;
  subject._hijacking = true;
  defineProp(target, SYMBOL_OBSERVABLE, subject);


  if (Array.isArray(target)) {
    hijackArray(target);
  }


  Object.keys(target).forEach(key => hijack(target, key, target[key]));
}

构造函数。基本没什么难点。设置 _hijacking 属性为 true,用来标示这个对象已经被劫持了。Object.keys 通过遍历来劫持每个属性。如果是数组,则调用 hijackArray。

第二部分

两个静态方法。

Subject.of = function(target) {
  if (!target || !canObserve(target)) {
    return target;
  }
  if (target[SYMBOL_OBSERVABLE]) {
    return target[SYMBOL_OBSERVABLE];
  }
  return new Subject(target);
};


Subject.is = function(target) {
  return target && target._hijacking;
};

Subject 的构造函数并不直接被外部调用,而是封装到了 Subject.of 静态方法中。

如果目标不能被观察,那么直接返回目标。如果

target[SYMBOL_OBSERVABLE] 不是 undefined,说明目标已经被初始化过了。

否则,调用构造函数初始化 Subject。

Subject.is 则用来判断目标是否被劫持过了。

第三部分

Subject.prototype.attach = function(key, observer) {
  if (typeof key === 'undefined' || !observer) {
    return;
  }
  if (!this._obsMap) {
    this._obsMap = {};
  }
  if (!this._obsMap[key]) {
    this._obsMap[key] = [];
  }
  const observers = this._obsMap[key];
  if (observers.indexOf(observer) < 0) {
    observers.push(observer);
    return function() {
      observers.splice(observers.indexOf(observer), 1);
    };
  }
};

这个方法很眼熟,对,就是上文的

Observer.prototype.subscribe 中调用的。作用是某个观察者用来订阅主题。而这个方法则是“主题是怎么订阅的”。

观察者维护这一个主题的哈希表 _obsMap。哈希表的 key 是需要订阅的 key。比如某个观察者订阅了 name 属性的变化,而另一个观察者订阅了 age 属性的变化。而且属性的变化还可以被多个观察者同时订阅,因此哈希表存储的值是一个数组,数据的每个元素都是一个观察者。

第四部分

Subject.prototype.notify = function(key) {
  if (
    typeof key === 'undefined' ||
    !this._obsMap ||
    !this._obsMap[key]
  ) {
    return;
  }
  this._obsMap[key].forEach(observer => observer.update());
};

当属性发生变化是,通知订阅了此属性的观察者们。遍历每个观察者,并调用观察者的 update 方法。我们上文中也提到了,脏检查就是在这个方法内完成的。

第五部分

Subject.prototype.setParent = function(parent, key) {
  this._parent = parent;
  this._key = key;
};


Subject.prototype.notifyParent = function() {
  this._parent && this._parent.notify(this._key);
};

这部分是用来处理属性嵌套(nested object)的问题的。就是类似这种对象:

{ user: { name: ‘JJC’ } }。

第六部分

function hijack(target, key, cache) {
  const subject = target[SYMBOL_OBSERVABLE];


  Object.defineProperty(target, key, {
    enumerable: true,
    get() {
      const observer = ObserverStack.top();
      if (observer) {
        observer.subscribe(subject, key);
      }


      const subSubject = Subject.of(cache);
      if (Subject.is(subSubject)) {
        subSubject.setParent(subject, key);
      }


      return cache;
    },
    set(value) {
      cache = value;
      subject.notify(key);
    }
  });
}

这一部分展示了如何使用

Object.defineProperty 进行属性劫持。当设置属性时,会调用 set(value),设置新的值,然后调用 subject 的 notify 方法。这里并不进行任何检查,只要设置了属性就会调用,即使属性的新值和旧值一样。notify 会通知所有的观察者。

第七部分

劫持数组方法。

const ObservedMethods = {
  PUSH: 'push',
  POP: 'pop',
  UNSHIFT: 'unshift',
  SHIFT: 'shift',
  SPLICE: 'splice',
  REVERSE: 'reverse'
};


const OBSERVED_METHODS = 
Object.keys(ObservedMethods).map(
    key => ObservedMethods[key]
);

ObservedMethods 定义了需要劫持的数组函数。前面大写的用来做 key,后面小写的是需要劫持的方法。

function hijackArray(target) {
  OBSERVED_METHODS.forEach(key => {
    const originalMethod = target[key];


    defineProp(target, key, function() {
      const args = Array.prototype.slice.call(arguments);
      originalMethod.apply(this, args);


      let inserted;
      if (ObservedMethods.PUSH === key || ObservedMethods.UNSHIFT === key) {
        inserted = args;
      } else if (ObservedMethods.SPLICE===key) {
        inserted = args.slice(2);
      }


      if (inserted && inserted.length) {
        inserted.forEach(Subject.of);
      }


      const subject = target[SYMBOL_OBSERVABLE];
      if (subject) {
        subject.notifyParent();
      }
    });
  });
}

数组的劫持和对象不同,不能使用

Object.defineProperty。我们需要劫持 6 个数组方法。分别是头部添加、头部删除、尾部添加、尾部删除、替换/删除某几项、数组反转。

通过重写数组方法实现了数组的劫持。但是这里有一个需要注意的地方,数据的每一个元素都是被观察过的,但是当在数组中添加了新元素时,这些元素还没有被观察。因此代码中还需要判断当前的方法如果是 push、unshift、splice,那么需要将新的元素放入观察者队列中。

另外两个文件分别是单元测试和性能分析,这里就不再分析了。

整体而言,比我预想的要好一些。


想了解更多内容,请访问:
51CTO和华为官方战略合作共建的鸿蒙技术社区
https://harmonyos.51cto.com?jssq

发表评论

邮箱地址不会被公开。 必填项已用*标注