前端工程師必備:從瀏覽器的渲染到性能優化

摘要:本文主要講談及瀏覽器的渲染原理、流程以及相關的性能問題。

問題前瞻

1. 為什麼css需要放在頭部?
2. js為什麼要放在body後面?
3. 圖片的加載和渲染會阻塞頁面DOM構建嗎?
4. dom解析完才出現頁面嗎?
5. 首屏時間根據什麼來判定?

瀏覽器渲染

1.瀏覽器渲染圖解

[來自google開發者文檔]

瀏覽器渲染頁面主要經歷了下面的步驟:

1.處理 HTML 標記並構建 DOM 樹。
2.處理 CSS 標記並構建 CSSOM 樹。
3.將 DOM 與 CSSOM 合併成一個渲染樹。
4.根據渲染樹來布局,以計算每個節點的幾何信息。
5.將各個節點繪製到屏幕上。

為構建渲染樹,瀏覽器大體上完成了下列工作:

從 DOM 樹的根節點開始遍歷每個可見節點。

某些節點不可見(例如腳本標記、元標記等),因為它們不會體現在渲染輸出中,所以會被忽略。
某些節點通過 CSS 隱藏,因此在渲染樹中也會被忽略,例如,上例中的 span 節點---不會出現在渲染樹中,---因為有一個顯式規則在該節點上設置了“display: none”屬性。
對於每個可見節點,為其找到適配的 CSSOM 規則並應用它們。

發射可見節點,連同其內容和計算的樣式。

根據以上解析,DOM樹和CSSOM樹的構建對於頁面性能有非常大的影響,沒有DOM樹,頁面基本的標籤塊都沒有,沒有樣式,頁面也基本是空白的。所以具體css的解析規則是什麼?js是怎麼影響頁面渲染的?了解了這些,我們才能有的放矢,對頁面性能進行優化。

2.css解析規則

1
<div id="div1">
2
<div class="a">
3
<div class="b">
4
...
5
</div>
6
<div class="c">
7
<div class="d">
8
...
9
</div>
10
<div class="e">
11
...
12
</div>
13
</div>
14
</div>
15
<div class="f">
16
<div class="c">
17
<div class="d">
18
...
19
</div>
20
</div>
21
</div>
22
</div>

 

1
#div1 .c .d {}
2
.f .c .d {}
3
.a .c .e {}
4
#div1 .f {}
5
.c .d{}

從左向右的匹配規則

從右向左的匹配規則

如果css從左向右解析,意味着我們需要遍歷更多的節點。不管樣式規則寫得多細緻,每一個dom結點仍然需要遍歷,因為整個style rules還會有其它公共樣式影響。如果從右向左解析,因為子元素只有一個父元素,所以能夠很快定位出當前dom符不符合樣式規則。

3.js加載和執行機制

首先明確一點,我們可以通過js去修改網頁的內容,樣式和交互等,這一意味着js會影響頁面的dom結構,如果js和dom構建并行執行,那麼很容易會出現衝突,所以js在執行時必然會阻塞dom和cssom的構建過程,不論是外部js還是內聯腳本。

js的位置是否影響dom解析?

首先我們為什麼提倡把js放在body標籤的後面去加載,因為從demo上看無論是放在head還是放在body后加載js,頁面domcontentload的時間都是一樣的:

我們從圖中可以看出js的加載和執行是阻塞dom解析的,但是因為頁面並不是一次就渲染完成,所以我們需要做的是盡量讓用戶看到首屏的部分被渲染出來,js放在頭部,則頁面的內容區域還沒有解析到就被阻塞了,導致用戶看到的是白屏,而js放在body後面,儘管此時頁面dom仍然沒有解析完成,但是已經渲染出一部分樓層了,這也是為什麼我們比較看重頁面的首屏時間。

只有DOM和CSSOM樹構建好后併合並成渲染樹才能開始繪製頁面圖形,那是不是把整個DOM樹和CSSOM樹構建好后才能開始繪製頁面?這顯然是不符合我們平時訪問頁面的認知的,實際上:

為達到更好的用戶體驗,呈現引擎會力求儘快將內容显示在屏幕上。它不必等到整個 HTML 文檔解析完畢之後,就會開始構建呈現樹和設置布局。在不斷接收和處理來自網絡的其餘內容的同時,呈現引擎會將部分內容解析並显示出來。

具體瀏覽器什麼時候進行首次繪製?可以查看本文對瀏覽器首次渲染時間點的探究。

4.圖片的加載和渲染機制

首先我們解答一下上面的問題:圖片的加載與渲染會不會阻塞頁面渲染?答案是圖片的加載和渲染不會影響頁面的渲染。

那麼標籤中的圖片和樣式中的圖片的加載和渲染時間是什麼樣的呢?

解析HTML【遇到標籤加載圖片】 —> 構建DOM樹
加載樣式 —> 解析樣式【遇到背景圖片鏈接不加載】 —> 構建樣式規則樹
加載javascript —> 執行javascript代碼
把DOM樹和樣式規則樹匹配構建渲染樹【遍歷DOM樹時加載對應樣式規則上的背景圖片】
計算元素位置進行布局
繪製【開始渲染圖片】

當然把DOM樹和樣式規則樹匹配構建渲染樹時,只會把可見元素和它對應的樣式規則結合一起產出到渲染樹,這就意味有不可見元素,當匹配DOM樹和樣式規則樹時,若發現一個元素的對應的樣式規則上有display:none,瀏覽器會認為該元素是不可見的,因此不會把該元素產出到渲染樹上。

性能優化

css優化

1.盡量減少層級

1
#div p.class {
2
color: red;
3
}
4

5
.class {
6
color: red;
7
}

層級減少,意味者匹配時遍歷的dom就少。
關於less嵌套的書寫規範也基於這個道理。

2.使用類選擇器而不是標籤選擇器

減少匹配次數

3.按需加載css

1
(function(){
2
window.gConfig = window.gConfig || {};
3
window.gConfig.isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
4
var hClassName;
5
if(window.gConfig.isMobile){
6
hClassName = ' phone';
7

8
document.write('<link rel="stylesheet" href="https://res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/m/index.css" />');
9
document.write('<link rel="preload" href="//res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/m/index.js" crossorigin="anonymous" as="script" />');
10

11
}else{
12
hClassName = ' pc';
13

14
document.write('<link rel="stylesheet" href="https://res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/pc/index.css" />');
15
document.write('<link rel="preload" href="//res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/pc/index.js" crossorigin="anonymous" as="script" />');
16

17
}
18
var root = document.documentElement;
19
root.className += hClassName ;
20

21
})();

async 與 defer

[來自https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html]

使用

  • 如果腳本是模塊化的並且不依賴於任何腳本,請使用async。
  • 如果該腳本依賴於另一個腳本或由另一個腳本所依賴,則使用defer。

減少資源請求

瀏覽器的併發數量有限,所以為了減少瀏覽器因為優先加載很多不必要資源,以及網絡請求和響應時間帶來的頁面渲染阻塞時間,我們首先應該想到的是減少頁面加載的資源,能夠盡量用壓縮合併,懶加載等方法減少頁面的資源請求。

延遲加載圖像

儘管圖片的加載和渲染不會影響頁面渲染,但是為了盡可能地優先展示首屏圖片和減少資源請求數量,我們需要對圖片做懶加載。

1
document.addEventListener("DOMContentLoaded", function() {
2
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
3
let active = false;
4

5
const lazyLoad = function() {
6
if (active === false) {
7
active = true;
8

9
setTimeout(function() {
10
lazyImages.forEach(function(lazyImage) {
11
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
12
lazyImage.src = lazyImage.dataset.src;
13
lazyImage.srcset = lazyImage.dataset.srcset;
14
lazyImage.classList.remove("lazy");
15

16
lazyImages = lazyImages.filter(function(image) {
17
return image !== lazyImage;
18
});
19

20
if (lazyImages.length === 0) {
21
document.removeEventListener("scroll", lazyLoad);
22
window.removeEventListener("resize", lazyLoad);
23
window.removeEventListener("orientationchange", lazyLoad);
24
}
25
}
26
});
27

28
active = false;
29
}, 200);
30
}
31
};
32

33
document.addEventListener("scroll", lazyLoad);
34
window.addEventListener("resize", lazyLoad);
35
window.addEventListener("orientationchange", lazyLoad);
36
});

詳情參考延遲加載圖像和視頻

大促活動實踐

2.1 懶加載與異步加載

懶加載與異步加載是大促活動性能優化的主要手段,直白的說就是把用戶不需要或者不會立即看到的頁面數據與內容全都挪到頁面首屏渲染完成之後去加載,極限減小頁面首屏渲染的數據加載量與js,css執行帶來的性能損耗。

2.1.1 導航下拉的異步加載

導航的下拉內容是一塊結構非常複雜的html片段,如果直接加載,瀏覽器渲染的時間會拖慢頁面整體的加載時間:

所有我們需要通過異步加載方式來獲取這段html片段,等頁面首屏渲染結束后再添加到頁面上,大致的代碼如下:

1
$.ajax({
2
url: url, async: false, timeout: 10000,
3
success: function (data) {
4
container.innerHTML = data;
5
var appendHtml = $('<div class="footer-wrapper">' + container.querySelector('#footer').innerHTML + '</div>');
6
var tempHtml = '<div style="display:none;">' + '<script type="text/html" id="header-lazyload-html-drop" class="header-lazyload-html" data-holder="#holder-drop">' + appendHtml.find('#header-lazyload-html-drop').html() + '<\/script><script type="text/html" id="header-lazyload-html-mbnav" class="header-lazyload-html" data-holder="#holder-mbnav">' + appendHtml.find('#header-lazyload-html-mbnav').html() + '<\/script></div>';
7
$('#footer').append(tempHtml);
8
feloader.onLoad(function () {
9
feloader.use('@cloud/common-resource/header', function () {
10
});
11
$('#footer').css('display', 'block');
12
});
13
},
14
error: function (XMLHttpRequest, textStatus, errorThrown) {
15
console.log(XMLHttpRequest.status, XMLHttpRequest.readyState, textStatus);
16
},
17
});

