wepy 的運行原理探索

前言

剛搭建了一個博客,咋都得寫上幾篇文章,否則不就白搭了。vue

關於 博客

一開始打算用 hexo + gitalk + github 搭建一個,可是人太懶,總想一次完事,之後更新就寫一篇 .md 文章就能夠了,不想弄其餘流程了,而後就用了 vue + github-issuse-api + gitalk + github 搭建了一個一勞永逸的辦法,除非 github 掛了。react

關於 wepy

  • 官方介紹
    是小程序【最先(wepy你已經很成熟了)】 的框架之一,是一款讓小程序支持 【組件化開發(如今小程序已經支持npm構建,好像沒啥優點)】 的框架,經過預編譯的手段讓開發者能夠選擇本身 【喜歡的開發風格(類 vue 語法,數據劫持,自動 setData)】 去開發小程序。框架的細節優化,Promise,【Async Functions(原生不支持)】的引入都是爲了能讓開發小程序項目變得更加簡單,高效git

  • 優缺點
    - 優勢
          - vue 語法,更好的開發體驗
          - 更好的組件化
          - 更多的組件庫
          - 支持更多 ECMAscript 語法
      - 缺點
          - 官方維護不太積極,遇到問題解決太慢
          - 引入了額外的代碼,增長了代碼體積
    複製代碼
  • 代碼庫
    - 研究源碼版本 2.0.0-alpha.8
      - wepy 核心代碼是 cli 和 core
      - wepy-cli 是文件轉換打包的處理
      - core 是對數據綁定生命週期注入的處理
    複製代碼

core 源碼探索

  • wepy 核心代碼分爲 wepy-cli 和 core,這篇文章主要對 core 進行探索
  • 源碼版本 2.0.0-alpha.8
  • 源碼庫 wepy/packages/core
  • 爲了看的更清晰一些我刪除了大部分佔時沒有用到代碼
  • 每一個 js 部分,頭部我會寫清對於源碼文件路徑
  • 牢記一點,運行環境和this是在運行時候確認,否則你看源碼時候你會很懵,明明 this 上沒有這個函數或屬性啊,明明全局搜不到這個方法啊,就像下面這個 App() 你不和小程序環境關聯起來,你全局都搜不到有這麼一個函數
  • 代碼裏面註釋很關鍵

wepy 是怎麼編譯以後運行到小程序中去的?

// core/weapp/apis/index.js
import { app, page, component } from '../native/index';

export function initGlobalAPI (wepy) {

 wepy.app = app; // return App({})
 wepy.page = page; // return Page({})
 wepy.component = component; // return Component({})

 return wepy;
}
複製代碼

寫過 wepy2.0 的就知道,經過 wepy.page(), wepy.component(), 注入頁面和組件,以及在app.wpy中的 wepy.app() 方法 ,經過這三個方法,分別會返回 App() Page() Component(),寫過原生到小夥伴是否是很熟悉這三個 函數,分別是實現組冊小程序,註冊頁面和組件,這樣就和原生關聯起來了github

wepy 怎樣實現生命週期綁定關聯的了?

拿 app 爲例, 首先這個函數作了什麼?app 函數裏運行 patchAppLifecycle 函數 並傳入小程序運行環境 appConfig 中的對象【至關於 APP({}) 函數中的對象】,而後將 全部生命週期綁定上去,每次原生環境運行,觸發原生生命週期,每一個生命週期都會觸發一個回調函數,在執行 .wey 文件中存在的生命週期函數邏輯,這樣,小程序的生命週期和你頁面的生命週期進行了關聯web

// core/weapp/native/app.js
export function app (option, rel) {
 let appConfig = {};
 patchAppLifecycle(appConfig, option, rel); // 註冊和初始化生命週期
 return App(appConfig); // 這裏最終運行於小程序中 appConfig 就是小程序js中的邏輯
}

// patchAppLifecycle 函數
// core/weapp/init/lifecycle
export function patchAppLifecycle (appConfig, options, rel = {}) {
 appConfig.onLaunch = function (...args) {
   let vm = new WepyApp();
   // 生命週期回調,觸發頁面生命週期函數
   return callUserMethod(vm, vm.$options, 'onLaunch', args);
 };
 // 至關於 App({onLaunch: function() {}})

 // 獲取生命週期列表
 let lifecycle = getLifecycycle(WEAPP_APP_LIFECYCLE, rel, 'app');
 
 // 循環綁定生命週期
 lifecycle.forEach(k => {
  appConfig[k] = function (...args) {
   return callUserMethod(app, app.$options, k, args);
  };
 });
};
複製代碼

