小程序開發(fā)指南要點整理

【導讀】

本文是對官方【小程序開發(fā)指南】的精簡 + 解讀春缕,幫助你更好地理解小程序算墨。理解小程序為什么出現(xiàn)橱野,理解小程序的底層原理等等逐工。

【注】

本文僅做筆記式記錄洛退,如有侵權牙寞,煩請告知忿磅。


第一部分:初識小程序

一囊陡、微信為什么要推出小程序

最初,在微信中展現(xiàn) web 頁面蛾绎,是通過 webview昆箕。

web 調用原生的一些能力,通過微信團隊未對外開放的 WeixinJSBridge租冠。

后來鹏倘,WeixinJSBridge 演變?yōu)閷ν忾_放的 JS-SDK。

JS-SDK 是對之前的 WeixinJSBrige 的一個包裝顽爹,以及新能力的釋放纤泵。

【存在痛點】

移動網頁體驗不良。

【方案 v1】

推出 JS-SDK 增強版(未對外開放)镜粤。

增加“微信 web 資源離線存儲”功能捏题。

方案 v1 的問題

(1)復雜頁面依然會有白屏問題。如?CSS肉渴、JavaScript 較多公荧,文件執(zhí)行會占用大量 UI 線程,造成白屏同规。

(2)緩存方案處理較為繁雜循狰,對開發(fā)者的要求較高窟社。

【愿景】

設計一個比較好的系統(tǒng),使得所有開發(fā)者在微信中都能獲得比較好的體驗绪钥。

包括:

(1)快速的加載

(2)更強大的能力(比如使用原生能力)

(3)原生的體驗

(4)易用且安全的微信數(shù)據(jù)開放

(5)高效和簡單的開發(fā)

【解決方案】

推出小程序灿里。

二、小程序與普通網頁開發(fā)的區(qū)別

四種主要區(qū)別:

UI 渲染和腳本執(zhí)行是否使用同一線程昧识、是否可進行 DOM 操作钠四、運行環(huán)境、開發(fā)依賴

1跪楞、UI 渲染和腳本執(zhí)行方式

網頁:UI 渲染和腳本執(zhí)行用同一個線程缀去,腳本執(zhí)行會阻塞 UI 渲染。

小程序:UI 渲染和邏輯執(zhí)行使用不同的線程甸祭,腳本執(zhí)行不會阻塞 UI 渲染缕碎。

2、DOM 操作

網頁:可以使用 DOM池户、BOM API咏雌,執(zhí)行 DOM、BOM 操作

小程序:邏輯層運行在 JSCore 中校焦,并沒有一個完整的瀏覽器對象赊抖,也就沒有 DOM、BOM API寨典,無法執(zhí)行?DOM氛雪、BOM 操作。

【注】

JSCore 的環(huán)境不同于瀏覽器耸成,也不同于 NodeJS报亩。因此,除了無法使用 DOM井氢、BOM API弦追,一些 NPM 包在小程序中也無法運行。

3花竞、運行環(huán)境

網頁:各種瀏覽器劲件、各式 webview

小程序:iOS、安卓约急、小程序開發(fā)者工具

4寇仓、開發(fā)依賴

網頁:編輯器、瀏覽器

小程序:小程序帳號烤宙、開發(fā)者工具



第二部分:小程序代碼組成

一、JSON 配置

JSON 文件在小程序代碼中扮演靜態(tài)配置的作用俭嘁,在小程序運行之前就決定了小程序一些表現(xiàn)躺枕。

【注意】

小程序是無法在運行過程中去動態(tài)更新 JSON 配置文件從而發(fā)生對應的變化的。


二、WXML 模板

1拐云、屬性大小寫敏感:class 和 Class 是不同的屬性。

2、變量名大小寫敏感:{{name}} 和 {{Name}} 是不同的變量湖蜕。

3提完、wx: for = "array":默認變量名 item,默認下標名 index薇缅。

4危彩、wx:key 的兩種形式:

wx: key = "uniqueKey" 或 wx:key = "*this"(代表 item 本身,要求 item 是唯一的 string / number)

5泳桦、模板:

可以在模板中定義代碼片段汤徽,然后在不同的地方調用。使用 name 屬性灸撰,作為模板的名字谒府。然后在?<template/>?內定義代碼片段。

定義模板:

<template name="msgItem">

? <view>

? ? <text> {{index}}: {{msg}} </text>

? ? <text> Time: {{time}} </text>

? </view>

</template>

使用模板:

<!--

item: {

? index: 0,

? msg: 'this is a template',

? time: '2016-06-18'

}

-->

<template is="msgItem" data="{{...item}}"/>

動態(tài)使用模板:

<template name="odd">

? <view> odd </view>

</template>

<template name="even">

? <view> even </view>

</template>

<block wx:for="{{[1, 2, 3, 4, 5]}}">

? <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>

</block>

6浮毯、引用:

WXML 提供兩種文件引用方式 import 和 include完疫。

(1)import:用于引入 template

在 index.wxml 中 import 其他 template 文件,就可以在 index.wxml 中使用相應 template债蓝。

eg:

模板定義:

<!-- item.wxml -->

<template name="item">

? <text>{{text}}</text>

</template>

模板引用與使用:

<import src="item.wxml"/>

<template is="item" data="{{text: 'forbar'}}"/>

【注】

import 有作用域的概念壳鹤。

即:C 引用 B,B 引用A惦蚊,在C中可以使用B定義的 template器虾,在B中可以使用A定義的 template ,但是C不能使用A定義的template 蹦锋。

(2)include:用于引入任意整塊代碼

include 可以將目標文件中?除了?<template/> <wxs/>?外?的整個代碼引入兆沙,相當于是拷貝到 include 位置。

<!-- header.wxml -->

<view> header </view>

<!-- footer.wxml -->

<view> footer </view>

<!-- index.wxml -->

<include src="header.wxml"/>

<view> body </view>

<include src="footer.wxml"/>

7莉掂、所有 wxml 標簽都支持的屬性:

三葛圃、WXSS 樣式

1、rpx 單位:對于 750px 寬的設計稿憎妙,1px = 2rpx

2库正、樣式引用:@import './test_0.wxss'