2.1.2 圖片懶加載

官網的cui套件中已經有lazyload的插件支持圖片懶加載,使用方法頁非常簡單:

1
<div class="list">
2
<img class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/1" src="佔位圖片URL" />
3
<img class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/2" src="佔位圖片URL" />
4
<img class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/3" src="佔位圖片URL" />
5
<div class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/3"></div>
6
...
7
</div>

從代碼我們差不多可以猜出圖片懶加載的原理,其實就是我們通過覆蓋img標籤src屬性,使得img標籤開始加載時由於沒有src的具體圖片地址而不去加載圖片,等到重要資源加載完之後,通過監聽onload的時間或者滾動條的滾動時機再去重寫對應標籤的src值來達到圖片懶加載:

1
/**
2
* load image
3
* @param {HTMLElement} el - the image element
4
* @private
5
*/
6
_load(el) {
7
let source = el.getAttribute(ATTR_IMAGE_URL);
8
if (source) {
9
let processor = this._config.processor;
10
if (processor) {
11
source = processor(source, el);
12
}
13

14
el.addEventListener('load', () => {
15
el.classList.remove(CLASSNAME);
16
});
17
// 判斷是否是什麼元素
18
if (el.tagName === 'IMG') {
19
el.src = source;
20
} else {
21
// 判斷source是不是一個類名,如果是類名的話,則加到class裏面去
22
if (/^[A-Za-z0-9_-]+$/.test(source)) {
23
el.classList.add(source);
24
} else {
25
let styles = el.getAttribute('style') || '';
26
styles += `;background-image: url(${source});`;
27
el.setAttribute('style', styles);
28
el.style.backgroundImage = source; // = `background-image: url(${source});`;
29
}
30
}
31

32
el.removeAttribute(ATTR_IMAGE_URL);
33
}
34
}

具體的插件代碼大家可以查看https://git.huawei.com/cnpm/lazyload

同時官網的頁腳部分也採用了採用其它的加載方式也實現了懶加載的效果,頁腳的圖片都在css中引用,想要延遲加載頁腳圖片就需要延遲加載頁腳的css,但是延遲加載css造成的後果就是頁面加載的一瞬間頁腳會因為樣式確實而显示錯亂,所以我們可以在css樣式加載前強勢隱藏掉頁腳部分,等css加載完成后,頁腳dom自帶的display:block會自動显示頁腳。(==因為頁腳的seo特性沒有對其進行懶加載==)

2.1.3 樓層內容的懶加載

基於xtpl自帶的懶加載能力,配合pep定製頁面模板的邏輯,我們可以實現html的懶加載。在頁面初次渲染的時候,只有每個樓層的大體框架和標題等關鍵信息,如果需要的話可以給默認圖片等佔位,或設置最小高度佔位,防止錨點定位失效。
當頁面滾動到該樓層的位置,js代碼方會執行,在初始化函數中,對該樓層的html進行加載,渲染,實現樓層圖片和html的懶加載,減少了首屏時間。
具體代碼如下:

1
<div class="nov-c6-cards j-content">
2
</div>

 

1
public render(){
2
this.$el.find('.j-content').html(new Xtemplate(tpl).render(mockData))
3
...
4
}

2.1.4 套餐數據懶加載

套餐數據的加載一直以來都是令人頭疼的,本次雙十一對於套餐腳本也做了優化,不僅對數據進行了緩存,同時也可以在指定的範圍進行套餐數據的渲染——和上述所說的樓層懶加載配合,可以做到未展示的樓層,套餐數據不請求,下拉框不渲染,詢價接口不調用,在首屏不出現大量套餐的情況下,可以大大提升首屏加載的性能。

2.2.資源整合

2.2.1.頁頭頁尾資源統一維護

基礎模板的優化涉及到資源的合併,壓縮與異步加載,dom的延遲加載和圖片的懶加載。首先我們給出官網基礎模板引用的一部分js資源的表格:

這部分js存在問題是分散在pep的各個資產庫路徑維護,有些壓縮了,有些沒有壓縮,js的加載也基本是順序執行,所以我們對這個部分的js和css資源進行了一個整合,進行的操作是遷移,合併,壓縮。

建立common-resource倉庫去統一維護管理頁頭頁腳及公共資源代碼。

2.2.2.合併加載方式相同的基礎功能js並壓縮

common.js

1
import './common/js/AGrid';
2
import './common/js/jquery.base64';
3
import './common/js/lang-tips';
4
import './common/js/setLocaleCookie';
5
import './common/js/pepDialog';

如上面代碼,將官網中用的分散的基礎功能js合併成一個common.js,經過伏羲流水線發布,cui套件會自動將js壓縮,這樣做的效果當然是減少官網頁面請求資源數,減小資源大小。

2.2.3.資源異步加載

觀察2.2.1中的表格可以發現,官網大部分js都是放在頭部或者是body后順序加載的,這些資源的加載時間必定是在DOMOnLoad之前

這些js都是會阻塞頁面的渲染,導致頁面首屏加載變慢,我們需要做的就是通過之前頭尾資源的整理得出哪些資源是可以在onload之後去加載的,這些我們就可以把頁面加載時不需要執行的js和css全部移到頁面渲染完成後去加載,少了這部分的js邏輯執行時的阻塞,頁面首屏渲染的時間也會大大降低。

通過cui套件中的feloader插件,我們可以比較便捷的控制js和css加載的時機:

1
feloader.onLoad(function () {
2
feloader.use([
3
'@cloud/link-to/index',
4
'@cloud/common-resource/uba',
5
'@cloud/common-resource/footer',
6
'@cloud/common-resource/header',
7
'@cloud/common-resource/common',
8
'@cloud/common-resource/prompt.css',
9
'@cloud/common-resource/footer.css',
10
]);
11
});

下圖可以明顯看到js的加載都轉移到onload之後了:

2.2.4 圖片壓縮

除了對設計給出的圖片有壓縮要求外,我們還通過對一部分不常更新的小圖標圖片進行base64編碼來減少頁面的圖片請求數量。

2.3預解析與預加載

除了延遲加載外,基礎模板還進行了諸如dns預解析,資源預加載的手段來提前解析dns和加載頁面資源。

2.3.1 DNS 預解析

當用戶訪問過官網頁面后,DNS預解析能夠使用戶在訪問雙十一活動頁之前提前進行DNS解析,從而減少雙十一活動頁面的dns解析時間,提高頁面的訪問性能,其實寫法也很簡單:

1
<link rel="dns-prefetch" href="//res.hc-cdn.com">
2
<link rel="dns-prefetch" href="//res-static1.huaweicloud.com">
3
<link rel="dns-prefetch" href="//res-static2.huaweicloud.com">
4
<link rel="dns-prefetch" href="//res-static3.huaweicloud.com">

2.3.2 preload 預加載

活動頁的部分js還使用了preload預加載的方式來提升頁面加載性能,preload的為什麼可以達到這種效果,我們需要看下面這段摘錄:

Preloader 簡介

HTML 解析器在創建 DOM 時如果碰上同步腳本(synchronous script),解析器會停止創建 DOM,轉而去執行腳本。所以,如果資源的獲取只發生在解析器創建 DOM時,同步腳本的介入將使網絡處於空置狀態,尤其是對外部腳本資源來說,當然,頁面內的腳本有時也會導致延遲。

預加載器(Preloader)的出現就是為了優化這個過程,預加載器通過分析瀏覽器對 HTML 文檔的早期解析結果(這一階段叫做“令牌化(tokenization)”),找到可能包含資源的標籤(tag),並將這些資源的 URL 收集起來。令牌化階段的輸出將會送到真正的 HTML 解析器手中,而收集起來的資源 URLs 會和資源類型一起被送到讀取器(fetcher)手中,讀取器會根據這些資源對頁面加載速度的影響進行有次序地加載。

基於以上原理,我們對官網相對重要的js資源進行preload預加載,以使得瀏覽器可以儘快地加載頁面所需的重要資源。

1
<link rel="preload" href="//res.hc-cdn.com/cnpm-feloader/1.0.6/feloader.js" as="script"/>
2
<link rel="preload" href="//polyfill.alicdn.com/polyfill.min.js?features=default,es6" as="script"/>
3
<link rel="preload" href="https://res-static3.huaweicloud.com/content/dam/cloudbu-site/archive/commons/3rdlib/jquery/jquery-1.12.4.min.js" as="script"/>
4
<link rel="preload" href="//res.hc-cdn.com/cnpm-wpk-reporter/1.0.6/wpk-performance.js" as="script"/>
5

6
<link rel="preload" href="//res.hc-cdn.com/cpage-pep-2019nov-promotion/1.1.15/components/activity-banner/images/banner_mb.jpg" as="image" media="(max-width: 767px)">

優化效果

3.總結

前端性能優化的方法手段並不僅限於文章陳述,官網前端團隊還會在前端性能優化的道路上學習更多,探索更多,將華為雲官網頁面的加載性能做到極致!

 

點擊關注,第一時間了解華為雲新鮮技術~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

自已做動畫及編寫程序搞清楚最大堆的實現原理

目錄

  • 背景
  • 概念
  • 最大堆
    • 最大堆的線性存儲
    • 動畫實現最大堆加入新元素
    • 代碼實現最大堆加入新元素
    • 動畫實現最大堆取出最大元素
    • 代碼實現最大堆取出最大元素
    • 程序測試
  • 最大堆的應用–優先隊列
  • 寫在最後