數據綁定

若是你瞭解過 vue 的數據綁定那這個實現原理也和 vue 同樣 (虛!!我懷疑是抄的,連函數名都同樣),也是實現了一個觀察者模式,只是,vue 作的更新處理是 patch 函數 diff 虛擬 dom 實現更新 web,而,wepy作調是調用 setData 更新數據,話很少說,上代碼。npm

// core/weapp/init/lifecycle
export function patchLifecycle (output, options, rel, isComponent) {
  // output 小程序的運行環境
  const initLifecycle = function (...args) {
    let vm = new initClass();
    // 將小程序 this 綁定到 vm.$wx 上去
    vm.$wx = this;
    // 初始化數據,進行數據綁定,進行綁定對數據只是 data 中的數據
    initData(vm, output.data, isComponent);
    // 進行依賴收集,作 setData
    initRender(vm, Object.keys(vm._data).concat(Object.keys(vm._props)).concat(Object.keys(vm._computedWatchers || {})));
    return callUserMethod(vm, vm.$options, 'created', args);
  };
 
  // 綁定到小程序到生命週期中去,作點事
  // 至關於 Page({created: initLifecycle(){}})
  output.created = initLifecycle;
};
複製代碼

來看看 initData 作了啥, 對 data 進行了處理,代理到 _data 屬性上去,進行觀察小程序

export function initData (vm, data) {
  vm._data = _data;
  Object.keys(_data).forEach(key => {proxy(vm, '_data', key)});

  observe({
    vm: vm,
    key: '',
    value: _data,
    parent: '',
    root: true
  });
}
複製代碼

observe 函數作了啥api

// core/weapp/observer/index.js
export function observe ({vm, key, value, parent, root}) {
  // 判斷是否爲觀察者 __ob__ 屬性定義
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  }
  // 觀察數據
  ob = new Observer({vm: vm, key: key, value: value,  parent});
  return ob
}

class Observer {
    // 若是是數組,則作特殊處理,由於數組 push, pop 等不能劫持,因此須要特殊處理
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(key, value);
    } else {
      // 不然劫持數據
      this.walk(key, value);
    }
}

walk (key, obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive({ vm: this.vm, obj: obj, key: keys[i], value: obj[keys[i]], parent: obj });
    }
}
defineReactive () {
   Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const val = getter ? getter.call(obj) : value
      // 依賴收集
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(val)) {
            dependArray(val)
          }
        }
      }
      return val
    },
    set: function reactiveSetter (newVal) {
      const val = getter ? getter.call(obj) : value
      // 更新數據
      value = newVal
      // 通知數據更新
      dep.notify();
    }
  })
}
複製代碼

若是是數組須要作特殊處理,數據,push,pop等方式改變等數據不能被劫持,若是不是,就作數據劫持 getter setter,數據劫持的初始化已經作完了,如今在來看如進行等依賴收集,回到 initRender() 上了,進行依賴收集,就得觸發一次數據到 getter, initRender 就是幹這個的,watch 觀察每一個頁面實例,觸發一次 data getter 進行依賴收集,每次 setter 到時候就會觸發這個函數 setData數組

// core/weapp/init/render.js
export function initRender (vm, keys) {
 return new Watcher(vm, function () {
     // 會觸發數據到 getter 進行依賴收集
     Object.keys(keys).forEach(key => clone(vm[key]))
     // 調用 setData 進行數據更新,vm.$wx 在頁面 created 鉤子到時候進行綁定,寫在了前面,是小程序中的 this
     vm.$wx.setData(dirty, renderFlushCallbacks)
   }
 }, function () {

 }, null, true);
};
複製代碼

編譯結果

能夠看到最後編譯出來就是執行最開始導出的三個函數,對應到不一樣的小程序頁中去hexo

// app.js
// vendor.js 爲編譯後的核心代碼,至關於,導出的 wepy
var _core = _interopRequireDefault(require('vendor.js')(1));

// 調用 app 函數,return App({}) 完成對小程序的註冊
_core["default"].app({}, {a: 1})
複製代碼
// page.js
// vendor.js 爲編譯後的核心代碼,至關於,導出的 wepy
var _core = _interopRequireDefault(require('../vendor.js')(1));

// 調用 page 函數,return Page({}) 完成對小程序頁面的註冊
_core["default"].page({}, {});
複製代碼

總結

  • 紙上得來終於覺淺,得知原理需讀源
  • 重點 ---- 勿噴 ----
  • Blog
相關文章
相關標籤/搜索