JavaScript$Data-Value-DataTypes
0.1 Data Types
程序是现实的映射。
现实生活中的“存在”,都可以用数据 data 来模拟。不同类型的“存在”需要不同的数据类型表示。在 JavaScript 中,数据类型分为两大类:
注意,是数据类型,不是变量类型。
- JavaScript 是动态类型语言 dynamic language:变量的类型是可以改变的
- JavaScript 是弱类型语言 weakly typed language:在需要 a 类型的地方使用 b 类型,值会自动转换成 a 类型(symbol 和 bigint 不会隐式转换,如果类型错误将会报错)
- primitive types (immutable):
- number:
1,0xff,0b100_001,.33,6.02e23,1.4E-32,NaN,Infinity - string:
'name','c' - boolean:
true,false - undefined:
undefined - null:
null - symbol:
Symbol(),Symbol('Tom') !== Symbol('Tom'),Symbol.for('Jerry) === Symbol.for('Jerry') - bigint:
12n,oxf2n,BigInt(10),BigInt('15')
- number:
- object types (mutable): properties(, methods)
- ordinary JavaScript Object (an unordered collection of named values):
{},{name: 'Tom'} - Array (an ordered collection of numbered values):
[],[1, 'Tom'] - Set (a set of values):
new Set([1, 2, 2]) - Map (a mapping from keys to values):
new Map([[1,2], [2,4]]) - Regexp:
/[1-5]/,new RegExp('[1-5]') - Date:
new Date() - Error:
new Error('not allowed') - ...
- ordinary JavaScript Object (an unordered collection of named values):
0.2 typeof
typeof 用来检测数据类型。需要注意的是:
typeof返回的是小写字符串typeof null === 'object'typeof undefinedVariable === 'undefined'- 对象类型的都返回
'object', 除了函数:typeof function(){} === 'function'
1. Primitive Types
除了 null 和 undefined,其他的基本数据类型都有包装类 wrapper types。当在这些数据上调用方法时,其实是调用包装类的方法。这也意味着在 null 和 undefined 上调用方法将报错 TypeError。(解决方式是 optional chaining operator ?.)
1.1 Numbers
一些语言中,数字类型分为整型和浮点型;在 JavaScript 中,只有数字类型,整型在内部也是以浮点型的形式存储的。The Number type is a double-precision 64-bit binary format IEEE 754 value.
- 浮点型数据范围: (
Number.MIN_VALUE) - (Number.MAX_VALUE),以及负数对应的范围。超过范围时可能会变成+Infinity/-Infinity/+0/-0(Number.MAX_VALUE + 1并不是Infinity,Number.MAX_VALUE * 2才是) - 整型数据范围: -() (
Number.MIN_SAFE_INTEGER) - (Number.MAX_SAFE_INTEGER)。超过这个范围就是去了精度。使用Number.isSafeInteger()来判断。- certain operations in JavaScript (such as array indexing and the bitwise operators) are performed with 32-bit integers.
因为浮点型的精度问题,ES2020 引入了 BigInt 来解决整型数据的精度问题。
numeric literal && constants
- Integer Literals
12// decimal0xff// hexadecimal literal (hexadecimal digits)0o377// octal (not recommended)0b100_001// binary, separators
- error:
_101, 101_, 0_01, 1._4
- Floating-Point Literals
3.14.3336.02e231.4E-32
- 内置对象 / 全局 定义的常量
Math.PI, Math.EInfinity // -Infinity; 1/0, Number.MAX_VALUE * 2NaN // Number.NaN; 0/0, Infinity/Infinity, NaN !== NaNNumber.POSITIVE_INFINITY // Number.NEGATIVE_INFINITYNumber.MAX_VALUE // Number.MIN_VALUENumber.MAX_SAFE_INTEGER // Number.MIN_SAFE_INTEGER // -(2**53-1)Number.EPSILON
Arithmetic in JavaScript
arithmetic operators: +(addition), -(subtraction), *(multiplication), /(division), %(modulo), **(exponentiation).
Math.pow(2, 53) // 2**53; Math.sqrt(3) // 3**(1/2);
Math.floor(.6), Math.round(.6), Math.ceil(.6), Math.turnc(3.9), Math.abs(-5)
Math.max(x, y, z), Math.min(x, y, z)
Math.random()
// other methods in Math
Number.parseInt(), Number.parseFloat()
Number.isNaN() // different from isNaN(), see below
Number.isFinite() // different form isFinite(), see below
Number.isInteger()
Number.isSafeInteger() // (-2**53, 2**53)
let a = {}
isNaN(a) // true
Number.isNaN(a) // false
let b = '15'
isFinite(b) // true
Number.isFinite(b) // false
Binary Floating-Point and Rounding Errors
let x = .3 - .2
let y = .2 - .1
x === y // => false
x === .1 // => false
y === .1 // => true
1.2 Strings (Text)
字符串 string 通过 16 位整型数来表示文本信息,一些字符需要超过 16 位来表示,这时 .length 表示的不是字符数,而是占用内存的 16 bits 的倍数。
The String type represents textual data and is encoded as a sequence of 16-bit unsigned integer values representing UTF-16 code units.
Each element in the string occupies a position in the string. The first element is at index 0, the next at index 1, and so on.
The length of a string is the number of UTF-16 code units in it, which may not correspond to the actual number of Unicode characters; see the String reference page for more details.
Most string-manipulation methods defined by JavaScript operate on 16-bit values, not characters. for/of loop or ... operator get the actual characters, not the 16-bit values.
1. String Literals
string delimiter: single / double quotes or backticks (', ", `)
break a string literal across multiple lines: backslash(\) at the end of the line.
\n: next line.
// contractions, possessives, apostrophe; single-quoted strings.
2. Escape Sequences in String Literals
We use escape sequences to express "special characters".
escape sequence: the backslash character (\) combined with the character that follows it.
常用的有:\n, \t, \\, \', \xnn, \unnnn, \u{n}
3. Template Literals
模板字符串的基本功能是在字符串中插入变量,插入的变量自动转换成字符串。
let name = "Tom";
let greeting = `Hello ${ name }.`; // greeting == "Hello Tom."
tagged template literals
如果把函数的() 换成两个反引号,那么引号里的字符串、表达式的值(如果使用${})将会作为参数传到该函数中。
If a function name (or "tag") comes right before the opening backtick, then the text and the values of the expressions within the template literal are passed to the function.
String.raw():
`\n`.length // => 1
String.raw`\n`.length
4. Working with Strings
let msg = 'Hello, ' + 'world'
===, !==; <, <=, >, >=. ('a'.localeCompare('b'))
s.length // unicode, not chars
let s = "Hello, world"
// get portions
s.substring(1,4) // => "ell"
s.slice(1,4) // => "ell"
s.slice(-3) // => "rld"
s.split(", ") // delimiter string
// search index
s.indexOf("l") // => 2
s.indexOf("l", 3) // => 3: at or after 3
s.indexOf("zz") // => -1
s.lastIndexOf("l") // => 10
// test
s.startsWith("Hell") // => true
s.endsWith("!") // => false
s.includes("or") // => true
// update (return another string)
s.replace("llo", "ya") // => "Heya, world"
s.toLowerCase() // => "hello, world"
s.toUpperCase() // => "HELLO, WORLD"
s.normalize() // Unicode NFC normalization: ES6
s.normalize("NFD") // NFD normalization. Also "NFKC", "NFKD"
// getOne: Inspecting individual (16-bit) characters of a string
s.charAt(0) // => "H"
s.charAt(s.length-1) // => "d"
s.charCodeAt(0) // => 72: 16-bit number at the specified position
s.codePointAt(0) // => 72: works for codepoints > 16 bits
// update (padding)
"x".padStart(3) // => " x"
"x".padEnd(3) // => "x "
"x".padStart(3, "*") // => "**x"
"x".padEnd(3, "-") // => "x--"
// update (trim)
" test ".trim() // => "test"
" test ".trimStart() // => "test ". Also trimLeft
" test ".trimEnd() // => " test". Also trimRight
// Miscellaneous string methods
s.concat("!") // => "Hello, world!": just use + operator instead
"<>".repeat(5) // => "<><><><><>": concatenate n copies
// strings can also be treated like read-only arrays
let s = 'hello, world'
s[0] // => "h"
s[s.length - 1] // => "d"
1.3 Boolean Values
true / false (truthy / falsy, truth / falsehood)
false: undefined, null, 0, -0, 0n, NaN, ""
Boolean(), !!
toString()
&&, ||, !
1.4 null and undefined
null: indicate the absence of an object. // NULL, nil, None.
typeof null => 'object'
undefined: a deeper kind of absence, the value of variables that have not been initialized, the value of undefined object property or array element.
typeof undefined => 'undefined'
typeof undefinedVariable => 'undefined'
1.5 Symbols
Symbol 的作用是为了预防字符串作为属性的冲突。后来语言的内部机制也利用了 Symbol。(比如 Symbol.iterator 作为方法名来实现对象的迭代。)
Symbols 用两种形式:
- 唯一值,每一次调用都不同:
Symbol(), Symbol('unique') - 全局仓库,多次调用返回同一值:
Symbol.for('shared')
let s = Symbol("sym_x"); // unique
s.toString() // => "Symbol(sym_x)"
// global Symbol registry
let s = Symbol.for("shared");
let t = Symbol.for("shared");
s === t // => true
s.toString() // => "Symbol(shared)"
Symbol.keyFor(t) // => "shared" // only for Symbol.for()
1.6 BigInt
123n
0b111111n
0o7777n
0x8000000000000000n // => 2n**63n: A 64-bit integer
BigInt(Number.MAX_SAFE_INTEGER) // => 9007199254740991n
let string = "1" + "0".repeat(100); // 1 followed by 100 zeros.
BigInt(string) // => 10n**100n: one googol
1000n + 2000n
3000n - 2000n
2000n * 3000n
3000n / 997n // => 3n
3000n % 997n // => 9n
(2n ** 131071n) - 1n // A Mersenne prime with 39457 decimal digits
1 < 2n // => true
2 > 1n // => true
0 == 0n // => true
0 === 0n // => false
BigInt.asintN(4, -1n) // -1n
BigInt.asUnitN(4, -1n) // 15n
// BigInt does not support JSON serialization
let data = {
bigNumber: 1234n
};
JSON.stringify(data); // TypeError
const replacer = (k, v) => typeof v === 'bigint' ? v.toString() : v;
JSON.stringify(data, replacer);
// {"bigNumber": "1234" }
const reviver = (k, v) => k === "bigNumber" ? BigInt(v) : v;
JSON.parse(`{"bigNumber": "1234"}`, reviver);
// { bigNumber: 1234n }
2. Objects
基本数据类型可以看作是一个个学生,它们不能改变。对象数据类型则像是教室,它们由一些学生和 / 或其他教室组成,可以改变内部内容。
这些学生或者教室,在对象看来,是属性 properties。虽然我们常说属性和方法,但是方法其实也是一种属性。
属性的 keys 是 strings / symbols,数组 1 这种的 key,会被转换成 '1'。
属性的值分为两种,每种属性由对应的 attributes 描述,可通过 Object.defineProperty() 来设置:
- data property:数据 value。
value:对应的值。多数情况我们定义对象时设置的都是这个值。writable:值是否可设置。enumerable:for...in是否可获取。configurable:值 property 是否可以删除、改为 accessor property, attributes 是否可改
- accessor property:行为 get / set。
get:函数,返回值。set:函数,设置值。enumerableconfigurable
The Global Object
环境加载后会有一个全局对象 global object。这个全局对象定义了一些数据:
- Global contants like undefined, Infinity, and NaN
- Global objects like Math and JSON
- Global functions like isNaN(), parseInt(), and eval()
- Constructor functions like Date(), RegExp(), String(), Object(), and Array()
不同环境的全局对象的名字不同,ES2020 统一为 globalThis。
- Window: window
- Node: global
- Web worker: self
- Immutable Primitive Values & Mutable Object References
- Primitives are immutable: there is no way to change (or "mutate") a primitive value. (compared by value)
- Objects are mutable. (compared by value => if you regard that value as an address / a pointer) (object values are references.)
// copy
let a = ["a","b","c"]; // An array we want to copy
let b = []; // A distinct array we'll copy into
for(let i = 0; i < a.length; i++) { // For each index of a[]
b[i] = a[i]; // Copy an element of a into b
}
let c = Array.from(b); // ES6
let obj2 = structureClone(obj)
function equalArrays(a, b) {
if (a === b) return true; // Identical arrays are equal
if (a.length !== b.length) return false; // Different-size arrays not equal
for(let i = 0; i < a.length; i++) { // Loop through all elements
if (a[i] !== b[i]) return false; // If any differ, arrays not equal
}
return true; // Otherwise they are equal
}
3. Type Conversions
类型转换:把 a 类型的数据转换成 b 类型。分为两种情况:
- 隐式类型转换:在需要 b 类型的地方使用 a 类型的数据,数据自动转换为 b 类型供环境使用
- 显式类型转换:强制将 a 类型转换成 b 类型
不是类型转换:使用 == 时,并没有将两边的数据进行隐式类型转换,而是由 JavaScript 标准定义的规则决定的。(运算符相关的内容将详解)
1. 隐式类型转换 Implicit Conversions
基本类型的隐式类型转换:
- Number:
undefined => NaN, null => 0, '' => 0; [] => 0, [9] => 9 - Boolean:
undefined, null, 0, -0, 0n, NaN, ""
2. 显式类型转换 Explicit Conversions
to Number
Number() // 可接受前后空格,不合规 => NaN (leading and trailing spaces)
parseInt(), parseFloat() // parseInt() accepts an optional radix/base argument
+x, x - 0
Intl.NumberFormat
parseInt("3 blind mice") // => 3
parseFloat(" 3.14 meters") // => 3.14
parseInt("-12.34") // => -12
parseInt("0xFF") // => 255
parseInt("0xff") // => 255
parseInt("-0XFF") // => -255
parseFloat(".1") // => 0.1
parseInt("0.1") // => 0
parseInt(".1") // => NaN
parseFloat("$72.47") // => NaN
parseInt("11", 2) // => 3: (1*2 + 1)
parseInt("ff", 16) // => 255: (15*16 + 15)
parseInt("zz", 36) // => 1295: (35*36 + 35)
parseInt("077", 8) // => 63: (7*8 + 7)
parseInt("077", 10) // => 77: (7*10 + 7)
to String
String()
toString() // 5.toString() wrong!, (5).toString()
x + ""
to String with Numbers
// toString with Numbers: (optional argument that specifies a radix, or base.)
let n = 17;
let binary = "0b" + n.toString(2); // binary == "0b10001"
let octal = "0o" + n.toString(8); // octal == "0o21"
let hex = "0x" + n.toString(16); // hex == "0x11"
// toString with Numbers for financial / scientific: (all 3 methods round the trailing digits or pad with zeros at appropriate)
let n = 123456.789;
n.toFixed(0) // => "123457"
n.toFixed(5) // => "123456.78900"
n.toExponential(1) // => "1.2e+5"
n.toExponential(3) // => "1.235e+5"
n.toPrecision(4) // => "1.235e+5"
n.toPrecision(7) // => "123456.8"
n.toPrecision(10) // => "123456.7890"
to Boolean
Boolean()
!!x