背景

  • 二叉樹是數據結構中的重點,也是難點。二叉樹比數組、棧、隊列等線性結構相比複雜度更高,想要做到心中有“樹”,需要自己動手畫圖、觀察、思考,才能領會其真諦。
  • 在上篇文章《自己動手作圖深入理解二叉樹、滿二叉樹及完全二叉樹》中,我們對完全二叉樹有了一定認識,該文將對一種特殊的完全二叉樹”最大堆”進行底層研究。

概念

堆(heap)通常是一個可以被看做一棵二叉樹的數組對象。堆總是滿足下列性質:

  • 堆總是一棵完全二叉樹。
  • 堆中某個節點的值總是不大於或不小於其父節點的值;

最大堆

  • 根節點最大的堆叫做最大堆
最大堆的線性存儲
  • 由於堆是一種特殊的完全二叉樹,可以利用數組集合形成線性存儲的數據結構。
/**
 * 最大堆的底層實現--數組集合形成線性存儲的數據結構
 *  * @author zhuhuix
 * @date 2020-06-28
 */
public class MaxHeap<E extends Comparable<E>> {

    // 存放元素的數組集合
    private ArrayList<E> list;

    MaxHeap() {
        this.list = new ArrayList<>();
    }

    // 得到左孩子索引
    private int getLeftChildIndex(int i) {
        return (2 * i + 1);
    }

    // 得到右孩子索引
    private int getRightChildIndex(int i) {
        return (2 * i + 2);
    }

    // 得到父結點索引
    private int getParentIndex(int i) {
        if (i == 0) {
            throw new IllegalArgumentException("非法索引值");
        } else {
            return ((i - 1) / 2);
        }
    }
}
動畫實現最大堆加入新元素
  • 加入到數組集合尾部的元素與父結點進行比較,通過上浮操作,保證所有子結點不能大於父結點。
代碼實現最大堆加入新元素
/**
 * 最大堆的底層實現
 *
 * @author zhuhuix
 * @date 2020-06-28
 */
public class MaxHeap<E extends Comparable<E>> {

    // 存放元素的數組集合
    private ArrayList<E> list;

    MaxHeap() {
        this.list = new ArrayList<>();
    }

    // 得到左孩子索引
    private int getLeftChildIndex(int i) {
        return (2 * i + 1);
    }

    // 得到右孩子索引
    private int getRightChildIndex(int i) {
        return (2 * i + 2);
    }

    // 得到父結點索引
    private int getParentIndex(int i) {
        if (i == 0) {
            throw new IllegalArgumentException("非法索引值");
        } else {
            return ((i - 1) / 2);
        }
    }

    // 添加元素
    public void add(E e) {
        this.list.add(e);
        /**
         * 將加入的結點與父結點進行比較:
         * 如果加入的結點大於父結點,則進行上浮
         * 直至新結點小於或等於父結點為止
         */

        // 獲取當前添加元素在數組中的索引
        int i = this.list.size() - 1;
        while (i > 0) {
            E current = this.list.get(i);
            E parent = this.list.get(getParentIndex(i));
            // 如果父結點元素大於當前加入的元素,則進行交換
            if (parent.compareTo(current) < 0) {
                // 交換新加入的結點與父結點的位置
                Collections.swap(this.list, i, getParentIndex(i));
            } else {
                break;
            }
            i = getParentIndex(i);
        }
    }
    
}
動畫實現最大堆取出最大元素
  • 獲取最大堆中的根結點,即為最大元素;並把尾部結點放置到根結點,並通過下沉操作,把子結點中的最大元素移動根結點。
代碼實現最大堆取出最大元素
/**
 * 最大堆的底層實現
 *
 * @author zhuhuix
 * @date 2020-06-28
 */
public class MaxHeap<E extends Comparable<E>> {

    // 存放元素的數組集合
    private ArrayList<E> list;

    MaxHeap() {
        this.list = new ArrayList<>();
    }

    // 得到左孩子索引
    private int getLeftChildIndex(int i) {
        return (2 * i + 1);
    }

    // 得到右孩子索引
    private int getRightChildIndex(int i) {
        return (2 * i + 2);
    }

    // 得到父結點索引
    private int getParentIndex(int i) {
        if (i == 0) {
            throw new IllegalArgumentException("非法索引值");
        } else {
            return ((i - 1) / 2);
        }
    }

    // 查找最大元素
    public E findMax() {
        if (this.list.size() == 0) {
            return null;
        }
        // 最大堆中的元素永遠在根結點
        return this.list.get(0);
    }

    // 取出最大元素
    public E getMax() {
        if (findMax() != null) {
            E e = findMax();

            /**
             * 取出最大元素后,需要把堆中第二大的元素放置在根結點:
             * 將根結點元素與最後面的元素進行交換,
             * 讓最後面的元素出現在根結點,並移除最大元素
             * 將根結點的元素與左右孩子結點比較,直至根結點的元素變成最大值
             */
            int i = 0;
            Collections.swap(this.list, i, this.list.size() - 1);
            this.list.remove(this.list.size() - 1);

            // 通過循環進行當前結點與左右孩子結點的大小比較
            while (getLeftChildIndex(i) < this.list.size() && getRightChildIndex(i) < this.list.size()) {
                int leftIndex = getLeftChildIndex(i);
                int rightIndex = getRightChildIndex(i);

                // 通過比較左右孩子的元素哪個較大,確定當前結點與哪個孩子進行交換
                int index = this.list.get(leftIndex).compareTo(this.list.get(rightIndex)) > 0 ? leftIndex : rightIndex;
                if (this.list.get(i).compareTo(this.list.get(index)) < 0) {
                    Collections.swap(this.list, i, index);
                } else {
                    // 如果當前結點都大於左右孩子,則結束比較
                    break;
                }
                i = index;
            }

            return e;
        } else {
            return null;
        }
    }
}

程序測試
/**
 * 最大堆的底層實現--測試程序
 *
 * @author zhuhuix
 * @date 2020-06-28
 */
public class MaxHeapTest {
    public static void main(String[] args) {
        MaxHeap<Integer> maxHeap = new MaxHeap<>();

        // 將10個数字加入形成最大堆
        int[] arrays = {19,29,4,2,27,0,38,15,12,31};
        for (int i = 0; i < arrays.length; i++) {
            maxHeap.add(arrays[i]);
        }

        // 依次從堆中取出最大值
        for (int i = 0; i < arrays.length; i++) {
            System.out.println("第"+(i+1)+"次取出堆目前的最大值:"+maxHeap.getMax());
        }
    }
}

最大堆的應用–優先隊列

優先隊列:出隊的和順序與入隊的順序無關,只與優先級相關;
優先隊列通常可以採用最大堆的數據結構來實現。

/**
 * 用最大堆的數據結構實現優先隊列
 * 
 * @author zhuhuix
 * @date 2020-06-28
 */
public class PriorityQueue<E extends Comparable<E>>  {
    private MaxHeap<E> mhp;
    PriorityQueue() {
        mhp=new MaxHeap<>();
    }

    // 入隊
    public void enqueue(E e) {
        mhp.add(e);
    }

    // 優選級最高的元素出隊
    public E dequeue() {
        return mhp.getMax();
    }

    // 查看優先級最高的元素
    public E getFront() {
        return mhp.findMax();
    }
}

寫在最後

  • 以上通過畫圖、動畫演示、代碼編寫對堆與最大堆的概念和底層實現方式,都作了深入分析;作為最大堆的反向結構,最小堆的實現也是一樣,讀者可參考以上動畫和代碼,動手練習。
  • 畫圖、編碼不易,請點贊、收藏、關注三連!!!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

日本國土百萬年的惡夢 每日數百噸的福島輻射污染水

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

馬來西亞最後一頭蘇門答臘犀牛病逝 全球剩80頭

摘錄自2019年11月24日中央通訊社馬來西亞報導

馬來西亞最後一隻蘇門答臘犀牛,25歲的母犀牛伊曼自2014年被捕獲後,一直在野生動物保護區接受妥善照顧,牠今天(24日)因癌病逝於婆羅洲(Borneo)沙巴(Sabah)。沙巴野生動物部門(Sabah Wildlife Department)主任奧古斯丁(Augustine Tuuga)說:「伊曼的死亡比預期要快,但我們知道,牠已經開始承受極大痛苦。」

蘇門答臘犀牛是體型最小的犀牛,曾廣布亞洲各地,野生蘇門答臘犀牛如今已近乎絕種,據保育人士估計,目前全球僅剩約30至80頭,大多棲息在蘇門答臘和印尼所管轄的婆羅洲地區。馬來西亞2015年宣布野生蘇門答臘犀牛絕種,最後一頭公蘇門答臘犀牛今年5月離世。

保育團體國際犀牛基金會(International Rhino Foundation)說,棲地減少和盜獵導致蘇門答臘犀牛生存於孤立區域,代表牠們繁衍困難,數十年內可能就會滅絕。

自2011年以來,馬來西亞不斷嘗試以體外受精方式,繁殖人工飼養的蘇門答臘犀牛,但迄今尚未成功。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

一口氣投80億人民幣 比亞迪建長沙、青島兩個新能源車生產基地

比亞迪汽車於5月18日與湖南省長沙市雨花區經開區簽署合作備忘錄,比亞迪將投資50億元人民幣(下同),在長沙比亞迪汽車城二期興建電動卡車及專用車生產基地,計畫年內投產。   長沙比亞迪專案將由電動轎車、電動客車和電動卡車及專用車三部分組成。電動卡車及專用車專案,現已動工建設,是比亞迪電動卡車及專用車全球製造中心,含整車及相應配套零部件,目標向全中國工廠提供電動底盤、車橋等核心部件。比亞迪預計2016年將達產2500輛,規劃至2020年達5000輛年產能,2025年擴建到10000輛年產能。   此外,比亞迪在青島市城陽區投資30億元的新能源汽車專案一期目前已開工建設,這是青島城陽首個新能源汽車產業專案,也是建區以來引進的最大工業專案。根據規劃分三期建設完成,預計2017年全部建成。專案全部建成後,將年產電動汽車5000輛,年可實現銷售收入105億元。   此專案位於棘洪灘軌道交通裝備製造產業園內,規劃佔地1000畝,主要生產純電動轎車、中巴、小巴、物流車等產品,以及從事車輛電池、電機、電控等核心零部件的研發和製造。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

