基本功 | Litho的使用及原理剖析
1. 什麼是Litho?
Litho是Facebook推出的一套高效構建Android UI的宣告式框架,主要目的是提升RecyclerView複雜列表的滑動效能和降低記憶體佔用。下面是Litho官網的介紹:
Litho is a declarative framework for building efficient user interfaces (UI) on Android. It allows you to write highly-optimized Android views through a simple functional API based on Java annotations. It was primarily built to implement complex scrollable UIs based on RecyclerView. With Litho, you build your UI in terms of components instead of interacting directly with traditional Android views. A component is essentially a function that takes immutable inputs, called props, and returns a component hierarchy describing your user interface.
Litho是高效構建Android UI的宣告式框架,通過註解API建立高優的Android檢視,非常適用於基於Recyclerview的複雜滾動列表。Litho使用一系列元件構建檢視,代替了Android傳統檢視互動方式。元件本質上是一個函式,它接受名為Props的不可變輸入,並返回描述使用者介面的元件層次結構。
Litho是一套完全不同於傳統Android的UI框架,它繼承了Facebook一向大膽創新的風格,突破性地在Android上實現了React風格的UI框架。架構圖如下:
應用層:上層Android應用接入層。
規範層(API):允許使用者使用宣告式的API(註解)來構建符合Flexbox規範的佈局。
佈局層:Litho使用可掛載元件、佈局元件和Flexbox元件來構建佈局,其中可掛載元件和佈局元件允許使用者使用規範來定義,各個元件的具體用法下面的元件規範中會詳細介紹。在Litho中每一個元件都是一個獨立的功能模組。Litho的元件和React的元件相類似,也具有屬性和狀態的概念,通過狀態的變更來控制組件的展示樣式。
佈局測量:Litho使用Yoga來完成元件佈局的非同步或同步(可根據場景定製)測量和計算,實現了佈局的扁平化。
佈局渲染:Litho不僅支援使用View來渲染檢視,還可以使用更輕量的Drawable來渲染檢視。Litho實現了大量使用Drawable來渲染的基礎元件,可以進一步拍平佈局。
除了上面提到的扁平化佈局,Litho還實現了佈局的細粒度複用和非同步計算佈局的能力,對於這些功能的實現在Litho的特性及原理剖析中詳細介紹。下面先介紹一下大家比較關心的Litho使用方法。
2. Litho的使用
Litho的使用方式相比於傳統的Android來說有些另類,它拋棄了通過XML定義佈局的方式,採用宣告式的元件在Java中構建佈局。
2.1 Litho和原生Android在使用上的區別
Android傳統佈局:首先在資原始檔res/layout目錄下定義佈局檔案xx.xml,然後在Activity或Fragment中引用佈局檔案生成檢視,示例如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
android:textAlignment="center"
android:textColor="#666666"
android:textSize="40dp" />
public class MainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.helloworld);
}
}
Litho佈局:Litho拋棄了Android原生的佈局方式,通過元件方式構建佈局生成檢視,示例如下:
public class MainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ComponentContext context = new ComponentContext(this);
final Text.Builder builder = Text.create(context);
final Component = builder.text("Hello World")
.textSizeDip(40)
.textColor(Color.parseColor("#666666"))
.textAlignment(Layout.Alignment.ALIGN_CENTER)
.build();
LithoView view = LithoView.create(context, component);
setContentView(view);
}
}
2.2 Litho自定義檢視
Litho中的檢視單元叫做Component,可以直觀的翻譯為“元件”,它的設計理念來自於React元件化的思想。每個元件持有描述一個檢視單元所必須的屬性和狀態,用於檢視佈局的計算工作。檢視最終的繪製工作是由元件指定的繪製單元(View或者Drawable)來完成的。
Litho元件的建立方式也和原生View的建立方式有著很大的區別。Litho使用註解定義了一系列的規範,我們需要使用Litho的註解來定義自己的元件生成規則,最終由Litho在編譯期自動編譯生成真正的元件。
2.2.1 元件規範
Litho提供了兩種型別的元件規範,分別是Layout Spec規範和Mount Spec規範。下面分別介紹兩種規範的使用方式:
Layout Spec規範:用於生成佈局型別元件的規範,佈局元件在邏輯上等同於Android中的ViewGroup,用於組織其他元件構成一個佈局。它要求我們必須使用@LayoutSpec註解來註明,並實現一個標註了@OnCreateLayout註解的方法。示例如下:
@LayoutSpec
class HelloComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop String name) {
return Column.create(c)
.child(Text.create(c)
.text("Hello, " + name)
.textSizeRes(R.dimen.my_text_size)
.textColor(Color.BLACK)
.paddingDip(ALL, 10)
.build())
.child(Image.create(c)
.drawableRes(R.drawable.welcome)
.scaleType(ImageView.ScaleType.CENTER_CROP)
.build())
.build();
}
}
最終Litho會在編譯時生成一個名為HelloComponent的元件。
public final class HelloComponent extends Component {
@Prop(resType = ResType.NONE,optional = false) String name;
private HelloComponent() {
super();
}
@Override
protected Component onCreateLayout(ComponentContext c) {
return (Component) HelloComponentSpec.onCreateLayout((ComponentContext) c, (String) name);
}
...
public static Builder create(ComponentContext context, int defStyleAttr, int defStyleRes) {
Builder builder = sBuilderPool.acquire();
if (builder == null) {
builder = new Builder();
}
HelloComponent instance = new HelloComponent();
builder.init(context, defStyleAttr, defStyleRes, instance);
return builder;
}
public static class Builder extends Component.Builder<Builder> {
private static final String[] REQUIRED_PROPS_NAMES = new String[] {"name"};
private static final int REQUIRED_PROPS_COUNT = 1;
HelloComponent mHelloComponent;
...
public Builder name(String name) {
this.mHelloComponent.name = name;
mRequired.set(0);
return this;
}
@Override
public HelloComponent build() {
checkArgs(REQUIRED_PROPS_COUNT, mRequired, REQUIRED_PROPS_NAMES);
HelloComponent helloComponentRef = mHelloComponent;
release();
return helloComponentRef;
}
}
}
Mount Spec規範:用來生成可掛載型別元件的規範,用來生成渲染具體View或者Drawable的元件。同樣,它必須使用@MountSpec註解來標註,並至少實現一個標註了@onCreateMountContent的方法。Mount Spec相比於Layout Spec更復雜一些,它擁有自己的生命週期:
- @OnPrepare,準備階段,進行一些初始化操作。
- @OnMeasure,負責佈局的計算。
- @OnBoundsDefined,在佈局計算完成後掛載檢視前做一些操作。
- @OnCreateMountContent,建立需要掛載的檢視。
- @OnMount,掛載檢視,完成佈局相關的設定。
- @OnBind,繫結檢視,完成資料和檢視的繫結。
- @OnUnBind,解綁檢視,主要用於重置檢視的資料相關的屬性,防止出現複用問題。
- @OnUnmount,解除安裝檢視,主要用於重置檢視的佈局相關的屬性,防止出現複用問題。
除了上述兩種元件型別,Litho中還有一種特殊的元件——Layout,它不能使用規範來生成。Layout是Litho中的容器元件,類似於Android中的ViewGroup,但是隻能使用Flexbox的規範。它可以包含子元件節點,是Litho各元件連線的紐帶。Layout元件只是Yoga在Litho中的代理,元件的所有佈局相關的屬性都會直接設定給Yoga,並由Yoga完成佈局的計算。Litho實現了兩個Layout元件Row和Column,分別對應Flexbox中的行和列。
2.2.2 Litho的屬性
在Litho中屬性分為兩種,不可變屬性稱為Props,可變屬性稱為State,下面分別介紹一下兩種屬性:
Props屬性:元件中使用@Prop註解標註的引數集合,具有單向性和不可變性。下面通過一個簡單的例子瞭解一下如何在元件中定義和使用Props屬性:
@MountSpec
class MyComponentSpec {
@OnPrepare
static void onPrepare(
ComponentContext c,
@Prop(optional = true) String prop1) {
...
}
@OnMount
static void onMount(
ComponentContext c,
SomeDrawable convertDrawable,
@Prop(optional = true) String prop1,
@Prop int prop2) {
if (prop1 != null) {
...
}
}
}
在上面的程式碼中,共使用了三次Prop註解,分別標註prop1和prop2兩個變數,即定義了prop1和prop2兩個屬性。Litho會在自動編譯生成的MyComponent類的Builder類中生成這兩個屬性的同名方法。按照如下程式碼,便可以去使用上面定義的屬性:
MyComponent.create(c)
.prop1("My prop 1")
.prop2(256)
.build();
State屬性:意為“狀態”屬性,State屬性雖然可變,但是其變化由元件內部控制,例如:輸入框、Checkbox等都是由元件內部去感知使用者行為,並更新元件的State屬性。所以一個元件一旦建立,我們便無法通過任何外部設定去更改它的屬性。元件的State屬性雖然不允許像Props屬性那樣去顯式設定,但是我們可以定義一個單獨的Props屬性來當做某個State屬性的初始值。
3. Litho的特性及原理剖析
Litho官網首頁通過4個段落重點介紹了Litho的4個特性。
3.1 宣告式元件
Litho採用宣告式的API來定義UI元件,元件通過一組不可變的屬性來描述UI。這種元件化的思想靈感來源於React,關於宣告式元件的用法上面已經詳細介紹過了。
傳統Android佈局因為UI與邏輯分離,所以開發工具都有強大的預覽功能,方便開發者調整佈局。而Litho採用React元件化的思想,通過元件連線了邏輯與佈局UI,雖然Litho也提供了對Stetho的支援,藉助於Chrome開發者工具對介面進行除錯,不過使用起來並沒有那麼方便。
3.2 非同步佈局
Android系統在繪製時為了防止頁面錯亂,頁面所有View的測量(Measure)、佈局(Layout)以及繪製(Draw)都是在UI執行緒中完成的。當頁面UI非常複雜、檢視層級較深時,難免Measure和Layout的時間會過長,從而導致頁面渲染時候丟幀出現卡頓情況。Litho為解決該問題,提出了非同步佈局的思想,利用CPU的閒置時間提前在非同步執行緒中完成Measure和Layout的過程,僅在UI執行緒中完成繪製工作。當然,Litho只是提供了非同步佈局的能力,它主要使用在RecyclerView等可以提前知道下一個檢視長什麼樣子的場景。
3.2.1 非同步佈局原理剖析
針對RecyclerView等滑動列表,由於可以提前知道接下來要展示的一個甚至多個條目的檢視樣式,所以只要提前建立好下一個或多個條目的檢視,就可以提前完成檢視的佈局工作。
那麼Android原生為什麼不支援非同步佈局呢?主要有以下兩個原因:
-
View的屬性是可變的,只要屬性發生變化就可能導致佈局變化,因此需要重新計算佈局,那麼提前計算佈局的意義就不大了。而Litho元件的屬性是不可變的,所以對於一個元件來說,它的佈局計算結果是唯一且不變的。
-
提前非同步佈局就意味著要提前建立好接下來要用到的一個或者多個條目的檢視,而Android原生的View作為檢視單元,不僅包含一個檢視的所有屬性,而且還負責檢視的繪製工作。如果要在繪製前提前去計算佈局,就需要預先去持有大量未展示的View例項,大大增加記憶體佔用。反觀Litho的元件則沒有這個問題,Litho的元件只是檢視屬性的一個集合,僅負責計算佈局,繪製工作由指定的繪製單元來完成,相比與傳統的View顯然Litho的元件要輕量的多。所以在Litho中,提前建立好接下來要用到的多個條目的元件,並不會帶來效能問題,甚至還可以直接把元件當成滑動列表的資料來源。如下圖所示:
3.3 扁平化的檢視
使用Litho佈局,我們可以得到一個極致扁平的檢視效果。它可以減少渲染時的遞迴呼叫,加快渲染速度。
下面是同一個檢視在Android和Litho實現下的檢視層級效果對比。可以看到,同樣的樣式,使用Litho實現的佈局要比使用Android原生實現的佈局更加扁平。
3.3.1 扁平化檢視原理剖析
Litho使用Flexbox來建立佈局,最終生成帶有層級結構的元件樹。然後Litho對佈局層級進行了兩次優化。
-
使用了Yoga來進行佈局計算,Yoga會將Flexbox的相對佈局轉成絕對佈局。經過Yoga處理後的佈局沒有了原來的佈局層級,變成了只有一層。雖然不能解決過度繪製的問題,但是可以有效地減少渲染時的遞迴呼叫。
-
前面介紹過Litho的檢視渲染由繪製單元來完成,繪製單元可以是View或者更加輕量的Drawable,Litho自己實現了一系列掛載Drawable的基本檢視元件。通過使用Drawable可以減少記憶體佔用,同時相比於View,Android無法檢查出Drawable的檢視層級,這樣可以使檢視效果看起來更加扁平。
原理如下圖所示,Litho會先把元件樹拍平成沒有層級的列表,然後使用Drawable來繪製對應的檢視單元。
Litho使用Drawable代替View能帶來多少好處呢?Drawable和View的區別在於前者不能和使用者互動,只能展示,因此Drawable不會像View那樣持有很多變數和引用,所以Drawable比View從記憶體上看要輕量很多。舉個例子:50個同樣展示“Hello world”的TextView和TextDrawable在記憶體佔比上,前者幾乎是後者的8倍。對比圖如下,Shallow Size表示物件自身佔用的記憶體大小。
3.3.2 繪製單元的降級策略
由於Drawable不具有互動能力,所以對於使用Drawable無法實現的互動場景,Litho會自動降級成View。主要有以下幾種場景:
- 有監聽點選事件。
- 限制子檢視繪出父佈局。
- 有監聽焦點變化。
- 有設定Tag。
- 有監聽觸控事件。
- 有光影效果。
對於以上場景的使用請仔細考慮,過多的使用會導致Litho的層級優化效果變差。
3.3.3 對比Android的約束佈局
為了解決佈局巢狀問題,Android推出了約束佈局(ConstraintLayout),使用約束佈局也可以達到扁平化檢視的目的,那麼使用Litho的好處是什麼呢?
Litho可以更好地實現複雜佈局。約束佈局雖然可以實現扁平效果,但是它使用了大量的約束來固定檢視的位置。隨著佈局複雜程度的增加,約束條件變得越來越多,可讀性也變得越來越差。而Litho則是對Flexbox佈局進行的扁平化處理,所以實際使用的還是Flexbox佈局,對於複雜的佈局Flexbox佈局可讀性更高。
3.4 細粒度的複用
Litho中的所有元件都可以被回收,並在任何位置進行復用。這種細粒度的複用方式可以極大地提高記憶體使用率,尤其適用於複雜滑動列表,記憶體優化非常明顯。
3.4.1 原生RecyclerView複用原理剖析
原生的RecyclerView檢視按模板型別進行儲存並複用,也就是說模板型別越多,所需儲存的模板種類也就越多,導致記憶體佔用越來越大。原理如下圖。滑出螢幕的itemType1和itemType2都會在Recycler快取池儲存,等待後面滑進螢幕的條目的複用。
3.4.2 細粒度複用優化記憶體原理剖析
在Litho中,item在回收前,會把LithoView中掛載的各個繪製單元拆分出來(解綁),由Litho自己的快取池去分類回收,在展示前由LithoView按照元件樹的樣式組裝(掛載)各個繪製單元,這樣就達到了細粒度複用的目的。原理如下圖。滑出螢幕的itemType1會被拆分成一個個的檢視單元。LithoView容器由Recycler快取池回收,其他檢視單元由Litho的快取池分類回收。
使用細粒度複用的RecyclerView的快取池不再需要區分模板型別來快取大量的檢視模板,只需要快取LithoView容器。細粒度回收的檢視單元數量要遠遠小於原來快取在各個檢視模板中的檢視單元數量。
4. 實踐
美團對Litho進行了二次開發,在美團的MTFlexbox動態化實現方案(簡稱動態佈局)中把Litho作為底層UI渲染引擎來使用。通過動態佈局的預覽工具,為Litho提供實時預覽能力,同時可以有效發揮Litho的效能優化效果。
目前Litho+動態佈局的實現方案已經應用在了美團App中,給美團App帶來了不錯的效能提升。後續博主會詳細介紹Litho+動態佈局在美團效能優化的實踐方案。
4.1 記憶體資料
由於Litho中使用了大量Drawable替換View,並且實現了檢視單元的細粒度複用,因此複雜列表滑動時記憶體優化比較明顯。美團首頁記憶體佔用隨滑動頁數變化走勢圖如下。隨著一頁一頁地滑動,記憶體優化了30M以上。(資料採集自Vivo x20手機記憶體佔用情況)
4.2 FPS資料
FPS的提升主要得益於Litho的非同步佈局能力,提前計算佈局可以減少滑動時的幀率波動,所以滑動過程較平穩,不會有高低起伏的卡頓感。(資料採集自魅藍2手機一段時間內連續fps的波動情況)
5. 總結
Litho相對於傳統Android是顛覆式的,它採用了React的思路,使用宣告式的API來編寫UI。相比於傳統Android,確實在效能優化上有很大的進步,但是如果完全使用Litho開發一款應用,需要自己實現很多元件,而Litho的元件需要在編譯時生成,實時預覽方面也有所欠缺。相對於直接使用Litho的高成本,把Litho封裝成Flexbox佈局的底層渲染引擎是個不錯的選擇。
6. 參考資料
7. 作者簡介
- 何少寬,美團Android開發工程師,2015年加入美團,負責美團平臺終端業務研發工作。
- 張穎,美團Android開發工程師,2017年加入美團,負責美團平臺終端業務研發工作。
相關推薦
基本功 | Litho的使用及原理剖析
開發十年,就只剩下這套架構體系了! >>>
HTML5總結及原理剖析
html5中新增的特性 1.語義化標籤,比如:<header> <footer> <article>等,可以使我們建立更友好的頁面結構,便於搜尋引擎抓取; div是division的縮寫,你在網頁中寫了大量的div,就算你寫了class
Linux平臺下使用者基本管理機制及原理剖析(二)
Linux平臺下使用者基本管理機制及原理剖析(二) 在前面的部落格中,我們對虛擬環境下使用者的新建、檢視、刪除以及使用者組的新建和管理有了一定的瞭解,這篇文章我們 接著瞭解虛擬環境下的使用者資訊的更改、使用者認證資訊的檢視,使用者密碼的管理以及使用者授權。 1.使用者資
Linux平臺下使用者基本管理機制及原理剖析(一)
Linux平臺下使用者基本管理機制及原理剖析(一) Linux是一個多使用者、多工的作業系統,具有很好的安全性和穩定性。 使用者和群組存在的意義 使用者的存在是為了使你的工作環境更加安全,就是有一個隱私空間,這個空間只有使用者本人有許可權。 而群組存在的意義是為了讓大家有
Shiro系統許可權管理、及原理剖析
1.簡介 常用的JavaEE安全框架有shiro、spring security。shiro被應用非常廣泛,可以整合cas,搭建單點登入系統。spring security則被認為比較重,應用沒有shiro廣泛。shiro提供使用者名稱、
SpringMVC的url-pattern配置及原理剖析
#### SpringMVC的url-pattern配置及原理剖析 xml裡面配置標籤: ```xml ``` 父xml的路徑: ![](https://img2020.cnblogs.com/blog/1230003/202006/1230003-20200615204052469-1474562
下載ASP.NET MVC5框架剖析與案例解析(MVC5原理剖析、漏洞及運維安全、設計模式)
mvc5框架剖析與案例解析 運維安全 mvc5原理剖析 地址:http://pan.baidu.com/s/1dFhBu2d 密碼:peas轉一播放碼,200多課!本課程針對MVC5版本的ASP.NET MVC,同時涉及太多底層實現的內容,所以大部分是找不到現成參考資料的,這些內容大都來自講師對源
項目實戰-大數據Kafka原理剖析及(實戰)演練
實戰 kafka 大數據 com nbsp attach forum ignore spa Kafka原理剖析及實戰演練 Kafka理論+實戰視頻教程 Kafka完美入門視頻教程 煉數成金<ignore_js_op> <ignore_js_op> &
Java 併發程式設計之Volatile原理剖析及使用
Java 併發程式設計之Volatile原理剖析及使用 在開始介紹Volatile之前,回顧一下在併發中極其重要的三個概念:原子性,可見行和有序性 原子性: 是指一個操作不可以被中斷.比如賦值操作a=1和返回操作return a,這樣的操作在JVM中只需要一步就可以完成,因此
HBase HFile Compact多種合併策略原理剖析及場景建議-OLAP商業環境實戰
本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。版權宣告:禁止轉載,歡迎學習。QQ郵箱地址:[email protected],如有任何學術交流,可隨時聯絡。
乾貨|區塊鏈技術入門——比特幣執行及交易原理剖析(分享實錄)
我一直從事服務端的開發工作,對各種計算機技術都有比較濃厚的興趣,從去年五六月份開始接觸區塊鏈技術,感覺和網際網路以及傳統軟體開發相比,區塊鏈有一些新的東西,甚至是一些顛覆性的創新,雖然技術方面還是基於現有的技術,但其應用和設計思想簡直是腦洞大開。 1
閘道器,路由,區域網內的通訊及不同的網路間通訊實現的原理剖析
百度百科定義閘道器: 閘道器(Gateway)又稱網間聯結器、協議轉換器。閘道器在網路層以上實現網路互連,是最複雜的網路互連裝置,僅用於兩個高層協議不同的網路互連。閘道器既可以用於廣域網互連,也可以用
【Python筆記】裝飾器語法糖(@staticmethod/@classmethod/@property)原理剖析及使用場景說明
在閱讀一些開源Python庫的原始碼時,經常會看到在某個類的成員函式前,有類似於@staticmethod或@classmethod或@property的語法糖。本質上,它們都是函式裝飾器,只不過通常被用來修飾類成員函式而已。 本筆記旨在說明這些語法糖的用途,關於普通函式裝
決策樹學習(上)——深度原理剖析及原始碼實現
引言 本文給大家分享的主題是決策樹(Decision Tree)的原理剖析並附上程式碼實現供大家參考。由於基於決策樹的演算法較多,因此文章分為上下篇。上篇主要剖析決策樹原理、需要掌握的資訊理論知識以及Java原始碼實現等內容。下篇內容包括基於決策樹的ID3、C
Kubernetes集群調度器原理剖析及思考
ima factory 大學 處理 git 占用 pub 經驗 mesos 簡述 雲環境或者計算倉庫級別(將整個數據中心當做單個計算池)的集群管理系統通常會定義出工作負載的規範,並使用調度器將工作負載放置到集群恰當的位置。好的調度器可以讓集群的工作處理更高效,同時提高資源
Kafka冪等性原理及實現剖析
1.概述 最近和一些同學交流的時候反饋說,在面試Kafka時,被問到Kafka元件組成部分、API使用、Consumer和Producer原理及作用等問題都能詳細作答。但是,問到一個平時不注意的問題,就是Kafka的冪等性,被卡主了。那麼,今天筆者就為大家來剖析一下Kafka的冪等性原理及實現。 2.內容
一個實驗搞定華為hybrid-vlan基本配置及原理
華為 hybrid-vlan實驗拓撲:2. 實驗需求:PC1和PC3屬於VLAN10 PC2和PC4屬於VLAN20 PC5和PC6屬於VLAN30。VLAN10和20的成員都可以和VLAN30中PC5通信,但是VLAN10和VLAN20的成員之間不能通信(通過二層技術實現此需求,就是華為Hybrid
NIO原理剖析與Netty初步----淺談高性能服務器開發(一)
返回 創建 基於 register 訪問 io操作 nbsp info class 除特別註明外,本站所有文章均為原創,轉載請註明地址 在博主不長的工作經歷中,NIO用的並不多,由於使用原生的Java NIO編程的復雜性,大多數時候我們會選擇Netty,m
async源碼學習 - waterfall函數的使用及原理實現
color logs 這一 per () create was ret ray waterfall函數會連續執行數組中的函數,每次通過數組下一個函數的結果。然而,數組任務中的任意一個函數結果傳遞失敗,那麽該函數的下一個函數將不會執行,並且主回調函數立馬把錯誤作為參數執行。以
常用 JavaScript 小技巧及原理詳解
this lin slice pen global 轉化 script lis fun 善於利用JS中的小知識的利用,可以很簡潔的編寫代碼 1. 使用!!模擬Boolean()函數 原理:邏輯非操作一個數據對象時,會先將數據對象轉換為布爾值,然後取反,兩個!!重復取反,就實