【注】

和 css 樣式引用的區(qū)別:

index.css 中?@import url('./test_0.css'),請求 index.css 的時候厘唾,會多一個 test_0.css 的請求褥符。

index.wxss 中?@import './test_0.wxss',請求 index.wxss 的時候不會多一個請求抚垃,test_0.wxss 會被編譯打包到?index.wxss 中喷楣。

3趟大、官方樣式庫:WeUI


四、JavaScript 腳本

1铣焊、概述

理解 JavaScript 與 ECMAScript:

JavaScript 是 ECMAScript 一種實現(xiàn)逊朽。小程序中的 JavaScript同瀏覽器中的 JavaScript 以及 NodeJS 中的 JavaScript 是不相同的。

瀏覽器中 JavaScript 構成

NodeJS 中 JavaScript 構成


小程序中 JavaScript 構成

【注】

1曲伊、相比瀏覽器環(huán)境:小程序無法操作 DOM叽讳、BOM

2、相比 NodeJS 環(huán)境:小程序中無法加載原生庫坟募,也無法直接使用大部分的 NPM 包

2岛蚤、小程序的執(zhí)行環(huán)境

三大執(zhí)行環(huán)境:iOS平臺、Android平臺婿屹、小程序IDE

【注】

iOS9 和 iOS10 會不支持一些 es6 語法灭美。

開發(fā)中,需要用開發(fā)者工具勾選“ES6 轉 ES5”昂利,才能確保代碼在所有的環(huán)境都能得到很好的執(zhí)行届腐。


3、模塊化

導出:

// moduleA.js

module.exports = function() {}

使用:

require('./moduleA')

4蜂奸、?腳本的執(zhí)行順序

入口:app.js

執(zhí)行完 app.js 及其 require 的腳本后犁苏,小程序會按照 app.json 中定義的 pages 的順序,逐一執(zhí)行扩所。

5围详、作用域

(1)在文件中聲明的變量和函數(shù),只在該文件中有效祖屏。

(2)全局變量的聲明:

在 app.js 中聲明:

// app.js

App({

? globalData: 1

})

使用:

var app = getApp()

console.log('app.globalData:', app.globalData)? // 1

也可以在其他文件中設置全局變量:

var app = getApp()

app.addGlobalData = 'Sherry'

其他文件可訪問到?addGlobalData:

var app = getApp()

console.log('app.addGlobalData:', app.addGlobalData) // 'Sherry'



第三部分:理解小程序宿主環(huán)境

一助赞、渲染層和邏輯層

wxml、wxss 工作在渲染層袁勺,js 腳本工作在邏輯層雹食。

1、通信模型

渲染層:使用 webview 線程渲染界面期丰。一個小程序存在多個頁面群叶,所以渲染層存在多個 webview 線程。

邏輯層:使用 JsCore 線程運行 JS 腳本钝荡。

webview 線程和 JsCore 線程通信街立,通過 Native,也就是微信客戶端埠通。


2赎离、數(shù)據(jù)驅動

數(shù)據(jù)變更,視圖自動更新端辱。即:數(shù)據(jù)驅動視圖梁剔。

WXML 結構轉 JS 對象圾浅,再轉 Dom 樹

數(shù)據(jù)更新,上圖 JS 對象對應的節(jié)點就會發(fā)生變化憾朴。

對比前后兩個JS對象得到變化的部分,然后把這個差異應用到原來的Dom樹上喷鸽,從而達到更新UI的目的众雷,這就是“數(shù)據(jù)驅動”的原理。

【個人解讀】

所謂數(shù)據(jù)驅動做祝,即不關心視圖砾省,僅操縱數(shù)據(jù)。

邏輯層的任務混槐,僅僅是執(zhí)行用戶的 setData 操作编兄,然后把更新后的數(shù)據(jù)傳給渲染層而已。

渲染層拿到最新的數(shù)據(jù)后声登,會根據(jù)當前的 wxml 結構以及最新數(shù)據(jù)狠鸳、生成新的 JS 對象,并與當前的 JS 對象進行對比悯嗓,得到改動的地方件舵,再把差異更新到原來的 Dom 樹(形如:document.getElementById() 獲取到需要變更的真實 Dom 節(jié)點,然后更改它的屬性值 or 子節(jié)點等)脯厨。


二铅祸、程序與頁面

1、初始化

1)微信客戶端初始化宿主環(huán)境合武,同時從網絡下載或者從本地緩存中拿到小程序的代碼包临梗,把它注入到宿主環(huán)境

2)初始化完成,微信客戶端就會給 App 實例派發(fā) onLaunch 事件稼跳,onLaunch 被調用

2盟庞、退出

點擊小程序右上角的關閉,或按手機設備的Home鍵離開小程序岂贩,小程序并沒有被直接銷毀茫经。

這時,只是進入后臺狀態(tài)萎津,App 構造器定義的 onHide 方法會被調用卸伞。


3、重新進入

再次回到微信或者再次打開小程序時锉屈,小程序進入前臺狀態(tài)荤傲,App 構造器定義的 onShow 方法會被調用。

4颈渊、全局數(shù)據(jù)

// app.js

App({

? globalData: 'I am global data' // 全局共享數(shù)據(jù)

})

由于 JS 邏輯統(tǒng)一運行在邏輯層遂黍,僅有一個線程终佛。因此,頁面切換雾家,切換 webview 時铃彰,邏輯層并沒有發(fā)生改變,JS 的數(shù)據(jù)依然可以被訪問到芯咧。

【注】

也因此牙捉,在頁面中寫的 setTimeout 或者 setInterval 定時器,離開頁面時敬飒,并沒有被刪除邪铲,需要開發(fā)者手動刪除。

5无拗、頁面生命周期

(1)onLoad =>?onShow =>?onReady

onLoad带到、onReady 在頁面銷毀之前,僅觸發(fā)一次英染。

onLoad:監(jiān)聽頁面加載揽惹。

onReady:監(jiān)聽頁面初次渲染完成。

onReady 觸發(fā)時税迷,表示頁面已經準備妥當永丝,在邏輯層就可以和視圖層進行交互了。

(2)setData