詳解電動汽車無線充電技術

Q1:無線充電有哪些方式?原理是什麼?  

  A:現在劃分的無線充電類型有好些種,比如感應式、共振式、微波傳輸式等等,不過總體來說,它們的基本原理都是一樣的,就是利用交變電磁場的電磁感應,來實現能量的無線傳輸。    感應式的無線電能傳輸算是目前比較成熟的技術,很多手機無線充電、甚至我們常見的電磁爐就是利用的這種原理。由於數碼設備空間小,接收線圈也小,加上充電設備功率小,所以通常充電的距離近(甚至需要與充電座接觸),不過相對電磁輻射也小。    共振式則是麻省理工目前在開發的一類充電技術,說起來也不複雜,他們利用電磁感應現象,加上共振的原理,能提升無線充電的效率。共振傳輸的距離比普通感應式更遠一些,而麻省理工目前正在進行小型化的研究——對於車長好幾米的電動車來說,這方面的技術壓力倒不是太大。    微波傳輸式此前更多出現在科幻電影或者小說裡面,實際上它也是無線電力傳輸的一個很好的方式,只不過受到發送功率等方面的限制,並未大規模實用化。微波傳輸的最大好處就是傳輸距離遠,甚至可以實現航天器與地面之間的能量傳輸,同時還可以實現定向傳輸(發射天線有方向性),未來前景值得期待。  

 

Q2:無線充電的好處有哪些?

  A:無線充電的第一個好處就是不需要線,不必為了到處找充電線而費神。第二就是無線充電在硬體方面的標準更容易統一。  
Q3:有待解決的問題有哪些?   A: 一、傳輸效率是所有無線充電都面臨的問題,對於電動車這樣充電功率更大的“電器”來說更是如此——電能首先轉換為無線電波,再由無線電波轉換成電能,這兩次轉換都會損失不少的能量。   二、電磁相容也是無線充電需要解決的技術瓶頸之一。電磁波很容易產生洩漏,當大功率的車用無線充電設備運行時,也會對周圍的生物和電子設備產生影響,甚至會危害人體健康。利用封閉的自動智慧化車庫安裝無線充電設備是解決電磁相容比較好的途徑,不過成本也確實不菲。   三、電氣標準等方面的問題。  
Q4:有哪些典型案例呢?   A:從國外車企來看,特斯拉、沃爾沃、奧迪、寶馬、賓士等傳統汽車都已經開始研發或測試旗下電動車的無線充電系統。全球通訊以及IT界的新貴們也將“觸角”伸向了電動車無線充電的新領域。而在無線充電的規劃和靜態還是動態充電的選擇上,國內外車企則各有不同。  
一、沃爾沃:利用道路進行無線充電  

  在瑞典,沃爾沃集團、瑞典電力公司 Alstom、瑞典能源局正在共同合作測試利用公路給電動汽車充電,通過將兩個電源線鋪設在公路上,電動車經過時便可獲得電力供應。這項技術的核心在於汽車得搭載集電器,集電器與公路上的電纜連線,利用直流電充電。汽車不必走在電纜的中央,但必須時速大於 60 公里。    沃爾沃已在瑞典的 H llered 測試中心建立了一條 1/4 英里長的軌道,用一輛卡車進行測試。未來,當電動汽車需要充電時,必須安裝無線發射器讓道路感知,然後經過加密信號啟動充電功能。由於對速度有要求,沃爾沃的這一充電系統適合在高速路上實行,如果未來成真,人們出遠門的時候就不用擔心電力問題。   利用公路地表進行無線充電很可能是未來的發展方向,相比地面上的設施,它的好處是不需要佔用地面空間,可減少建設、維護成本。汽車不需要停下來進行充電,可持續駕駛。   沃爾沃的這一舉措是為了給電動汽車打造一個良好的充電網路。不過它需要面對很多普及的問題,比如公路建設、設計問題、集電器、電動汽車的支持等。就像無人駕駛一樣,這是一個浩大的工程,短期內還難以實現。  
二、高通:Halo的感應充電系統  

  在2015年4月22日的Formula E電動方程式錦標賽上,高通就展示了自己研發的Halo無線汽車充電技術。只要將車開到充電墊的正上方,當充電線圈對齊之後,電流便會開始輸送到汽車當中。如果汽車和墊子之間存在外來物體,系統還可自動暫停充電。   高通Halo的感應充電系統是個相對直接明瞭的構想:一個由兩個鐵氧體組成的變壓器,兩者的旁邊還各有一個電線線圈。一般來講,這兩個部分是連接在一起的。交流電會在第一個線圈中被轉換成磁場,隨後再被第二個線圈轉換成直流電。而高通Halo卻將兩個鐵氧體分離開來,並讓系統跨越空氣間隔實現最大功率傳輸。    Halo的無線充電器被放置在了車尾的位置,是一個比機上盒稍大一些的金屬盒子,並連接著幾條橙色的電線。至於另一半的充電器,就在汽車的下方。感應充電其實是可以作用於移動中的車輛的。Halo目前已經具備了半動態充電的能力,可在最高30mph的速度下進行電能傳輸。  
三、日產無線充電汽車   日產魔方電動車採用了可在供電線圈和受電線圈之間提供電力的電磁感應方式。即將一個受電線圈裝置安裝在汽車的底盤上,將另一個供電線圈裝置安裝在地面,當電動汽車駛到供電線圈裝置上,受電線圈即可接受到供電線圈的電流,從而對電池進行充電。目前,這套裝置的額定輸出功率為 10kW,一般的電動汽車可在7-8小時內完成充電。    日本無線充電式混合動力巴士:電磁感應式,供電線圈是埋入充電台的混凝土中的。車開上充電台後,當車載線圈對準供電線圈後(重合),車內的儀錶板上有一個指示燈會亮,司機按一下充電按鈕,就開始充電。   
三、中興:無線供電系統   中興通訊的無線供電系統是通過非接觸的電磁感應方式進行電力傳輸。當充電車輛在充電停車位停泊後,就能自動通過無線接入充電場的通信網路,建立起地面系統和車載系統的通信鏈路,並完成車輛鑒權和其他相關資訊交換。   充電位元也可以通過有線或者無線的方式和雲服務中心進行互聯。一旦出現充電和受電的任何隱患,地面充電模組將立即停止充電並報警,確保充電過程安全可靠。最重要的是,無線充電系統在車輛運行時完全不工作,即使車輛在上面駛過,或者在雷雨等惡劣天氣情況下,也能確保安全。  
四、比亞迪:WAVE無線充電墊   比亞迪早在2005年12月就申請了非接觸感應式充電器專利。在2014年7月賣給猶他大學一輛40英尺的純電動巴士,這款巴士就裝配著最新的WAVE無線充電墊。  
五、奧迪:可升降的無線充電系統   奧迪的可升降的無線充電系統最大的特點就是可讓供電線圈更靠近車輛底部的受電線圈,實現了超過90%的電力傳輸效率。這種方式能讓一些高底盤的SUV在充電時保證更好的充電效率。奧迪的無線充電技術僅需要使用者將停車位元元上安置一塊配置線圈和逆變器(AC/AC)充電板,並連接至電網,當車輛停在電板上時,充電過程會自動開啟。   這種充電的原理是充電板內的交變磁場將3.3千瓦的交變電流感應至集成在車內次級線圈的空氣層中,實現電網電流逆向並輸入到車輛的充電系統中。當電池組充滿電時,充電將自動中止。感應式無線充電所需的充電時間與電纜充電所需的充電時間大致相同,且用戶可以隨時中斷充電並使用車輛。   奧迪的無線充電技術效率超過90%,不受譬如雨雪或結冰等天氣因素的影響。同時,交變磁場只有當車輛在充電板上方時才會產生,且不對人體或動物構成傷害。未來利用感應線圈的充電原理,奧迪電動汽車不僅可以在駛入車位後自動開始充電,甚至可以在設有感應線圈的公路上,一邊行駛一邊充電。  
六、特斯拉   在19世紀90年代,尼古拉•特斯拉發明瞭“特斯拉線圈”,能夠通過空氣傳播電力,開啟了無線式電力傳播的時代。在“2011年國際消費電子展”上,美國安利公司旗下子公司富爾頓創新公司展示了無線充電技術,並推出了世界上第一輛無線充電的特斯拉汽車。目前,特斯拉希望能在各個大城市中建立起一張張相互連接的充電網,以解決電動車很容易出現的電力不足問題。   (圖片來源:EEPW)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

用Visual C++創建WPF項目的三種主要方法

用Visual C++創建WPF項目的三種主要方法

The problem with using XAML from C++

Because C++ doesn’t support partial class definitions, it isn’t possible to directly support XAML in VC++ projects using this mechanism. That isn’t, however, the core reason why VC++ doesn’t directly support XAML. In addition to using the x:Class attribute, you can also use the x:Subclass attribute so that the XAML gets compiled into the class specified by the x:Class attribute, and the code behind will define the class specified by x:Subclass, which will be derived from the x:Class type. Thus, the lack of partial classes isn’t that big of a block. The main issue is that, right now, no 100-percent CodeDOM support is available to convert

the XAML to C++, and that is the single biggest reason why VC++ doesn’t support XAML intrinsically. I don’t know this for sure, but it’s possible that on a later date, the Visual C++ team may work on their CodeDOM support and provide a fully functional XAML-to-C++ converter. Once that’s available, XAML support can be integrated into VC++ projects. As of today, however, that isn’t an option.

NOTE: CodeDOM is a term used to represent a bunch of types available in the System.

