首頁 web前端 js教程 解剖式分析 鴻蒙系統的JavaScript框架

解剖式分析 鴻蒙系統的JavaScript框架

Sep 17, 2020 pm 05:04 PM
javascript 鴻蒙系統

解剖式分析 鴻蒙系統的JavaScript框架

相關學習推薦:javascript

#我在前文中曾經介紹過鴻蒙的Javascript 框架,這幾天終於把JS 倉庫編譯通過了,期間踩了不少坑,也為鴻蒙貢獻了幾個PR。今天我們就來逐行分析鴻蒙系統中的 JS 框架。

文中的所有程式碼都基於鴻蒙的當前最新版(版本為 677ed06,提交日期為 2020-09-10)。

鴻蒙系統使用 JavaScript 開發 GUI 是一種類似微信小程式、輕應用的模式。而這個 MVVM 模式中,V 其實是由 C 來承擔的。 JavaScript 程式碼只是其中的 ViewModel 層。

鴻蒙 JS 框架是零依賴的,只在開發打包過程中使用到了一些 npm 套件。打包完之的程式碼是沒有依賴任何 npm 套件的。我們先來看看用鴻蒙 JS 框架寫出來的 JS 程式碼到底長什麼樣子。

export default {
  data() {    return { count: 1 };
  },
  increase() {
    ++this.count;
  },
  decrease() {
    --this.count;
  },
}复制代码
登入後複製

如果我不告訴你這是鴻蒙,你甚至會以為它是 vue 或小程式。如果單獨把 JS 拿出來使用(脫離鴻蒙系統),程式碼是這樣:

const vm = new ViewModel({
  data() {    return { count: 1 };
  },
  increase() {
    ++this.count;
  },
  decrease() {
    --this.count;
  },
});console.log(vm.count); // 1vm.increase();console.log(vm.count); // 2vm.decrease();console.log(vm.count); // 1复制代码
登入後複製

倉庫中的所有 JS 程式碼實作了一個響應式系統,充當了 MVVM 中的 ViewModel。

下面我們逐行分析。

src 目錄中總共有 4 個目錄,總計 8 個檔案。其中 1 個是單元測試。還有 1 個效能分析。再除去 2 個 index.js 文件,有用的文件總共是 4 個。也是本文分析的重點。

src
├── __test__
│   └── index.test.js
├── core
│   └── index.js
├── index.js
├── observer
│   ├── index.js
│   ├── observer.js
│   ├── subject.js
│   └── utils.js
└── profiler
    └── index.js复制代码
登入後複製

首先是入口文件,src/index.js,只有 2 行程式碼:

import { ViewModel } from './core';export default ViewModel;复制代码
登入後複製

其實就是重新匯出。

另一個類似的檔案是 src/observer/index.js,也是 2 行程式碼:

export { Observer } from './observer';export { Subject } from './subject';复制代码
登入後複製

observer 和 subject 實作了一個觀察者模式。 subject 是主題,也就是被觀察者。 observer 是觀察者。當 subject 有任何變化時需要主動通知被觀察者。這就是響應式。

這 2 個檔案都使用到了 src/observer/utils.js,所以我們先分析一下 utils 檔案。分 3 部分。

第一部分

export const ObserverStack = {  stack: [],
  push(observer) {    this.stack.push(observer);
  },
  pop() {    return this.stack.pop();
  },
  top() {    return this.stack[this.stack.length - 1];
  }
};复制代码
登入後複製

首先是定義了一個用來存放觀察者的堆疊,遵循後進先出的原則,內部使用stack數組來儲存。

  • 入堆疊運算 push,和陣列的 push 函式一樣,在堆疊頂端放入一個觀察者 observer。
  • 出棧操作 pop,和陣列的 pop 函數一樣,在將堆疊頂部的觀察者刪除,並傳回這個被刪除的觀察者。
  • 取棧頂元素 top,和 pop 操作不同,top 是把堆疊頂元素取出來,但是不會刪除。

第二部分

export const SYMBOL_OBSERVABLE = '__ob__';export const canObserve = target => typeof target === 'object';复制代码
登入後複製

定義了一個字串常數 SYMBOL_OBSERVABLE。為了後面用方便。