this.setData 把數(shù)據(jù)傳遞給渲染層箭养,從而達到更新界面的目的慕嚷。

setData 的第二個參數(shù)是一個 callback 回調,在這次 setData 對界面渲染完畢后觸發(fā)毕泌。

【注】

1)我們只要保持一個原則就可以提高小程序的渲染性能:每次只設置需要改變的最小單位數(shù)據(jù)喝检。

如:

// page.js

Page({

? data: {

? ? a: 1, b: 2, c: 3,

? ? d: [1, {text: 'Hello'}, 3, 4]

? }

? onLoad: function(){

? ? ? // a需要變化時,只需要setData設置a字段即可

? ? this.setData({a : 2})

? }

})

this.setData({"d[1].text": 'Goodbye'});

2)不要把 data 中的任意一項的 value 設為 undefined撼泛,否則可能會有引起一些不可預料的 bug挠说。

6、小程序頁面棧

小程序宿主環(huán)境限制了頁面棧的最大層級為 10 層愿题。

wx.navigateTo({ url: 'pageD' }):插入新頁面损俭。

wx.navigateBack():銷毀最上層的頁面。

wx.redirectTo({ url: 'pageE' }):替換最上層的頁面潘酗。

wx.switchTab({ url: 'pageF' }):頁面棧先清空杆兵,然后 push pageF。變?yōu)椋篬 pageF ]

wx. reLaunch({ url: 'pageH' }) :重啟小程序仔夺,并且打開 pageH琐脏,此時頁面棧為 [ pageH ]


【注】

1)wx.navigateTo 和 wx.redirectTo 只能打開非TabBar頁面,wx.switchTab 只能打開 Tabbar 頁面。

2)Tabbar 頁面初始化之后不會被銷毀

(這句話是官方給的描述日裙,但看官方的 demo吹艇,應該只是,第一個 Tabbar 頁面初始化之后不會被銷毀)

3)銷毀了的頁面再次打開昂拂,會再次觸發(fā) onLoad受神、onShow、onReady格侯,未銷毀的頁面再次打開路克、僅觸發(fā) onShow。


三养交、事件

事件捕獲和事件冒泡觸發(fā)時序:

先執(zhí)行捕獲事件,再執(zhí)行冒泡事件

eg:點擊 inner view瓢宦,調用順序:handleTap2碎连、handleTap4、handleTap3驮履、handleTap1鱼辙。

<view

? id="outer"

? bind:touchstart="handleTap1"

? capture-bind:touchstart="handleTap2"

>

? outer view

? <view

? ? id="inner"

? ? bind:touchstart="handleTap3"

? ? capture-bind:touchstart="handleTap4"

? >

? ? inner view

? </view>

</view>


bind* 和 catch* 的區(qū)別:

bind 事件綁定不會阻止冒泡事件向上冒泡,catch 事件綁定可以阻止冒泡事件向上冒泡玫镐。

capture-catch 將中斷捕獲階段和取消冒泡階段倒戏。

eg:點擊 inner view,只觸發(fā) handleTap2恐似。

<view

? id="outer"

? bind:touchstart="handleTap1"

? capture-catch:touchstart="handleTap2"

>

? outer view

? <view

? ? id="inner"

? ? bind:touchstart="handleTap3"

? ? capture-bind:touchstart="handleTap4"

? >

? ? inner view

? </view>

</view>

四杜跷、兼容

1、wx.getSystemInfo 與?wx.getSystemInfoSync

使用 wx.getSystemInfo 或者 wx.getSystemInfoSync 來獲取手機品牌矫夷、操作系統(tǒng)版本號葛闷、微信版本號以及小程序基礎庫版本號等,通過這個信息双藕,我們可以針對不同平臺做差異化的服務淑趾。

2、判斷當前 api 是否存在:

if (wx.openBluetoothAdapter) {

? wx.openBluetoothAdapter()

} else {

// 如果希望用戶在最新版本的客戶端上體驗您的小程序忧陪,可以這樣子提示

? wx.showModal({

? ? title: '提示',

? ? content: '當前微信版本過低扣泊,無法使用該功能,請升級到最新微信版本后重試嘶摊。'

? })

}

3延蟹、wx.canIUse

// 判斷接口及其參數(shù)在宿主環(huán)境是否可用

wx.canIUse('openBluetoothAdapter')

wx.canIUse('getSystemInfoSync.return.screenWidth')


// 判斷組件及其屬性在宿主環(huán)境是否可用

wx.canIUse('contact-button')

wx.canIUse('text.selectable')



第四部分:場景應用

1、要兼容到iOS8以下版本更卒,需要開啟樣式自動補全等孵。(項目設置中設置)


2、一些特殊的樣式設置方式:

(1)hover 樣式(手指觸摸時的樣式)

/*page.wxss */

.hover {

? background-color: gray;

}

<!--page.wxml -->

<button hover-class="hover"> 點擊button </button>

(2)button 的 loading

<!--page.wxml -->

<button loading="{{loading}}" bindtap="tap">操作</button>

(3)彈窗

wx.showToast蹂空、wx.showModal


3俯萌、HTTPS 網絡通信

(1)小程序要求: wx.request 發(fā)起的必須是 https 協(xié)議請求果录。并且,請求的域名需要在管理平臺進行配置

(2)wx.request url 長度限制:不超過 1024字節(jié)

(3)設置超時時間:app.json ??

{

? "networkTimeout": {

? ? "request": 3000

? }

}

(4)?排查異常的方法參考:4.4.6 排查異常方法


4咐熙、微信登錄

(1)AppId 是公開信息弱恒,泄露 AppId 不會帶來安全風險

(2)code 有效期 5 分鐘。但在成功換取一次用戶信息之后棋恼,code 會立即失效

(3)開發(fā)者服務器用 code 去微信服務器換取用戶信息返弹,會拿到:

openid、session_key爪飘、unionid

其中义起,session_key 用作開發(fā)者服務器后續(xù)與微信服務器通信的憑證。

由于 session_key 有效期長于 code师崎,因此默终,可以減少多次獲取 code、換取用戶信息的通信成本犁罩。


5齐蔽、本地數(shù)據(jù)緩存:wx.getStorage / wx.getStorageSync、wx.setStorage / wx.setStorageSync