CodeDom namespace that lets you abstract code into an object model. Source code is represented using the CodeDOM tree and can be converted into source code for a specific language using the CodeDOM code generator for that specific language.

Still, the fact that you can’t directly use XAML in a Visual C++ project doesn’t mean that WPF applications can’t be written with Visual C++.

Three ways to write WPF apps using VC++

You can use three different approaches to write WPF applications using Visual C++. Each has its pros and cons, and we’ll cover each of these approaches in the next section:

  • Use procedural code.

For one thing, you can directly use procedural code to

write Avalon-based applications and avoid using XAML. Of course, if you

do that, you automatically give up the advantages of declarative programming

that XAML brings in, but for certain scenarios, procedural code often

serves the purpose well.

  • Dynamically load XAML.

Alternatively, you can dynamically load XAML during runtime to create your Avalon windows, although the disadvantage is that you’d be distributing a bunch of XAML files with your application.

  • Derive from a class in a C# DLL

A third technique uses a C# project to create your XAML-based Avalon controls and have a class (or classes) in your C++ project that derives from the classes in the C#-based Avalon DLL. With that mechanism, the UI is created using XAML in the C# project, and the business logic is kept in the C++ project.

When you’re developing WPF applications with C++, you can use one or more of these approaches to achieve whatever functionality you want. In the next section, you’ll see how to write a simple WPF app with C++/CLI using each of the three techniques mentioned here.

7.2 Using C++/CLI to write a WPF application

If Visual C++ doesn’t have support for XAML, and there are no project templates for building an Avalon application (as of the June 2006 CTP), how much extra effort does it take to write Avalon applications using C++? In this section, you’ll find out. You’ll put the three different techniques I described at the end of section

7.1.2 into action. All three mechanisms have their advantages and disadvantages; you can decide which is most suitable for your specific scenario. First, though, let’s briefly go over how to create a new C++/CLI project for Avalon.

7.2.1 Creating a new C++/CLI Avalon project

Avalon is a managed framework, and as such any Visual C++ project that needs to access and use Avalon needs to have the /clr compilation mode turned on.

Creating a new C++/CLI project with support for Avalon is fortunately not a difficult task. Table 7.1 lists the few simple steps you need to follow each time you create an application (or library, as the case might be) that uses Avalon.

Table 7.1 Steps to create a C++/CLI Avalon project

Step Action How To
1 Generate a new project Using the application wizard, specify the CLR Empty Project template.
2 Set the SubSystem to /SUBSYSTEM:WINDOWS Apply this change in the Project properties, Linker settings, System sub-setting.
3 Set the Entry Point to main From Project properties, choose Linker settings and then the Advanced sub-setting.
4 Add references to the following assemblies: System PresentationCore PresentationFramework WindowsBase Note: Except for System, the other three are required for Avalon.

At this point, your empty project is ready for writing Avalon code. Of course, you don’t have any code yet to compile, but you’ll fix that soon.

7.2.2 Using procedural code

You’ll now write your first Avalon application using C++/CLI, and you’ll do so entirely using procedural code. Think of it as analogous to an instruction book for putting together a table that contains only textual instructions (analogous to the procedural code) and no pictures (analogous to the XAML).

Create a new CLR project using the steps outlined in the previous section, and add an App.cpp file to it (you can call it whatever you want). Listing 7.2 shows the code for the simplest Avalon application that shows a window onscreen.

Listing 7.2 A simple Avalon app in procedural code

If you compile and run the application, you’ll see a window onscreen that can be moved, resized, minimized, maximized, and closed. Avalon requires you to set the COM threading model to single threaded apartment (STA). You do so using the STAThread attribute on the main function . You then create a new instance of the Application object (using gcnew) and invoke the Run method on that instance, passing in a new instance of a Window object (again using gcnew) . The Application class represents an Avalon application and provides the core functionality for running the application. It has a Run method that is called to initiate the application’s main thread. The Run method has an overload that accepts a Window object, which you use in the code. This overload launches the application and uses the specified Window as the main application window. The Window class represents the core functionality of a window and by default provides you with basic windowing functionality such as moving, resizing, and so on, which you verified when you ran the application and saw a fully functional window onscreen.

Note: Those of you who have an MFC background may see a faint similarity between this model and MFC, where the CWinApp class is analogous to the Application class, and the CFrameWnd class is analogous to the Window

class. CWinApp has a Run method that provides the default message loop, and Application::Run does something similar. Of course, you shouldn’t infer too much from these minor similarities because they’re totally different UI programming models, but it’s possible that a similar design model was used by the architects of Avalon.

This little program doesn’t have a lot of functionality; it just uses the default Window object to create and show a window onscreen. Let’s write a more refined application with its own Application-derived object as well as a window with some controls. Figure 7.4 shows a screenshot of what the enhanced application

will look like.

The main steps involved would be to derive two classes-one from the Window class, and the other from the Application class. You’ll start with the Window-derived class.

Figure 7.4

Enhanced WPF app in C++ (procedural code)

Writing the Window-derived class

The first thing you’ll do is add a new class called FirstWindow to your project, which will be derived from the Window class. You’ll also add some member variables for the various controls and set some of the window properties in the constructor. Listing 7.3 shows the code once you’ve done that.

Listing 7.3 A more functional Avalon app in procedural code

using namespace System;

using namespace System::Windows;

using namespace System::Windows::Controls;

It’s much like Windows Forms programming, except that the controls you declare ①. are from the System::Windows::Controls namespace (which contains various WPF controls). You set properties like Title, Width, Height, and so on on the window object in the constructor ②. There’s also a call to a method called InitControls ③, where you initialize the child controls (I put it into a separate method to improve the code’s readability). Listing 7.4 shows the InitControls method. Basically, you instantiate each of the child controls, instantiate a container control, add the child controls to the container controls, and finally set the container control as the main Content of the parent window.

Listing 7.4 Function to initialize the Avalon controls

void InitControls(void)

{
      listbox = gcnew ListBox();
      listbox->Width = 180;

      listbox->Height = 350;

      Canvas::SetTop(listbox, 10);

      Canvas::SetLeft(listbox, 10);

      textbox = gcnew TextBox();

      textbox->Width = 180;

      textbox->Height = 25;
    
      Canvas::SetTop(textbox, 10);

      Canvas::SetLeft(textbox, 200);

      addbutton = gcnew Button();

      addbutton->Width = 80;

      addbutton->Height = 25;

      addbutton->Content = "Add";

      Canvas::SetTop(addbutton, 45);

      Canvas::SetLeft(addbutton, 200);

      addbutton->Click += gcnew RoutedEventHandler(this, &FirstWindow::OnAddButtonClick);

      maincanvas = gcnew Canvas();

      maincanvas->Children->Add(listbox);

      maincanvas->Children->Add(textbox);

      maincanvas->Children->Add(addbutton);

      Content = maincanvas;
}

Again, you probably notice the similarity with Windows Forms programming.

You instantiate the child controls ①, ②, and ③, and set various properties like Width and Height, and you also use the Canvas::SetTop and Canvas::SetLeft methods to position them on their container. For the button control, you also add an event handler for the Click event ④. Then, you instantiate the Canvas control (which is a container control for other child controls) and add the child controls as its children ⑤. Finally, you set the Content property of the window to this Canvas control ⑥.

Now, you need to add the Click event handler for the button control, where you add the text entered into the TextBox to the ListBox:

void OnAddButtonClick(Object^ sender, RoutedEventArgs^ e)
{
​	listbox->Items->Add(textbox->Text);
​	textbox->Text = "";
​	textbox->Focus();
}

Notice that you set the text of the TextBox to an empty string once you’ve added it to the ListBox. You also call the Focus() method so that the user can continue

adding more entries into the ListBox. The Window-derived class is ready. Let’s now write the Application-derived class.

Writing the Application-derived class

You derive a class called FirstApp from Application and add an override for the OnStartup method where you create and show the main window:

#include "FirstWindow.h"

ref class FirstApp : Application
{
public:
FirstApp(void){}

protected:
      virtual void OnStartup(StartupEventArgs^ e) override
      {
          Application::OnStartup(e);
          FirstWindow^ mainwnd = gcnew FirstWindow();
          mainwnd->Show();
      }
};

The OnStartup method is called, not surprisingly, when the application has just started. You override that function so that you can instantiate and show the window.

The base function is responsible for invoking any event handlers associated with the Startup event, and thus you need to call the base method in the override.

Now, all that’s left is to modify the main function to use the custom Application object instead of the default, as shown here:

#include "FirstApp.h"

[STAThread]

int main(array<String^>^ args)
{
	return (gcnew FirstApp())->Run();
}

Notice that you don’t specify a window object to the Run method, because the window object is created in the OnStartup override of your Application-derived class.

Compile and run the application, and try entering some text into the TextBox and clicking the Add button. You should see the text being entered into the ListBox.

When you use procedural code with Avalon, it’s much like using Windows Forms, where you derive classes from the default controls, set some properties, add some event handlers, and are done. Procedural code is all right to develop WPF applications for simple user interfaces, but sometimes it makes better sense

to take advantage of XAML and declarative programming. As I’ve mentioned a few times already, XAML isn’t directly supported in VC++, so you’ll have to look at alternate options to make use of XAML. One such option is to dynamically load the XAML at runtime.

7.2.3 Dynamically loading XAML

In this section, you’ll rewrite the application you wrote in the previous section, using dynamically loaded XAML. This way, you get to leverage the power of XAML and declarative programming (which you couldn’t in the procedural code technique you used in the previous section). Continuing the instruction-book analogy, this will be like one that has textual instructions that refer to pictures (which describe the various steps needed) and are loosely distributed along with the book but not directly printed in the book. You’ll define the UI using XAML instead of procedural code. When you’re done, you’ll have an identical application

to the one you previously created.