定義了一個函數 canObserve,目標是否可以被觀察到。只有物件才能被觀察,所以使用 typeof 來判斷目標的類型。等等,好像有什麼不對。如果 targetnull 的話,函數也會回傳 true。如果 null 不可觀察,那麼這就是一個 bug。 (寫這篇文章的時候我已經提了一個 PR,並詢問了這種行為是否是期望的行為)。

第三個部分

export const defineProp = (target, key, value) => {  Object.defineProperty(target, key, { enumerable: false, value });
};复制代码
登入後複製

這個沒有什麼好解釋的,就是Object.defineProperty 程式碼太長了,定義一個函數來避免程式碼重複。

下面再來分析觀察者 src/observer/observer.js,分 4 部分。

第一部分

export function Observer(context, getter, callback, meta) {  this._ctx = context;  this._getter = getter;  this._fn = callback;  this._meta = meta;  this._lastValue = this._get();
}复制代码
登入後複製

建構子。接受 4 個參數。

context 目前觀察者所處的上下文,類型是 ViewModel。當第三個參數 callback 呼叫時,函數的 this 就是這個 context

getter 類型是一個函數,用來取得某個屬性的值。

callback 類型是一個函數,當某個值變更後執行的回呼函數。

meta 元資料。觀察者(Observer)並不關注 meta 元資料。

在建構子的最後一行,this._lastValue = this._get()。下面來分析 _get 函數。

第二部分

Observer.prototype._get = function() {  try {
    ObserverStack.push(this);    return this._getter.call(this._ctx);
  } finally {
    ObserverStack.pop();
  }
};复制代码
登入後複製

ObserverStack 就是上面分析過的用來儲存所有觀察者的堆疊。將目前觀察者入堆疊,並透過 _getter 取得目前值。結合第一部分的建構函數,這個值儲存在了 _lastValue 屬性中。

執行完這個過程後,這個觀察者就已經初始化完成了。

第三部分

#
Observer.prototype.update = function() {  const lastValue = this._lastValue;  const nextValue = this._get();  const context = this._ctx;  const meta = this._meta;  if (nextValue !== lastValue || canObserve(nextValue)) {    this._fn.call(context, nextValue, lastValue, meta);    this._lastValue = nextValue;
  }
};复制代码
登入後複製

这部分实现了数据更新时的脏检查(Dirty checking)机制。比较更新后的值和当前值,如果不同,那么就执行回调函数。如果这个回调函数是渲染 UI,那么则可以实现按需渲染。如果值相同,那么再检查设置的新值是否可以被观察,再决定到底要不要执行回调函数。

第四部分

Observer.prototype.subscribe = function(subject, key) {  const detach = subject.attach(key, this);  if (typeof detach !== 'function') {    return;
  }  if (!this._detaches) {    this._detaches = [];
  }  this._detaches.push(detach);
};

Observer.prototype.unsubscribe = function() {  const detaches = this._detaches;  if (!detaches) {    return;
  }  while (detaches.length) {
    detaches.pop()();
  }
};复制代码
登入後複製

订阅与取消订阅。

我们前面经常说观察者和被观察者。对于观察者模式其实还有另一种说法,叫订阅/发布模式。而这部分代码则实现了对主题(subject)的订阅。

先调用主题的 attach 方法进行订阅。如果订阅成功,subject.attach 方法会返回一个函数,当调用这个函数就会取消订阅。为了将来能够取消订阅,这个返回值必需保存起来。

subject 的实现很多人应该已经猜到了。观察者订阅了 subject,那么 subject 需要做的就是,当数据变化时即使通知观察者。subject 如何知道数据发生了变化呢,机制和 vue2 一样,使用 Object.defineProperty 做属性劫持。

下面再来分析观察者 src/observer/subject.js,分 7 部分。

第一部分

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 === &#39;undefined&#39; ||
    !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: &#39;JJC&#39; } }

第六部分

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: &#39;push&#39;,  POP: &#39;pop&#39;,  UNSHIFT: &#39;unshift&#39;,  SHIFT: &#39;shift&#39;,  SPLICE: &#39;splice&#39;,  REVERSE: &#39;reverse&#39;};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) {
        inserted = args.slice(2);
      }      if (inserted && inserted.length) {
        inserted.forEach(Subject.of);
      }      const subject = target[SYMBOL_OBSERVABLE];      if (subject) {
        subject.notifyParent();
      }
    });
  });
}复制代码
登入後複製

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

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

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

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

