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小时内与您取得联系。