Create a new C++/CLI Avalon project using the steps mentioned in the introduction to section 7.2, and call it FirstAvalonDynamic (or whatever you want to call it). The first thing you’ll do is write the XAML (MainWindow.xaml) that represents the UI; see listing 7.5.

Listing 7.5 XAML for the main window

<Window

     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

     Title="First Avalon App (dynamically load XAML)"

     Height="400" Width="400"

     ResizeMode="NoResize"

     > 

     <Canvas>
           <ListBox Canvas.Left="10" Canvas.Top="10"
                 Width="180" Height="350"
                 Name="listbox" />
               <TextBox Canvas.Left="200" Canvas.Top="10"
                 Width="180" Height="25"
                 Name="textbox" />
           <Button Canvas.Left="200" Canvas.Top="45"
                 Width="80" Height="25"
                 Name="addbutton">Add</Button>
     </Canvas>
</Window>

The XAML shown does exactly what you did with the procedural code earlier. For the control elements, you use the same names using the Name attribute as you use for the member variables in the procedural code. Next, you need to hook an event handler to the Button so that the text entered into the TextBox is inserted

into the ListBox. For that, you’ll write a helper class, as shown in listing 7.6.

Listing 7.6 WindowHelper class that implements the event handler

using namespace System;

using namespace System::Windows;

using namespace System::Windows::Controls;

using namespace System::Windows::Markup;

using namespace System::IO;

ref class WindowHelper
{

     ListBox^ listbox;

     TextBox^ textbox;

     Button^ addbutton;



public:
WindowHelper(Window^ window)
{
   addbutton = (Button^)window->FindName("addbutton");

   textbox = (TextBox^)window->FindName("textbox");

   listbox = (ListBox^)window->FindName("listbox");

   addbutton->Click += gcnew RoutedEventHandler(

   this,&WindowHelper::OnAddButtonClick);
}

void OnAddButtonClick(Object^ sender, RoutedEventArgs^ e)
{
   listbox->Items->Add(textbox->Text);

   textbox->Text = "";

   textbox->Focus();
}

};

The WindowHelper constructor accepts a Window argument and uses the FindName method ① to get the control with the specified identifier (which maps to the Name attributes you used in the XAML). You also hook an event handler to the addbutton control ②. Finally, you have the event handler③, which is identical to the one you used in the procedural code project. Listing 7.7 shows the code for the Application-derived class, where you override OnStartup as before, except that you create a window dynamically by loading the XAML file from the disk.

Listing 7.7 The Application-derived class

ref class FirstAppDynamic : Application

{

public:

     FirstAppDynamic(void)
     {

     }

protected:
     virtual void OnStartup(StartupEventArgs^ e) override
     {

           Application::OnStartup(e);

           Stream^ st = File::OpenRead("MainWindow.xaml");

           Window^ mainwnd = (Window^)XamlReader::Load(st);

           st->Close();

           WindowHelper^ mainwndhelper = gcnew WindowHelper(mainwnd);

           mainwnd->Show();

     }

};

You open a file stream to the XAML using File::OpenRead ① and use the overload of XamlReader::Load ② that takes a Stream^ as parameter to create a Window object. This Load method works the magic, by reading and parsing the XAML and building a Window object out of it. You instantiate the WindowHelper object and pass

this Window object as the argument, so that the event handler for the addbutton control is properly set up ③. You then show the window ④ with a call to Show().

The main method is much the same as before, where you instantiate the Application object and call Run on it:

[STAThread]
int main(array<String^>^ args)
{

      return (gcnew FirstAppDynamic())->Run();

}

The advantage of using this technique over using procedural code is that you get to design your UI in XAML, thereby achieving a level of UI/code separation. You can also use Cider or some other XAML designer to quickly design flexible user interfaces, which would involve a good bit of hand-coding in procedural code.

The disadvantage is that you have to distribute the XAML file with your application, and if you have multiple windows, you then need that many XAML files.

There’s always the risk of a loosely-distributed XAML file getting corrupted (accidentally or otherwise) or even being deleted. You can embed all the XAML files as resources in the C++/CLI assembly and load them at runtime, but even that involves a lot of extra work. To avoid distributing XAML files loosely with your

application or embedding them as resources, you may want to use the technique we’ll discuss in the next section: putting the XAML into a C# project and accessing it via a derived class in a C++ project.

7.2.4 Deriving from a class in a C# DLL

You’ll write a third variation of the same application in this section. You’ll use a C# control library project for the XAML, and a C++ project that will utilize that XAML control by deriving a control from it. Using the instruction-book analogy again, this is essentially a picture-based, step-by-step guide with the textual

instructions printed alongside each picture providing some meta-information for the step indicated by that picture. First, use the New Project Wizard to generate a new C# .NET 3.0 Custom Control Library project, and delete the default XAML file generated by the wizard. The default XAML is derived from User-

Control and isn’t what you want. Add a new XAML file to the C# project that represents a Window, and either use Cider or hand-code the XAML from listing 7.8 into that file.

Listing 7.8 The Window class definition using XAML

<Window x:Class="CSXamlLibrary.BaseWindow"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="First Avalon App (dynamically load XAML)"
     Height="400" Width="400"
     ResizeMode="NoResize"
     > 

     <Canvas>
           <ListBox Canvas.Left="10" Canvas.Top="10"
                 Width="180" Height="350"
                 Name="listbox" x:FieldModifier="protected" />

           <TextBox Canvas.Left="200" Canvas.Top="10"
                 Width="180" Height="25"
                 Name="textbox" x:FieldModifier="protected" />

           <Button Canvas.Left="200" Canvas.Top="45"
                 Width="80" Height="25"
                 Name="addbutton" x:FieldModifier="protected">Add</Button>
     </Canvas>

</Window>

The XAML is identical to that used in the previous project (where you dynamically loaded it) except for the x:Class attribute for the Window element, which specifies the name of the class that will be generated, and the x:FieldModifier attributes that are applied to the child control elements so they’re generated as protected members in the class (rather than as private which is the default). Build the C# project, and generate the control library. Once that’s done, create a new C++/CLI Avalon project (using the same steps as before), and then add a reference to this C# project. Now, you can write a new Window class that’s derived from the class in the C# DLL, as shown in listing 7.9.

Listing 7.9 Deriving the main window from the XAML-defined Window class

using namespace System;

using namespace System::Windows;

using namespace System::Windows::Controls;



ref class AppMainWindow : CSXamlLibrary::BaseWindow
{
     public:
           AppMainWindow(void)
           {
                 addbutton->Click += gcnew RoutedEventHandler(this, &AppMainWindow::OnAddButtonClick);
           }

           void OnAddButtonClick(Object^ sender, RoutedEventArgs^ e)
           {

                 listbox->Items->Add(textbox->Text);

                 textbox->Text = "";

                 textbox->Focus();

           }

};

The code is similar to what you’ve seen thus far, except that it’s a lot cleaner.

Unlike the first example, you don’t have a lot of clogged procedural code to create the UI. Unlike the second example, you don’t need a helper class to map the XAML elements to the control variables and event handlers. It’s definitely an improvement over the previous two examples, but you have to bring in the C#

project just for the XAML. The rest of the code needed for the application is more or less similar to what you saw earlier:

ref class FirstAppDerived : Application
{
      protected:
            virtual void OnStartup(StartupEventArgs^ e) override
            {
                  Application::OnStartup(e);
                  AppMainWindow^ mainwnd = gcnew AppMainWindow();
                  mainwnd->Show();
            }

};

[STAThread]

int main(array<String^>^ args)
{
      return (gcnew FirstAppDerived())->Run();
}

In some ways, the third technique is a sort of hybrid of the previous two techniques.

A lot of the code is identical to that in the first technique – as with the declaration of a custom class derived from Window and an Application-derived class with the OnStartup method creating the custom window. But, like the second technique, the UI definition is in the XAML, except that in this case, it’s compiled into the C# DLL. You also reduce lines of code with each successive technique. You had the most lines of code with procedural code (as is to be expected) and improved on that considerably when you moved the UI definition to the XAML in the dynamically-loaded XAML example. In the last example, you saved even further on lines of code, such as the helper class from the second example that had to wire the XAML elements to the member variables. Of course, the total lines of code (LOC) isn’t always the single deciding factor that determines what technique you choose. Table 7.2 shows a comparison of the three techniques; for each factor, the cells with the bold text reflect the technique (or techniques) that offer maximum performance (or convenience).

Table 7.2 Comparison of the three techniques

Procedural code Dynamically load XAML XAML in C# DLL
Cluttered code that generates the UI Yes No No
Dependency on loose XAML files No Yes No
Dependency on C#-based DLL No No Yes
Lines of code Maximum In-between Minimum
UI design convenience Poor Excellent Excellent
UI/business logic separation Poor Good Excellent
Level of Visual C++ project support Total Partial (Not applicable)

It’s hard to pinpoint a specific technique and claim that it’s the best one, because depending on your requirements, each has advantages and disadvantages. Of course, in the future, if Visual C++ has direct support for XAML (as I believe it will), that will be your best option for the majority of scenarios.

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

前端進階筆記之核心基礎知識—那些HTML標籤你熟悉嗎?

目錄

  • 1、交互實現
    • 1.1 meta標籤:自動刷新/跳轉
    • 1.2 title標籤:消息提醒
  • 2、性能優化
    • 2.1 script標籤:調整加載順序提升渲染速度
    • 2.2 link標籤:通過預處理提升渲染速度
  • 3、搜索優化
    • 3.1 meta標籤:提取關鍵信息
    • 3.2 link標籤:減少重複
    • 3.3 延伸內容:OGP(開放圖表協議)
  • 總結

