Ison Lee

vuePress-theme-reco Ison Lee    2019 - 2020
Ison Lee Ison Lee
Home
Category
  • 前端
Tag
TimeLine
Contact
  • 掘金
  • GitHub
author-avatar

Ison Lee

5

Article

3

Tag

Home
Category
  • 前端
Tag
TimeLine
Contact
  • 掘金
  • GitHub

JavaScript数据类型

vuePress-theme-reco Ison Lee    2019 - 2020

JavaScript数据类型

Ison Lee 2020-08-06 19:55:35 JavaScript基础

# 变量


# 字面量

字面量即常量,是固定值,不可改变。字面量分为三种

  • 数字-
    console.log(233)
1
  • 字符串-
    console.log('233')
1
  • 布尔字面量-
    let f = true
1

# 总结

字面量即不变的值,都可以直接使用,不需要声明。
一般我们不会直接使用字面量,而是声明一个变量保存字面量进行操作。

# 变量

用于存放数据的容器。我们通过变量名获取存放的数据

  • 声明/定义
var x//ES6 之前
let x//ES6 变量
const x//ES6 常量
1
2
3
  • 赋值
let/const/var x = 1
1
  • 初始化

我们通常会把声明和赋值写在一起

let x = 1
1

声明一个变量并赋值,我们将这个过程称之为变量的初始化

# 扩展

  • 修改变量的值
let x = 1
x = 2//一个变量被重新赋值后,原有的值就会被覆盖
1
2
  • 声明多个变量
let x = 1,y = 2
1
  • 命名规范
    • 只能由字母( 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、undefined 
    
    1
    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、volatile
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# 数据类型的分类


在计算机中,不同的数据所需占用的存储空间不同,为了充分利用存储空间,于是定义了不同的数据类型。而且,不同的数据类型,寓意也不同

  • JS中,所有的变量都是保存在栈内存中
  • 基本数据类型的值,直接保存在栈内存中,值与值独立存在,修改一个变量不会影响其他变量
  • 引用数据类型是保存到堆内存中,创建新的对象,会在堆内存中开辟一个新的空间,如果两个变量保存的是同一个对象的引用地址,修改其中一个变量,另一个也会收到影响

# 基础数据类型

# String 字符串


语法: 字符串是引号中的任意文本

注意事项
  • 单引号双引号不可混用
  • 同类引号不能嵌套
  • 单引号可以嵌套双引号,双引号可以其那套单引号
  • 规范尽量使用单引号

转义字符
在字符串中我们可以使用\当作转义字符,避免出现二义性,避免系统识别错误,防止与程序关键字冲突

let x = '我说:\'真好!\''
console.log(x)
//输出  我说:'真好'!
1
2
3

获取字符串的长度
字符串是由若干个字符组成,获取字符串长度就是获取这些字符的总数。

let str1 = 'hello'
let str2 = 'hello,world!'
let str3 = 'h  llo,你'

console.log(str1.length,str2.length,str3.length)
//输出   5,12,8
1
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('')}`
}
1
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
1
2
  • undefined和任何数值计算都是NaN
  • NaN与任何值不相等,包括NaN本身

连字符和加号的区别

let a = 'a'
let b = 'b'
console.log(a+b,'a+b',1+2);
//输出  ab,a+b , 3
1
2
3
4

如果加号两边都是Number类型,就是数字相加,否则就是同化为字符串

隐式转换

let a = "1" + 2
let b = "1" - 2
console.log(a, b)
//输出 12 -1
1
2
3
4

- * / % 这几个运算符会自动进行隐式转换

浮点数的运算

运算精度问题:
在JS中,整数的运算基本可以保证精确;但是小数的运算,可能会得到一个不精确的结果。所以,千万不要使用JS进行对精确度要求比较高的运算

let a = 0.1
let b = 0.2
console.log(a + b)
//输出 0.30000000000000004
1
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')
1
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
1
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
1
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]]
1
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]
1
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]
1
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
1
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"
1
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
1
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
1
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
1
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"
1
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' 
1
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]"
1
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
}
1
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
1
2
3

变量未声明时

console.log(typeof a); // undefined
console.log(a); // 打印结果:Uncaught ReferenceError: a is not defined
1
2

函数无返回值时

//如果一个函数没有返回值,那么,这个函数的返回值就是 undefined。或者,也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是 return undefined。
function foo() {}

console.log(foo()); // 打印结果:undefined
1
2
3
4

调用函数时,未传参


function foo(name) {
    console.log(name);
}

foo(); // 调用函数时,未传参。执行函数后的打印结果:undefined

//实际开发中
function foo(name) {
    name = name || 'qianguyihao';
}

foo();

//ES6
function foo(name = 'qianguyihao') {}

foo();
1
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
1
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
1
2
3

相反,可以使用等号运算符,它在处理操作数之前执行隐式类型转换

console.log(10n == 10);    // → true
1

除一元加号( +)运算符外,所有算术运算符都可用于 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
1
2
3
4
5
6
7
8
9
10
11

不支持一元加号( +)运算符的原因是某些程序可能依赖于 +始终生成 Number的不变量,或者抛出异常。 更改 +的行为也会破坏 asm.js代码。

当然,与 BigInt操作数一起使用时,算术运算符应该返回 BigInt值。因此,除法( /)运算符的结果会自动向下舍入到最接近的整数。例如:

25 / 10;      // → 2.5	
25n / 10n;    // → 2n
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18