性能優化(網絡方向)
web應用無非是兩臺主機之間互相傳輸數據包的一個過程; 如何減少傳輸過程的耗時就是網絡方向優化的重點, 優化出發點從第一篇文章中說起
DNS解析過程的優化
當瀏覽器從第三方服務跨域請求資源的時候,在瀏覽器發起請求之前,這個第三方的跨域域名需要被解析為一個IP地址,這個過程就是DNS解析;
DNS緩存可以用來減少這個過程的耗時,DNS解析可能會增加請求的延遲,對于那些需要請求許多第三方的資源的網站而言,DNS解析的耗時延遲可能會大大降低網頁加載性能。
dns-prefetch
當站點引用跨域域上的資源時,都應在<head>元素中放置dns-prefetch提示,但是要記住一些注意事項。首先,dns-prefetch僅對跨域域上的DNS查找有效,因此請避免將其用于您當前訪問的站點
<link rel="dns-prefetch" >
preconnect
由于dns-prefetch僅執行DNS查找,但preconnect會建立與服務器的連接。如果站點是通過HTTPS服務的,則此過程包括DNS解析,建立TCP連接以及執行TLS握手。將兩者結合起來可提供機會,進一步減少跨源請求的感知延遲
<!-- 注意順序, precontent和dns-prefetch的兼容性 -->
<link rel="preconnect" crossorigin>
<link rel="dns-prefetch" >
TCP傳輸階段優化
這個前端方面好像能做的有限, 我們都知道 http協議 是基于 tcp的;
升級http協議版本可以考慮下, 比如把 http/1.0 -> http/1.1 -> http/2;
這個需要我們在應用服務器上配置(nginx, Apache等), 不做概述了, 另外還需要客戶端和服務器都支持哦, 目前還沒開發出穩定版本,好多只支持https,不過也不遠了...
http2 的優勢
# 1.多路復用: 同一個tcp連接傳輸多個資源
這樣可以突破統一域名下只允許有限個tcp同時連接,
這樣http1.1所做的減少請求數優化就沒有太大必要了
如多張小圖合成一張大圖(雪碧圖),合并js和css文件
# 2.報文頭壓縮和二進制編碼: 減少傳輸體積
http1 中第一次請求有完整的http報文頭部,第二次請求的也是;
http2 中第一次請求有完整的http報文頭部,第二次請求只會攜帶 path 字段;
這樣就大大減少了發送的量。這個的實現要求客戶端和服務同時維護一個報文頭表。
# 3.Server Push
http2可以讓服務先把其它很可能客戶端會請求的資源(比如圖片)先push發給你,
不用等到請求的時候再發送,這樣可以提高頁面整體的加載速度
但目前支持性不太好...emm...
總的來說, 在 c 端業務下不會太普及, 畢竟需要軟件支持才行...
http 請求響應階段優化
為了讓數據包傳輸的更快, 我們可以從兩個方面入手: 請求的數據包大小(服務器), 請求數據包的頻率(客戶端)
減少請求文件的大小
請求文件對應的是我們項目完成后,打包所指的靜態資源文件(會被部署到服務器), 文件越小, 傳輸的數據包也會相對較小, 講道理也會更快到達客戶端
how to reduce a package size?
目前我們都會使用打包工具了(比如webpack, rollup, glup 等), 如何使用工具來減小包的體積呢? 這邊建議您去官網文檔呢...當然這里列舉一下常用的手段(webpack 的), 但是注意要插件版本更新哦
JS文件壓縮
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [
new UglifyJsPlugin({
// 允許并發
parallel: true,
// 開啟緩存
cache: true,
compress: {
// 刪除所有的console語句
drop_console: true,
// 把使用多次的靜態值自動定義為變量
reduce_vars: true,
},
output: {
// 不保留注釋
comment: false,
// 使輸出的代碼盡可能緊湊
beautify: false
}
})
]
}
CSS 文件壓縮
// optimize-css-assets-webpack-plugin
plugins: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano'),
}),
];
html 文件壓縮
// html-webpack-plugin
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'),
filename: 'index.html',
chunks: ['index'],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false,
},
}),
];
source map 文件關閉
tree shaking
1.代碼不會被執行,不可到達,比如 if(false){// 這里邊的代碼}
2.代碼執行的結果不會被用到
3.代碼只會影響死變量(只寫不讀)
4.方法不能有副作用
// 原理相關: 以后在研究
利用 ES6 模塊的特點:
只能作為模塊頂層的語句出現
import 的模塊名只能是字符串常量
import binding 是 immutable 的
代碼擦除: uglify 階段刪除無用代碼
scope hoisting(作用域提升)
分析出模塊之間的依賴關系,盡可能的把打散的模塊合并到一個函數中去,但前提是不能造成代碼冗余
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
resolve: {
// 針對 Npm 中的第三方模塊優先采用 jsnext:main 中指向的 ES6 模塊化語法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
// 開啟 Scope Hoisting
new ModuleConcatenationPlugin(),
],
};
項目中使用按需加載,懶加載(路由,組件級)
const router = new VueRouter({
routes: [
{ path: '/foo', component: () => import(/* webpackChunkName: "foo" */ './Foo.vue') }
{ path: '/bar', component: () => import(/* webpackChunkName: "bar" */ './Bar.vue') }
]
})
開啟 gizp 壓縮
有時候啟用也會消耗服務器性能, 看情況使用吧
暫時先提這么些吧...后續想到了再加
減少請求頻率
因為同一域名下 tcp 連接數的限制導致過多的請求會排隊阻塞, 所以我們需要盡量控制請求的數量和頻率
常見措施
將靜態資源的內聯到HTML中
這樣這些資源無需從服務器獲取, 但可能影響到渲染進程...
<!-- 1.小圖片內聯 base64 (url-loader) -->
<!-- 2.css內聯 -->
<!-- 3.js內聯 -->
<script>
${require('raw-loader!babel-loader!./node_modules/lib-flexible/flexible.js')}
</script>
利用各級緩存(下一篇存儲方面介紹)
通常都是在服務端做相關配置, 但你要知道
我們可以利用http緩存(瀏覽器端)來減少和攔截二次請求, 當然一般都是在服務端設置的;
服務器端也可以設置緩存(redis等), 減少數據查詢的時間同樣可以縮短整個請求時間
利用本地存儲
我們可以將常用不變的信息存在本地(cookie,storage API 等);
判斷存在就不去請求相關的接口, 或者定期去請求也是可以的
花錢買 CDN 加速
CDN 又叫內容分發網絡,通過把資源部署到世界各地,用戶在訪問時按照就近原則從離用戶最近的服務器獲取資源,從而加速資源的獲取速度。 CDN 其實是通過優化物理鏈路層傳輸過程中的網速有限、丟包等問題來提升網速的...
購買 cdn 服務器;
然后把網頁的靜態資源上傳到 CDN 服務上去,
在請求這些靜態資源的時候需要通過 CDN 服務提供的 URL 地址去訪問;
# 注意, cdn 緩存導致的新版本發布后不生效的問題
所以打包的時候常在文件后面加上 hash 值
然后在 HTML 文件中的資源引入地址也需要換成 CDN 服務提供的地址
/alicdn/xx12dsa311.js
# 利用不同域名的 cdn 去存放資源, (tcp連接限制)
webpack 構建時添加 cdn
// 靜態資源的導入 URL 需要變成指向 CDN 服務的絕對路徑的 URL 而不是相對于 HTML 文件的 URL。
// 靜態資源的文件名稱需要帶上有文件內容算出來的 Hash 值,以防止被緩存。
// 不同類型的資源放到不同域名的 CDN 服務上去,以防止資源的并行加載被阻塞。
module.exports = {
// 省略 entry 配置...
output: {
// 給輸出的 JavaScript 文件名稱加上 Hash 值
filename: '[name]_[chunkhash:8].js',
path: path.resolve(__dirname, './dist'),
// 指定存放 JavaScript 文件的 CDN 目錄 URL
publicPath: '//js.cdn.com/id/',
},
module: {
rules: [
{
// 增加對 CSS 文件的支持
test: /\.css$/,
// 提取出 Chunk 中的 CSS 代碼到單獨的文件中
use: ExtractTextPlugin.extract({
// 壓縮 CSS 代碼
use: ['css-loader?minimize'],
// 指定存放 CSS 中導入的資源(例如圖片)的 CDN 目錄 URL
publicPath: '//img.cdn.com/id/'
}),
},
{
// 增加對 PNG 文件的支持
test: /\.png$/,
// 給輸出的 PNG 文件名稱加上 Hash 值
use: ['file-loader?name=[name]_[hash:8].[ext]'],
},
// 省略其它 Loader 配置...
]
},
plugins: [
// 使用 WebPlugin 自動生成 HTML
new WebPlugin({
// HTML 模版文件所在的文件路徑
template: './template.html',
// 輸出的 HTML 的文件名稱
filename: 'index.html',
// 指定存放 CSS 文件的 CDN 目錄 URL
stylePublicPath: '//css.cdn.com/id/',
}),
new ExtractTextPlugin({
// 給輸出的 CSS 文件名稱加上 Hash 值
filename: `[name]_[contenthash:8].css`,
}),
// 省略代碼壓縮插件配置...
],
};
/*
以上代碼中最核心的部分是通過 publicPath 參數設置存放靜態資源的 CDN 目錄 URL,
為了讓不同類型的資源輸出到不同的 CDN,需要分別在:
output.publicPath 中設置 JavaScript 的地址。
css-loader.publicPath 中設置被 CSS 導入的資源的的地址。
WebPlugin.stylePublicPath 中設置 CSS 文件的地址。
設置好 publicPath 后,WebPlugin 在生成 HTML 文件和 css-loader 轉換 CSS 代碼時,會考慮到配置中的 publicPath,用對應的線上地址替換原來的相對地址。
*/
參考
DNS MDN]
webpack 文檔
深入淺出 Webpack
Scope Hoisting
在了解了javascript的語言基礎和特性后
javascript真正大放光彩的地方來了——這就是javascript DOM
Javascript DOM
DOM(Document Object Model),文檔對象模型。
是W3C組織推薦的處理可擴展標記語言(HTML或者XML)的標準編程接口;W3C已經定義了一系列DOM接口,通過這些DOM接口可以改變網頁的內容、結構和樣式。
簡單的說就是一套操作文檔內容的方法。
需要注意的是,我們需要把DOM當作一個整體,不能分割看待,即DOM(文檔對象模型)是一套操作文檔內容的方法。
DOM把以上內容看作都是對象
<!DOCTYPE html>
<html>
<head>
<title>Shopping list</title>
<meta charset="utf-8">
</head>
<body>
<h1>What to buy</h1>
<p id="buy" title="a gentle reminder">Don't forget to buy this stuff</p>
<ul id="purchases">
<li>A tin od beans</li>
<li>Cheese</li>
<li>Milk</li>
</ul>
</body>
</html>
1、獲取DOM四種基本方法
1、getElementById()
2、getElementsByTagname()
3、getAttribute()
4、setAttribute()
常用的兩種解析:
1. getElementById():
參數:元素的ID值。 (元素節點簡稱元素)
返回值:一個有指定ID的元素對象(元素是對象)
注:這個方法是與document對象相關聯,只能由document對象調用。
用法:document.getElementById(Id)
例:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="time">2020-04-16</div>
<script>
// 1. 因為我們文檔頁面從上往下加載,所以先得有標簽 所以我們script寫到標簽的下面
// 2. get 獲得 element 元素 by 通過 駝峰命名法
// 3. 參數 id是大小寫敏感的字符串
// 4. 返回的是一個元素對象
var timer = document.getElementById('time');
console.log(timer);
console.log(typeof timer);
// 5. console.dir 打印我們返回的元素對象 更好的查看里面的屬性和方法
console.dir(timer);
</script>
</body>
</html>
看一下控制臺打印的是什么
可以看到 console.log(timer)打印出來的是整個div標簽
timer類型是個對象
2. getElementsByTagName():
參數:元素名
返回值:一個對象數組。這個數組里每個元素都是對象,每個對象分別對應著文檔里給定標簽的一個元素。
注:這個方法可和一般元素關聯。這個方法允許我們把通配符當作它的參數,返回在某份html文檔里總共有多少個元素節點。
用法:element.getElementsByTagName(TagName)
例:
var items=document.getElementsByTagName("li");
items.length;//3
document.getElementsByTagName(“*”);//12
2、事件基礎
3.1 事件概述
JavaScript使我們有能力創建動態頁面,而事件是可以被JavaScript偵測到的行為。
簡單理解:觸發——>響應機制
網頁中每個元素都可以產生某些可以觸發JavaScript的事件,例如,我們可以在用戶點擊某按鈕產生一個事件,然后去執行某些操作
3.2 事件三要素
事件源 、事件類型、事件處理程序,我們也稱為事件三要素
(1) 事件源 事件被觸發的對象 誰
(2) 事件類型 如何觸發 什么事件 比如鼠標點擊(onclick) 還是鼠標經過 還是鍵盤按下
(3) 事件處理程序 通過一個函數賦值的方式 完成
代碼實例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button id="btn">唐伯虎</button>
<script>
// 點擊一個按鈕,彈出對話框
// 1. 事件是有三部分組成 事件源 事件類型 事件處理程序 我們也稱為事件三要素
//(1) 事件源 事件被觸發的對象 誰 按鈕
var btn = document.getElementById('btn');
//(2) 事件類型 如何觸發 什么事件 比如鼠標點擊(onclick) 還是鼠標經過 還是鍵盤按下
//(3) 事件處理程序 通過一個函數賦值的方式 完成
btn.onclick = function() {
alert('點秋香');
}
</script>
</body>
</html>
運行結果
1、獲取事件源
2、注冊事件(綁定事件)
3、添加事件處理程序(采取函數賦值形式)
代碼實戰
-
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>123</div>
<script>
// 執行事件步驟
// 點擊div 控制臺輸出 我被選中了
// 1. 獲取事件源
var div = document.querySelector('div');
// 2.綁定事件 注冊事件
// div.onclick
// 3.添加事件處理程序
div.onclick = function() {
console.log('我被選中了');
}
</script>
</body>
</html>
常用的DOM事件
onclick事件---當用戶點擊時執行
onload事件---當用戶進入時執行
onunload事件---用用戶離開時執行
onmouseover事件---當用戶鼠標指針移入時執行
onmouseout事件---當用戶鼠標指針移出時執行
onmousedown事件---當用戶鼠標摁下時執行
onmouseup事件---當用戶鼠標松開時執行
————————————————
版權聲明:本文為CSDN博主「那是我吶」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42402867/article/details/105567787
文章目錄
繼承性的描述:
繼承性是指被包在內部的標簽將擁有外部標簽的樣式性,即子元素可以繼承父類的屬性。
例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div{
color: blue;
}
</style>
</head>
<body>
<div>父元素
<div>子元素
<p>我依舊是子元素</p>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
p{
font-size: 32px;
}
</style>
</head>
<body>
<p style="color: blue;">我這里體現了層疊性呀</p>
</body>
</html>
使用結論
由于內容有限,但是結論是一定的,所以我直接給出結論:
若多個選擇器定義的樣式不沖突,則元素應用所有選擇器定義的樣式。
若多個選擇器定義的樣式發生沖突(比如:同時定義了字體顏色屬性),則CSS按照選擇器的優先級,讓元素應用優先級搞得選擇器樣式。
CSS定義的選擇器優先級從高到低為:行內樣式–>ID樣式–>類樣式–>標記樣式。
如若想直接定義使用哪個樣式,不考慮優先級的話,則使用!important,把這個加在樣式后面就行了。
優先級
定義CSS樣式時,經常出現兩個或更多規則應用在同一個元素上,這時就會出現優先級的問題。層疊性和選擇器的圈中有很大的關系。
優先級的使用說明
權重分析:
內聯樣式:如:style="",權重為1000。
ID選擇器,如:#content,權重為100。
類,偽類和屬性選擇器,如.content,權重為10。
標簽選擇器和偽元素選擇器,如div p,權重為1。
繼承樣式,權重為0。
將基本選擇器的權重相加之和,就是權重大小,值越大,權重越高。
計算權重方法
數標簽:先數權重最高的標簽,然后數第二高權重的標簽,以此類推,就會生成一個數組,里面包含四個數字。
比如(0,0,0,0)分別對應(行內式個數,id選擇器個數,類選擇器個數,標簽選擇器個數)
然后兩個選擇器通過對別四個數字的大小,確定權重關系。
例:
#box ul li a.cur有1個id標簽,1個類,3個標簽,那么4個0就是(0,1,1,3)
.nav ul .active .cur有0個id,3個類,1個標簽,那么4個0就是(0,0,3,1)
例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.p1{
color: blue;
}
#p1{
color: red;
}
</style>
</head>
<body>
<p id="p1" class="p1">我們來試一下優先級</p>
</body>
</html>
先推測一波,因為前面講到了ID選擇器的權重是大于類選擇器的,所以這里顏色應該為red。
效果如下:
推測正確!優先級GET!
今天講一下使用vant Swipe 輪播控件過程中遇到的問題
主要是使用swiper自定義的大小的時候,寬度適應不同分辨率的移動設備
適應寬度的同時還需控件的正常使用
先看一下需要實現的功能,
一個簡單的輪播圖,但是每個輪播的寬度需要低于100%,使第二個輪播的van-swipe-item可以展示到第一個位置一部分
這時我們再去vant的文檔查看一下控件
剛好有一個自定義控件大小的可以使用,完美解決了我們的問題
當我們使用控件之后
<van-swipe :loop="false" @change="onChange" :width="350">
<van-swipe-item v-bind:id="item0"><div class="swipe0">
<div class="contion">
<p class="title">家中有事,申請請假一天</p>
<p class="title1"><span class="rice"></span>部門經理核審中</p>
<p class="time">03.8 14.25</p>
<p class="type">放假申請</p>
</div>
<img src="../../assets/images/index/xx/fangjia.png">
</div></van-swipe-item>
<van-swipe-item ><div class="swipe1"></div></van-swipe-item>
<van-swipe-item ><div class="swipe2"></div></van-swipe-item>
<template #indicator>
<div class="custom-indicator">
{{ current + 1 }}/3
</div>
</template>
</van-swipe>
發現功能可以使用,但是再 iPhone8/7 plus 以及iPhone5/se 等分辨率下出現了寬度固定而不適應的情況,
簡單來說,我們把van-swipe-item寬度控制在了80% 第二個van-swipe-item自然可以展示出來一部分
但是當滑到第二頁的時候 由于第一頁的寬度還是80% 所以就出現了這樣的情況,所以我打算采用
動態的改變 滑動到第幾頁的時候 把當頁的寬度變為80% 其他頁保持不變,
于是
<van-swipe :loop="false" @change="onChange" >
<van-swipe-item v-bind:id="item0"><div class="swipe0">
<div class="contion">
<p class="title">家中有事,申請請假一天</p>
<p class="title1"><span class="rice"></span>部門經理核審中</p>
<p class="time">03.8 14.25</p>
<p class="type">放假申請</p>
</div>
<img src="../../assets/images/index/xx/fangjia.png">
</div></van-swipe-item>
<van-swipe-item v-bind:id="item1"><div class="swipe1"></div></van-swipe-item>
<van-swipe-item v-bind:id="item2"><div class="swipe2"></div></van-swipe-item>
<template #indicator>
<div class="custom-indicator">
{{ current + 1 }}/3
</div>
</template>
</van-swipe>
首先 我們為每個swipe-item添加id
data(){
return {
android: true,
ios: true,
iphoneX: true,
current: 0,
item0:'item0',
item1:'item1',
item2:'item2',
}
},
mounted(){
},
methods: {
onChange(index){
console.log('當前 Swipe 索引:' + index);
if(index==1){
var div =document.getElementById("item0").style.setProperty('width', '10rem', 'important');
var div1 =document.getElementById("item1").style.setProperty('width', '9.3333333rem', 'important');
var div2 =document.getElementById("item2").style.setProperty('width', '9.3333333rem', 'important');
} else if(index==2){
var div1 =document.getElementById("item1").style.setProperty('width', '10rem', 'important');
var div0 =document.getElementById("item0").style.setProperty('width', '10rem', 'important');
var div2 =document.getElementById("item2").style.setProperty('width', '9.3333333rem', 'important');
} else if(index==0){
var div =document.getElementById("item2");
var div0 =document.getElementById("item0").style.setProperty('width', '9.3333333rem', 'important');
var div1 =document.getElementById("item1").style.setProperty('width', '9.3333333rem', 'important');
}
},
此外,監聽滑動事件,根據滑動到第幾頁 更改當前頁面的寬度,
這樣就解決了
蘭蘭設計:前端達人
emcc [options] file ...
這個輸入文件file,既可以是clang可以編譯的C/C++語言,也可以是二進制形式的llvm bitcode或者人類可讀形式的llvm assembly文件。
大部分clang或者gcc的選項(option)都是可以工作的,比如:
# 顯示信息 emcc --help # 顯示編譯器版本信息 emcc --version
如果想看當前Emscripten中clang版本支持的全部選項列表,可以直接使用命令:
clang --help.
emcc修改的或者emcc中新的選項列在下面:
首先是一些編譯優化flag,它們-O0,-O1,-O2,-Os,-Oz,-O3。
-O0:
不進行編譯優化(這是默認情況)。當你剛開始移植項目是推薦使用它,因為它會包含許多斷言。
-O1:
簡單優化。推薦你在既想縮短編譯時間又想編譯優化時使用。它畢竟比-O2級別的優化編譯起來快多了。它會進行asm.js和llvm的-O1進行優化,它會relooping,會刪除運行時斷言和C++異常捕獲,它也會使得-s ALIASING_FUNCTION_POINTERS=1。
想要C++異常捕獲重新可用,請設置:-s DISABLE_EXCEPTION_CATCHING=0。
-O2:
和-O1類似,不過多了JavaScript級別的優化以及一些llvm -O3的優化項。當你想發布項目的時候,推薦使用本級別優化。
-O3:
和-O2類似,不過比-O2又多了一些JavaScript優化,而且編譯時間明顯比-O2長。這個也推薦在發布版本的時候使用。
-Os:
和-O3類似,不過增加了額外的優化以減小生成的代碼體積,代價是比-O3性能差一點。-Os優化會同時影響llvm bitcode 和JavaScript文件的生成。
-Oz:
和-Os類似,不過進一步減小了代碼體積。
-s OPTION=VALUE
傳給編譯器的所有涉及到JavaScript代碼生成的選項。選項列表,請見settings.js。
對于某個選項的值,不僅可以直接在emcc命令行里面設定,也可以把他們寫成json文件。比如下面,就是將DEAD_FUNCTIONS選項的值放到了path/to/file文件里,emcc里面傳這個文件的路徑。
-s DEAD_FUNCTIONS=@/path/to/file
note: 1、文件內容可以是:["_func1","_func2"]; 2、文件路徑必須是絕對的,不能是相對的。
-g:
這是保留調試信息flag。
-g<level>
控制打印的調試信息數量,每一個level都是在前一個level的基礎上編譯的:
note:
優化級別越高,編譯時間越長
--profiling:
--profiling-funcs:
--tracing:
啟用Emscripten的tracing API。
--emit-symbol-map:
--js-opts<level>:
允許JavaScript優化,有兩個值:
0:不允許JavaScript優化器允許;
1:使用JavaScript優化器。
通常用不到我們設置這一項, 因為設置-O后面的level的時候,這個項就能順便取到一個合適的值。
note:
有些選項會重寫這個flag的值,比如EMTERPRETIFY, DEAD_FUNCTIONS, OUTLINING_LIMIT, SAFE_HEAP 和 SPLIT_MEMORY會將js-opts=1,因為他們依賴js優化器。
--llvm-opts<level>:
啟用llvm優化。它的取值有有:
和--js-opts<level>一樣,通常用不到我們設置這一項, 因為設置-O后面的level的時候,這個項就能順便取到一個合適的值。
--llvm-lto<level>:
啟用llvm 連接時 優化??梢匀≈?,1,2,3。
--closure <on>:
運行壓縮編譯器(Closure Compiler),可能的取值有,0,1,2:
--pre-js <file>
生成代碼前,指定一個要把內容添加進來的文件。
--post-js <file>
生成代碼后,指定一個要把內容添加進來的文件。
--embed-file <file>
指定一個帶路徑的文件嵌入到編譯生成的js代碼里。路徑是相對于編譯時的當前路徑。如果傳的是一個目錄,則目錄下所有文件的內容都會被嵌入到將來生成的js代碼中。
--preload-file <name>
異步運行編譯代碼前,指定一個預加載的文件。路徑是相對于編譯時的當前路徑。如果傳的是一個目錄,則目錄下所有文件的內容都會被預加載到一個.data文件中。
--exclude-file <name>
從 –embed-file and –preload-file后面的目錄中排除一些文件,支持使用通配符*。
--use-preload-plugins
告訴文件打包器當文件加載時,運行預加載插件。它用來執行諸如使用瀏覽器解碼器解碼圖片和音頻等。
--shell-file <path>
指定要生成HTML的模板文件。
--source-map-base <base-url>
--minify 0
等于-g1。
--js-transform <cmd>
優化之前,生成代碼之后,設定這一條命令。這條命令可以讓你修改JavaScript代碼。之后,編譯器會將修改的和未修改的一起進行編譯優化。
--bind
啟用bingdings編譯源代碼。bingings是Emscripten中連接C++和JavaScript代碼的一類API。
--ignore-dynamic-linking
告訴編譯器忽視動態鏈接,之后用戶就得手動鏈接到共享庫。
--js-library <lib>
定義除了核心庫(src/library_*)以外的js庫。
-v
打開詳細輸出。
這個設置為把-v傳給clang,并且啟用EMCC_DEBUG生成編譯階段的中間文件。它也會運行Emscripten關于工具鏈的內部的完整性檢查。
tip: emcc -v是診斷錯誤的有用工具,不管你是否附加其他參數。
--cache
--clear-cache
--clear-ports
--show-ports
--save-bc PATH
--memory-init-file <on>
規定是否單獨生成一個內存初始化文件。取值包括0和1.
-Wwarn-absolute-paths
啟用在-I和-L命令行指令中使用絕對路徑的警告。這是用來警告無意中使用了絕對路徑的。在引用非可移植的本地系統頭文件時,使用絕對路徑有時是很危險的。
--proxy-to-worker
--emrun
使生成的代碼能夠感知emrun命令行工具。當運行emran生成的應用程序時,這樣設置就允許stdout、stderr和exit(returncode)被捕獲。
--cpuprofiler
在生成的頁面上嵌入一個簡單的CPU分析器。使用這個來執行粗略的交互式性能分析。
--memoryprofiler
在生成的頁面上嵌入內存分配跟蹤器,使用它來分析應用程序Emscripten堆的使用情況。
--threadprofiler
在生成的頁面上嵌入一個線程活動分析器。當進行多線程編譯時,使用它來分析多線程應用程序。
--em-config
--default-obj-ext .ext
--valid-abspath path
設置一個絕對路徑的白名單,以防止關于絕對路徑的警告。
-o <target>
編譯輸出的文件格式。target可以取值為:
note:
如果你用了--memory-init-file,則還會從js文件中再單獨分出一部分代碼為.mem文件。
-c
生成llvm bitcode代碼,而不是JavaScript。
--separate-asm
把asm.js文件單獨生成到一個文件中。這樣可以減少啟動時的內存加載。
--output_eol windows|linux
規定生成的文本文件的行尾,如果是–output_eol windows,就是windows rn行尾,如果是–output_eol linux,則生成Linux行尾的文本文件。
--cflags
emcc會受到幾個環境變量的影響,如下:
這幾個里面比較有意思的是EMCC_DEBUG。比如,如果你在編譯之前設置set EMCC_DEBUG=1,那么編譯的時候會把編譯過程的調試信息和編譯各個階段的中間文件輸出到一個臨時目錄,這算是給開發者提供一些編譯期間的幫助或者說調試信息吧。
Emscripten主題系列文章是emscripten中文站點的一部分內容。
第一個主題介紹代碼可移植性與限制
第二個主題介紹Emscripten的運行時環境
第三個主題第一篇文章介紹連接C++和JavaScript
第三個主題第二篇文章介紹embind
第四個主題介紹文件和文件系統
第六個主題介紹Emscripten如何調試代碼
這篇文章的主要目的是學會使用koa框架搭建web服務,從而提供一些后端接口,供前端調用。
搭建這個環境的目的是: 前端工程師在跟后臺工程師商定了接口但還未聯調之前,涉及到向后端請求數據的功能能夠走前端工程師自己搭建的http路徑,而不是直接在前端寫幾個死數據。即,模擬后端接口。
當然在這整個過程(搭建環境 + 開發示例demo)中,涉及到以下幾點知識點。
包括:
首先是vue + vue-router + vuex的環境。我們用vue-cli腳手架生成項目,會用vue的同學對這塊應該很熟了。
// 全局安裝腳手架工具 npm i vue-cli -g // 驗證腳手架工具安裝成功與否 vue --version // 構建項目 vue init webpack 項目名 // 測試vue項目是否運行成功 npm run dev
因為腳手架生成的vue項目不包含vuex,所以再安裝vuex。
// 安裝vuex npm i vuex --save
前端項目構建好了,就開始構建我們的后端服務。
首先在你的開發工具(不管是webstorm還是sublime)里新建一個目錄,用來搭建基于koa的web服務。
在這里,我們不妨給這個目錄起名為koa-demo。
然后執行:
// 進入目錄 cd koa-demo // 生成package.json npm init -y // 安裝以下依賴項 npm i koa npm i koa-router npm i koa-cors
安裝好koa和兩個中間件,環境就算搭建完成了。
搭建環境是為了使用,所以我們立馬來寫一個demo出來。
demo開發既是一個練習如何在開發環境中寫代碼的過程,反過來,也是一個驗證環境搭建的對不對、好不好用的過程。
本例中,后端我們只提供一個服務,就是給前端提供一個返回json數據的接口。代碼中包含注釋,所以直接上代碼。
server.js文件
// server.js文件 let Koa = require('koa'); let Router = require('koa-router'); let cors = require('koa-cors'); // 引入modejs的文件系統API let fs = require('fs'); const app = new Koa(); const router = new Router(); // 提供一個/getJson接口 router
.get('/getJson', async ctx => { // 后端允許cors跨域請求 await cors(); // 返回給前端的數據 ctx.body = JSON.parse(fs.readFileSync( './static/material.json'));
}); // 將koa和兩個中間件連起來 app.use(router.routes()).use(router.allowedMethods()); // 監聽3000端口 app.listen(3000);
這里面用到了一個json文件,在'./static/material.json'路徑,該json文件的代碼是:
// material.json文件 [{ "id": 1, "date": "2016-05-02", "name": "張三", "address": "北京 清華大學",
}, { "id": 2, "date": "2016-05-04", "name": "李四", "address": "上海 復旦大學",
}, { "id": 3, "date": "2016-05-01", "name": "王五", "address": "廣東 中山大學",
}, { "id": 4, "date": "2016-05-03", "name": "趙六", "address": "廣東 深圳大學",
}, { "id": 5, "date": "2016-05-05", "name": "韓梅梅", "address": "四川 四川大學",
}, { "id": 6, "date": "2016-05-11", "name": "劉小律", "address": "湖南 中南大學",
}, { "id": 7, "date": "2016-04-13", "name": "曾坦", "address": "江蘇 南京大學",
}]
然后我們是用以下命令將服務啟動
node server.js
打開瀏覽器,輸入http://127.0.0.1:3000/getJson??匆豢错撁嫔鲜欠駥son文件中的json數據顯示出來,如果能夠顯示出來,則說明這個提供json數據的服務,我們已經搭建好了。
為突出重點,排除干擾,方便理解。我們的前端就寫一個組件,組件有兩部分:首先是一個按鈕,用來調用web服務的getJson接口;然后是一個內容展示區域,拿到后端返回的數據以后,將其在組件的這塊區域顯示出來。
首先我們看組件文件吧
<template> <div class="test"> <button type="button" @click="getJson">從后端取json</button> <div class="showJson">{{json}}</div> </div> </template> <script> import {store} from '../vuex' export default { computed: {
json(){ return store.state.json;
}
}, methods: {
getJson(){
store.dispatch("getJson");
}
}
} </script> <style scoped> .showJson{ width:500px; margin:10px auto; min-height:500px; background-color: palegreen;
} </style>
非常簡單,就不多解釋了。
然后看我們的vuex文件。
import Vue from 'vue' import Vuex from 'vuex';
Vue.use(Vuex) const state = { json: [],
}; const mutations = {
setJson(state, db){
state.json = db;
}
} const actions = {
getJson(context){ // 調用我們的后端getJson接口 fetch('http://127.0.0.1:3000/json', { method: 'GET', // mode:'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json',
},
}).then(function (res) { if(res.status === 200){ return res.json()
}
}).then(function (json) { //console.log(typeof Array.from(json), Array.from(json)); context.commit('setJson', Array.from(json));
})
}
}; export const store = new Vuex.Store({ state: state, mutations: mutations, actions: actions,
})
ok, 代碼擼完了,獲取后端數據之前是這樣的。
獲取后端數據之后是這樣的。
想要把本demo的fetch改為axios方式,要做的工作有以下幾處:
1、安裝axios、在vuex文件引用axios
npm i axios import axios from 'axios'
2、將fetch部分代碼替換為:
const actions = {
getJson(context){
axios.get('/json', { method: 'GET', // mode:'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json',
},
}).then(function (res) { if(res.status === 200){ return res.data
}
}).then(function (json) { //console.log(typeof Array.from(json), Array.from(json)); context.commit('setJson', Array.from(json));
})
}
};
3、又會遇到跨域,在webpack中修改,路徑config/index.js文件中添加proxyTable項的配置:
proxyTable: { '/json': { target: 'http://127.0.0.1:3000', changeOrigin: true, pathRewrite: { '^/json': '/json' }
}
},
基于vue腳手架搭建的項目,模擬異步取數據,也可以直接在腳手架生成的static文件夾下放置數據,假裝是后臺拿過來的數據。
不過搭建一個基于express或者koa的web服務,確實也該是一個前端工程師應該掌握的。
OK,以上就是全文了。
如果這篇文章使你有所收獲,不勝榮幸。
歡迎點贊,以期能幫助更多同學!
https://github.com/xiangshuo1992/preload.git git@github.com:xiangshuo1992/preload.git
這兩個地址展示的是同一個項目,但是這兩個地址之間有什么聯系呢?
前者是https url 直接有效網址打開,但是用戶每次通過git提交的時候都要輸入用戶名和密碼,有沒有簡單的一點的辦法,一次配置,永久使用呢?當然,所以有了第二種地址,也就是SSH URL,那如何配置就是本文要分享的內容。
GitHub配置SSH Key的目的是為了幫助我們在通過git提交代碼是,不需要繁瑣的驗證過程,簡化操作流程。
步驟
一、設置git的user name和email
如果你是第一次使用,或者還沒有配置過的話需要操作一下命令,自行替換相應字段。
git config --global user.name "Luke.Deng"
git config --global user.email "xiangshuo1992@gmail.com"
二、檢查是否存在SSH Key
cd ~/.ssh
ls
或者
ll
//看是否存在 id_rsa 和 id_rsa.pub文件,如果存在,說明已經有SSH Key
如果沒有SSH Key,則需要先生成一下
ssh-keygen -t rsa -C "xiangshuo1992@gmail.com"
三、獲取SSH Key
cat id_rsa.pub
//拷貝秘鑰 ssh-rsa開頭
四、GitHub添加SSH Key
GitHub點擊用戶頭像,選擇setting
新建一個SSH Key
取個名字,把之前拷貝的秘鑰復制進去,添加就好啦。
五、驗證和修改
測試是否成功配置SSH Key
ssh -T git@github.com //運行結果出現類似如下 Hi xiangshuo1992! You've successfully authenticated, but GitHub does not provide shell access.之前已經是https的鏈接,現在想要用SSH提交怎么辦?
直接修改項目目錄下.git文件夾下的config文件,將地址修改一下就好了。
————————————————
版權聲明:本文為CSDN博主「前端向朔」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u013778905/java/article/details/83501204
vue中關于插槽的文檔說明很短,語言又寫的很凝練,再加上其和methods,data,computed等常用選項使用頻率、使用先后上的差別,這就有可能造成初次接觸插槽的開發者容易產生“算了吧,回頭再學,反正已經可以寫基礎組件了”,于是就關閉了vue說明文檔。
實際上,插槽的概念很簡單,下面通過分三部分來講。這個部分也是按照vue說明文檔的順序來寫的。
進入三部分之前,先讓還沒接觸過插槽的同學對什么是插槽有一個簡單的概念:插槽,也就是slot,是組件的一塊HTML模板,這塊模板顯示不顯示、以及怎樣顯示由父組件來決定。 實際上,一個slot最核心的兩個問題這里就點出來了,是顯示不顯示和怎樣顯示。
由于插槽是一塊模板,所以,對于任何一個組件,從模板種類的角度來分,其實都可以分為非插槽模板和插槽模板兩大類。
非插槽模板指的是html模板,指的是‘div、span、ul、table’這些,非插槽模板的顯示與隱藏以及怎樣顯示由插件自身控制;插槽模板是slot,它是一個空殼子,因為它顯示與隱藏以及最后用什么樣的html模板顯示由父組件控制。但是插槽顯示的位置確由子組件自身決定,slot寫在組件template的哪塊,父組件傳過來的模板將來就顯示在哪塊。
首先是單個插槽,單個插槽是vue的官方叫法,但是其實也可以叫它默認插槽,或者與具名插槽相對,我們可以叫它匿名插槽。因為它不用設置name屬性。
單個插槽可以放置在組件的任意位置,但是就像它的名字一樣,一個組件中只能有一個該類插槽。相對應的,具名插槽就可以有很多個,只要名字(name屬性)不同就可以了。
下面通過一個例子來展示。
父組件:
-
<template>
-
<div class="father">
-
<h3>這里是父組件</h3>
-
<child>
-
<div class="tmpl">
-
<span>菜單1</span>
-
<span>菜單2</span>
-
<span>菜單3</span>
-
<span>菜單4</span>
-
<span>菜單5</span>
-
<span>菜單6</span>
-
</div>
-
</child>
-
</div>
-
</template>
子組件:
-
<template>
-
<div class="child">
-
<h3>這里是子組件</h3>
-
<slot></slot>
-
</div>
-
</template>
在這個例子里,因為父組件在<child></child>里面寫了html模板,那么子組件的匿名插槽這塊模板就是下面這樣。也就是說,子組件的匿名插槽被使用了,是被下面這塊模板使用了。
-
<div class="tmpl">
-
<span>菜單1</span>
-
<span>菜單2</span>
-
<span>菜單3</span>
-
<span>菜單4</span>
-
<span>菜單5</span>
-
<span>菜單6</span>
-
</div>
最終的渲染結果如圖所示:

-
-
注:所有demo都加了樣式,以方便觀察。其中,父組件以灰色背景填充,子組件都以淺藍色填充。
匿名插槽沒有name屬性,所以是匿名插槽,那么,插槽加了name屬性,就變成了具名插槽。具名插槽可以在一個組件中出現N次。出現在不同的位置。下面的例子,就是一個有兩個具名插槽和單個插槽的組件,這三個插槽被父組件用同一套css樣式顯示了出來,不同的是內容上略有區別。
父組件:
-
<template>
-
<div class="father">
-
<h3>這里是父組件</h3>
-
<child>
-
<div class="tmpl" slot="up">
-
<span>菜單1</span>
-
<span>菜單2</span>
-
<span>菜單3</span>
-
<span>菜單4</span>
-
<span>菜單5</span>
-
<span>菜單6</span>
-
</div>
-
<div class="tmpl" slot="down">
-
<span>菜單-1</span>
-
<span>菜單-2</span>
-
<span>菜單-3</span>
-
<span>菜單-4</span>
-
<span>菜單-5</span>
-
<span>菜單-6</span>
-
</div>
-
<div class="tmpl">
-
<span>菜單->1</span>
-
<span>菜單->2</span>
-
<span>菜單->3</span>
-
<span>菜單->4</span>
-
<span>菜單->5</span>
-
<span>菜單->6</span>
-
</div>
-
</child>
-
</div>
-
</template>
子組件:
-
<template>
-
<div class="child">
-
// 具名插槽
-
<slot name="up"></slot>
-
<h3>這里是子組件</h3>
-
// 具名插槽
-
<slot name="down"></slot>
-
// 匿名插槽
-
<slot></slot>
-
</div>
-
</template>
顯示結果如圖:

可以看到,父組件通過html模板上的slot屬性關聯具名插槽。沒有slot屬性的html模板默認關聯匿名插槽。
最后,就是我們的作用域插槽。這個稍微難理解一點。官方叫它作用域插槽,實際上,對比前面兩種插槽,我們可以叫它帶數據的插槽。什么意思呢,就是前面兩種,都是在組件的template里面寫
-
匿名插槽
-
<slot></slot>
-
具名插槽
-
<slot name="up"></slot>
但是作用域插槽要求,在slot上面綁定數據。也就是你得寫成大概下面這個樣子。
-
<slot name="up" :data="data"></slot>
-
export default {
-
data: function(){
-
return {
-
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
-
}
-
},
-
}
我們前面說了,插槽最后顯示不顯示是看父組件有沒有在child下面寫模板,像下面那樣。
-
<child>
-
html模板
-
</child>
寫了,插槽就總得在瀏覽器上顯示點東西,東西就是html該有的模樣,沒寫,插槽就是空殼子,啥都沒有。
OK,我們說有html模板的情況,就是父組件會往子組件插模板的情況,那到底插一套什么樣的樣式呢,這由父組件的html+css共同決定,但是這套樣式里面的內容呢?
正因為作用域插槽綁定了一套數據,父組件可以拿來用。于是,情況就變成了這樣:樣式父組件說了算,但內容可以顯示子組件插槽綁定的。
我們再來對比,作用域插槽和單個插槽和具名插槽的區別,因為單個插槽和具名插槽不綁定數據,所以父組件是提供的模板要既包括樣式由包括內容的,上面的例子中,你看到的文字,“菜單1”,“菜單2”都是父組件自己提供的內容;而作用域插槽,父組件只需要提供一套樣式(在確實用作用域插槽綁定的數據的前提下)。
下面的例子,你就能看到,父組件提供了三種樣式(分別是flex、ul、直接顯示),都沒有提供數據,數據使用的都是子組件插槽自己綁定的那個人名數組。
父組件:
-
<template>
-
<div class="father">
-
<h3>這里是父組件</h3>
-
<!--第一次使用:用flex展示數據-->
-
<child>
-
<template slot-scope="user">
-
<div class="tmpl">
-
<span v-for="item in user.data">{{item}}</span>
-
</div>
-
</template>
-
-
</child>
-
-
<!--第二次使用:用列表展示數據-->
-
<child>
-
<template slot-scope="user">
-
<ul>
-
<li v-for="item in user.data">{{item}}</li>
-
</ul>
-
</template>
-
-
</child>
-
-
<!--第三次使用:直接顯示數據-->
-
<child>
-
<template slot-scope="user">
-
{{user.data}}
-
</template>
-
-
</child>
-
-
<!--第四次使用:不使用其提供的數據, 作用域插槽退變成匿名插槽-->
-
<child>
-
我就是模板
-
</child>
-
</div>
-
</template>
子組件:
-
<template>
-
<div class="child">
-
-
<h3>這里是子組件</h3>
-
// 作用域插槽
-
<slot :data="data"></slot>
-
</div>
-
</template>
-
-
export default {
-
data: function(){
-
return {
-
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
-
}
-
}
-
}
結果如圖所示:
以上三個demo就放在GitHub了,有需要的可以去取。使用非常方便,是基于vue-cli搭建工程。
做過前端開發的小伙伴就算不是非常理解重排與重繪,但是肯定都聽過這兩個詞。那為什么這兩個東西這么重要?因為他與我們的頁面性能息息相關,今天,我們就來好好研究一下這兩個東西。
瀏覽器的渲染流程
在講解重排和重繪之前,我們有必要說一下瀏覽器的渲染流程。下面是瀏覽器渲染過程中最關鍵的幾個部分。如果想了解完整的瀏覽器渲染流程,推薦大家去閱讀李兵老師的瀏覽器工作原理實踐,需要付費閱讀。后期我也會整理一下再出一篇博客詳細介紹瀏覽器的渲染過程。
JavaScript:一般來說,我們會使用 JavaScript 來實現一些視覺變化的效果。比如用 jQuery 的 animate 函數做一個動畫、對一個數據集進行排序或者往頁面里添加一些 DOM 元素等。當然,除了 JavaScript,還有其他一些常用方法也可以實現視覺變化效果,比如:CSS Animations、Transitions 和 Web Animation API。
樣式計算:此過程是根據匹配選擇器(例如 .headline 或 .nav > .nav__item)計算出哪些元素應用哪些 CSS 規則的過程。從中知道規則之后,將應用規則并計算每個元素的最終樣式。
布局:在知道對一個元素應用哪些規則之后,瀏覽器即可開始計算它要占據的空間大小及其在屏幕的位置。網頁的布局模式意味著一個元素可能影響其他元素,例如 元素的寬度一般會影響其子元素的寬度以及樹中各處的節點,因此對于瀏覽器來說,布局過程是經常發生的。
繪制:繪制是填充像素的過程。它涉及繪出文本、顏色、圖像、邊框和陰影,基本上包括元素的每個可視部分。繪制一般是在多個表面(通常稱為層)上完成的。
合成:由于頁面的各部分可能被繪制到多層,由此它們需要按正確順序繪制到屏幕上,以便正確渲染頁面。對于與另一元素重疊的元素來說,這點特別重要,因為一個錯誤可能使一個元素錯誤地出現在另一個元素的上層。
其中,重排和重繪影響的就是其中的布局和繪制過程。
什么是重排和重繪制
重排:當DOM的變化引發了元素幾何屬性的變化,比如改變元素的寬高,元素的位置,導致瀏覽器不得不重新計算元素的幾何屬性,并重新構建渲染樹,這個過程稱為“重排”。
重繪:完成重排后,要將重新構建的渲染樹渲染到屏幕上,這個過程就是“重繪”。
簡單來說,涉及元素的幾何更新時,叫重排。而只涉及樣式更新而不涉及幾何更新時,叫重繪。對于兩者來說,重排必定引起重繪,但是重繪并不一定引起重排。所以,當涉及重排時,瀏覽器會將上述的步驟再次執行一遍。當只涉及重繪時,瀏覽器會跳過Layout步驟,即:
而如果既不需要重排,也不需要重繪,那么就是下面這樣:
瀏覽器會直接跳到合成階段。顯然,對于頁面性能來說,不重排也不重繪 > 重繪 > 重排。
什么操作會引起重排和重繪
顯然,觸發重排的一般都是幾何因素,這是比較好理解的:
頁面第一次渲染 在頁面發生首次渲染的時候,所有組件都要進行首次布局,這是開銷最大的一次重排
瀏覽器窗口尺寸改變
元素位置和尺寸發生改變的時候
新增和刪除可見元素
內容發生改變(文字數量或圖片大小等等)
元素字體大小變化
還有其他一些操作也可能引發重排
查詢某些屬性或調用某些方法
offset(Top|Left|Width|Height)
scroll(Top|Left|Width|Height)
client(Top|Left|Width|Height)
getComputedStyle()
我們可能不太理解為什么這些操作也能引起重排,這里我先簡單解釋一下。因為現在的瀏覽器已經非常完善了,會自動幫我們做一些優化。當我們用js操作DOM的時候,瀏覽器并不是立馬執行的,而是將操作存儲在一個隊列中。當達到一定數量或者經過一定時間以后瀏覽器再統一的去執行隊列中的操作。那么回到我們剛才的問題,為什么查詢這些屬性也會導致重排?因為當你查詢這些屬性時,瀏覽器就會強制刷新隊列,因為如果不立馬執行隊列中的操作,有可能得到的結果就是錯誤的。所以相當于你強制打斷了瀏覽器的優化流程,引發了重排。下面我們通過一些小例子來進一步理解這段話:
首先我們來一個顯然會引發重排的操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#test {
width: 100px;
height: 100px;
background-color: red;
position: relative;
}
</style>
</head>
<body>
<div id="test">
</div>
<button onclick="reflow()">click</button>
<script>
function reflow() {
var div = document.querySelector("#test");
div.style.left = '200px';
}
</script>
</body>
</html>
把時間軸往后拉,可以看到這幾個過程,先簡單介紹一些這些名詞代表的含義:
Recalculate Style:這個過程就是生成CSSOM的過程
Layout:這就是布局階段,即重排的過程
Update Layer Tree:這個階段是更新層樹的過程
Paint:該階段是為每一層準備繪制列表的過程
Composite Layers:該階段是利用繪制列表來生成相應圖層的位圖了,還涉及到合成線程和光柵化,performence面板中的Raster就是光柵化線程池 。
這里只做一個簡單的介紹,對其中內容不太明白的同學可以參考李兵老師的文章或者在我的下一篇介紹瀏覽器渲染過程的文章中會詳細解釋。
那通過這個圖我們可以看到,我們改變了div的left之后就觸發了Layout,即重排的過程。下面我們僅改變div的背景顏色,給大家一個對比。
即不重排也不重繪
說完了重排和重繪,不要忘記我們最開始提到的,最的方式就是跳過重排和重繪階段。你可能會想,什么情況下可以做到這一點?其實這就是我們平時說的GPU加速,具體是如何實現呢?在開發過程中,如果我們使用了某些屬性,瀏覽器會幫助我們將使用了該屬性的div提升到一個單獨的合成層,而在后面的渲染中,提升到該層的div將跳過重排和重繪的操作,直接到合成階段。在stack overflow上有問題提到了這塊內容。我們翻譯一下就是:
下面幾個屬性能讓瀏覽器幫助我們將div提升到一個單獨的合成層:
圖層具有3D或透視變換CSS屬性
使用加速視頻解碼的 video 元素
擁有 3D(WebGL) 上下文或者加速 2D 上下文的 canvas 元素
混合插件(Flash)
對自己的 opacity 做 CSS 動畫或使用一個動畫 webkit 變換的元素
圖層使用加速的CSS過濾器
層具有作為合成層的后代
圖層具有較低z索引的同級元素,該同級元素具有合成層(換句話說,該層在合成層的頂部渲染)
css will-change屬性
最后一點是我加上去的,同時根據文中的內容我們可以知道,css3硬件加速是瀏覽器的行為,所以在不同瀏覽器下可能會有不同的表現形式。下面我們用一個例子來理解一下。這是李兵老師在他的專欄中提出的一個例子,我拿過來借用一下,注意box中的will-change屬性:
<html>
<head>
<title>觀察will-change</title>
<style>
.box {
will-change: transform, opacity;
display: block;
float: left;
width: 40px;
height: 40px;
margin: 15px;
padding: 10px;
border: 1px solid rgb(136, 136, 136);
background: rgb(187, 177, 37);
border-radius: 30px;
transition: border-radius 1s ease-out;
}
body {
font-family: Arial;
}
</style>
</head>
<body>
<div id="controls">
<button id="start">start</button>
<button id="stop">stop</button>
</div>
<div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
</div>
<script>
let boxes = document.querySelectorAll('.box');
let boxes1 = document.querySelectorAll('.box1');
let start = document.getElementById('start');
let stop = document.getElementById('stop');
let stop_flag = false
start.addEventListener('click', function () {
stop_flag = false
requestAnimationFrame(render);
})
stop.addEventListener('click', function () {
stop_flag = true
})
let rotate_ = 0
let opacity_ = 0
function render() {
if (stop_flag)
return 0
rotate_ = rotate_ + 6
if (opacity_ > 1)
opacity_ = 0
opacity_ = opacity_ + 0.01
let command = 'rotate(' + rotate_ + 'deg)';
for (let index = 0; index < boxes.length; index++) {
boxes[index].style.transform = command
boxes[index].style.opacity = opacity_
}
requestAnimationFrame(render);
}
</script>
</body>
</html>
————————————————
版權聲明:本文為CSDN博主「溪寧」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_38164763/article/details/105406580
藍藍設計的小編 http://m.skdbbs.com