本地數(shù)據(jù)緩存是小程序存儲在當前設備硬盤上的數(shù)據(jù)床估。

(1)緩存空間隔離:

1)不同小程序的本地緩存空間相互隔離

2)不同用戶的緩存相互隔離

(2)每個小程序的緩存空間上限為10MB

(3)可以利用緩存 — 提前渲染頁面:

比如商品列表數(shù)據(jù)含滴,在用戶第一次進入小程序、請求回列表數(shù)據(jù)后丐巫,緩存到本地谈况。

用戶再次進入,直接從緩存讀取上一次的數(shù)據(jù)递胧,顯示到頁面鸦做。同時,wx.request 請求最新列表數(shù)據(jù)谓着,請求回之后泼诱,頁面重新渲染最新數(shù)據(jù)。

注意赊锚,這種性能提升方式治筒,僅適合數(shù)據(jù)實時性要求不高的場景。

(4)可以利用緩存 —?緩存用戶登錄態(tài) SessionId

按官方文檔舷蒲,建議同步手動緩存一個過期時間耸袜。

wx.setStorageSync('EXPIREDTIME',expiredTime)

使用緩存的 SessionId 前,先判斷是否過期牲平,如果過期堤框,直接 wx.login 重新登錄,減少攜帶舊 SessionId 的不必要請求。


6蜈抓、設備能力

(1)wx.scanCode:調起微信掃一掃

(2)wx.getNetworkType:獲取網絡狀態(tài)(wifi启绰、2G、3G沟使、4G委可、5G)

(3)wx.onNetworkStatusChange:動態(tài)監(jiān)聽網絡狀態(tài)變化的接口



第五部分:小程序的發(fā)布

1、發(fā)布正式版后腊嗡,正式小程序無法使用着倾,問題排查

(1)如果小程序使用到 Flex 布局,并且需要兼容 iOS8 以下系統(tǒng)時燕少,請檢查上傳小程序包時卡者,開發(fā)者工具是否已經開啟“上傳代碼時樣式自動補全”

(2)小程序使用的服務器接口應該走 HTTPS 協(xié)議客们,并且對應的網絡域名確保已經在小程序管理平臺配置好虎眨。

(3)在測試階段不要打開小程序的調試模式進行測試,因為在調試模式下镶摘,微信不會校驗域名合法性,容易導致開發(fā)者誤以為測試通過岳守,導致正式版小程序因為遇到非法域名無法正常工作凄敢。

(4)發(fā)布前請檢查小程序使用到的網絡接口已經在現(xiàn)網部署好,并且評估好服務器的機器負載情況湿痢。


2涝缝、發(fā)布模式

全量發(fā)布和分階段發(fā)布(灰度發(fā)布)

【注】

并非全量發(fā)布之后,用戶就會立即使用到最新版的小程序譬重。

因為微信客戶端有舊版本小程序包緩存拒逮。

微信客戶端在某些特定的時機異步去更新最新的小程序包。

一般我們認為全量發(fā)布的24小時后臀规,所有用戶才會真正使用到最新版的小程序滩援。


3、數(shù)據(jù)分析

(1)常規(guī)分析

開發(fā)網頁和 App 應用都需要開發(fā)者自己通過編寫代碼來上報訪問數(shù)據(jù)塔嬉,小程序平臺則直接內置在宿主環(huán)境底層玩徊,無需開發(fā)者新增一行代碼。

開發(fā)者可以登錄小程序管理平臺谨究,通過左側“數(shù)據(jù)分析”菜單可以進入數(shù)據(jù)分析查看恩袱。

(2)自定義分析:見官網


4、監(jiān)控與告警

小程序宿主環(huán)境已經內置了異常檢測的模塊胶哲,并且上報到小程序平臺畔塔。

目前只提供了腳本錯誤告警,如果需要監(jiān)控異常的訪問或者服務接口耗時時,需要開發(fā)者自行開發(fā)監(jiān)控系統(tǒng)澈吨,并在小程序邏輯代碼加上對應的數(shù)據(jù)上報把敢。

比較推薦的方法是通過運維中心的監(jiān)控告警功能,開發(fā)者設置合理的錯誤閾值棚辽,再通過加入微信告警群技竟,當小程序運行發(fā)生大量異常現(xiàn)象時屈藐,微信告警群會提醒開發(fā)者榔组,此時開發(fā)者再登錄小程序管理平臺查閱錯誤日志。


第六部分:底層框架

一联逻、雙線程模型

1搓扯、什么是雙線程模型?

渲染層 + 邏輯層包归。

渲染層負責 UI 渲染锨推,每個頁面使用一個 webview 線程來渲染。

邏輯層負責執(zhí)行 JS 邏輯公壤。

二者通過微信客戶端(native)進行通信换可。


2、為什么是雙線程模型厦幅?

1)渲染層運行環(huán)境:確保性能

由成熟的 Web 技術渲染頁面沾鳄,并以大量的接口提供豐富的客戶端原生能力。

與邏輯層分離确憨,頁面渲染不被 JS 的執(zhí)行阻塞译荞。

2)邏輯層運行環(huán)境:確保安全

由于 web 技術的靈活性,使用 web 技術渲染頁面休弃,外部可以通過 JavaScript 操作頁面吞歼,并獲取到敏感組件的數(shù)據(jù)信息,缺乏安全性塔猾。

因此篙骡,需要一個安全的沙箱環(huán)境,去運行開發(fā)者的 JavaScript 代碼丈甸。這個沙箱環(huán)境医增,僅僅提供純 JavaScript 的解釋執(zhí)行環(huán)境。

由于客戶端系統(tǒng)都具有JavaScript 的解釋引擎(iOS 有內置?JavaScriptCore 框架老虫、安卓有騰訊 x5 內核提供的?JsCore 環(huán)境)叶骨,因此,我們可以創(chuàng)建一個單獨的線程去執(zhí)行 JavaScript祈匙。這個單獨的線程忽刽,就是小程序的邏輯層天揖。

二、組件系統(tǒng):Exparser