提到HTML標籤,我們會非常熟悉,開發中經常使用。但我們往往關注更多的是頁面渲染效果及交互邏輯,也就是對用戶可見可操作的部分,比如表單、菜單欄、列表、圖文等。其實還有一些非常重要卻容易忽視的標籤,這些標籤大多數用在頁面頭部head標籤內,雖然對用戶不可見,但如果在某些場景下,比如交互實現、性能優化、搜索優化,合理利用它們可以讓我們在開發中達到事半功倍的效果。

1、交互實現

在實現一個功能的時候,我們編寫的代碼越多,不僅開發成本越高,而且代碼的健壯性也越差。因此我們在開發中提倡編碼簡約原則:Less code, less bug

1.1 meta標籤:自動刷新/跳轉

meta標籤妙用場景一:假如每隔一分鐘就需要刷新頁面,這個時候就可以用到meta標籤:

<meta http-equiv="Refresh" content="60">

meta標籤妙用場景二:假如想讓某個頁面在對用戶展示一段時間后,然後跳轉到其他頁面去,也可用到meta標籤:

<meta http-equiv="Refresh" content="5; URL=page2.html">

上面這行代碼的意思是當前頁面展示5s之後,跳轉到page2.html頁面去。

1.2 title標籤:消息提醒

B/S架構有很多優點,比如版本更新方便、跨平台、跨終端,但在處理某些場景時,比如即時通信時,會變得有點麻煩。

因為前後端通信深度依賴HTTP協議,而HTTP協議採用“請求-響應”模式,這就決定了服務端也只能被動地發送數據。一種低效的解決方案是客戶端通過輪詢機制獲取最新消息(HTML5下可使用WebSocket協議)。

另外在HTML5標準發布之前,瀏覽器沒有開放圖標閃爍、彈出系統消息之類的接口,因此消息提醒功能實現比較困難。但是我們可以通過修改title標籤來達到類似的效果(HTML5下可使用Web Notifications API彈出系統消息)。

下面這段代碼,通過定時修改title標籤內容,模擬了類似消息提醒的閃爍效果:

let msgNum = 1 // 消息條數
let cnt = 0 // 計數器
const inerval = setInterval(() => {
  cnt = (cnt + 1) % 2
  if(msgNum===0) {
    // 通過DOM修改title
    document.title += `聊天頁面`
    clearInterval(interval)
    return
  }
  const prefix = cnt % 2 ? `新消息(${msgNum})` : ''
  document.title = `${prefix}聊天頁面`
}, 1000)

實現效果如下圖所示,可以看到title標籤名稱上有提示文字在閃爍。

通過模擬消息閃爍,可以讓用戶在瀏覽其他頁面的時候,及時得知服務端返回的消息。

通過定時修改title標籤內容,除了用來實現閃爍效果之外,還可以製作其他動畫效果,比如文字滾動,但需要注意瀏覽器會對title標籤文本進行去空格操作;還可以將一些關鍵信息显示到標籤上(比如下載時的進度、當前操作步驟),從而提升用戶體驗。

2、性能優化

性能優化是前端開發中避不開的問題,性能問題無外乎兩方面原因:渲染速度慢請求時間長。性能優化雖然涉及很多複雜的原因和解決方案,但其實只要通過合理地使用標籤,就可以在一定程度上提升渲染速度,以及減少請求時間。

2.1 script標籤:調整加載順序提升渲染速度

由於瀏覽器的底層運行機制,一般情況下,渲染引擎在解析HTML時從上往下執行,若遇到script標籤引用文件,則會暫停解析過程,同時通知網絡線程加載引用文件。
文件加載完成后,再切換至JavaScript引擎來執行對應代碼,代碼執行完成之後,再切換至渲染引擎繼續渲染頁面。
即默認情況下,加載HTML的過程主要有四個步驟:

  • 從上往下解析HTML;
  • 碰到script標籤引用文件,暫停解析,同時通知網絡線程加載引用文件;
  • 文件加載完成,切換至JavaScript引擎來執行對應代碼;
  • 代碼執行完成后,再切換至渲染頁面,繼續渲染HTML。

從這一過程可以看出,頁面渲染過程包含了請求文件以及執行文件的時間,但頁面的首次渲染可能並不依賴這些文件。這些請求和執行文件的動作反而延長了用戶看到頁面的時間,從而降低了用戶體驗。

為了減少這些時間損耗,可以藉助script標籤的三個屬性來實現:

  • async屬性:立即請求文件,但不阻塞渲染引擎,而是文件加載完成后,再阻塞渲染引擎並立即執行文件內容。
  • defer屬性:立即請求文件,但不阻塞渲染引擎,等到解析完HTML之後再執行文件內容。
  • HTML5標準type屬性,對應值為“module”:讓瀏覽器按照ECMA Script6標準將文件當作模塊進行解析,默認阻塞效果同defer,也可以配合async在請求完成后立即執行。

通過對比,我們看出,設置defer和type=”module”最推薦,都是在HTML渲染完成后才執行script引用的文件代碼。
效果圖比較見下面:

另外注意,當渲染引擎解析HTML遇到script標籤引入文件時,會立即進行一次渲染。

所以這也就是為什麼構建工具會把編譯好的引用JavaScript代碼的script標籤放入到body標籤底部。因為當渲染引擎執行到body底部時,會先將已解析的內容渲染出來,然後再去請求相應的JavaScript文件。

如果是內聯腳本(即不通過src屬性引用外部腳本文件直接在HTML中編寫JavaScript代碼的形式),渲染引擎則不會渲染,先執行腳本代碼再渲染頁面。

我們可以來做個試驗驗證下,第一個測試:在HTML頁面中間引用外部js文件

<!DOCTYPE html>
<html lang="en">
    <head><meta charset="UTF-8"><title>引用js腳本</title></head>
    <body>
        <br/><br/><br/><br/><br/>
        <h3>古人學問無遺力,少壯工夫老始成;</h3>
        <script type="text/javascript" src="./test.js"></script>
        <h3>紙上得來終覺淺,絕知此事要躬行。</h3>
    </body>
</html>

引用外部js腳本test.js:alert('男兒何不帶吳鈎,收取關山五十州');
效果圖:

第二個測試:在HTML頁面中間內聯js腳本

<!DOCTYPE html>
<html lang="en">
    <head><meta charset="UTF-8"><title>內聯js腳本</title></head>
    <body>
        <br/><br/><br/><br/><br/>
        <h3>古人學問無遺力,少壯工夫老始成;</h3>
        <script type="text/javascript">
            alert('男兒何不帶吳鈎,收取關山五十州');
        </script>
        <h3>紙上得來終覺淺,絕知此事要躬行。</h3>
    </body>
</html>

效果圖:

2.2 link標籤:通過預處理提升渲染速度

在大型單頁應用進行性能優化時,也許會用到按需賴加載的方式來加載對應的模塊。但是如果能合理利用link標籤的rel屬性值來進行預加載,就能進一步提升渲染速度。

  • dns-prefetch:當link標籤的rel屬性值為“dns-prefetch”時,瀏覽器會對某個域名預先進行dsn解析並緩存。這樣,當瀏覽器在請求同域名資源的時候,能省去從域名查詢IP的過程,從而減少時間損耗。下圖是淘寶網設置的dns預解析。
  • preconnect:讓瀏覽器在一個HTTP請求正式發給服務器前預先執行一些操作,這包括dns解析、TLS協商、TCP握手,通過消除往返延遲來為用戶節省時間。
  • prefetch/preload:兩個值都是讓瀏覽器預先下載並緩存某個資源,但不同的是,prefetch可能會在瀏覽器忙時被忽略,而preload則是一定會被預先下載。
  • prerender:瀏覽器不僅會加載資源,還會解析執行頁面,進行預渲染。

這幾個屬性值恰好反映了瀏覽器獲取文件的過程,它們獲取文件的流程:

  1. 設置dns-prefetch, 然後判斷是否有對dns進行預解析。沒有則進行dns解析,有則執行下一步preconnect;
  2. 執行preconnect, 對ddns、TLS、TCP進行預連接,然後判斷是否已經TCP連接。沒有則進行TCP連接,有則執行下一步prefetch/preload;
  3. 執行prefetch/preload,加載資源文件。然後判斷資源文件是否已經預加載。沒有則進行http進行資源請求下載,有則進行下一步prerender;
  4. 執行prerender, 預渲染頁面。然後判斷預渲染是否成功。沒有預渲染成功則進行渲染,預渲染成功則呈現給用戶看。

流程圖如下:

3、搜索優化

我們寫的前端代碼,除了要讓瀏覽器更好的執行,有時候也要考慮更方便其他程序(如搜索引擎)理解。合理地使用meta標籤和link標籤,恰好能讓搜索引擎更好的理解和收錄我們的頁面。

3.1 meta標籤:提取關鍵信息

通過meta標籤可以設置頁面的描述信息,從而讓搜索引擎更好的展示搜索結果。
比如在百度中搜索“拉勾”,就會發現網站的描述,這些描述信息就是通過meta標籤專門為搜索引擎設置的,目的是方便用戶預覽搜索到的結果。
為了讓搜索引擎更好的識別頁面,除了描述信息之外還可以使用關鍵字,這樣即使頁面其他地方沒有包含搜索內容,也可以被搜索到(當然搜索引擎有自己的權重和算法,如果濫用關鍵字是會被降權的,比如Google引擎會對堆砌大量相同關鍵詞的網頁進行懲罰,降低它被搜索的權重)。

當我們搜索關鍵字“垂直互聯網招聘”的時候搜索結果會显示拉勾網的信息,雖然显示的搜索內容上並沒有看到“垂直互聯網招聘”字樣,實際上因為拉勾網頁面中設置了這個關鍵字。
對應代碼如下:

<meta content="拉勾,拉勾網,拉勾招聘,拉鈎, 拉鈎網 ,互聯網招聘,拉勾互聯網招聘, 移動互聯網招聘, 垂直互聯網招聘, 微信招聘, 微博招聘, 拉勾官網, 拉勾百科,跳槽, 高薪職位, 互聯網圈子, IT招聘, 職場招聘, 獵頭招聘,O2O招聘, LBS招聘, 社交招聘, 校園招聘, 校招,社會招聘,社招" name="keywords">

