JavaScript数据类型
# 变量
# 字面量
字面量即常量,是固定值,不可改变。字面量分为三种
- 数字-
console.log(233)
- 字符串-
console.log('233')
- 布尔字面量-
let f = true
# 总结
字面量即不变的值,都可以直接使用,不需要声明。
一般我们不会直接使用字面量,而是声明一个变量保存字面量进行操作。
# 变量
用于存放数据的容器。我们通过变量名获取存放的数据
- 声明/定义
var x//ES6 之前
let x//ES6 变量
const x//ES6 常量
2
3
- 赋值
let/const/var x = 1
- 初始化
我们通常会把声明和赋值写在一起
let x = 1
声明一个变量并赋值,我们将这个过程称之为变量的初始化
# 扩展
- 修改变量的值
let x = 1
x = 2//一个变量被重新赋值后,原有的值就会被覆盖
2
- 声明多个变量
let x = 1,y = 2
- 命名规范
- 只能由字母( A-Z,a-z ),数字( 0-9 ),下划线( _ ),美元符号( $ )
- 不能以数字开头
- 不允许出现空格
- 不能使用JS语言中的
关键字和保留字作为变量名 - 长度不能超过255个字符
建议使用驼峰命名,变量名区分大小写
- 标识符,关键字,保留字
- 标识符
在JS中所有可以由我们
自主命名的字符都可以称之为标识符。例如变量名,函数名,属性名,参数名都属于标识符。- 关键字
JS本身在使用的字符,我们不能用他们作为标识符
break、continue、case、default、 if、else、switch、for、in、do、while、 try、catch、finally、throw、 var、void、function、return、new、 this、typeof、instanceof、delete、with、 true、false、null、undefined1
2
3
4
5
6
7
8
9
10
11- 保留字
预留的关键字
abstract、boolean、byte、char、class、const、 debugger、double、enum、export、extends、final、float、goto implements、import、int、interface、long、native、package、 private、protected、public、short、static、super、synchronized、throws、 transient、volatile1
2
3
4
5
6
7
8
9
# 数据类型的分类
在计算机中,不同的数据所需占用的存储空间不同,为了充分利用存储空间,于是定义了不同的数据类型。而且,不同的数据类型,寓意也不同
- JS中,所有的变量都是保存在
栈内存中- 基本数据类型的值,直接保存在栈内存中,值与值独立存在,修改一个变量不会影响其他变量
- 引用数据类型是保存到
堆内存中,创建新的对象,会在堆内存中开辟一个新的空间,如果两个变量保存的是同一个对象的引用地址,修改其中一个变量,另一个也会收到影响
# 基础数据类型
# String 字符串
语法: 字符串是引号中的任意文本
注意事项
- 单引号双引号不可混用
- 同类引号不能嵌套
- 单引号可以嵌套双引号,双引号可以其那套单引号
- 规范尽量使用单引号
转义字符
在字符串中我们可以使用\当作转义字符,避免出现二义性,避免系统识别错误,防止与程序关键字冲突
let x = '我说:\'真好!\''
console.log(x)
//输出 我说:'真好'!
2
3
获取字符串的长度
字符串是由若干个字符组成,获取字符串长度就是获取这些字符的总数。
let str1 = 'hello'
let str2 = 'hello,world!'
let str3 = 'h llo,你'
console.log(str1.length,str2.length,str3.length)
//输出 5,12,8
2
3
4
5
6
字符串拼接
多个字符串之间可以用+进行拼接,拼接前会把相加的这个数据类型转换为字符串
字符串的不可变性
字符串里面的值不可被改变,修改字符串变量只是在内存中开辟了一个内存空间,引用这个新的字符串,原来的字符串不会修改,依然保存在内存中
模板字符串
语法:`${变量}`
//字符串拼接
let name = 'lx'
let sex = 'm'
console.log('我是'+name+',age:'+sex+'的')//字符串拼接
console.log(`我是${name},是${sex}的`)//ES6模板字符串
//=================插入表达式
const a = 5;
const b = 10;
console.log(`this is ${a + b} and
not ${2 * a + b}.`);
//==================引用函数
function getName() {
return 'lx'
}
console.log(`我是${getName()}`)
//===================嵌套使用
let nameList = ['lx','xj']
function getTemplate() {
return `<ul>${nameList.map(item = > `<li>${item}</li>`).join('')}`
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Boolean 布尔型
布尔型有两个值:true和false。主要用来做逻辑判断。true表示真,false表示假。
布尔型和数字型相加时,true按1算,false按0算。
# Number 数值型
在JS中所有数值都属于Number类型,包括整数和浮点数
数值范围
由于内存的限制,ECMAScript并不能保存世界上所有的数值
- 最大值
Number.MAX_VALUE这个值为1.7976931348623157e+308 - 最小值
Number.MIN_VALUE,这个值为5e-324
如果使用Nummber的变量超过了最大值,则会返回Infinity
- 无穷大:
Infinity - 无穷小:
-Infinity
NaN
NaN是一个特殊的数字,表示Not a Number
console.log("abc"/18)
//输出 NaN
2
- undefined和任何数值计算都是NaN
- NaN与任何值不相等,包括NaN本身
连字符和加号的区别
let a = 'a'
let b = 'b'
console.log(a+b,'a+b',1+2);
//输出 ab,a+b , 3
2
3
4
如果加号两边都是Number类型,就是数字相加,否则就是同化为字符串
隐式转换
let a = "1" + 2
let b = "1" - 2
console.log(a, b)
//输出 12 -1
2
3
4
-*/%这几个运算符会自动进行隐式转换
浮点数的运算
运算精度问题:
在JS中,整数的运算基本可以保证精确;但是小数的运算,可能会得到一个不精确的结果。所以,千万不要使用JS进行对精确度要求比较高的运算
let a = 0.1
let b = 0.2
console.log(a + b)
//输出 0.30000000000000004
2
3
4
计算机在做运算时,所有的运算都要转换成二进制去计算。然而,有些数字转换成二进制之后,无法精确表示。比如说,0.1和0.2转换成二进制之后,是无穷的,因此存在浮点数的计算不精确的问题。
处理数学运算的精度问题
如果只是一些简单的精度问题,可以使用 toFix() 方法进行小数的截取。备注:关于 toFixed()方法, 详见《JavaScript基础/内置对象:Number 和 Math》。
在实战开发中,关于浮点数计算的精度问题,往往比较复杂。市面上有很多针对数学运算的开源库,比如decimal.js(轻量库,够用)、 Math.js(大型库)。这些开源库都比较成熟,我们可以直接拿来用。
# Symbol
背景
- ES5中对象的属性名都是字符串,容易造成命名污染。
- ES6中引入了一种新的原始数据类型Symbol,表示独一无二的值。
特点
- Symbol属性对应的值是唯一的,解决命名冲突问题
- Symbol值不能与其他数据进行计算,包括字符串拼接
- for遍历也不会遍历Symbol属性
使用
//==========创建Symbol属性值
//Symbol是函数,但并不是构造函数。
let mySymbol = Symbol()
console.log(type of mySymbol)//symbol
console.log(mySymbol)//Symbol()
//==========将Symbol作为对象属性值
let obj = {
name: 'lx',
sex: '20'
}
//obj.mySymol = 'xj' 错误,不能用.符号给对象添加Symbol属性
obj[mySymbol] = 'xj' //通过属性选择器给对象添加Symbol属性。
//=============遍历测试
for (let i in obj) {
console.log(i)
}
//打印 name sex
//遍历时不会遍历到Symbol属性
//=============传参作为标识
let mySymbol1 = Symbol()
let mySymbol2 = Symbol()
console.log(mySymbol1 == mySymbol2)//false
console.log(mySymbol1)//打印结果:Symbol()
console.log(mySymbol2)//打印结果Symbol()
/*
* 通过Symbol函数创建的两个值是不同的,但是打印结构看起来是相同的。
*/
//Symbol是函数,函数就可以传参,我们可以通过不同的参数来作为标识
mySymbol1 = Symbol('one')
mySymbol2 = Symbol('two')
console.log(mySymbol1 == mySymbol2)//false
console.log(mySymbol1)//打印结果:Symbol(one)
console.log(mySymbol2)//打印结果Symbol(two)
//==============定义常量
const MY_NAME = Symbol('my_name')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
内置属性
- Symbol.hasInstance
用来定义一个构造对象/类(class)如何识别一个变量是否是他的实例,对应的instanceof命令符
// 示例
class Array1 {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof Array1); // true
2
3
4
5
6
7
8
即使不是构造对象,普通对象也能定义它的Symbol.hasInstance。这样甚至可以用instanceof来进行值的校验,比如判断一个字符串是否是手机号码格式,就可以改写成这样:
const Ismoblie = {
[Symbol.hasInstance]: function(text) {
var pattern = /^1[3-9]\d{9}$/
return pattern.test(text)
}
}
'abc' instanceof Ismoblie // false
'18312345678' instanceof Ismoblie // true
2
3
4
5
6
7
8
- Symbol.isConcatSpreadable
用来配置一个数组对象,表示他在使用Array.prototype.concat()
方法时,数组元素是否能够被展开。对应的值为真值true时就可以,false时就不展开
const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];
let alphaNumeric = alpha.concat(numeric);
console.log(alphaNumeric);
// expected output: Array ["a", "b", "c", 1, 2, 3]
numeric[Symbol.isConcatSpreadable] = false;
alphaNumeric = alpha.concat(numeric);
console.log(alphaNumeric);
// expected output: Array ["a", "b", "c", Array [1, 2, 3]]
2
3
4
5
6
7
8
9
10
11
12
Symbol.isConcatSpreadable属性只能规定在调用concat函数时不能展开,使用...时仍然还是能够展开然后拼接的
let arr1 = [1,2]
let arr2 = [3,4]
arr1[Symbol.isConcatSpreadable] = false
arr1[Symbol.isConcatSpreadable] = false
console.log([...arr1, ...arr2])
// expected output: Array [1,2,3,4]
2
3
4
5
6
- Symbol.iterator
Symbol.iterator用来声明一个对象的默认遍历器(iterator),主要结合for...of使用,for...of会遍历那些可遍历(Iterable)的对象,执行对象的Symbol.iterator所对应的generator函数。普通对象是没有遍历器接口的,为它指定了Symbol.iterator之后,就可以用for...of遍历它了,注意Symbol.iterator对应的一定是一个generator函数。
const iterable1 = new Object();
iterable1[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
console.log([...iterable1]);
// expected output: Array [1, 2, 3]
2
3
4
5
6
7
8
9
10
11
- Symbol.match
定义了Symbol.match属性的对象,在执行*.match(obj)时,就会调用Symbol.match对应的函数,如果不是函数,则会报错。(这个Symbol感觉没什么用)
let str = new String('123')
str[Symbol.match] = function (arg) {
console.log(arg);
return false
}
'123'.match(str)
// console输出123,返回值为false
2
3
4
5
6
7
- Symbol.replace
与Symbol.match类似,定义了Symbol.replace属性的对象,在执行string.replace(obj)时,就会调用Symbol.match对应的函数,如果不是函数,则会报错。(这个Symbol感觉没什么用)
class Replace1 {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return `s/${string}/${this.value}/g`;
}
}
console.log('foo'.replace(new Replace1('bar')));
// expected output: "s/foo/bar/g"
2
3
4
5
6
7
8
9
10
11
- Symbol.search
同上,定义了Symbol.search属性的对象,再执行String.prototype.search方法时,会调用Symbol.search指向的函数。
class Search1 {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
console.log('foobar'.search(new Search1('bar')));
// expected output: 3
2
3
4
5
6
7
8
9
10
11
- Symbol.species
Symbol.species指向一个构造函数。创建衍生对象时,会使用该函数返回的属性。正常情况下,例如一个类Array1继承自Array,那么Array1的实例对象产生的衍生对象,既是Array1的实例,又是Array的实例。如果像下面这样定义Symbol.species之后则会这样:
class Array1 extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new Array1(1, 2, 3);
const mapped = a.map(x => x * x);
console.log(mapped instanceof Array1);
// expected output: false
console.log(mapped instanceof Array);
// expected output: true
2
3
4
5
6
7
8
9
10
11
12
所谓产生衍生对象,对于数组对象来说,包括了filter、map、slice这些函数生成的对象。而对于promise对象,then、catch函数返回的都是一个新的promise对象,这也是衍生对象。
class T1 extends Promise {
}
class T2 extends Promise {
static get [Symbol.species]() {
return Promise;
}
}
new T1(r => r()).then(v => v) instanceof T1 // true
new T2(r => r()).then(v => v) instanceof T2 // false
2
3
4
5
6
7
8
9
10
11
至此Symbol.species的主要作用就在于,子类的实例生成衍生对象时,可以通过修改Symbol.species,让衍生对象通过父类来创造,不通过子类。
- Symbol.split
对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
class Split1 {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
var index = string.indexOf(this.value);
return this.value + string.substr(0, index) + "/"
+ string.substr(index + this.value.length);
}
}
console.log('foobar'.split(new Split1('foo')));
// expected output: "foo/bar"
2
3
4
5
6
7
8
9
10
11
12
13
- Symbol.toPrimitive
Symbol.toPrimitive用于声明一个值为函数的属性,当一个对象要转换为一个相应的原始(primitive)值时,会调用该函数,该函数接收一个字符串参数,根据场景有不同的值:
- Number:该场合需要转成数值
- String:该场合需要转成字符串
- Default:该场合可以转成数值,也可以转成字符串
let a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
}
2 * a // 246
3 + a // '3default'
a == 'default' // true
String(a) // 'str'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
注意上面代码中,我同时还为a对象定义了valueOf和toString函数,但实际发生toPrimitive类型转换时,实际执行的是Symbol.toPrimitive对应的函数,也就是说Symbol.toPrimitive的优先级更高。
- Symbol.toStringTag
对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。
class ValidatorClass {
get [Symbol.toStringTag]() {
return 'Validator';
}
}
console.log(Object.prototype.toString.call(new ValidatorClass()));
// expected output: "[object Validator]"
let c = {}
c[Symbol.toStringTag] = 'nike sb'
c.toString()
// expected output: "[object nike sb]"
2
3
4
5
6
7
8
9
10
11
12
- Symbol.unscopables
对象的Symbol.unscopables属性,指向一个对象。 该对象指定了使用with关键字时,哪些属性会被with环境排除
//没有 unscopables 时
class MyClass {
foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1
}
//有 unscopables 时
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Null
Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象
如果你想定义一个变量保存引用类型,但是还没想好放什么内容,这个时候可以初始化将其设置为null
# Undefined
Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined
变量已声明,未赋值
let name;
console.log(name); // 打印结果:undefined
console.log(typeof name); // 打印结果:undefined
2
3
变量未声明时
console.log(typeof a); // undefined
console.log(a); // 打印结果:Uncaught ReferenceError: a is not defined
2
函数无返回值时
//如果一个函数没有返回值,那么,这个函数的返回值就是 undefined。或者,也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是 return undefined。
function foo() {}
console.log(foo()); // 打印结果:undefined
2
3
4
调用函数时,未传参
function foo(name) {
console.log(name);
}
foo(); // 调用函数时,未传参。执行函数后的打印结果:undefined
//实际开发中
function foo(name) {
name = name || 'qianguyihao';
}
foo();
//ES6
function foo(name = 'qianguyihao') {}
foo();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
null 和 undefined 区别
相同点
if 判断语句中,两者都会被转换为false不同点
- Number转换的值不同,Number(null)输出为0, Number(undefined)输出为NaN
- null表示一个值被定义了,但是这个值是空值
- 作为函数的参数,表示函数的参数不是对象
- 作为对象原型链的终点 (Object.getPrototypeOf(Object.prototype))
- 定义一个值为null是合理的,但定义为undefined不合理(var name = null)
- undefined表示缺少值,即此处应该有值,但是还没有定义
- 变量被声明了还没有赋值,就为undefined
- 调用函数时应该提供的参数还没有提供,该参数就等于undefined
- 对象没有赋值的属性,该属性的值就等于undefined
- 函数没有返回值,默认返回undefined
null 和 undefined 有很大的相似性。看看 null == undefined 的结果为 true ECMAScript认为undefined是从null派生出来的,所以把它们定义为相等的 但是 null === undefined 的结果是 false。
# BigInt (目前是第4阶段标准)
背景
对于学过其他语言的程序员来说,JS中缺少显式整数类型常常令人困惑。许多编程语言支持多种数字类型,如浮点型、双精度型、整数型和双精度型,但JS却不是这样。在JS中,按照IEEE 754-2008标准的定义,所有数字都以双精度64位浮点格式表示。
在此标准下,无法精确表示的非常大的整数将自动四舍五入。确切地说,JS 中的 Number类型只能安全地表示 -9007199254740991(-(2^53-1)) 和 9007199254740991(2^53-1)之间的整数,任何超出此范围的整数值都可能失去精度
为了解决这些限制,一些JS开发人员使用字符串类型表示大整数。 例如,Twitter API 在使用 JSON 进行响应时会向对象添加字符串版本的 ID。 此外,还开发了许多库,例如 bignumber.js,以便更容易地处理大整数。
使用BigInt,应用程序不再需要变通方法或库来安全地表示 Number.MAX_SAFE_INTEGER和 Number.Min_SAFE_INTEGER之外的整数。 现在可以在标准JS中执行对大整数的算术运算,而不会有精度损失的风险。
要创建 BigInt,只需在整数的末尾追加n即可
console.log(9007199254740995n); // → 9007199254740995n
BigInt("9007199254740995"); // → 9007199254740995n
//BigInt文字也可以用二进制、八进制或十六进制表示
// binary
console.log(0b100000000000000000000000000000000000000000000000000011n);
// → 9007199254740995n
// hex
console.log(0x20000000000003n);
// → 9007199254740995n
// octal
console.log(0o400000000000000003n);
// → 9007199254740995n
// note that legacy octal syntax is not supported
console.log(0400000000000000003n);
// → SyntaxError
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
请记住,不能使用严格相等运算符将 BigInt与常规数字进行比较,因为它们的类型不同:
console.log(10n === 10); // → false
console.log(typeof 10n); // → bigint
console.log(typeof 10); // → number
2
3
相反,可以使用等号运算符,它在处理操作数之前执行隐式类型转换
console.log(10n == 10); // → true
除一元加号( +)运算符外,所有算术运算符都可用于 BigInt
10n + 20n; // → 30n
10n - 20n; // → -10n
+10n; // → TypeError: Cannot convert a BigInt value to a number
-10n; // → -10n
10n * 20n; // → 200n
20n / 10n; // → 2n
23n % 10n; // → 3n
10n ** 3n; // → 1000n
const x = 10n;
++x; // → 11n
--x; // → 9n
2
3
4
5
6
7
8
9
10
11
不支持一元加号( +)运算符的原因是某些程序可能依赖于 +始终生成 Number的不变量,或者抛出异常。 更改 +的行为也会破坏 asm.js代码。
当然,与 BigInt操作数一起使用时,算术运算符应该返回 BigInt值。因此,除法( /)运算符的结果会自动向下舍入到最接近的整数。例如:
25 / 10; // → 2.5
25n / 10n; // → 2n
2
BigInt是一种新的数据类型,用于当整数值大于 Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。 重要的是要记住,不能使用 Number和 BigInt操作数的混合执行算术运算,需要通过显式转换其中的一种类型。 此外,出于兼容性原因,不允许在 BigInt上使用一元加号( +)运算符。
# 引用数据类型
# object
内置对象 Function、Array、Date、RegExp、Error等都是属于 Object 类型。也就是说,除了那五种基本数据类型之外,其他的,都称之为 Object类型
基本数据类型:参数赋值的时候,传数值
引用数据类型:参数赋值的时候,传地址
//========================基本数据类型
let a = 1
let b = a
a++
console.log(a,b)
//print 2,1
//=========================引用数据
let obj = new Object()
obj.name = 'lx'
let obj2 = obj1
obj2.name = 'xj'
console.log(obj.name,obj2.name)
//print xj,xj
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18