ECMAScript中有5中簡單的數據類型(也成為基本數據類型):Undefined、Null、Boolean、Number、String。還有一種復雜的數據類型——Object
注:Object本質上是有一組無序的鍵值對組成的
鑒于ECMAScript是松散類型的,因此需要一個手段來檢測給定的變量時什么類型,typeof就是負責這個功能的操作符。對一個值使用typeof操作符可能返回下列某個字符串:
下面舉一些使用typeof操作符的例子:
var message="some string";
alert(typeof message); // "string"
alert(typeof (message)); // "string"
alert(typeof 95); // "number"
Undefined類型只有一個值,級特殊的undefined。在使用var聲明變量,但未對其加以初始化時,這個變量就是undefined,例如:
var message;
alert(message==undefined); // true
也可以將變量直接初始化為undefined:
var message=undefined;
alert(message==undefined); // true
Null類型是第二個只有一個值得數據類型,這個特殊的值是null。從邏輯角度來看,null值表示一個空**對象**指針,這也正是為什么用typeof檢測null值會返回object的原因:
var car=null;
alert(typeof car); //object
實際上undefined值是派生自null值得,如下例:
alert(null==undefined); //true
但是兩者還是有區別的,如果一個變量暫時不賦值,將來要使用。我們不會把一個變量顯示的定義為undefined,而是會定義為null。
Boolean類型是ECMAScript中使用得最多的一種類型,該類型有兩個字面值:true和false。
var found=true;
var lost=false;
雖然Boolean類型的字面值只有兩個,但ECMAScript中所有類型的值都有與這兩個Boolean值等價的值。也就是說能夠定義的所有值要么能轉成true,要么能轉成false,可以用轉型函數Boolean()轉化。
var message="Hello world";
var messageAsBoolean=Boolean(message);
alert(messageAsBoolean); //true
下表給出了各種數據類型及其對應的轉換規則:
注:n/a(或N/A),是not applicable的縮寫,意思是“不適用”。
在流程控制語句中,會自動執行Boolean轉換,如下所示:
var message="Hello world!";
if (message) { //這里執行了Boolean轉換,將message轉為true
alert("Value is true");
}
Number類型應該是ECMAScript中最令人關注的數據類型了,直接上例子:
var intNum=55; // 十進制的55
var octalNum=070; // 八進制的56
var hexNum=0xA; // 十六進制的10
1.浮點數值
所謂浮點數值,就是該數值中必須包含一個小數點,并且小數點后面必須至少有以為數字,浮點數值的例子:
var floatNum1=1.1;
var floatNum2=0.1;
var floatNum3=.1; //有效,但不推薦
由于浮點數值占用的內存是整數值的兩倍,所以ECMAScript會不失時機地將浮點數值轉換為整數值,如下所示:
var floatNum1=1.; //小數后面沒有數字——解析為1
var floatNum2=10.0; //整數——解析為10
還有一種可以表示極大或極小的浮點數值的方法——e表示法,如下所示:
var floatNum=3.125e7;
其含義是3.125*10^7
var floatNum=3e-7;
其含義是3*10^(-7)
2.數值范圍
由于內存的限制,ECMAScript并不能保存世界上所有的數值。ECMAScript能夠表示的最小數值保存在Number.MIN_VALUE中,這個數值是5e-324;能夠表示的最大數值保存在Number.MAX_VALUE中,這個數值是1.7976931348623157e+308。如果某次計算的結果超過這個范圍,則會被轉換成特殊的Infinity值。
var result=Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result)); //false
3.NaN
NaN,即非數值(Not a Number),這個數值用于表示一個本來要返回數值的操作數未返回數值的情況,當計算錯誤的時候,不會拋出錯誤,而是返回一個NaN,這樣就不會影響后面代碼的執行。
alert(NaN==NaN); //false
alert(isNaN(NaN)); //true
4.數值轉換
有3個函數可以把非數值轉換為數值:Number()、parseInt()、parseFloat()。
Number()函數的轉換規則如下:
例如:
var num1=Number("Hello world!"); //NaN
var num2=Number(""); //0
var num3=Number("000011"); //11
var num4=Number(true); //1
由于Number()在轉換字符串時比較復雜而且不夠合理,因此更常用的是parseInt()函數。parseInt()會從字符串第一個非空格字符開始掃描,如果第一個字符不是數字符號或者負號,parseInt()就會返回NaN。如果第一個字符是數字字符,parseInt()會繼續解析第二個字符,知道遇到一個非數字字符。例如,"1234blue"會被轉換為1234。"22.5"會被解析成22。
var num1=parseInt("1234blue"); // 1234
var num2=parseInt(""); // NaN
var num3=parseInt("0xA"); // 10(十六進制數)
var num4=parseInt(22.5); // 22
var num5=parseInt("070"); // 56(八進制數)
var num6=parseInt("70"); // 70(十進制數)
var num7=parseInt("0xf"); // 15(十六進制數)
var num8=parseInt("f",16); // 15(十六進制數,另一種寫法,推薦這么寫)
parseFloat()與parseInt()類似,但有兩點區別:
var num1=parseFloat("1234blue"); // 1234(整數)
var num2=parseFloat("0xA"); // 0
var num3=parseFloat("22.5"); // 22.5
var num4=parseFloat("22.34.5"); // 22.34
var num5=parseFloat("0908.5"); // 908.5
var num6=parseFloat("3.125e7"); // 31250000
String類型用于表示有零或多個16位Unicode字符組成的字符序列,即字符串。字符串可以用雙引號(")或單引號(')表示,因此下面的寫法都是有效的:
var firstName="Kobe";
var lastName='Bryant';
1.字符字面量
String數據類型包含一些特殊的字符字面量,也叫轉義序列,用于表示非打印字符,或者具有其他用途的字符。這些字符字面量如下表所示:
2.字符串的特點
ECMAScript中的字符串是**不可變**的,也就是說,字符串一旦創建,它們的值就不能改變。要改變某個變量保存的字符,首先要銷毀原來的字符串,然后在用另一個包含新值得字符串填充該變量,例如:
var lang="Java";
lang=lang + "Script";
第二行的動作是,創建一個能容納10個字符的新字符串,然后在這個字符串中填充"Java"和"Script",然后將原來的字符串"Java"和字符串"Script"銷毀。
3.轉換為字符串
要把一個值轉換為一個字符串有兩種方式。
a. toString()方法,該方法返回字符串的一個副本
var age=11;
var ageAsString=age.toString(); // 字符串"11"
var found=true;
var foundAsString=found.toString(); // 字符串"true"
由于null和undefined值沒有toString()方法,可以用下面的String()方法轉換為字符串
b.String()方法,可以將任何類型的值轉換為字符串。
下面看幾個例子:
var value1=10;
var value2=true;
var value3=null;
var value4;
alert(String(value1)); // "10"
alert(String(value2)); // "true"
alert(String(value3)); // "null"
alert(String(value4)); // "undefined"
ECMAScript中的對象其實就是一組數據和功能的集合。Object對象創建的方法如下:
var o=new Object();
Object的每個實例具有如下屬性和方法:
如果這篇博客對你有用,點個贊再走唄~
- 數值就是現實生活中的數字,包括整數(int)和浮點數(float)
- 在js中,所有的數字都是number類型
- 數值在計算機底層使用64位的二進制數據保存
- 所以在JS中,并不是可以精確的表示任意數字
- 對于整數來說,精確表示最大值 九千萬億,9后邊15個零
- 特殊的數字:
Infinity 表示正無窮
-Infinity 表示負無窮
NaN (Not a Number) 表示非法數字
了解:
- 為了解決JS中不能存儲大整數的問題,在ES2020版本中,
定義了一種新的數據的數據類型(bigint),專門用來表示大整數
- 大整數以n結尾,大小沒有限制,但是目前標準還沒有正式推出,
即使推出也不會立即被所有瀏覽器支持(主要是IE),所以現在只需了解一下即可。
var a=10; // 整數
a=3.5; // 浮點數
// 創建數字時,默認創建的都是10進制的數字,可以通過一些特殊方式,來通過其他的進制來創建數字
// 0b 開頭表示二進制數字
// 0o 開頭表示八進制數字
// 0x 開頭表示十六進制數字
a=0b10; // 使用二進制的形式創建數字
a=0o10; // 使用八進制的形式創建數字
a=0xff; // 使用十六進制的形式創建數字
//在JS中可以確保大部分整數運算得到一個精確的結果,但是一旦超過16位數字,運算結果是不精確的值
a=9111111111111432; // 16位整數可以精確表示
a=91111111111114325; // 超過16位,數字顯示為近似值
a=911111111111143259111111111111432591; // 當超過一定范圍,數字將會以科學計數法來顯示
a=911111111111143259111111111111432591 ** 10; // 當數字再大的時候,會顯示Infinity,表示正無窮
//在JS中,小數可以確保小數點后15位的值準確,超過15位則會顯示近似值
a=1.123456789012345;
//在JS中,小數的計算可能會出現近似的結果
//如果計算要求的精度比較高,千萬不要在JS中直接算
a=0.1 + 0.2;
a=10 - 'hello';
// console.log(a);
Var b=911111111111143251239111111111111432512391111111111114325123n;
// console.log(b);
- 在JS中使用字符串來表示語音、文字這些內容
- 字符串需要使用引號引起來
- 單引號雙引號都可以,但是注意不要混合使用
- 同類型的引號是不能嵌套的
- 在JS中,使用 \ 作為轉義字符
例子:
\" --> "
\' --> '
\ --> \
\t --> 制表符(縮進)
\n --> 在console換行
- 引號只在當前行起作用
var a='Hello';
a="Hello";
// a="hello'; //不要這么寫
// a='Hello Hello "aaaa"';
a="Hello H\nello \"aa\\aa\"";
// console.log(a);
a="hello hello How are you";
布爾值(boolean),主要用來進行邏輯判斷
- 布爾值只有兩個值:
true 表示真
false 表示假
var a=false;
console.log(typeof a) // boolean
- typeof的情況:
typeof 檢查 數字時,返回 number
typeof 檢查 字符串時,返回 string
typeof 檢查 布爾值時, 返回 Boolean
空值(null)
- 空值就表示沒有,表示空的對象
- 空值只有一個 null
未定義(undefined)
- 當我們聲明了一個變量,又不給變量賦值時,它的值就是undefined
- 一般我們不會主動為一個變量賦值為undefined
typeof的返回值:
檢查 null時,它會返回 object (這個是JS中的bug,一個歷史悠久的bug)
檢查 undefined時,它會返回 undefined
var a=null;
a=undefined; // 通常情況下不會這么寫
console.log(typeof null);
console.log(typeof undefined);
在JS中的這幾種數據類型,被統稱為基本數據類型:
數值(number)
字符串(string)
布爾值(boolean)
空值(null)
未定義(undefined)
- 基本數據類型是構建程序的基石,所有的數據都是以上幾種數據組合而成。
- 所有的基本數據類型,都是不可變的。
- 在JS中,變量并不真正的存儲值
- 在JS中,變量就相當于值的別名
Symbol是一種原始類型,可以使用 Symbol() 來創建一個新的 Symbol 對象。Symbol 通常用于作為對象屬性的鍵(key),因為添加到對象中的 Symbol 屬性不會出現在 for…in 循環、Object.keys()、Object.getOwnPropertyNames() 等方法中。這使得 Symbol 成為一種適合用于定義隱藏的屬性的機制。
需要注意的是,雖然 Symbol 是基本數據類型之一,但是在邏輯運算中,Symbol 類型的值不能被強制轉換為其他數據類型。
var a=10;
var b='10';
//typeof 用來檢查一個值的類型
// 注意:typeof檢查的是值的類型,而不是變量的類型(變量也沒有類型)
//用法:tyepof 值
console.log(typeof a); //number
console.log(typeof NaN); //number
console.log(typeof '10'); // string
console.log(typeof false); // boolean
console.log(typeof undefined); //undefined
console.log(typeof null); // 'object' 是js底層遺留問題
console.log(typeof Array) // 'function'
console.log(typeof Function) // 'function'
console.log(typeof Objcet) // 'function'
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object'
歡迎關注我的原創文章:小伙伴們!我是一名熱衷于前端開發的作者,致力于分享我的知識和經驗,幫助其他學習前端的小伙伴們。在我的文章中,你將會找到大量關于前端開發的精彩內容。
學習前端技術是現代互聯網時代中非常重要的一項技能。無論你是想成為一名專業的前端工程師,還是僅僅對前端開發感興趣,我的文章將能為你提供寶貴的指導和知識。
在我的文章中,你將會學到如何使用HTML、CSS和JavaScript創建精美的網頁。我將深入講解每個語言的基礎知識,并提供一些實用技巧和最佳實踐。無論你是初學者還是有一定經驗的開發者,我的文章都能夠滿足你的學習需求。
此外,我還會分享一些關于前端開發的最新動態和行業趨勢。互聯網技術在不斷發展,新的框架和工具層出不窮。通過我的文章,你將會了解到最新的前端技術趨勢,并了解如何應對這些變化。
我深知學習前端不易,因此我將盡力以簡潔明了的方式解釋復雜的概念,并提供一些易于理解的實例和案例。我希望我的文章能夠幫助你更快地理解前端開發,并提升你的技能。
如果你想了解更多關于前端開發的內容,不妨關注我的原創文章。我會不定期更新,為你帶來最新的前端技術和知識。感謝你的關注和支持,我們一起探討交流技術共同進步,期待與你一同探索前端開發的奇妙世界!
#web網站#
于 JS 初學者,理解鏈表可能是一項比較困難的任務,因為 JS 沒有提供內置的鏈表。在像 JS 這樣的高級語言中,我們需要從頭開始實現此數據結構,如果你不熟悉此數據結構的工作方式,則實現部分會變得更加困難 ?。
在本文中,我們將討論如何將鏈表存儲在數據庫中,實現鏈表的添加和刪除,查找以及反轉鏈表等操作。在實現鏈表之前,需要知道相比數組和對象,鏈表的優點是什么。
我們知道,數組中的元素以索引編號和順序存儲在數據庫中:
在使用數組時,在開始或特定索引處添加/刪除元素這樣的操作可能是一項性能較低的任務,因為我們必須移動所有其他元素的索引,造成這種原因是由數組的編號索引特性導致的。
使用對象可以解決上述問題。由于在對象中,元素存儲位置是隨機的,因此,在執行諸如在開始處或特定索引處添加/刪除元素之類的操作時,無需移動元素的索引:
盡管在對象中添加和刪除元素速度很快,但是從上圖可以看出,在進行迭代操作時,對象并不是最佳選擇,因為對象的元素存儲在隨機位置。因此,迭代操作可能需要很長時間。這是鏈表引出的原因。
那么什么是鏈表呢 ?
從名字本身可以看出它是一個以某種方式鏈表。那么它是如何鏈接的,列表包含什么呢?
鏈表由具有兩個屬性的節點組成:數據和指針。
節點內的指針指向列表中的下一個節點。鏈表中的第一個節點稱為head。為了更好地理解,讓我們看一下描述鏈表圖示:
從上圖可以看出,每個節點都有兩個屬性,data和pointer。指針指向列表中的下一個節點,最后一個節點的指針指向null,上圖是一個單鏈表 ?。
鏈表和對象時有很大的不同。在鏈表中,每個節點都通過指針(pointer)連接到下一個節點。因此,我們在鏈表的每個節點之間都有連接,而在對象中,鍵值對是隨機存儲的,彼此之間沒有連接。
本文由小智翻譯,歡迎關注《大遷世界》
接著,我們實現一個存儲整數的鏈表。由于 JS 不提供內置的鏈表支持,因此我們將使用對象和類來實現鏈表 ?
class Node {
constructor (value) {
this.value = value
this.next = null
}
}
class LinkedList {
constructor () {
this.head = null
this.tail = this.head
this.length = 0
}
append (value) {
}
prepend (value) {
}
insert (value, index) {
}
lookup (index) {
}
remove (index) {
}
reverse () {
}
}
在上面的代碼中,我們創建了兩個類,一個用于來鏈表本身,一個是節點本身。如我們所討論的,每個節點將具有兩個屬性,一個值和一個指針(對應 next 字段)。
LinkedList類包含三個屬性,head(初始值為null),用于存儲鏈表的最后一個節點的tail(也指向null)和用于保存鏈表長度的length屬性。接著,我們來實現里面的方法 ?。
這個函數將一個節點添加到鏈表的末尾。為了實現這個函數,我們需要理解它需要執行的一些操作:
從上圖中,我們可以通過以下方式實現append函數:
append (value) {
const newNode = new Node(value)
if (!this.head) {
this.head = newNode
this.tail = newNode
} else {
this.tail.next = newNode
this.tail = newNode
}
this.length++
}
簡單的對 append 方法解釋一下 ?:
const linkedList1 = new LinkedList()
linkedList1.append(2)
檢查head是否指向null,此時的head指向null,因此我們創建一個新對象,并將新對象分配給head和tail:
let node = new Node(2)
this.head = newNode
this.tail = newNode
現在,head 和 tail 都指向同一個對象,這一點很重要,要記住。
接著,我們再向鏈表添加兩個值:
linkedList1.append(3)
linkedList1.append(4)
現在,head 不指向null,所以我們進入append函數的else分支:
this.tail.next = node
由于head 和tail 都指向同一個對象,tail的變化都會導致head對象的變化,這是JS 中對象的工作方式。在JavaScript中,對象是通過引用傳遞的,因此 head 和tail都指向存儲對象的相同地址空間。上面這行代碼相當于
this.head.next = node;
下一行:
this.tail = node
現在,在執行完上面的代碼行之后,this.head.next和this.tail指向同一對象,因此,每當我們添加新節點時,head對象都會自動更新。
執行三次append之后,linkedList1 的結構應該是這樣的:
head: {value: 2 , next: {value: 3, next: {value: 4,next: null}}}
tail : {value: 4, next: null}
length:3
從上面的代碼中我們可以看到,鏈表的append函數的復雜度是O(1),因為我們既不需要移動索引,也不需要遍歷鏈表。
我們來看下一個函數 ?
為了實現此函數,我們使用Node類創建一個新節點,并將該新節點的下一個對象指向鏈表的head 。接下來,我們將新節點分配給鏈表的head:
與append函數一樣,這個函數的復雜度也是O(1)。
prepend (value) {
const node = new Node(value)
node.next = this.head
this.head = node
this.length++
}
就像append函數一樣,此函數的復雜度也為O(1)。
在實現此函數之前,我們先看看它的一個轉化過程。因此,出于理解目的,我們先創建一個值很少的鏈表,然后可視化insert函數。insert 函數接受兩個參數,值和索引:
let linkedList2 = new LinkedList()
linkedList2.append(23)
linkedList2.append(89)
linkedList2.append(12)
linkedList2.append(3)
linkedList2.insert(45,2)
第1步:
遍歷鏈表,直到到達index-1位置:
第2步:
將索引為1的節點的指針(在本例中為89)分配給新節點(在本例中為45):
第3步:
將新節點(45)的 next 指向給下一個節點(12)
這就是執行插入操作的方式。通過以上可視化,我們觀察到需要在index-1位置和index位置找到節點,以便可以在它們之間插入新節點。在代碼中實現:
insert (value, index) {
if (index >= this.length) {
this.append(value)
}
const node = new Node(value)
const { prevNode, nextNode } = thisg.getPrevNextNodes(index)
prevNode.next = node
node.next = nextNode
this.length++
}
簡單分析一下上面的函數:
如果index的值大于或等于length屬性,則將操作移交給append函數。對于 else 分支,我們使用 Node 類創建一個新節點,接下來觀察一個新函數getPrevNextNodes(),通過該函數我們可以接收prevNode和nextNode的值。getPrevNextNodes函數的實現如下:
getPrevNextNodes(index){
let count = 0;
let prevNode = this.head;
let nextNode = prevNode.next;
while(count < index - 1){
prevNode = prevNode.next;
nextNode = prevNode.next;
count++;
}
return {
prevNode,
nextNode
}
}
通過遍歷鏈表返回在index-1位置和index位置的節點,并將prevNode的next屬性指向新節點,并將新節點的next屬性指向nextNode。
鏈表的插入操作的復雜度為 O(n),因為我們必須遍歷鏈表并在index-1和 index 位置搜索節點。盡管復雜度為O(n),但我們發現此插入操作比對數組的插入操作快得多,在數組中,我們必須將所有元素的索引移到特定索引之后,但是在鏈接中,我們僅操縱 index-1 和index 位置的節點的下一個屬性。
實現了插入操作之后,刪除操作就比較容易理解,因為它幾乎與插入操作相同,當我們從getPrevNextNodes函數獲取prevNode和nextNode值時,我們必須在remove中執行以下操作:
remove(index){
let {previousNode,currentNode} = this.getNodes(index)
previousNode.next = currentNode.next
this.length--
}
刪除操作的復雜度也為 O(n),類似于插入操作,鏈表中的刪除操作比數組中的刪除操作要快。
雖然看起來很簡單,但反轉鏈表常常是實現起來最令人困惑的操作,因此,在面試中會經常詢問這個操作。在實現這個函數之前,讓我們先把反轉鏈表的策略可視化一下。
為了反轉鏈表,我們需要跟蹤三個節點,previousNode,currentNode和nextNode。
考慮下面的鏈表:
let linkedList2 = new LinkedList()
linkedList2.append(67)
linkedList2.append(32)
linkedList2.append(44)
第一步:
開始,previousNode的值為null,而currentNode的值為head:
第二步:
接下來,我們將nextNode分配給currentNode.next:
第三步:
接下來,我們將currentNode.next屬性指向previousNode:
第三步:
現在,我們將previousNode移至currentNode,將currentNode移至nextNode:
這個過程從步驟2重復操作,一直到currentNode 等于 null。
reverse (){
let previousNode = null
let currentNode = this.head
while(currentNode !== null) {
let nextNode = currentNode.next
currentNode.next = previousNode
previousNode = currentNode
currentNode = nextNode
}
this.head = previousNode
}
就像我們看到的一樣,直到currentNode===null,我們一直在遍歷和移動這些值。最后,我們將previousNode值分配給head。
反向運算的復雜度為O(n)。
這個操作很簡單,我們只是遍歷鏈表并返回特定索引處的節點。這個操作的復雜度也是O(n)。
lookup(index){
let counter = 0;
let currentNode = this.head;
while(counter < index){
currentNode = currentNode.next;
counter++;
}
return currentNode;
}
好了,我們已經完成了用javascript實現單個鏈表的基本操作。單鏈表和雙鏈表的區別在于,雙鏈表的節點具有指向前一個節點和下一個節點的指針。
鏈表為我們提供了快速的append(末尾添加元素)和prepend(開頭添加元素)操作。盡管鏈表中的插入操作的復雜度為O(n),但比數組的插入操作要快得多。使用數組時我們面臨的另一個問題是大小復雜性,當使用動態數組時,在添加元素時,我們必須將整個數組復制到另一個地址空間,然后添加元素,而在鏈表中,我們不需要 面對這樣的問題。
在使用對象時,我們面臨的問題是元素在內存中的隨機位置,而在鏈表中,節點是通過指針相互連接的,指針提供了一定的順序。
我是小智,我們下期見!
作者:Vivek Bisht 譯者:前端小智 來源:soshace
原文:https://blog.soshace.com/understanding-data-structures-in-javascript-linked-lists/
*請認真填寫需求信息,我們會在24小時內與您取得聯系。