3.2 link標籤:減少重複

有時候為了用戶訪問方便或者出於歷史原因,對於同一個頁面會有多個網址,又或者在某些重定向頁面,比如:https://xx.com/a.html、 https://xx.com/detail?id=abcd,那麼在這些頁面中可以設置:<link href="https://xx.com/a.html" rel="canonical">這樣可以讓搜索引擎避免花費時間抓取重複網頁。不過需要注意的是,它還有個限制條件,那就是指向的網站不允許跨域。
當然,要合併網址還有其他的方式,比如使用站點地圖,或者在http請求響應頭部添加rel=”canonical”。

3.3 延伸內容:OGP(開放圖表協議)

前面說的是HTML5標準的一些標籤和屬性,下面再延伸說一說基於meta標籤擴展屬性值實現的第三方協議—OGP(open graph protocal, 開放圖表協議)。

OGP是Facebook公司在2010年提出的,目的是通過增加文檔信息來提升社交網頁在被分享時的預覽效果。你只需要在一些分享頁面中添加一些meta標籤及屬性,支持OGP協議的社交網站就會在解析頁面時生成豐富的預覽信息,比如站點名稱、網頁作者、預覽圖片。具體預覽效果會因各個網站而有所變化。

下面是微信文章支持OGP協議的代碼,可以看到通過meta標籤屬性值聲明了:標題、網址、預覽圖片、描述信息、站點名稱、網頁類型和作者信息。

總結

本篇從交互實現、性能優化、搜索優化場景觸發,分別講解了meta標籤、title標籤、link標籤,一級script標籤在這些場景中的重要作用,希望這些內容你都能應用到工作場景中,不再只是了解,而是能夠熟練運用。
最後在思考一下:你還知道哪些“看不見”的標籤及用法?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

“你開廠門 我開方便之門” ——福建省市場監管系統“促產”側記

  中國消費者報報道(記者 張文章)最近是企業復工復產的高峰期,然而復工復產、疫情防控兩手抓,兩手都要硬。復工復產面臨許多困難和擔憂:員工食堂就餐怎麼才安全?商事辦理如何才簡便?企業趕工,設備運作如何跟進?別擔心,福建省市場監管局早幫企業想好了,及時推出6方面16條服務措施,全力支持推動生產企業復產、重點項目復工。並呼籲相關企業大膽開門復產,把失去的時間搶回來,補回來。

  復工怎麼吃飯 築防線

  隨着復工復產高峰期臨近,集體用餐需求顯著增加,為深入做好新冠肺炎疫情防控工作,防範群體性聚餐可能引發的風險,福建省市場監管部門牽頭整合美團、餓了么等網絡訂餐平台和餐飲企業搭建疫情期間集體用餐配送服務平台,在保供應、保質量、強信心方面起到了积極作用。

  福州市台江區對復工企業食堂衛生狀況進行檢查,嚴查食材進貨來源,要求分時段分餐制,避免扎堆就餐。同時嚴格環境消殺,下發《預防性消毒工作手冊》,督促農貿市場、超市、餐飲店、非星級酒店嚴格落實清潔消毒制度,每日開市前、收市后全面消毒,加大對重點區域、重點設施設備消毒頻次。規範網絡外賣平台經營,推行無接觸配送。

  廈門市集美區杏林市場監管所“嚴把三關”,做好“小餐飲”復工安全。復工前,申請報備關。全面摸排掌握轄區“小餐飲”數據庫,通過電話、微信等,向2571家餐飲經營者詳細告知復工標準,督促餐飲單位在恢復營業前,提前向市場監管部門報備。復工時,現場檢查關。收到經營者報備后,執法人員按照網格劃分及時到現場檢查,對從人員健康狀況、體溫檢測、場所消殺、庫存食材清理等各方面進行詳細指導和嚴格把關。復工后,日常監督關。通過網格員每日現場巡查、微信宣傳、發動社區群眾舉報等方式,做好已復工“小餐飲”的日常監督。

  泉州市市場監管部門加強外賣訂餐平台監管,在疫情防控期間全面實行“無接觸送餐”;鼓勵入網餐飲單位使用“食安封簽”,避免送餐過程二次污染;指導公司選擇證照齊全、供餐資質和能力符合要求的單位訂餐,並設立專門接收台,實行“不見面取餐”;鼓勵企業發動交通方便的員工回家用餐,或自帶餐食解決用餐問題,進一步減少辦公場所用餐時段的人員密度和出入頻次。

  莆田市荔城區已形成市場、超市、餐飲、藥店常態化監管模式,持續提示餐飲服務單位轉變經營思路,採取打包、外賣、分餐配送等非堂食堂聚的經營模式,避免人群聚集傳播風險;有效督促市場開辦者及檔口經營者自律經營,嚴格落實禁止野生動物交易要求,建立健全索證索票、進貨查驗等制度,切實做好食品快檢及公示工作,做到“早發現、早溯源、早處置”,有效保障群眾“舌尖上的安全”。

  備案申請怎麼審批 特事特辦

  為全面有效防控疫情,全力保障人民群眾的生命安全和身體健康,切實提供便捷優質的企業註銷及商事登記服務,福建省市場監管部門為企業提供足不出戶的“網上辦“服務。

  福州市高新區市場監管部門開啟企業復工復產網上辦,利用釘釘App在線受理企業復工申請,開展不見面服務。對企業數據進行實時分析,對於符合高新區復工要求的予以備案。全面推行自主年報制度,通過微信、電話、短信等方式指導企業依法履行信息公示義務,激活更多企業投入生產經營,避免信用受損。

  漳州市長泰縣市場監管局為有效減少人員聚集,暫停窗口現場服務,全面推行“網上辦、掌上辦、郵寄辦、預約辦”審批服務模式,並公示了窗口諮詢電話。對於保障疫情防控工作需要的緊急業務,立足職能,特事特辦、急事急辦,着力打通抗‘疫’審批最後一公里。

  莆田市荔城區市場監管部門充分依託“網上辦、掌上辦、寄遞辦、預約辦”等有效手段,為市場主體提供“零見面、零跑腿、零成本”的高效優質服務,進一步壓減登記註冊環節、時間和成本。對生產企業轉產生產口罩、防護服等應急物資的,簡化生產資質審批程序,實行非要件容缺後補,對可通過數據共享獲取的材料,不要求申請人重複提交。

  三明市大田縣市場監管局加碼提速簡化審批程序,推行“非接觸式”的电子辦照渠道,實行企業名稱自由申報、企業設立網上辦,同時积極引導群眾通過“網上預審+雙向快遞”的方式寄送申請材料並領取證照和相關文書。深化個體工商戶登記制度改革,針對需線下辦證的特殊人群實現“15分鐘辦證圈”。

  設備安全怎麼保障 提供專業服務

  為全面保障疫情期間和節后復工復產企業的特種設備安全,進一步落實特種設備使用單位安全主體責任,福建省市場監管部門對特種設備安全進行了專項檢查。

  廈門市集美區市場監管局第一時間採取微信監管工作群轉發的宣傳形式,督促相關責任單位嚴格落實責任制。聚焦公眾聚集場所使用的特種設備和盛裝極度或高度危害介質、易燃易爆介質的承壓特種設備,採取科所聯動的監管模式,圍繞轄區復工復產企業、已開業的大型商場、樓座較多的居民小區、醫院、地鐵等主要場所加大巡查力度,重點查看在用電梯、鍋爐、液氧儲氣罐等特種設備的檢驗報告、維保記錄、使用單位的安全管理制度、事故應急措施和救援預案等情況。

  泉州市市場監管局組織省特檢院泉州分院和石化中心等兩個特種設備檢驗單位,充分利用特種設備安全監察平台,全面排查受疫情影響需要檢驗的特種設備,主動電話逐家聯繫各使用單位,及時了解復產復工安排,為即將復產復工企業開闢綠色通道,確保企業順利開工。

責任編輯:邊靜

本站聲明:網站內容來源再生能源資訊網http://www.ccn.com.cn/,如有侵權請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

一秒看完,2020 最新各縣市電動機車補助總整理

電動機車補助是所有想要購車的朋友最關心的議題,由於各縣市環保局的補助金額不同,常常看得一頭霧水,今年我們一樣整理了這張比較表讓你一眼就能看完,還可以幫你排序找出補助最多的縣市喔。

電動機車補助分為中央與地方兩部分,2020 年的中央補助來自工業局與環保署,不分縣市都能獲得汰舊換新最高 1 萬 2 千元補助,新購電動機車則是 7 千元。

比較麻煩的是,地方政府的補助配合施政方針而有所不同,我們幫各位全部整理在一張表內,這個金額包含了中央與地方政府的補助,讓大家可以快速看清楚,點擊欄位還能自動排序唷。

金門的補助金額從去年 4 月公佈後就從最後一名變成冠軍,今年依然延續高額補助,成為各縣市補助最給力的地方政府。

花蓮台東則是依靠花東基金的補助,而擁有不錯的額度,然而花東基金有名額限制,要獲得補助的朋友需要把握時間申請。

在今年度的補助中,還有一些縣市佛心提供了中低收入戶補助,我們另外整理出列表如下,趕快分享給符合資格的朋友看看吧。

以上是我們為各位整理 2020 年電動機車補助金額的資料,其中未包含交通部提供的 ABS 與 CBS 煞車系統補助(1,000 元),此外值得注意的是,各縣市對於新款七期燃油機車也都有提供相關補助,本表僅供參考,實際購車金額還是要與經銷商確認。

如果想要了解各縣市政府補助金額的細節資料,也可以參考環保署提供的,裡面還有聯絡方式可供確認唷。

(合作媒體:。首圖來源:攝)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?