JavaScript,我們一般簡(jiǎn)稱為 JS,是一種具有函數(shù)優(yōu)先的輕量級(jí),解釋型或即時(shí)編譯型的編程語(yǔ)言。
JavaScript 現(xiàn)在已經(jīng)被用到了很多非瀏覽器環(huán)境中,JavaScript 基于原型編程、多范式的動(dòng)態(tài)腳本語(yǔ)言,并支持面向?qū)ο蟆⒚钍胶吐暶魇斤L(fēng)格。
HTML、CSS、JavaScript三者不同的功能:
JavaScript 是一種屬于網(wǎng)絡(luò)的腳本語(yǔ)言,已經(jīng)被廣泛用于 Web 應(yīng)用開發(fā),常用來(lái)為網(wǎng)頁(yè)添加各式各樣的動(dòng)態(tài)功能,為用戶提供更流暢美觀的瀏覽效果。通常 JavaScript 腳本是通過(guò)嵌入在 HTML 中來(lái)實(shí)現(xiàn)自身的功能的(就是寫在 HTML 頁(yè)面中的 <script> 標(biāo)簽中)。
JavaScript 的基本特點(diǎn)如下所示:
JavaScript 可以做一些什么事情呢,我們下面簡(jiǎn)單的介紹一下,可能還沒學(xué)過(guò) JavaScript 的同學(xué)看不懂,不要急,后面我們都會(huì)詳細(xì)的介紹。
<script type="text/javascript">
document.write("<h1>俠課島</h1>");
document.write("<p>俠課島歡迎你!</p>");
</script>
<button type="button" onclick="alert('俠課島')"> 點(diǎn)擊彈出 </button>
<body>
<p id="content">原來(lái)的內(nèi)容</p>
<button type="button" onclick="change()">點(diǎn)擊修改</button>
</body>
<script type="text/javascript">
function change(){
document.getElementById('content').innerHTML='新的內(nèi)容'
}
</script>
<body>
<button onclick="changeImage()">修改圖片</button>
<div>
<img id="myImage" src="./img/part1-01.jpg">
</div>
</body>
<script type="text/javascript">
function changeImage(){
element=document.getElementById('myImage');
if(element.src.match("02")){
element.src="./img/part1-01.jpg";
}else{
element.src="./img/part1-02.jpg";
}
}
</script>
<p id="style">JavaScript改變HTML樣式</p>
//點(diǎn)擊之后呈現(xiàn)的效果是:顏色變綠
<button type="button" onclick="document.getElementById('style').style.color='green'">點(diǎn)擊</button>
if isNaN(x) {alert("不是數(shù)字")};
本節(jié)我們基本講了一下JavaScript是什么,它能做什么,下一節(jié)我們講JavaScript最基本的語(yǔ)法,學(xué)習(xí)一門語(yǔ)言,我們都要先弄清楚這門語(yǔ)言的基本語(yǔ)言,才好開始編寫代碼。
.為何學(xué)習(xí) JavaScript?
?JavaScript 是 web 開發(fā)者必學(xué)的三種語(yǔ)言之一:
HTML 定義網(wǎng)頁(yè)的內(nèi)容----[制作網(wǎng)頁(yè)]----簡(jiǎn)單枯燥
CSS 規(guī)定網(wǎng)頁(yè)的布局和樣式—[美化網(wǎng)頁(yè)]
HTML+CSS—網(wǎng)頁(yè)—沒有動(dòng)態(tài)的網(wǎng)頁(yè)【1.無(wú)動(dòng)態(tài)效果 2.數(shù)據(jù)沒有動(dòng)態(tài)化】
JavaScript 對(duì)網(wǎng)頁(yè)行為進(jìn)行編程【動(dòng)態(tài)效果,數(shù)據(jù)動(dòng)態(tài)加載】
2. 什么是JavaScript?
?? 1.javascript和java是完全不同的語(yǔ)言,不論是概念還是設(shè)計(jì)。
?? 網(wǎng)景----瀏覽器【Netscape Navigator】----LiveScript-----JavaScript
?? 微軟----瀏覽器[IE]----JScript
…等等
?? ECMA—?dú)W洲計(jì)算機(jī)廠家協(xié)會(huì)–ECMA-262標(biāo)準(zhǔn)—ECMAScript
?? 2.JavaScript面向?qū)ο蟮哪_本程序設(shè)計(jì)語(yǔ)言
?? 面向?qū)ο蟆幊趟枷?br>?? 腳本程序—依據(jù)一定的格式編寫的可執(zhí)行文件【直接運(yùn)行】
?? 3.JavaScript使用
??1.標(biāo)記的事件中使用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>1.標(biāo)記的事件中使用</h1>
<input type="button" value="測(cè)試Javascript的使用"
onclick="alert('歡迎使用Javascript')" />
</body>
</html>
2.script標(biāo)記中的使用
?? script–<script>javascript程序</script>
?? 位置–<head>標(biāo)記中/<body>標(biāo)記中【最后】
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
alert("歡迎使用JavaScript!");
</script>
</head>
<body>
<h1>script標(biāo)記的中使用</h1>
<h1>script標(biāo)記出現(xiàn)在head標(biāo)記中</h1>
<input type="button" value="測(cè)試javascript的使用" onclick="test1();" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>script標(biāo)記的使用</h1>
<h1>script標(biāo)記出現(xiàn)在body標(biāo)記中</h1>
<input type="button" value="測(cè)試JavaScript的使用" onclick="test1()" />
<script type="text/javascript">
function test1(){
alert("歡迎使用javascript!");
}
</script>
</body>
</html>
3.外部腳本—將JavaScript程序編寫到獨(dú)立的文件【.js】,通過(guò)<script>標(biāo)記提供的src屬性將獨(dú)立的文件【.js】,導(dǎo)入到html文件中。
?? 您可以在<head>或<body>中放置外部腳本引用。
?? 格式:<script src=“js 文件路徑”></script>
獨(dú)立的jsWENJIAN 【myscript1.js】
function test1(){
?alert("歡迎使用javascript!”);
}
.js文件
function test1(){
alert("歡迎使用JavaScript!")
}
html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="javascript.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<h1>script外部腳本使用</h1>
<input type="button" value="測(cè)試JavaScript的使用" onclick="test1();" />
</body>
</html>
3.JavaScript 輸出
JavaScript能夠以不同方式“顯示”數(shù)據(jù):
?? 1.使用window.alert()寫入警告框
?? 2.使用document.write()寫入HTML輸出
?? 3.使用innerHTML寫入HTML元素
?? 4.使用console.log()寫入瀏覽器控制臺(tái)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>1.使用window.alert()寫入警告框</h1>
<!--
<script type="text/javascript">
window.alert("測(cè)試window.alert()寫入警告框")
</script>
-->
<h1>2.使用document.write()寫入HTML輸出</h1>
<!--
<script type="text/javascript">
document.write("測(cè)試document.write()寫入HTML輸出")
</script>
-->
<h1>3.使用innerHTML寫入HTML元素</h1>
<p id="p1"></p>
<!--
<script type="text/javascript">
document.getElementById("p1").innerHTML="<font size='7'>測(cè)試innerHTML寫入HTML元素</font>"
</script>
-->
<h1>4.使用console.log()寫入瀏覽器控制臺(tái)</h1>
<script type="text/javascript">
console.log("console.log()寫入瀏覽器控制臺(tái)")
</script>
</body>
</html>
4.JavaScript 語(yǔ)句
JavaScript 語(yǔ)句由以下構(gòu)成:
?? 值、運(yùn)算符、表達(dá)式、關(guān)鍵詞和注釋。
?? document.getElementById(“demo”).innerHTML=“Hello Kitty.”;
“Hello Kitty.”----值
??=— 賦值運(yùn)算符
?? 關(guān)鍵詞–常常通過(guò)某個(gè)關(guān)鍵詞來(lái)標(biāo)識(shí)需要執(zhí)行的 JavaScript 動(dòng)作
注釋–解釋說(shuō)明代碼的含義/調(diào)試程序【注釋的內(nèi)容不會(huì)被執(zhí)行】
// 單行注釋 【保證一行代碼被注釋掉不會(huì)被執(zhí)行】
/*
多行注釋 【保證多行代碼被注釋掉不會(huì)被執(zhí)行】
// 可以包含單行注釋
*/
<script>
/*
//console.log("console.log() 寫入瀏覽器控制臺(tái)");
console.log("console.log() 寫入瀏覽器控制臺(tái)");
console.log("console.log() 寫入瀏覽器控制臺(tái)");
console.log("console.log() 寫入瀏覽器控制臺(tái)");
*/
</script>
分號(hào) ; 表示一條javascript語(yǔ)句的結(jié)束。
??可以將有分號(hào)的語(yǔ)句寫在同一行,通常我們都不這么寫,出錯(cuò)以后查找不方便,所以一行一句。
JavaScript代碼塊【{代碼塊}】
function myFunction(){
??document.getElementById(“demo”).innerHTML=“Hello Kitty.”;
??document.getElementById(“myDIV”).innerHTML=“How are you?”;
}
5.JavaScript 語(yǔ)法
JavaScript 值
JavaScript 語(yǔ)句定義兩種類型的值:混合值和變量值。
混合值被稱為字面量(literal)— 常量值
寫數(shù)值有無(wú)小數(shù)點(diǎn)均可:12.5 1001
寫字符串:”zhangsan”
變量值被稱為變量[程序運(yùn)行的最小單位]保存數(shù)據(jù)值。
需要通過(guò)var 關(guān)鍵詞 來(lái)定義變量
用法1 : var a; //定義變量
??a=1001; //變量賦值
用法2: var b=”hello”;
JavaScript 運(yùn)算符
JavaScript使用算是運(yùn)算符(+ - * /)來(lái)計(jì)算值。
JavaScript使用(=)賦值運(yùn)算符。
JavaScript使用(> <==>=<=!=)比較運(yùn)算符。
JavaScript使用(|| && !)邏輯運(yùn)算符。
…
JavaScript表達(dá)式
表達(dá)式是值、變量和運(yùn)算符的組合,計(jì)算結(jié)果是值。
100+123【算數(shù)表達(dá)式】
var b=“hello”; 【賦值表達(dá)式】
12>23 【關(guān)系表達(dá)式】
(12>23)&&(23<12) —【邏輯表達(dá)式】
…
JavaScript 標(biāo)識(shí)符
標(biāo)識(shí)符就是為JavaScript元素【函數(shù),變量,對(duì)象…】起名字的字符串。
1.可以由字母、下劃線(_)或美元符號(hào)($),數(shù)字組成,數(shù)字不能開頭。
2.不能用關(guān)鍵詞
3.區(qū)分大小寫
駝峰式命名規(guī)則—首字母大寫【FirstName, LastName, MasterCard, InterCity.】
JavaScript 程序員傾向于使用以小寫字母開頭的駝峰大小寫
6. JavaScript 變量
??JavaScript 變量是存儲(chǔ)數(shù)據(jù)值的容器。
??JavaScript 數(shù)據(jù)類型
??在編程中,文本值被稱為字符串。
??JavaScript 可處理多種數(shù)據(jù)類型,但是現(xiàn)在,我們只關(guān)注數(shù)值和字符串值。
??字符串被包圍在雙引號(hào)或單引號(hào)中。數(shù)值不用引號(hào)。
??如果把數(shù)值放在引號(hào)中,會(huì)被視作文本字符串。
??true/false 布爾型
??null null型
聲明(創(chuàng)建) JavaScript 變量
在 JavaScript 中創(chuàng)建變量被稱為“聲明”變量。
您可以通過(guò) var 關(guān)鍵詞來(lái)聲明 JavaScript 變量:
var carName;
聲明之后,變量是沒有值的。(技術(shù)上,它的值是 undefined。)我們?cè)谑褂玫淖兞康臅r(shí)候需要賦予初始值,否則被視為未定義。
通過(guò)”=”給變量賦值。
var carName;
carName=”BMW”;
var carName=”BMW”;
一條語(yǔ)句,多個(gè)變量
var person=“Bill Gates”, carName=“porsche”, price=15000;
7.JavaScript運(yùn)算符
JavaScript 使用(=)賦值運(yùn)算符。
JavaScript 使用算數(shù)運(yùn)算符(+ - * / % ++ --)來(lái)計(jì)算值。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>變量-運(yùn)算符-表達(dá)式</title>
<script>
function suanshu(){
//聲明變量
var num1=10;
var num2=3;
document.getElementById("h1").innerHTML=num1+"+"+num2+"="+(num1+num2);
document.getElementById("h2").innerHTML=num1+"-"+num2+"="+(num1-num2);
document.getElementById("h3").innerHTML=num1+"*"+num2+"="+(num1*num2);
document.getElementById("h4").innerHTML=num1+"/"+num2+"="+(num1/num2);
document.getElementById("h5").innerHTML=num1+"%"+num2+"="+(num1%num2);
// ++ [自動(dòng)加1]
// 變量++ [先用后加]
//num1++; //10
//document.getElementById("h6").innerHTML=num1; //11
// ++變量 [先加后用]
//++num1; //11
//document.getElementById("h6").innerHTML=num1; //11
// -- [自動(dòng)減1]
// 變量-- [先用后減]
//num1-- ; //10
//document.getElementById("h6").innerHTML=num1; //9
// -- 變量
--num1; // 9
document.getElementById("h6").innerHTML=num1; //9
}
</script>
</head>
<body>
<input type="button" value="測(cè)試算數(shù)運(yùn)算符" onclick="suanshu()"/><br>
<h1 id="h1"></h1>
<h1 id="h2"></h1>
<h1 id="h3"></h1>
<h1 id="h4"></h1>
<h1 id="h5"></h1>
<h1 id="h6"></h1>
</body>
</html>
JavaScript 使用(> <==>=<=!=)比較運(yùn)算符運(yùn)算符。
JavaScript 使用(|| && ! )邏輯運(yùn)算符運(yùn)算符。
為一名前端愛好者, 我利用空余時(shí)間研究了幾個(gè)國(guó)外網(wǎng)站的源碼,發(fā)現(xiàn)不管是庫(kù),還是業(yè)務(wù)代碼,都會(huì)用到了一些比較有意思的API,雖然平時(shí)在工作中部分接觸過(guò),但是經(jīng)過(guò)這次的研究,覺得很有必要總結(jié)一下,畢竟已經(jīng)2020年了,是時(shí)候更新一下技術(shù)儲(chǔ)備了,本文主要通過(guò)實(shí)際案例來(lái)帶大家快速了解以下幾個(gè)知識(shí)點(diǎn):
我會(huì)對(duì)部分API做一些比較有意思的案例,那么開始我們的學(xué)習(xí)吧~
Observer是瀏覽器自帶的觀察者,它主要提供了Intersection, Mutation, Resize, Performance這四類觀察者, 這里筆者重點(diǎn)介紹Intersection Observer.
IntersectionObserver提供了一種異步觀察目標(biāo)元素與其祖先元素交叉狀態(tài)的方法。當(dāng)一個(gè)IntersectionObserver對(duì)象被創(chuàng)建時(shí),其被配置為監(jiān)聽根中一段給定比例的可見區(qū)域,并且無(wú)法更改其配置,所以一個(gè)給定的觀察者對(duì)象只能用來(lái)監(jiān)聽可見區(qū)域的特定變化值;然而,我們可以在同一個(gè)觀察者對(duì)象中配置監(jiān)聽多個(gè)目標(biāo)元素。
說(shuō)簡(jiǎn)單點(diǎn)就是該api可以異步監(jiān)聽目標(biāo)元素在根元素里的位置變動(dòng),并觸發(fā)響應(yīng)事件.我們可以利用它來(lái)實(shí)現(xiàn)更為高效的圖片懶加載, 無(wú)限滾動(dòng)以及內(nèi)容埋點(diǎn)上報(bào)等.接下來(lái)我們通過(guò)一個(gè)例子來(lái)說(shuō)明一下它的使用步驟.
// 1.定義觀察者及觀察回調(diào)
const intersectionObserver=new IntersectionObserver((entries, observer)=> {
entries.forEach(entry=> {
console.log(entry)
// ...一些操作
});
},
{
root: document.querySelector('#root'),
rootMargin: '0px',
threshold: 0.5
}
)
// 2. 定義要觀察的目標(biāo)對(duì)象
const target=document.querySelector(“.target”);
intersectionObserver.observe(target);
以上代碼就實(shí)現(xiàn)了一個(gè)基本的Intersection Observer,雖然已有代碼中還體現(xiàn)不出什么實(shí)質(zhì)性功能. 接下來(lái)介紹一下代碼中使用到的參數(shù)的含義: * callback IntersectionObserver實(shí)例的第一個(gè)參數(shù), 當(dāng)目標(biāo)元素與根元素通過(guò)閾值 時(shí)就會(huì)觸發(fā)該回調(diào).回調(diào)中第一個(gè)參數(shù)是被觀察對(duì)象列表,一旦被觀察對(duì)象發(fā)生突變就會(huì)被移入該列表, 列表中每一項(xiàng)都保留有觀察者的位置信息;第二個(gè)參數(shù)為observer,觀察者本身.如下圖控制臺(tái)打印:
其中rootBounds表示根元素的位置信息, boundingClientRect表示目標(biāo)元素的位置信息,intersectionRect表示叉部分的位置信息, intersectionRatio表示目標(biāo)元素的可見比例.
當(dāng)我們?cè)O(shè)置rootMargin為10px時(shí),我們的root會(huì)增大影響范圍,但目標(biāo)元素移動(dòng)到淡紅色區(qū)域式就會(huì)被監(jiān)聽到,當(dāng)然我們還可以設(shè)置rootMargin為負(fù)值來(lái)減少影響區(qū)域.其支持的值為百分比和px,如下:
rootMargin: '10px'
rootMargin: '10%'
rootMargin: '10px 0px 10px 10px'
thresholds可以如下圖理解:
由上圖所示,當(dāng)我們?cè)O(shè)置閾值為[0.25, 0.5]時(shí), 目標(biāo)元素的25%和50%進(jìn)入根元素的影響范圍時(shí)都會(huì)觸發(fā)回調(diào).利用這個(gè)特性我們往往可以實(shí)現(xiàn)位差動(dòng)畫,或者更根據(jù)目標(biāo)元素的位置變化做不同的交互. 當(dāng)然Intersection還提供了以下幾個(gè)方法來(lái)控制觀察對(duì)象: disconnect() 使IntersectionObserver對(duì)象停止監(jiān)聽工作 takeRecords() 返回所有觀察目標(biāo)的IntersectionObserverEntry對(duì)象數(shù)組 * unobserve() 使IntersectionObserver停止監(jiān)聽特定目標(biāo)元素
了解了使用方法和api之后,我們來(lái)看看一個(gè)實(shí)際應(yīng)用--實(shí)現(xiàn)圖片懶加載:
<img src="loading.gif" data-src="absolute.jpg">
<img src="loading.gif" data-src="relative.jpg">
<img src="loading.gif" data-src="fixed.jpg">
<script>
let observerImg=new IntersectionObserver(
(entries, observer)=> {
entries.forEach(entry=> {
// 替換為正式的圖片
entry.target.src=entry.target.dataset.src;
// 停止監(jiān)聽
observer.unobserve(entry.target);
});
},
{
root: documennt.getElementById('scrollView'),
threshold: 0.3
}
);
document.querySelectorAll('img').forEach(img=> { observerImg.observe(img) });
</script>
以上代碼就實(shí)現(xiàn)了一個(gè)圖片懶加載功能, 當(dāng)圖片的30%進(jìn)入根元素時(shí)才加載真實(shí)的圖片,這又讓我想起了之前在某條做廣告埋點(diǎn)上報(bào)時(shí)使用react-lazyload的畫面.大家還可以利用它實(shí)現(xiàn)無(wú)限滾動(dòng), H5視差動(dòng)畫等有意思的交互場(chǎng)景.
Mutation Observer主要用來(lái)實(shí)現(xiàn)dom變動(dòng)時(shí)的監(jiān)聽,同樣也是異步觸發(fā),對(duì)監(jiān)聽性能非常友好. Resize Observer主要用來(lái)監(jiān)聽元素大小的變化,相比于每次窗口變動(dòng)都觸發(fā)的window.resize事件, Resize Observer有更好的性能和對(duì)dom有更細(xì)粒度的控制,它只會(huì)在繪制前或布局后觸發(fā)調(diào)用. 以上兩個(gè)api的使用和Intersection使用非常類似,官方資料也寫得很全,大家可以好好研究一下.
這個(gè)問(wèn)題主要是之前有朋友問(wèn)過(guò)我,當(dāng)時(shí)的想法就是簡(jiǎn)單的認(rèn)為script內(nèi)的代碼執(zhí)行完之后以及與dom綁定了,存放在了瀏覽器內(nèi)存中,最近查了很多資料發(fā)現(xiàn)有一個(gè)有點(diǎn)意思的解釋,放出來(lái)大家可以感受一下:
JavaScript解釋器在執(zhí)行腳本時(shí),是按塊來(lái)執(zhí)行的,也就是說(shuō)瀏覽器在解析HTML文檔流時(shí),如果遇到一個(gè)script標(biāo)簽,javascript解釋器會(huì)等待這個(gè)代碼塊都加載完了,才進(jìn)行預(yù)編譯,然后才執(zhí)行。所以,當(dāng)開始執(zhí)行這個(gè)代碼塊的代碼時(shí),這個(gè)代碼段已經(jīng)被解析完了。這時(shí)再?gòu)腄OM中刪去也就不影響代碼的執(zhí)行了。
Proxy/Reflect雖然是es6的api,出現(xiàn)也已經(jīng)有幾年了,但是在項(xiàng)目中用的還是比較少,如果是做底層架構(gòu)方面的工作,還是建議大家多去使用,畢竟vue/react這種框架源碼把這些api玩的如火純青,還是很有必要掌握一下的。
其實(shí)我們認(rèn)真看mdn的介紹或者阮一峰老師的文章,還是很好理解這些api的用法的,接下來(lái)我們?cè)敿?xì)介紹一下這兩個(gè)api以及應(yīng)用場(chǎng)景.
Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫。Proxy在很多場(chǎng)景中都會(huì)和Reflect一起使用. 用法也很簡(jiǎn)單,我們看看Proxy的基本用法:
const obj={
name: '徐小夕',
age: '120'
}
const proxy=new Proxy(obj, {
get(target, propKey, receiver) {
console.log('get:' + propKey)
return Reflect.get(target, propKey, receiver)
},
set(target, propKey, value, receiver) {
console.log('set:' + propKey)
return Reflect.set(target, propKey, value, receiver)
}
})
console.log(proxy.name) // get:name 徐小夕
proxy.work='frontend' // set:work frontend
以上代碼攔截了obj對(duì)象,并重新定義了讀寫(get/set)方法,這樣我們就可以在訪問(wèn)對(duì)象時(shí)進(jìn)行額外的操作了.
Proxy還有apply(攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作)和construct(攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作)等屬性可以使用,我們可以在對(duì)象操作的不同階段進(jìn)行攔截,這里我就不一一樣舉例了.接下來(lái)看看Proxy的實(shí)際應(yīng)用場(chǎng)景. * 實(shí)現(xiàn)數(shù)組讀取負(fù)數(shù)的索引
我們一般操作數(shù)組大多數(shù)都是正向操作的,不能通過(guò)指定負(fù)數(shù)來(lái)逆向查找數(shù)組,如下圖:
我們不能通過(guò)arr[-1]來(lái)拿到數(shù)組的尾部元素(字符串同理),這個(gè)時(shí)候我們就可以用Proxy來(lái)實(shí)現(xiàn)這一功能,這是我們的結(jié)構(gòu)有點(diǎn)像環(huán)狀:
這種實(shí)現(xiàn)的好處是如果我們想訪問(wèn)數(shù)組的最后一個(gè)元素時(shí),我們不需要先拿到長(zhǎng)度,再通過(guò)索引訪問(wèn)了:
// 原始寫法
arr[arr.length -1]
// 通過(guò)proxy改造后寫法
arr[-1]
實(shí)現(xiàn)代碼如下:
function createArray(...elements) {
let handler={
get(target, propKey, receiver) {
let index=Number(propKey);
if (index < 0) {
propKey=String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target=[];
target.push(...elements);
return new Proxy(target, handler);
}
我們可以發(fā)現(xiàn)以上代碼使用proxy來(lái)代理數(shù)組的讀取操作,在內(nèi)部封裝了支持負(fù)值查找的功能,當(dāng)然我們也可以不用proxy來(lái)實(shí)現(xiàn)同樣的功能,這里實(shí)現(xiàn)參考阮一峰老師的實(shí)現(xiàn). * 利用proxy實(shí)現(xiàn)更優(yōu)雅的校驗(yàn)器
一般我們?cè)谧霰韱涡r?yàn)的時(shí)候會(huì)寫一些if else或者switch判斷來(lái)實(shí)現(xiàn)對(duì)不同屬性值的校驗(yàn),同樣我們也可以用proxy來(lái)優(yōu)雅的實(shí)現(xiàn)它,代碼如下:
const formData={
name: 'xuxi',
age: 120,
label: ['react', 'vue', 'node', 'javascript']
}
// 校驗(yàn)器
const validators={
name(v) {
// 檢驗(yàn)name是否為字符串并且長(zhǎng)度是否大于3
return typeof v==='string' && v.length > 3
},
age(v) {
// 檢驗(yàn)age是否為數(shù)值
return typeof v==='number'
},
label(v) {
// 檢驗(yàn)label是否為數(shù)組并且長(zhǎng)度是否大于0
return Array.isArray(v) && v.length > 0
}
}
// 代理校驗(yàn)對(duì)象
function proxyValidator(target, validator) {
return new Proxy(target, {
set(target, propKey, value, receiver) {
if(target.hasOwnProperty(propKey)) {
let valid=validator[propKey]
if(!!valid(value)) {
return Reflect.set(target, propKey, value, receiver)
}else {
// 一些其他錯(cuò)誤業(yè)務(wù)...
throw Error(`值驗(yàn)證錯(cuò)誤${propKey}:${value}`)
}
}
}
})
}
有了以上實(shí)現(xiàn)模式,我們就可以實(shí)現(xiàn)對(duì)表單中某個(gè)值進(jìn)行設(shè)置時(shí)進(jìn)行校驗(yàn)了,用法如下:
let formObj=proxyValidator(formData, validators)
formObj.name=333; // Uncaught Error: 值驗(yàn)證錯(cuò)誤name:f
formObj.age='ddd' // Uncaught Error: 值驗(yàn)證錯(cuò)誤age:f
以上代碼中當(dāng)設(shè)置了不合法的值時(shí),控制臺(tái)將會(huì)剖出錯(cuò)誤,如果在實(shí)際業(yè)務(wù)中,我們可以給用戶做出適當(dāng)?shù)奶嵝? 實(shí)現(xiàn)請(qǐng)求攔截和錯(cuò)誤上報(bào) 實(shí)現(xiàn)數(shù)據(jù)過(guò)濾
以上幾點(diǎn)筆者在之前的文章中也寫過(guò),所以這里不在詳細(xì)介紹了.大家也可以根據(jù)實(shí)際情況自己實(shí)現(xiàn)更加靈活的攔截操作.當(dāng)然Proxy提供的API遠(yuǎn)遠(yuǎn)不止這幾個(gè),我們可以在MDN或者其他渠道了解更多高級(jí)用法.
Reflect對(duì)象與Proxy對(duì)象一樣,也是 ES6 為了操作對(duì)象而提供的新 API,更多的應(yīng)用場(chǎng)景是配合proxy一起使用,在上文中已經(jīng)用到了.可以將Object對(duì)象的一些明顯屬于語(yǔ)言內(nèi)部的方法放到Reflect對(duì)象上,并修改某些Object方法的返回結(jié)果. Reflect對(duì)象的方法與Proxy對(duì)象的方法一一對(duì)應(yīng),只要是Proxy對(duì)象的方法,就能在Reflect對(duì)象上找到對(duì)應(yīng)的方法。
CustomEvent API是個(gè)非常有意思的api, 而且非常實(shí)用, 更重要的是學(xué)起來(lái)非常簡(jiǎn)單,而且被大部分現(xiàn)代瀏覽器支持.我們可以讓任意dom元素監(jiān)聽和觸發(fā)自定義事件,只需要如下操作:
// 添加一個(gè)適當(dāng)?shù)氖录O(jiān)聽器
dom1.addEventListener("boom", function(e) { something(e.detail.num) })
// 創(chuàng)建并分發(fā)事件
var event=new CustomEvent("boom", {"detail":{"num":10}})
dom1.dispatchEvent(event)
我們來(lái)看看CustomEvent的參數(shù)介紹: type 事件的類型名稱,如上面代碼中的'boom' CustomEventInit 提供了事件的配置信息,具體有以下幾個(gè)屬性 * bubbles 一個(gè)布爾值,表明該事件是否會(huì)冒泡 * cancelable 一個(gè)布爾值,表明該事件是否可以被取消 * detail 當(dāng)事件初始化時(shí)傳遞的數(shù)據(jù)
我們可以通過(guò)dispatchEvent來(lái)觸發(fā)自定義事件.其實(shí)他的用途有很多,比如創(chuàng)建觀察者模式, 實(shí)現(xiàn)數(shù)據(jù)雙向綁定, 亦或者在游戲開發(fā)中實(shí)現(xiàn)打怪掉血,比如下面的例子:
筆者上面畫了一個(gè)打boss的草圖, 現(xiàn)在的場(chǎng)景是兩個(gè)玩家一起打boss, 我們可以在玩家發(fā)動(dòng)攻擊的時(shí)候觸發(fā)dispatch掉血的自定義事件, boss監(jiān)聽到事件后將血量自動(dòng)扣除, 至于不同角色的傷害值,我們可以存放在detail中,然后通過(guò)策略模式去分發(fā)傷害.筆者曾今在學(xué)校開發(fā)的H5游戲時(shí)就大量采用類似的模式,還是非常有意思的.
File API使得我們?cè)跒g覽器端可以訪問(wèn)文件的數(shù)據(jù),比如預(yù)覽文件,獲取文件信息(比如文件名,文件內(nèi)容,文件大小等), 并且可以在前端實(shí)現(xiàn)文件下載(可以借助canvas和 window.URL.revokeObjectURL的一些能力).當(dāng)然我們還可以實(shí)現(xiàn)拖拽上傳文件這樣高用戶體驗(yàn)的操作.接下來(lái)我們來(lái)看看幾個(gè)實(shí)際例子. * 顯示縮略圖
function previewFiles(files, previewBox) {
for (var i=0; i < files.length; i++) {
var file=files[i];
var imageType=/^image\//;
if (!imageType.test(file.type)) {
continue;
}
var img=document.createElement("img");
previewBox.appendChild(img); // 假設(shè)"preview"就是用來(lái)顯示內(nèi)容的div
var reader=new FileReader();
reader.onload=(function(imgEl) {
return function(e) { imgEl.src=e.target.result; };
})(img);
reader.readAsDataURL(file);
}
}
以上代碼可以在reviewBox容器中顯示已上傳好的圖片,當(dāng)然我們還可以基于此來(lái)擴(kuò)展,利用canvas將圖片畫到canvas上,然后進(jìn)行圖片壓縮,最后再把壓縮后的圖片上傳到服務(wù)器.這中方式其實(shí)目前很多工具型網(wǎng)站都在用,比如在線圖片處理網(wǎng)站,提供的批量壓縮圖片,批處理水印等功能,套路都差不多,感興趣的朋友可以嘗試研究一下. * 封裝文件上傳組件
這塊筆者之前也寫過(guò)詳細(xì)的文章,這里就不一一舉例了.
6. Fullscreen
全屏API主要是讓網(wǎng)頁(yè)能在電腦屏幕中全屏顯示,它允許我們打開或者退出全屏模式,以便我們根據(jù)需要進(jìn)行對(duì)應(yīng)的操作,比如我們常用的網(wǎng)頁(yè)圖形編輯器或者富文本編輯器, 為了讓用戶專心于內(nèi)容設(shè)計(jì),我們往往提供切換全屏的功能供用戶使用.由于全屏API比較簡(jiǎn)單,這里我們直接上代碼:
// 開啟全屏
document.documentElement.requestFullscreen();
// 退出全屏
document.exitFullscreen();
以上代碼的document.documentElement也可以換成任何一個(gè)你想讓其全屏的元素.默認(rèn)情況下我們還可以通過(guò)document.fullscreenElement來(lái)判斷當(dāng)前頁(yè)面是否處于全屏狀態(tài),來(lái)實(shí)現(xiàn)屏幕切換的效果.如果是react開發(fā)者,我們也可以將其封裝成一個(gè)自定義hooks來(lái)實(shí)現(xiàn)與業(yè)務(wù)相關(guān)的全屏切換功能.
URL API是URL標(biāo)準(zhǔn)的組成部分,URL標(biāo)準(zhǔn)定義了構(gòu)成有效統(tǒng)一資源定位符的內(nèi)容以及訪問(wèn)和操作URL的API。
我們利用URL組件可以做很多有意思的事情.比如我們有個(gè)需求需要提取url的參數(shù)傳給后臺(tái),傳統(tǒng)的做法是自己寫一個(gè)方法來(lái)解析url字符串,手動(dòng)返回一個(gè)query對(duì)象.但是利用URL對(duì)象,我們可以很方便的拿到url參數(shù),如下:
let addr=new URL(window.location.href)
let host=addr.host // 獲取主機(jī)地址
let path=addr.pathname // 獲取路徑名
let user=addr.searchParams.get("user") // 獲取參數(shù)為user對(duì)應(yīng)的值
以上代碼可知,我們?nèi)绻麑rl轉(zhuǎn)化為URL對(duì)象,那么我們就可以很方便的通過(guò)searchParams提供的api來(lái)拿到url參數(shù)而無(wú)需自己再寫一個(gè)方法了.
另一方面,如果網(wǎng)站安全性比較高,我們還可以對(duì)參數(shù)進(jìn)行自然數(shù)排序然后再加密上傳給后端.具體代碼如下:
function sortMD5WithParameters() {
let url=new URL(document.location.href);
url.searchParams.sort();
let keys=url.searchParams.keys();
let params={}
for (let key of keys) {
let val=url.searchParams.get(key);
params[key]=val
};
// ...md5加密
return MD5(params)
}
地理位置 API 通過(guò) navigator.geolocation 提供, 這個(gè)瀏覽器API也比較實(shí)用, 我們?cè)诰W(wǎng)站中可以用此方式確定用戶的位置信息,從而讓網(wǎng)站有不同的展現(xiàn),增強(qiáng)用戶體驗(yàn).
舉幾個(gè)有意思的例子可以讓大家感受一下: 根據(jù)不同地區(qū),網(wǎng)站展示不同的主題:
根據(jù)用戶所在地區(qū),展示不同推薦內(nèi)容 這一點(diǎn)電商網(wǎng)站或者內(nèi)容網(wǎng)站用的比較多, 比如用戶在新疆,則給他推薦瓜果類廣告, 在北京,則給他推薦旅游景點(diǎn)類廣告等,雖然實(shí)際應(yīng)用中往往會(huì)更復(fù)雜,但是也是一種思路.
其實(shí)應(yīng)用遠(yuǎn)遠(yuǎn)不止如此,程序員可以發(fā)揮想象來(lái)實(shí)現(xiàn)更有意思的事情,讓自己的網(wǎng)站更智能.接下來(lái)筆者就基于promise寫一段獲取用戶位置的代碼:
function getUserLocation() {
return new Promise((resolve, reject)=> {
if (!navigator.geolocation) {
reject()
} else {
navigator.geolocation.getCurrentPosition(success, error);
}
function success(position) {
const latitude=position.coords.latitude;
const longitude=position.coords.longitude;
resolve({latitude, longitude})
}
function error() {
reject()
}
})
}
使用方式和結(jié)果如下圖所示:
我們基于獲取到的經(jīng)緯度調(diào)用第三方api(比如百度,高德)就可以獲取用戶所在為精確位置信息了.
Notifications API 允許網(wǎng)頁(yè)或應(yīng)用程序在系統(tǒng)級(jí)別發(fā)送在頁(yè)面外部顯示的通知;這樣即使應(yīng)用程序空閑或在后臺(tái),Web應(yīng)用程序也會(huì)向用戶發(fā)送信息。
我們舉個(gè)實(shí)際的例子,比如我們網(wǎng)站內(nèi)容有更新,通知用戶,效果如下:
相關(guān)代碼如下:
Notification.requestPermission( function(status) {
console.log(status); // 僅當(dāng)值為 "granted" 時(shí)顯示通知
var n=new Notification("趣談前端", {body: "從零搭建一個(gè)CMS全棧項(xiàng)目"}); // 顯示通知
});
當(dāng)然瀏覽器的Notification還給我們提供了4個(gè)事件觸發(fā)api方便我們做更全面的控制: onshow 當(dāng)通知被顯示給用戶時(shí)觸發(fā) (已廢棄, 但部分瀏覽器仍然能用) onclick 當(dāng)用戶點(diǎn)擊通知時(shí)觸發(fā) onclose 當(dāng)通知被關(guān)閉時(shí)觸發(fā)(已廢棄, 但部分瀏覽器仍然能用) onerror 當(dāng)通知發(fā)生錯(cuò)誤的時(shí)候觸發(fā)
有了這樣的事件監(jiān)聽,我們就可以控制當(dāng)用戶點(diǎn)擊通知時(shí), 跳轉(zhuǎn)到對(duì)應(yīng)的頁(yè)面或者執(zhí)行相關(guān)的業(yè)務(wù)邏輯.如下代碼所示:
Notification.requestPermission( function(status) {
console.log(status); // 僅當(dāng)值為 "granted" 時(shí)顯示通知
var n=new Notification("趣談前端", {body: "從零搭建一個(gè)CMS全棧項(xiàng)目"}); // 顯示通知
n.onshow=function () {
// 消息顯示時(shí)執(zhí)行的邏輯
console.log('show')
}
n.onclick=function () {
// 消息被點(diǎn)擊時(shí)執(zhí)行的邏輯
history.push('/detail/1232432')
}
n.onclose=function () {
// 消息關(guān)閉時(shí)執(zhí)行的邏輯
console.log('close')
}
});
當(dāng)然我們?cè)谑褂们靶枰@取權(quán)限,方式也很簡(jiǎn)單,大家可以在mdn上學(xué)習(xí)了解.
Battery Status API提供了有關(guān)系統(tǒng)充電級(jí)別的信息并提供了通過(guò)電池等級(jí)或者充電狀態(tài)的改變提醒用戶的事件。 這個(gè)可以在設(shè)備電量低的時(shí)候調(diào)整應(yīng)用的資源使用狀態(tài),或者在電池用盡前保存應(yīng)用中的修改以防數(shù)據(jù)丟失。
之前的版本中Battery Status API提供了幾個(gè)事件監(jiān)聽函數(shù)來(lái)監(jiān)聽電量的變化以及監(jiān)聽設(shè)備是否充電,但是筆者看文檔時(shí)這些api都已經(jīng)廢棄,如下: chargingchange 監(jiān)聽設(shè)別是否充電 levelchange 監(jiān)聽電量充電等級(jí) chargingtimechange 充電時(shí)間變化 dischargingtimechange 放電時(shí)間變化
雖然以上幾個(gè)看似有用的api已經(jīng)被棄用,但是筆者親測(cè)谷歌還是可以正常使用的,但是為了讓自己代碼更可靠,我們可以用其他方式代替,比如用定時(shí)器定期去檢測(cè)電量情況,進(jìn)而對(duì)用戶做出不同的提醒.
接下來(lái)我們看看基本的用法:
navigator.getBattery().then(function(battery) {
console.log("是否在充電? " + (battery.charging ? "是" : "否"));
console.log("電量等級(jí): " + battery.level * 100 + "%");
console.log("充電時(shí)間: " + battery.chargingTime + " s");
console.log("放電時(shí)間: " + battery.dischargingTime + "s");
});
我們可以通過(guò)getBattery拿到設(shè)備電池信息,這個(gè)api非常有用,比如我們可以在用戶電量不足時(shí)禁用網(wǎng)站動(dòng)畫或者停用一些耗時(shí)任務(wù),亦或者是對(duì)用戶做適當(dāng)?shù)奶嵝?改變網(wǎng)站顏色等,對(duì)于webapp中播放視頻或者直播時(shí),我們也可以用css畫一個(gè)電量條,當(dāng)電量告急時(shí)提醒用戶.作為一個(gè)優(yōu)秀的網(wǎng)站體驗(yàn)師,這一塊還是不容忽視的.
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。