* js的引入方式
https://www.dcloud.io/hbuilderx.html
1.行內引入
<button id="bt1" onclick="console.log('頁面加載成功')">點擊實現效果</button>
<button id="bt2" ondblclick="alert('頁面加載成功')">點擊</button>
2.js內部引入
<script type="text/javascript">
//window表示當前的瀏覽器頁面 onload觸發事件進行加載
document.write(Date())
document.write("Hello World ! ! !")
window.onload=function(){
alert('公用的js頁面加載成功了!!!')
}
var a=20;
console.log(typeof(a))
var b=document.getElementById('demo')
console.log(b)
//用按鈕中的onclick中的函數實現js點擊事件
function button(){
alert('點擊按鈕實現')
}
</script>
<body>
<p id='demo' class='demo'>頁面內容展示</p>
<button id='button' onclick='button()'>點擊</button>
</body>
3.外部引入
<!--外部引入js頁面-->
<script src="js/a.js" type='text/javascript'></script>
二 * 關于js 運算 邏輯
1.js運算
<script type='text/javascript'>
//運算符運算
var a=20;
var b=30;
console.log(a+b)
a++;
b--;
console.log(a)
console.log(b)
var c=50;
c++;
var d=++c;
console.log(c)
console.log(d)
var num=10;
var nums=num++;
console.log(nums)
console.log(num)
</script>
2.三目運算
3.判斷語句
4.switch case 選擇語句
//switch case 選擇語句
console.log('歡迎進入銀行管理系統*1.存款 2.取款 3.轉賬 4.退出')
var choice=parseInt(prompt('請輸入選擇的行為*'))
switch(choice){
case 1:
put()
break;
case 2:
get()
break;
case 3:
trun()
break;
default:
console.log('退出銀行管理系統')
break;
}
function put(){
console.log('選擇存款功能')
}
function get(){
console.log('選擇取款功能')
}
function trun(){
console.log('選擇轉賬功能')
}
5.for while 語句
6.do while 語句
7.break語句
button onclick語句實例*
JavaScript 是世界上最流行的腳本語言。 JavaScript 是屬于 web 的語言,它適用于 PC、筆記本電腦、平板電腦和移動電話。 JavaScript 被設計為向 HTML 頁面增加交互性。 許多 HTML 開發者都不是程序員,但是 JavaScript 卻擁有非常簡單的語法。
JavaScript對大小寫敏感。
1.1 JavaScript的用法
HTML 中的腳本必須位于 <script> 與 </script> 標簽之間。
腳本可被放置在 HTML 頁面的 <body> 和 <head> 部分中。
? <script> 標簽
如需在 HTML 頁面中插入 JavaScript,請使用 <script> 標簽。
<script> 和 </script> 會告訴 JavaScript 在何處開始和結束。
<script> 和 </script> 之間的代碼行包含了 JavaScript:
那些老舊的實例可能會在 <script> 標簽中使用 type="text/javascript"。現在已經不必這樣做了。JavaScript 是所有現代瀏覽器以及 HTML5 中的默認腳本語言。
? <body> 中的 JavaScript
在本例中,JavaScript 會在頁面加載時向 HTML 的 <body> 寫文本:
1.2 JavaScript 數據類型
JavaScript 有多種數據類型:數字,字符串,數組,對象等等:
在 JavaScript 中有 5 種不同的數據類型:
l string
l number
l boolean
l object
l function
3 種對象類型:
l Object
l Date
l Array
2 個不包含任何值的數據類型:
l null
l undefined
你可以使用 typeof 操作符來查看 JavaScript 變量的數據類型。
1.3 JavaScript函數
我們把一個 JavaScript 函數放置到 HTML 頁面的 <head> 部分:
我們把一個 JavaScript 函數放置到 HTML 頁面的 <body> 部分:
也可以把腳本保存到外部文件中。外部文件通常包含被多個網頁使用的代碼。
外部 JavaScript 文件的文件擴展名是 .js。
如需使用外部文件,請在 <script> 標簽的 "src" 屬性中設置該 .js 文件:
1.4 JavaScript注釋
雙斜杠 // 后的內容將會被瀏覽器忽略。
1.5 JavaScript變量
在編程語言中,變量用于存儲數據值。
JavaScript 使用關鍵字 var 來定義變量, 使用等號來為變量賦值:
1.6 JavaScript操作符
1.7 JavaScript關鍵字
和其他任何編程語言一樣,JavaScript 保留了一些標識符為自己所用。
JavaScript 保留了一些關鍵字,這些關鍵字在當前的語言版本中并沒有使用,但在以后 JavaScript 擴展中會用到。
JavaScript 標識符必須以字母、下劃線(_)或美元符($)開始。
后續的字符可以是字母、數字、下劃線或美元符(數字是不允許作為首字符出現的,以便 JavaScript 可以輕易區分開標識符和數字)。
以下是 JavaScript 中最??重要的保留字(按字母順序):
1.8 JavaScript If...Else 語句
通常在寫代碼時,總是需要為不同的決定來執行不同的動作。可以在代碼中使用條件語句來完成該任務。
在 JavaScript 中,我們可使用以下條件語句:
l if 語句 - 只有當指定條件為 true 時,使用該語句來執行代碼
l if...else 語句 - 當條件為 true 時執行代碼,當條件為 false 時執行其他代碼
l JavaScript三目運算 - 當條件為true 時執行代碼,當條件為 false 時執行其他代碼
l if...else if....else 語句- 使用該語句來選擇多個代碼塊之一來執行
l switch 語句 - 使用該語句來選擇多個代碼塊之一來執行
1.9 JavaScript for循環
JavaScript 支持不同類型的循環:
l for - 循環代碼塊一定的次數
l for/in - 循環遍歷對象的屬性
l while - 當指定的條件為 true 時循環指定的代碼塊
l do/while - 同樣當指定的條件為 true 時循環指定的代碼塊
1.10 JavaScript JSON
JSON 是用于存儲和傳輸數據的格式。
JSON 通常用于服務端向網頁傳遞數據 。
什么是 JSON?
l JSON 英文全稱 JavaScript Object Notation
l JSON 是一種輕量級的數據交換格式。
l JSON是獨立的語言 *
l JSON 易于理解。
注:JSON 使用 JavaScript 語法,但是 JSON 格式僅僅是一個文本。文本可以被任何編程語言讀取及作為數據格式傳遞。
以下 JSON 語法定義了 employees 對象: 3 條員工記錄(對象)的數組:
{"employees":[
{"firstName":"John", "lastName":"Doe"},
{"firstName":"Anna","lastName":"Smith"},
{"firstName":"Peter", "lastName":"Jones"}
]}
JSON 語法規則:
l 數據為 鍵/值 對。
l 數據由逗號分隔。
l 大括號保存對象
l 方括號保存數組
JSON 字符串轉換為 JavaScript 對象(兩種方式):
首先,創建 JavaScript 字符串,字符串為 JSON 格式的數據:
var text='{ "employees" : [' +
'{ "firstName":"John" , "lastName":"Doe" },' +
'{ "firstName":"Anna" , "lastName":"Smith" },' +
'{ "firstName":"Peter" , "lastName":"Jones" } ]}';
l 使用 JavaScript 內置函數 JSON.parse() 將字符串轉換為 JavaScript 對象:
var obj=JSON.parse(text);
l JavaScript 函數 eval() 可用于將 JSON 文本轉換為 JavaScript 對象。
var obj=eval ("(" + txt + ")");
eval() 函數使用的是 JavaScript 編譯器,可解析 JSON 文本,然后生成 JavaScript 對象。必須把文本包圍在小括號中,這樣才能避免語法錯誤。
eval() 函數可編譯并執行任何 JavaScript 代碼。這隱藏了一個潛在的安全問題。
使用 JSON 解析器將 JSON 轉換為 JavaScript 對象是更安全的做法。JSON 解析器只能識別 JSON 文本,而不會編譯腳本。而且 JSON 解析器的速度更快。
最后,在頁面中使用新的 JavaScript 對象:
<p id="demo"></p>
<script>
document.getElementById("demo").innerHTML=
obj.employees[1].firstName + " " + obj.employees[1].lastName;
</script>
使用JSON.stringify方法Javascript對象轉換為JSON字符串:
var str={"name":"小牛學堂", "site":"http://www.edu360.cn"}
str_pretty1=JSON.stringify(str)
alert(str_pretty1);
1.11 Javascript void
href="#"與href="javascript:void(0)"的區別:
l # 包含了一個位置信息,默認的錨是#top 也就是網頁的上端。
l 而javascript:void(0), 僅僅表示一個死鏈接。
l 在頁面很長的時候會使用 # 來定位頁面的具體位置,格式為:# + id。
l 如果你要定義一個死鏈接請使用 javascript:void(0) 。
<a href="javascript:void(0);">點我沒有反應的!</a>
<a href="#pos">點我定位到指定位置!</a>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<p id="pos">尾部定位點</p>
1.12 Javascript驗證表單
JavaScript 可用來在數據被送往服務器前對 HTML 表單中的這些輸入數據進行驗證。
表單數據經常需要使用 JavaScript 來驗證其正確性:
l 驗證表單數據是否為空?
l 驗證輸入是否是一個正確的email地址?
l 驗證日期是否輸入正確?
l 驗證表單輸入內容是否為數字型?
必填(或必選)項目
下面的函數用來檢查用戶是否已填寫表單中的必填(或必選)項目。假如必填或必選項為空,那么警告框會彈出,并且函數的返回值為 false,否則函數的返回值則為 true(意味著數據沒有問題):
function validateForm()
{
var x=document.getElementById(“”).value;
if (x==null || x=="")
{
alert("姓名必須填寫");
return false;
}
}
……
<form name="myForm" action="test.html" onsubmit="return validateForm()" method="post">
姓名: <input type="text" name="fname">
<input type="submit" value="提交">
</form>
E-mail 驗證:xxxx@asd.cc
下面的函數檢查輸入的數據是否符合電子郵件地址的基本語法。
意思就是說,輸入的數據必須包含 @ 符號和點號(.)。同時,@ 不可以是郵件地址的首字符,并且 @ 之后需有至少一個點號:
function validateForm(){
var x=document.forms["myForm"]["email"].value;
var atpos=x.indexOf("@");
var dotpos=x.lastIndexOf(".");
if (atpos<1 || dotpos<atpos+2 || dotpos+2>=x.length){
alert("不是一個有效的 e-mail 地址");
return false;
}
}
……
<form name="myForm" action="test.html" onsubmit="return validateForm();" method="post">
Email: <input type="text" name="email">
<input type="submit" value="提交">
</form>
1.13 JavaScript正則表達式
正則表達式(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE)使用單個字符串來描述、匹配一系列符合某個句法規則的字符串搜索模式。
搜索模式可用于文本搜索和文本替換。
1) 什么是正則表達式?
正則表達式是由一個字符序列形成的搜索模式。
當你在文本中搜索數據時,你可以用搜索模式來描述你要查詢的內容。
正則表達式可以是一個簡單的字符,或一個更復雜的模式。
正則表達式可用于所有文本搜索和文本替換的操作。
2) 語法
/正則表達式主體/修飾符(可選)
3) 使用字符串方法
在 JavaScript 中,正則表達式通常用于兩個字符串方法 : search() 和 replace()。
search() 方法 用于檢索字符串中指定的子字符串,或檢索與正則表達式相匹配的子字符串,并返回子串的起始位置。
replace() 方法 用于在字符串中用一些字符替換另一些字符,或替換一個與正則表達式匹配的子串。
function myFunction() {
var str="My Test!";
// var n=str.search("Test");
var n=str.search(/test/i);
alert(n);
var str="My Test";
var txt=str.replace(/test/i,"Javascript");
alert(txt);
}
4) 正則表達式修飾符
修飾符 可以在全局搜索中不區分大小寫:
i執行對大小寫不敏感的匹配。
g執行全局匹配(查找所有匹配而非在找到第一個匹配后停止)。
m執行多行匹配。
5) 正則表達式模式
l 方括號用于查找某個范圍內的字符:
[abc]查找方括號之間的任何字符。
[0-9]查找任何從 0 至 9 的數字。
(x|y)查找任何以 | 分隔的選項。
l 元字符是擁有特殊含義的字符:
\d查找數字。
\s查找空白字符。
\uxxxx查找以十六進制數 xxxx 規定的 Unicode 字符。
l 量詞:
n+匹配任何包含至少一個 n 的字符串。
n*匹配任何包含零個或多個 n 的字符串。
n?匹配任何包含零個或一個 n 的字符串。
6) 使用 RegExp 對象
在 JavaScript 中,RegExp 對象是一個預定義了屬性和方法的正則表達式對象。
test() 方法是一個正則表達式方法。
test() 方法用于檢測一個字符串是否匹配某個模式,如果字符串中含有匹配的文本,則返回 true,否則返回 false。
Eg:
/\d/.test(“123”) 返回true。
/^1\d{10}$/
/^0\d{2,3}-?\d{7,8}$/
驗證郵箱的正則表達式:
function isEmail(str){
var reg=/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/;
return reg.test(str);
}
1.14 Javascript高級編程
1.14.1 Javascript對象
1) 創建 JavaScript 對象
通過 JavaScript,能夠定義并創建自己的對象。
創建新對象有兩種不同的方法:
l 定義并創建對象的實例
l 使用函數來定義對象,然后創建新的對象實例
var person=new Object();
person.firstname="John";
person.lastname="Doe";
person.age=50;
p={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"};
2) 使用對象構造器
使用函數來構造對象:
function person(firstname,lastname,age,eyecolor){
this.firstname=firstname;
this.lastname=lastname;
this.age=age;
this.eyecolor=eyecolor;
}
var myFather=new person("John","Doe",50,"blue");
alert(myFather.firstname + " is " + myFather.age + " years old.");
在JavaScript中,this通常指向的是我們正在執行的函數本身,或者是指向該函數所屬的對象(運行時)。
1.14.2 Javascript Array(數組)對象
1) 什么是數組?
數組對象是使用單獨的變量名來存儲一系列的值。
2) 創建一個數組,
有三種方法, 下面代碼定義了一個名為 myCars的數組對象:
1: 常規方式:
var myCars=new Array();
myCars[0]="Saab";
myCars[1]="Volvo";
myCars[2]="BMW";
2: 簡潔方式:
var myCars=new Array("Saab","Volvo","BMW");
3: 字面:
var myCars=["Saab","Volvo","BMW"];
3) 訪問數組
通過指定數組名以及索引號碼,你可以訪問某個特定的元素。
以下實例可以訪問myCars數組的第一個值:
var name=myCars[0];
以下實例修改了數組 myCars 的第一個元素:
myCars[0]="Opel";
4) 在一個數組中你可以有不同的對象
所有的JavaScript變量都是對象。數組元素是對象。函數是對象。
因此,你可以在數組中有不同的變量類型。
可以在一個數組中包含對象元素、函數、數組:
myArray[0]=Date.now;
myArray[1]=myFunction;
myArray[2]=myCars;
數組方法和屬性
使用數組對象預定義屬性和方法:
var x=myCars.length // myCars 中元素的數量
var y=myCars.indexOf("Volvo") // "Volvo" 值的索引值
5) 數組常用方法
合并數組 - concat()
刪除數組的最后一個元素 - pop()
數組的末尾添加新的元素 - push()
將一個數組中的元素的順序反轉排序 - reverse()
刪除數組的第一個元素 - shift()
從一個數組中選擇元素 - slice()
數組排序(按字母順序升序)- sort()
數字排序(按數字順序升序)- sort() eg:var arrs=[40,100,1,5,25,10]; arrs.sort(function(a,b){return a-b});
數字排序(按數字順序降序)- sort() eg:var arrs=[40,100,1,5,25,10]; arrs.sort(function(a,b){return b-a});
轉換數組到字符串 -toString()
據類型通常是一門編程語言的基礎知識
JavaScript DataType
JavaScript 的數據類型可以分為 7 種:空(Null)、未定義(Undefined)、數字(Number)、字符串(String)、布爾值(Boolean)、符號(Symbol)、對象(Object)。
其中前 6 種類型為基礎類型,最后 1 種為引用類型。這兩者的區別在于:
基礎類型的數據在被引用或拷貝時,是值傳遞,也就是說會創建一個完全相等的變量;
而引用類型只是創建一個指針指向原有的變量,實際上兩個變量是“共享”這個數據的,并沒有重新創建一個新的數據。
下面我們就來分別介紹這 7 種數據類型的重要概念及常見操作。
Undefined
Undefined 是一個很特殊的數據類型,它只有一個值,也就是 undefined。
可以通過下面幾種方式來得到 undefined:
對應代碼如下:
var a; // undefined
var o={}
o.b // undefined
(()=> {})() // undefined
void 0 // undefined
window.undefined // undefined
其中比較推薦通過 void 表達式來得到 undefined 值,因為這種方式既簡便(window.undefined 或 undefined 常量的字符長度都大于 "void 0" 表達式)
又不需要引用額外的變量和屬性;同時它作為表達式還可以配合三目運算符使用,代表不執行任何操作。
如下面的代碼就表示滿足條件 x 大于 0 且小于 5 的時候執行函數 fn,否則不進行任何操作:
x>0 && x<5 ? fn() : void 0;
如何判斷一個變量的值是否為 undefined 呢?
下面的代碼給出了 3 種方式來判斷變量 x 是否為 undefined,你可以先思考一下哪一種可行。
方式 1 直接通過邏輯取非操作來將變量 x 強制轉換為布爾值進行判斷;
方式 2 通過 3 個等號將變量 x 與 undefined 做真值比較;
方式 3 通過 typeof 關鍵字獲取變量 x 的類型,然后與 'undefined' 字符串做真值比較:
// 方式1
if(!x) {
...
}
// 方式2
if(x===undefined) {
...
}
// 方式3
if(typeof x==='undefined') {
...// 推薦使用可行
}
現在來揭曉答案,方式 1 不可行,因為只要變量 x 的值為 undefined、空字符串、數值 0、null 時都會判斷為真。方式 2 也存在一些問題,雖然通過 “===” 和 undefined 值做比較是可行的,但如果 x 未定義則會拋出錯誤 “ReferenceError: x is not defined” 導致程序執行終止,這對于代碼的健壯性顯然是不利的。方式 3 則解決了這一問題。
Null
Null 數據類型和 Undefined 類似,只有唯一的一個值 null,都可以表示空值,甚至我們通過 “==” 來比較它們是否相等的時候得到的結果都是 true,
但 null 是 JavaScript 保留關鍵字,而 undefined 只是一個常量。
也就是說我們可以聲明名稱為 undefined 的變量(雖然只能在老版本的 IE 瀏覽器中給它重新賦值),但將 null 作為變量使用時則會報錯。
Boolean
Boolean 數據類型只有兩個值:true 和 false,分別代表真和假,理解和使用起來并不復雜。但是我們常常會將各種表達式和變量轉換成 Boolean 數據類型來當作判斷條件,這時候就要注意了。
下面是一個簡單地將星期數轉換成中文的函數,比如輸入數字 1,函數就會返回“星期一”,輸入數字 2 會返回“星期二”,以此類推,如果未輸入數字則返回 undefined。
function getWeek(week) {
const dict=['日', '一', '二', '三', '四', '五', '六'];
if(week) return `星期${dict[week]}`;
}
這里在 if 語句中就進行了類型轉換,將 week 變量轉換成 Boolean 數據類型,而 0、空字符串、null、undefined 在轉換時都會返回 false。
所以這段代碼在輸入 0 的時候不會返回“星期日”,而返回 undefined。
我們在做強制類型轉換的時候一定要考慮這個問題。
Number
兩個重要值
Number 是數值類型,有 2 個特殊數值得注意一下,即 NaN 和 Infinity。
進制轉換
當我們需要將其他進制的整數轉換成十進制顯示的時候可以使用 parseInt 函數,該函數第一個參數為數值或字符串,第二個參數為進制數,默認為 10,當進制數轉換失敗時會返回 NaN。所以,如果在數組的 map 函數的回調函數中直接調用 parseInt,那么會將數組元素和索引值都作為參數傳入。
['0', '1', '2'].map(parseInt) // [0, NaN, NaN]
而將十進制轉換成其他進制時,可以通過 toString 函數來實現。
(10).toString(2) // "1010"
精度問題
對于數值類型的數據,還有一個比較值得注意的問題,那就是精度問題,在進行浮點數運算時很容易碰到。比如我們執行簡單的運算 0.1 + 0.2,得到的結果是 0.30000000000000004,如果直接和 0.3 作相等判斷時就會得到 false。
0.1 + 0.2 // 0.30000000000000004
出現這種情況的原因在于計算的時候,JavaScript 引擎會先將十進制數轉換為二進制,然后進行加法運算,再將所得結果轉換為十進制。在進制轉換過程中如果小數位是無限的,就會出現誤差。同樣的,對于下面的表達式,將數字 5 開方后再平方得到的結果也和數字 5 不相等。
Math.pow(Math.pow(5, 1/2), 2) // 5.000000000000001
對于這個問題的解決方法也很簡單,那就是消除無限小數位。
一種方式是先轉換成整數進行計算,然后再轉換回小數,這種方式適合在小數位不是很多的時候。比如一些程序的支付功能 API 以“分”為單位,從而避免使用小數進行計算。
還有另一種方法就是舍棄末尾的小數位。比如對上面的加法就可以先調用 toPrecision 截取 12 位,然后調用 parseFloat 函數轉換回浮點數。
parseFloat((0.1 + 0.2).toPrecision(12)) // 0.3
String
String 類型是最常用的數據類型了,關于它的基礎 API 函數大家應該比較熟悉了,這里我就不多介紹了。下面通過一道筆試題來重點介紹它的使用場景。
千位分隔符是指為了方便識別較大數字,每隔三位數會加入 1 個逗號,該逗號就是千位分隔符。如果要編寫一個函數來為輸入值的數字添加千分位分隔符,該怎么實現呢?
一種很容易想到的方法就是從右往左遍歷數值每一位,每隔 3 位添加分隔符。為了操作方便,我們可以將數值轉換成字符數組,而要實現從右往左遍歷,一種實現方式是通過 for 循環的索引值找到對應的字符;而另一種方式是通過數組反轉,從而變成從左到右操作。
function sep(n) {
let [i, c]=n.toString().split(/(\.\d+)/)
return i.split('').reverse().map((c, idx)=> (idx+1) % 3===0 ? ',' + c: c).reverse().join('').replace(/^,/, '') + c
}
這種方式就是將字符串數據轉化成引用類型數據,即用數組來實現。
第二種方式則是通過引用類型,即用正則表達式對字符進行替換來實現。
function sep2(n){
let str=n.toString()
str.indexOf('.') < 0 ? str+='.' : void 0
return str.replace(/(\d)(?=(\d{3})+\.)/g, '$1,').replace(/\.$/, '')
}
Symbol
Symbol 是 ES6 中引入的新數據類型,它表示一個唯一的常量,通過 Symbol 函數來創建對應的數據類型,創建時可以添加變量描述,該變量描述在傳入時會被強行轉換成字符串進行存儲。
var a=Symbol('1')
var b=Symbol(1)
a.description===b.description // true
var c=Symbol({id: 1})
c.description // [object Object]
var _a=Symbol('1')
_a==a // false
基于上面的特性,Symbol 屬性類型比較適合用于兩類場景中:常量值和對象屬性。
避免常量值重復
假設有個 getValue 函數,根據傳入的字符串參數 key 執行對應代碼邏輯。代碼如下所示:
function getValue(key) {
switch(key){
case 'A':
...
...
case 'B':
...
}
}
getValue('B');
這段代碼對調用者而言非常不友好,因為代碼中使用了魔術字符串(魔術字符串是指在代碼之中多次出現、與代碼形成強耦合的某一個具體的字符串或者數值),導致調用 getValue 函數時需要查看函數源碼才能找到參數 key 的可選值。所以可以將參數 key 的值以常量的方式聲明出來。
const KEY={
alibaba: 'A',
baidu: 'B',
...
}
function getValue(key) {
switch(key){
case KEY.alibaba:
...
...
case KEY.baidu:
...
}
}
getValue(KEY.baidu);
但這樣也并非完美,假設現在我們要在 KEY 常量中加入一個 key,根據對應的規則,很有可能會出現值重復的情況:
const KEY={
alibaba: 'A',
baidu: 'B',
...
bytedance: 'B'
}
這顯然會出現問題:
getValue(KEY.baidu) // 等同于 getValue(KEY.bytedance)
所以在這種場景下更適合使用 Symbol,我們不關心值本身,只關心值的唯一性。
const KEY={
alibaba: Symbol(),
baidu: Symbol(),
...
bytedance: Symbol()
}
避免對象屬性覆蓋
假設有這樣一個函數 fn,需要對傳入的對象參數添加一個臨時屬性 user,但可能該對象參數中已經有這個屬性了,如果直接賦值就會覆蓋之前的值。此時就可以使用 Symbol 來避免這個問題。
創建一個 Symbol 數據類型的變量,然后將該變量作為對象參數的屬性進行賦值和讀取,這樣就能避免覆蓋的情況,示例代碼如下:
function fn(o) { // {user: {id: xx, name: yy}}
const s=Symbol()
o[s]='zzz'
...
}
補充:類型轉換 什么是類型轉換?
JavaScript 這種弱類型的語言,相對于其他高級語言有一個特點,那就是在處理不同數據類型運算或邏輯操作時會強制轉換成同一數據類型。如果我們不理解這個特點,就很容易在編寫代碼時產生 bug。
通常強制轉換的目標數據類型為 String、Number、Boolean 這三種。下面的表格中顯示了 6 種基礎數據類型轉換關系。
六 種基礎數據類型轉換關系
除了不同類型的轉換之外,操作:
同種數據類型也會發生轉換。把基本類型的數據換成對應的對象過程稱之為“裝箱轉換”,
反過來,把數據對象轉換為基本類型的過程稱之為“拆箱轉換”。
對于裝箱和拆箱轉換操作,我們既可以顯示地手動實現,比如將 Number 數據類型轉換成 Number 對象;也可以通過一些操作觸發瀏覽器顯式地自動轉換,比如將對 Number 對象進行加法運算。
var n=1
var o=new Number(n) // 顯式裝箱
o.valueOf() // 顯式拆箱
n.toPrecision(3) // 隱式裝箱, 實際操作:var tmp=new Number(n);tmp.toPrecision(3);tmp=null;
o + 2 // 隱式拆箱,實際操作:var tmp=o.valueOf();tmp + 2;tmp=null;
什么時候會觸發類型轉換?
下面這些常見的操作會觸發隱式地類型轉換,我們在編寫代碼的時候一定要注意。
Object
相對于基礎類型,引用類型 Object 則復雜很多。簡單地說,Object 類型數據就是鍵值對的集合,鍵是一個字符串(或者 Symbol) ,值可以是任意類型的值;
復雜地說,Object 又包括很多子類型,比如 Date、Array、Set、RegExp。
對于 Object 類型,我們重點理解一種常見的操作,即深拷貝。
由于引用類型在賦值時只傳遞指針,這種拷貝方式稱為淺拷貝。
而創建一個新的與之相同的引用類型數據的過程稱之為深拷貝。
現在我們來實現一個拷貝函數,支持上面 7 種類型的數據拷貝。
對于 6 種基礎類型,我們只需簡單的賦值即可,而 Object 類型變量需要特殊操作。因為通過等號“=”賦值只是淺拷貝,要實現真正的拷貝操作則需要通過遍歷鍵來賦值對應的值,這個過程中如果遇到 Object 類型還需要再次進行遍歷。
為了準確判斷每種數據類型,我們可以先通過 typeof 來查看每種數據類型的描述:
[undefined, null, true, '', 0, Symbol(), {}].map(it => typeof it)// ["undefined", "object", "boolean", "string", "number", "symbol", "object"]
發現 null 有些特殊,返回結果和 Object 類型一樣都為"object",
所以需要再次進行判斷。按照上面分析的結論,我們可以寫出下面的函數:
function clone(data) {
let result = {}
const keys = [...Object.getOwnPropertyNames(data),
...Object.getOwnPropertySymbols(data)]
if(!keys.length) return data
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {
result[key] = clone(item)
} else {
result[key] = item
}
})
return result
}
在遍歷 Object 類型數據時,我們需要把 Symbol 數據類型也考慮進來,
所以不能通過 Object.keys 獲取鍵名或 for...in 方式遍歷,
而是通過 getOwnPropertyNames 和 getOwnPropertySymbols 函數將鍵名組合成數組,
然后進行遍歷。對于鍵數組長度為 0 的非 Object 類型的數據可直接返回,然后再遍歷遞歸,最終實現拷貝。
我們在編寫遞歸函數的時候需要特別注意的是,遞歸調用的終止條件,避免無限遞歸。
那在這個 clone 函數中有沒有可能出現無限遞歸調用呢?
答案是有的。那就是當對象數據嵌套的時候,比如像下面這種情況,對象 a 的鍵 b 指向對象 b,對象 b 的鍵 a 指向對象 a,那么執行 clone 函數就會出現死循環,從而耗盡內存。
var a={
var b={}
a.b=b
b.a=a
怎么避免這種情況呢?一種簡單的方式就是把已添加的對象記錄下來,這樣下次碰到相同的對象引用時,直接指向記錄中的對象即可。
要實現這個記錄功能,我們可以借助 ES6 推出的 WeakMap 對象,該對象是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是對象,而值可以是任意的。
我們對 clone 函數改造一下,添加一個 WeakMap 來記錄已經拷貝過的對象,如果當前對象已經被拷貝過,那么直接從 WeakMap 中取出,否則重新創建一個對象并加入 WeakMap 中。具體代碼如下:
function clone(obj) {
let map = new WeakMap()
function deep(data) {
let result = {}
const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
if(!keys.length) return data
const exist = map.get(data)
if (exist) return exist
map.set(data, result)
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {
result[key] = deep(item)
} else {
result[key] = item
}
})
return result
}
return deep(obj)
}
總結
本文通過實例與原理相結合,帶你深入理解了 JavaScript 的 6 種基礎數據類型和 1 種引用數據類型。對于 6 種基礎數據類型,我們要熟知它們之間的轉換關系,而引用類型則比較復雜,重點講了如何深拷貝一個對象。其實引用對象的子類型比較多。
最后留一道思考題:你能否寫出一個函數來判斷兩個變量是否相等?
*請認真填寫需求信息,我們會在24小時內與您取得聯系。