Exparser 是微信小程序的組件組織框架跪帝,內置在小程序基礎庫中今膊,為小程序的各種組件提供基礎的支持。小程序內的所有組件伞剑,包括內置組件和自定義組件斑唬,都由Exparser組織管理。


1黎泣、運行原理:

以 Component 為例(Page流程大致相仿恕刘,只是參數(shù)形式不一樣)

在小程序啟動時,構造器會將開發(fā)者設置的properties抒倚、data褐着、methods等定義段,寫入Exparser 的組件注冊表中

這個組件在被其它組件引用時,就可以根據(jù)這些注冊信息來創(chuàng)建自定義組件的實例鞭执。


2、組件間通信

父 => 子:WXML 屬性值

子 => 父:事件系統(tǒng)


1)事件冒泡:

input-with-label 的 WXML:

<label>

? <input />

? <slot />

</label>

頁面 WXML:

<view>

? <input-with-label>

? ? <button />

? </input-with-label>

</view>


l 如果事件是非冒泡的馅扣,那只能在 button 上監(jiān)聽到事件;

l 如果事件是在 Shadow Tree 上冒泡的着降,那 button 差油、 input-with-label 、view 可以依次監(jiān)聽到事件鹊碍;

l 如果事件是在 Composed Tree 上冒泡的,那 button 食绿、 slot 侈咕、label 、 input-with-label 器紧、 view 可以依次監(jiān)聽到事件耀销。


【附】

Shadow Tree?對應一個組件,Composed Tree?對應一個頁面铲汪。

一個??Composed Tree?由多個?Shadow Tree?構成熊尉。


2)triggerEvent

在自定義組件中使用 triggerEvent 觸發(fā)事件時,可以指定事件的 bubbles掌腰、composed 和 capturePhase 屬性狰住,用于標注事件的冒泡性質。

triggerEvent 事例:

Component({

? methods: {

? ? helloEvent: function() {

? ? ? this.triggerEvent('hello', {}, {

? ? ? ? bubbles: true,? ? ? // 這是一個冒泡事件

? ? ? ? composed: true,? ? // 這個事件在Composed Tree 上冒泡

? ? ? ? capturePhase: false // 這個事件沒有捕獲階段

? ? ? })

? ? }

? }

})


三齿梁、原生組件:由客戶端渲染的組件(非 webview 渲染)

部分內置組件催植,是由客戶端原生渲染的肮蛹,如 vedio、map创南、canvas伦忠、picker 組件。

原生組件的層級比所有在 WebView 層渲染的普通組件要高稿辙。

限制:

一些CSS樣式無法應用于原生組件昆码。


四、小程序與客戶端通信原理

1邻储、視圖層與客戶端的通信

iOS:利用了WKWebView 的提供 messageHandlers 特性赋咽。

安卓:往 WebView 的 window 對象注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個兼容層芥备。


2冬耿、邏輯層與客戶端的通信

iOS:同上。另外萌壳,可以往 JavaScripCore 框架注入一個全局的原生方法

安卓:同上亦镶。




第七部分:性能優(yōu)化

一、啟動

1袱瓮、主流程

在小程序啟動時缤骨,微信會為小程序展示一個固定的啟動界面,界面內包含小程序的圖標尺借、名稱和加載提示圖標绊起。

小程序啟動過程


2、代碼包下載

這里下載的燎斩,是經過編譯虱歪、壓縮、打包之后的代碼包栅表。

【性能優(yōu)化點】

控制代碼包大小?有助于減少小程序的啟動時間笋鄙。

【具體方式】

(1)精簡代碼,去掉不必要的 WXML 結構和未使用的 WXSS 定義怪瓶。

(2)減少在代碼包中直接嵌入的資源文件萧落。

(3)壓縮圖片,使用適當?shù)膱D片格式洗贰。


必要時找岖,進行分包處理。

小程序啟動時敛滋,只需要先將主包下載完成许布,就可以立刻啟動小程序。


3绎晃、代碼包加載

微信會在小程序啟動前為小程序準備好通用的運行環(huán)境爹脾。

這個運行環(huán)境包括幾個供小程序使用的線程帖旨,并在其中完成小程序基礎庫的初始化預先執(zhí)行通用邏輯灵妨,盡可能做好小程序的啟動準備解阅。

小程序代碼包下載(或從緩存中讀取)完成后泌霍,小程序的代碼會被加載到適當?shù)木€程中執(zhí)行货抄。

加載過程???此時,所有 app.js朱转、頁面所在的 JS 文件和所有其他被 require 的 JS 文件會被自動執(zhí)行一次蟹地,小程序基礎庫會完成所有頁面的注冊。

其中藤为,頁面注冊怪与、即:在小程序代碼調用 Page 構造器的時候,基礎庫會記錄頁面的基礎信息缅疟,如初始數(shù)據(jù)(data)分别、方法等。

【注意】

如果一個頁面被多次創(chuàng)建存淫,并不會使得這個頁面所在的 JS 文件被執(zhí)行多次耘斩,而僅僅是根據(jù)初始數(shù)據(jù)多生成了一個頁面實例(this),在頁面 JS 文件 Page 構造器外定義的變量桅咆,在所有這個頁面的實例(this)間是共享的括授。

也就是說,Page 構造器外定義的變量岩饼,相當于當前頁面的全局變量荚虚,無論創(chuàng)建幾個頁面實例,訪問的都是同一個全局變量籍茧。


二版述、?頁面層級準備

在小程序啟動前,微信會提前準備好一個頁面層級用于展示小程序的首頁硕糊。

除此以外院水,每當一個頁面層級被用于渲染頁面腊徙,微信都會提前開始準備一個新的頁面層級简十。


頁面層級的準備工作(3個階段):

(1)啟動一個 WebView

(2)在 WebView 中初始化基礎庫,此時還會進行一些基礎庫內部優(yōu)化撬腾,以提升頁面渲染性能

(3)注入小程序 WXML 結構和 WXSS 樣式螟蝙,使小程序能在接收到頁面初始數(shù)據(jù)之后馬上開始渲染頁面(這一階段無法在小程序啟動前執(zhí)行)

頁面層級的準備工作

【注】

小程序未加載時,微信客戶端根本沒拿到小程序代碼民傻,所以無法進行注入胰默。