想了解更多编程学习,敬请关注php培训栏目!

以上是解剖式分析 鴻蒙系統的JavaScript框架的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1654
14
CakePHP 教程
1413
52
Laravel 教程
1306
25
PHP教程
1252
29
C# 教程
1225
24
鴻蒙系統支援哪些手機 鴻蒙系統支援哪些手機 Mar 20, 2024 pm 03:01 PM

鴻蒙系統是華為自主研發的分散式作業系統,提供使用者流暢、安全、智慧的全場景體驗。自發布以來,鴻蒙系統不斷更新迭代,支援的手機型號範圍逐步擴大,包括華為旗艦機型Mate系列、P系列,以及nova系列等。隨著鴻蒙系統開放給其他品牌手機進行適配,未來可能會有更多手機支持鴻蒙系統。此外,鴻蒙系統也支援平板、智慧穿戴、智慧螢幕等多種終端設備,實現快速連線、能力互助、資源共享。

如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

榮耀手機鴻蒙系統升級全攻略 榮耀手機鴻蒙系統升級全攻略 Mar 22, 2024 pm 09:18 PM

榮耀手機自面市以來,憑藉著出色的性能表現和頗具創新的設計理念,一直備受消費者的青睞。而隨著鴻蒙系統的發布,榮耀手機再次引起了廣泛關注。鴻蒙系統作為一款全新的自研作業系統,大幅提升了榮耀手機的使用體驗,更為使用者帶來了全新的功能和操控方式。在這樣的背景下,榮耀手機用戶迫不及待想要升級自己的手機至鴻蒙系統。為了幫助大家更好地進行升級,本文將為您提供一份全面的榮

鴻蒙系統好用嗎 鴻蒙系統好用嗎 Mar 18, 2024 pm 02:15 PM

鴻蒙系統是一款好用且值得考慮的作業系統。它擁有簡潔直覺的使用者介面、流暢穩定的系統效能、豐富的功能應用以及完善的生態支援。從使用者體驗、系統效能、功能豐富度到生態支持,鴻蒙系統在各方面都表現出色,能夠滿足使用者的日常使用需求,為使用者帶來便利高效的體驗。

鴻蒙系統和安卓系統資料互通嗎 鴻蒙系統和安卓系統資料互通嗎 Mar 18, 2024 pm 02:21 PM

鴻蒙系統和安卓系統,基於Linux內核,在底層架構上存在相似性,為資料互通提供了基礎。然而,兩系統在設計理念、系統架構和功能實現方面存在差異,導致資料互通性存在挑戰。實現落差系統和安卓系統的資料互通需要解決相容性、資料格式轉換等技術問題,以及製定統一的資料交換標準、建立可靠的資料傳輸機制等問題。同時,也需要應用開發者和生態系統合作夥伴的積極參與和支持,共同推動兩個系統之間的資料互通。

用戶口碑揭秘:鴻蒙系統到底好不好用? 用戶口碑揭秘:鴻蒙系統到底好不好用? Mar 25, 2024 am 09:39 AM

鴻蒙系統,作為華為推出的全新作業系統,在推出之初就引起了廣泛的關注和熱議。作為華為公司自主研發的作業系統,鴻蒙系統被寄予了厚望,被認為有望與目前流行的Android和iOS等作業系統一較高下。然而,隨著鴻蒙系統逐漸在市場上推出和普及,用戶開始對其使用體驗、功能性、穩定性等方面展開了各種討論和評價。那麼,鴻蒙系統到底好不好用?用戶的口碑又是如何?接下來,將從

詳解榮耀手機升級到鴻蒙系統的方法 詳解榮耀手機升級到鴻蒙系統的方法 Mar 25, 2024 am 11:51 AM

在一片新的科技領域中,新的作業系統總是備受關注。近日,榮耀手機宣布將會升級到華為開發的全新作業系統-鴻蒙系統。對許多榮耀手機用戶來說,這無疑是一大利好消息。但是,許多用戶或許對如何升級鴻蒙系統還存在著疑惑。本文將詳細解釋榮耀手機升級到鴻蒙系統的方法,幫助使用者更了解並操作。首先,要升級榮耀手機到鴻蒙系統,用戶需要確保手機已經連接到網絡,而且電量充足。此

See all articles