三场斑、數(shù)據(jù)通信

數(shù)據(jù)通信性能提升原則:

(1)減少通信

(2)減少通信傳輸?shù)臄?shù)據(jù)量


1、頁面初始數(shù)據(jù)通信

一個新的頁面打開牵署,邏輯層將初始 data 發(fā)送給 Native漏隐,Native 做兩件事

1)將數(shù)據(jù)傳給視圖層

2)向用戶展示一個新的頁面層級(視圖層在這個頁面層級上進行界面繪制)

視圖層拿到數(shù)據(jù)后,根據(jù)頁面路徑來選擇合適的 WXML 結構奴迅,WXML 結構與初始數(shù)據(jù)相結合青责,得到頁面的第一次渲染結果。


【性能瓶頸】

1)頁面初始數(shù)據(jù)通信時間

2)初始渲染時間

【性能優(yōu)化點】

減少頁面初始數(shù)據(jù)通信時間

【具體方式】

減少傳輸數(shù)據(jù)量取具。

2脖隶、?更新數(shù)據(jù)通信

更新數(shù)據(jù)傳輸時,邏輯層首先執(zhí)行?JSON.stringify暇检,去除掉 setData 數(shù)據(jù)中不可傳輸?shù)牟糠植澹髮?shù)據(jù)發(fā)送給視圖層。

同時块仆,邏輯層會將 setData 與 data 合并构蹬,更新數(shù)據(jù)。


【性能優(yōu)化點】

1)setData 的調用頻率

2)setData 設置的數(shù)據(jù)大小

3)data 數(shù)據(jù)的精簡

【具體方式】

1)減少 setData 的調用榨乎,將多次 setData 合并成一次

2)精簡 setData 設置的數(shù)據(jù)怎燥,界面無關或比較復雜的長字符串、盡量不要用 setData 設置

3)與界面渲染無關的數(shù)據(jù)最好不要設置在 data 中


3蜜暑、用戶事件通信

用戶觸發(fā)一個事件铐姚,且這個事件存在監(jiān)聽函數(shù),視圖層會將觸發(fā)信息反饋給邏輯層肛捍。

如果事件沒有綁定監(jiān)聽函數(shù)隐绵,則不反饋給邏輯層。


【性能優(yōu)化點】

提升視圖層與邏輯層的通信性能拙毫。

【具體方式】

1)去掉不必要的事件綁定(WXML中的 bind 和 catch)依许,減少通信的數(shù)據(jù)量和次數(shù)

2)事件綁定時需要傳輸 target 和 currentTarget 的 dataset,因而不要在節(jié)點的 data 前綴屬性中放置過大的數(shù)據(jù)


四缀蹄、視圖層渲染

1峭跳、初始渲染

將初始數(shù)據(jù)套用在對應的 WXML 片段上生成節(jié)點樹。


【性能優(yōu)化點】

減少初始渲染時間缺前。

【具體方式】

減少 WXML 中節(jié)點的數(shù)量(時間開銷大體上與節(jié)點樹中節(jié)點的總量成正比)

2蛀醉、重渲染

初始渲染中得到的 data當前節(jié)點樹保留下來用于重渲染。

【重渲染步驟】

1)將 data 和 setData 數(shù)據(jù)套用在 WXML 片段上衅码,得到一個新節(jié)點樹

2)將新節(jié)點樹與當前節(jié)點樹進行比較(diff)

3)將 setData 數(shù)據(jù)合并到 data 中拯刁,并用新節(jié)點樹替換舊節(jié)點樹


【性能優(yōu)化點】

提升 diff 過程速度。

【具體方式】

1)去掉不必要設置的數(shù)據(jù)

2)減少 setData 的數(shù)據(jù)量


五逝段、原生組件通信

一些原生組件支持使用 context 來更新組件垛玻。

與 setData 的不同:

數(shù)據(jù)從邏輯層傳到 native 層后割捅,直接傳入組件中(不需要 native => 視圖層,視圖層再傳入組件)

這種通信方式帚桩,可以顯著降低傳輸延遲亿驾。


六、性能優(yōu)化方案匯總

主要的優(yōu)化策略可以歸納為三點:

1)精簡代碼账嚎,降低 WXML 結構和 JS 代碼的復雜性

2)合理使用 setData 調用颊乘,減少 setData 次數(shù)和數(shù)據(jù)量

3)必要時使用分包優(yōu)化。




第八部分:小程序基礎庫的更新迭代

一醉锄、什么是基礎庫

1乏悄、職責

包裝提供組件、API恳不,處理數(shù)據(jù)綁定檩小、組件系統(tǒng)、事件系統(tǒng)烟勋、通信系統(tǒng)等一系列框架邏輯规求。

2、載入時機

啟動小程序后先載入基礎庫卵惦,接著再載入業(yè)務代碼阻肿。

渲染層 WebView 層注入的稱為 WebView 基礎庫,邏輯層注入的稱為 AppService 基礎庫沮尿。

WebView 基礎庫 +?AppService 基礎庫 ==?小程序基礎庫丛塌。

由于所有小程序在微信客戶端打開的時候,都需要注入相同的基礎庫 畜疾,所以赴邻,小程序的基礎庫不會被打包在某個小程序的代碼包里邊,它會被提前內置在微信客戶端啡捶。

這樣做的好處:

1)降低業(yè)務小程序的代碼包大小姥敛。

2)可以單獨修復基礎庫中的 Bug,無需修改到業(yè)務小程序的代碼包瞎暑。


二彤敛、基礎庫的異常捕獲

【可選方案】

1、try-catch方案了赌。

2墨榄、window.onerror方案(window.addEventListener("error", function(evt){}))

【注】

1)對比 window.onerror 的方案,try-catch 的方案有個缺點:沒法捕捉到全局的錯誤事件揍拆。

2)邏輯層不存在 window 對象渠概,因此邏輯層 AppService 側無法通過 window.onerror 來捕捉異常茶凳。

【最終方案】

1)在 WebView 側使用 window.onerror 方案進行捕捉異常嫂拴。

2)在邏輯層 AppService 側通過把 App 實例和 Page 實例的各個生命周期等方法包裹在 try-catch 里進行捕捉異常播揪。

3)在 App 構造器里提供了 onError 的回調,當業(yè)務代碼運行產生異常時筒狠,這個回調被觸發(fā)猪狈,同時能夠拿到異常的具體信息。




第九部分:微信開發(fā)者工具

一辩恼、代碼編譯

微信開發(fā)者工具和微信客戶端都無法直接運行小程序的源碼雇庙,因此我們需要對小程序的源碼進行編譯。

代碼編譯過程:

1)本地預處理

2)本地編譯

3)服務器編譯

微信開發(fā)者工具模擬器運行的代碼只經過本地預處理灶伊、本地編譯疆前,沒有服務器編譯過程

微信客戶端運行的代碼是額外經過服務器編譯的聘萨。


1竹椒、WXML 編譯:WXML =>?JavaScript

微信開發(fā)者工具內置了一個二進制的 WXML 編譯器,這個編譯器接受 WXML 代碼文件列表米辐,處理完成之后輸出 JavaScript 代碼胸完,這段代碼是各個頁面的結構生成函數(shù)。

編譯過程將所有的 WXML 代碼最終變成一個 JavaScript 函數(shù)翘贮,預先注入在 WebView 中赊窥。

(注意,是所有的 WXML狸页,所有的锨能,統(tǒng)一變成一個?JavaScript 函數(shù),叫做:“頁面結構生成函數(shù)”)

這個函數(shù)接收頁面路徑(pagePath)作為參數(shù)芍耘,返回“頁面結構生成函數(shù)”腹侣,“頁面結構生成函數(shù)”接受頁面數(shù)據(jù)(pageData)作為參數(shù),輸出一段描述頁面結構的 JSON齿穗。

最終傲隶,通過小程序組件系統(tǒng)生成對應的 HTML。

所有的 WXML 代碼最終變成一個 JavaScript 函數(shù)

頁面結構生成函數(shù)的使用:

//$gwx 是 WXML 編譯后得到的函數(shù)

//根據(jù)頁面路徑獲取頁面結構生成函數(shù)

var generateFun = $gwx('name.wxml')

//頁面結構生成函數(shù)接受頁面數(shù)據(jù)窃页,得到描述頁面結構的JSON

var virtualTree = generateFun({

? name:? 'miniprogram'

})

/** virtualTree == {

? tag: 'view'跺株,

? children: [{

? ? ? tag: 'view',

? ? ? children: ['miniprogram']

? ? }]

}**/

//小程序組件系統(tǒng)在虛擬樹對比后將結果渲染到頁面上

virtualDom.render(virtualTree)


2、WXSS 編譯:WXSS => 樣式信息數(shù)組

微信開發(fā)者工具內置了一個二進制的 WXSS 編譯器脖卖,這個編譯器接受 WXSS 文件列表乒省,分析文件之間的引用關系,同時預處理 rpx畦木,輸出一個樣式信息數(shù)組袖扛。


3、JavaScript 編譯:多個 js =>?app-service.js

微信客戶端在運行小程序的邏輯層的時候只需要加載一個 JS 文件(我們稱為 app-service.js)。

也就是蛆封,我們小程序代碼經過編譯(es6 => es5)唇礁、打包之后,得到的 bundle.js惨篱。

【具體步驟】

1)代碼上傳之前的編譯盏筐、壓縮(預處理)

在代碼上傳之前,微信開發(fā)者工具會對開發(fā)者的 JS 文件做一些預處理砸讳,包括 ES6 轉 ES5 和代碼壓縮(開發(fā)者可以選擇關閉預處理操作)琢融,然后上傳(上傳的還是多個 JS 文件

2)服務器編譯、打包

將每個 JS 文件的內容分別包裹在 define 域中簿寂,再按一定的順序合并成 app-service.js 漾抬。

同時,添加主動 require app.js 和頁面 JS 的代碼常遂。


代碼上傳之前的編譯
代碼上傳之前的壓縮 + 服務器編譯奋蔚、打包


二、模擬器

1烈钞、?邏輯層模擬

邏輯層的真實運行環(huán)境:

iOS:JavaScriptCore 中

安卓:x5 的 JSCore 中

模擬運行環(huán)境:

微信開發(fā)者工具:采用一個隱藏著的 Webivew 來模擬小程序的邏輯運行環(huán)境

微信客戶端小程序運行環(huán)境模型簡圖
微信開發(fā)者工具小程序運行環(huán)境模型簡圖


在微信開發(fā)者工具上 WebView 是一個chrome的?<webview />?標簽泊碑。與<iframe />標簽不同的是,<webview/> 標簽是采用獨立的線程運行的毯欣。

WebView 在請求開發(fā)者 JS 代碼時馒过,開發(fā)者工具讀取 JS 代碼進行必要的預處理后,將處理結果返回酗钞。

然后腹忽,由 WebView 解析執(zhí)行(這樣,邏輯層的 JS 文件砚作,就得到了執(zhí)行)窘奏。

雖然開發(fā)者工具上是沒有對 JS 代碼進行合并的,但是還是按照相同的加載順序進行解析執(zhí)行葫录。


【更好的模擬】

WebView 是一個瀏覽器環(huán)境着裹,支持 BOM 操作。但邏輯層本不該支持這種操作米同,一旦出現(xiàn)骇扇,我們需要正確報錯才對。

因此面粮,開發(fā)者工具將開發(fā)者的代碼包裹在 define 域的時候少孝,將瀏覽器的 BOM 對象局部變量化,從而使得在開發(fā)階段就能發(fā)現(xiàn)問題熬苍。


2稍走、 渲染層模擬

微信開發(fā)者工具使用 chrome 的?<webview />標簽來加載渲染層頁面。

每個渲染層 WebView 加載:

http://127.0.0.1:9973/pageframe/pageframe.html

開發(fā)者工具底層搭建的 HTTP 本地服務器在收到這個請求的時候,就會編譯 WXML 文件和 WXSS 文件婿脸,然后將編譯結果作為 HTTP 請求的返回包粱胜。

當確定加載頁面的路徑之后,如 index 頁面盖淡,開發(fā)工具會動態(tài)注入如下一段腳本:

// 改變當前 webview 的路徑,確保之后的圖片網絡請求能得到正確的相對路徑

history.pushState('', '', 'pageframe/index')

// 創(chuàng)建自定義事件凿歼,將頁面結構生成函數(shù)派發(fā)出去褪迟,由小程序渲染層基礎庫處理

document.dispatchEvent(new CustomEvent("generateFuncReady", {

? detail: {

? ? generateFunc: $gwx('./index.wxml')

? }

}))

// 注入對應頁面的樣式,這段函數(shù)由 WXSS 編譯器生成

setCssToHead()


3答憔、客戶端模擬

在微信開發(fā)者工具上味赃,通過借助 BOM(瀏覽器對象模型)以及 node.js 訪問系統(tǒng)資源的能力,同時模擬客戶端的 UI 和交互流程虐拓,使得大部分的 API 能夠正常執(zhí)行心俗。


4、通訊模擬

我們需要一個有效的通訊方案使得小程序的邏輯層蓉驹、渲染層和客戶端之間進行數(shù)據(jù)交流城榛,才能將這三個部分串聯(lián)成為一個有機的整體。

【原理】

微信開發(fā)者工具的有一個消息中心底層模塊維持著一個 WebSocket 服務器态兴,小程序的邏輯層的 WebView 和渲染層頁面的 WebView 通過 WebSocket 與開發(fā)者工具底層建立長連狠持,使用 WebSocket 的 protocol 字段來區(qū)分 Socket 的來源。


三瞻润、調試器:界面調試 + 邏輯調試

nw.js 對 <webview/> 提供打開 Chrome Devtools 調試界面的接口喘垂,使得開發(fā)者工具具備對小程序的邏輯層和渲染層進行調試的能力。

同時绍撞,為了方便調試小程序正勒,開發(fā)者工具在 Chrome Devtools 的基礎上進行擴展和定制。

【界面調試】

微信小程序團隊通過腳本注入的方式傻铣、將 Chrome Devtools 的 Element 面板隱藏章贞,同時開發(fā)了 Chrome Devtools 插件 WXML 面板。

開發(fā)者工具會在每個渲染層的 WebView 中注入界面調試的腳本代碼非洲,負責獲取 WebView 中的 DOM 樹阱驾、獲取節(jié)點樣式、監(jiān)聽節(jié)點變化怪蔑、高亮選中節(jié)點里覆、處理界面調試命令。并將界面調試信息通過 WebSocket 經由開發(fā)者工具轉發(fā)給 WXML 面板進行處理缆瓣。

【邏輯調試】

直接使用 Chrome Devtools 的 Sources 面板調試邏輯層 JS 代碼喧枷。


四、微信開發(fā)者工具原理總結

1、渲染層

通過編譯過程我們將 WXML 文件和 WXSS 文件都處理成 JS 代碼隧甚,使用 script 標簽注入在一個空的 html文件中(我們稱為:page-frame.html)

2车荔、邏輯層

我們將所有的 JS 文件編譯成一個單獨的 app-service.js

3、小程序運行時

1)邏輯層使用 JsCore 直接加載 app-service.js戚扳,渲染層使用 WebView 加載 page-frame.html

2)在確定頁面路徑之后忧便,通過動態(tài)注入 script 的方式調用 WXML 文件和 WXSS 文件生成的 JS 代碼,再結合邏輯層的頁面數(shù)據(jù)帽借,最終渲染出指定的頁面

4珠增、開發(fā)者工具使用一個隱藏著的 <webview/> 標簽來模擬 JSCore 作為小程序的邏輯層運行環(huán)境

5、開發(fā)者工具利用 BOM砍艾、node.js 以及模擬的 UI 和交互流程實現(xiàn)對大部分客戶端 API 的支持

6蒂教、開發(fā)者工具底層有一個 HTTP 服務器來處理來自 WebView 的請求,并將開發(fā)者代碼編譯處理后的結果作為 HTTP 請求的返回脆荷,WebView 按照普通的網頁進行渲染

7凝垛、開發(fā)者工具底層維護著一個 WebSocket 服務器,用于在 WebView 與開發(fā)者工具之間建立可靠的消息通訊鏈路

8蜓谋、微信開發(fā)者工具使用 webview.showDevTools 打開 Chrome Devtools 調試邏輯層 WebView 的 JS 代碼

9梦皮、微信小程序團隊開發(fā)了 Chrome Devtools 插件 WXML 面板對渲染層頁面 WebView 進行界面調試

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市桃焕,隨后出現(xiàn)的幾起案子届氢,更是在濱河造成了極大的恐慌,老刑警劉巖覆旭,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件退子,死亡現(xiàn)場離奇詭異,居然都是意外死亡型将,警方通過查閱死者的電腦和手機寂祥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來七兜,“玉大人丸凭,你說我怎么就攤上這事⊥笾” “怎么了惜犀?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狠裹。 經常有香客問我虽界,道長,這世上最難降的妖魔是什么涛菠? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任莉御,我火速辦了婚禮撇吞,結果婚禮上,老公的妹妹穿的比我還像新娘礁叔。我一直安慰自己牍颈,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布琅关。 她就那樣靜靜地躺著煮岁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涣易。 梳的紋絲不亂的頭發(fā)上画机,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音都毒,去河邊找鬼色罚。 笑死碰缔,一個胖子當著我的面吹牛账劲,可吹牛的內容都是我干的。 我是一名探鬼主播金抡,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼瀑焦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梗肝?” 一聲冷哼從身側響起榛瓮,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巫击,沒想到半個月后禀晓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡坝锰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年粹懒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顷级。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡凫乖,死狀恐怖,靈堂內的尸體忽然破棺而出弓颈,到底是詐尸還是另有隱情帽芽,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布翔冀,位于F島的核電站盛嘿,受9級特大地震影響碧信,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望僵娃。 院中可真熱鬧,春花似錦、人聲如沸徽职。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姆钉。三九已至,卻和暖如春抄瓦,著一層夾襖步出監(jiān)牢的瞬間潮瓶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工钙姊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毯辅,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓煞额,卻偏偏與公主長得像思恐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膊毁,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345