Undefined
当声明了变量却没有初始化时,会默认给该变量赋予一个值 —— undefined
let msg;
console.log(msg); // undefined
增加undefined的目的是为了正式明确 空对象指针(null) 和 未初始化变量 的区别
undefined值是由null值派生而来的,所以对他们进行 “==” 判断,返回true
对于未声明的变量,只能执行一个有用的操作 —— typeof
对于未声明的变量 和 声明后没初始化 的变量,typeof都返回“undefined”
Null
逻辑上讲,null表示一个空对象指针,用于初始化将来要保存对象值的变量
这也是使用 typeof 判断null返回“object”的原因
简单来说,永远不必显示地将变量值设置为undefined,但任何时候,只要变量是要保存对象,而当时又没有那个对象可保存,就用null来占位。
String
js使用UTF-16编码来标识一个字符。UTF-16编码以两个字节作为一个编码单元,每个字符使用一个编码单元或两个编码单元来表示。
字符串可以使用 ""(双引号)、 ''(单引号)、 ``(反引号) 标识
- length
str.length
使用length属性可以获取字符串长度, 返回的是字符串中包含的编码单元的数量
> 若字符串中包含需要使用两个编码单元表示的字符,那么获取字符串长度的结果可能不符合预期
- 字符串特点
ECMAScript中的字符串是不可变的,要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。这也是一些早期的浏览器在拼接字符串时非常慢的原因
-
转换为字符串的方法
- val.toString() 返回当前值的字符串等价物
在对数值调用这个方法时,可以传一个底数参数,比如二进制,八进制,十六进制,默认返回数值的十进制
- String(val)
因为null和undefined没有toString()方法,所以调用此方法会直接返回这两个值的字面量文本
- val + ''
-
字符串插值 —— ${}
可在模板字面量中使用:
let msg = `my name is ${name}`模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从他们最接近的作用域中取值
Number
表示一个数字,js不详细区分整数类型、浮点数类型和带符号的数字类型等。
Number类型使用IEEE 754格式表示数字(所有数字本职上都是浮点数)。
最基本的数值字面量格式是十进制整数,整数也可以用八进制或十六进制字面量标识
如果某个计算得到的数值结果超出了js可表示的范围,那么这个数值会被自动转换为一个特殊的Infinity值,可使用isFinite()函数确定一个值是不是有限大
八进制字面量在严格模式下是无效的,会导致Javasript引擎抛出语法错误
因为存储浮点数使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。浮点值的精确度最高可达17位小数,但在算数计算中远不如整数精确。例如0.1+0.2得到的不是0.3,而是0.30000000000000004。
之所以存在这种舍入错误,是因为使用了IEEE 754数值,其他使用相同格式的语言也会有这个问题
NaN 用于表示本来要返回数值的操作失败了。NaN的几个独特属性:
任何涉及NaN的操作始终返回NaN
NaN不等于包括NaN在内的任何值,可使用isNaN判断某个值是不是 “不是数值”
-
数值转换
-
Number(val)
可用于任何数据类型
考虑到用
Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数let num1 = Number('hello'); // NaN let num2 = Number(''); // 0 let num3 = Number('00011'); // 11 let num4 = Number(true); // 1 let num5 = Number(null); // 0 let num6 = Number(undefined); // NaN -
parseInt(val, baseNumber)
主要用于将字符串转换为数值,更专注于字符串是否包含数值模式
也接收第二个参数,用于指定底数,例:16表示要解析的值是十六进制
字符串最前面的空格会被忽略,从第一个非空格字符串开始转换;如果第一个字符不是数值字符、加号或减号,会立即返回NaN
let num1 = parseInt(''); // NaN let num2 = parseInt('1234blue'); // 1234 let num3 = parseInt('0xA'); // 10, 解释为十六进制整数 let num4 = parseInt(22.5); // 22 let num5 = parseInt("0xAF", 16); // 175 let num6 = parseInt("AF", 16); // 175, 如果提供了十六进制参数,字符串前的“0x”可以省略 let num7 = parseInt("AF"); // NaN -
parseFloat()
主要用于将字符串转换为数值,跟
parseInt()函数类似,都是从位置0开始检测每个字符,解析到字符串末尾或解析到一个无效的浮点数值字符为止 另一个与不同之处在于,它始终忽略字符串开头的领,因为parseFloat()只解析十进制值,因此不能指定底数
-
Boolean
包含两个逻辑值:true 和 false
显示转换: Boolean(val) 可以再任意类型的数据上调用,而且始终返回一个布尔值
隐式转换: if判断,!!val等
let a = Boolean(''); // false
let b = Boolean('hello'); // true
let c = Boolean(0); // false
let d = Boolean(NaN); // false
let e = Boolean(2); // true
let f = Boolean(null); // false
let g = Boolean(undefined); // false
let h = Boolean({}); // true
Symbol
是ECMAScript新增的数据类型。每个Symbol值都是是唯一且不可变的。
主要用途是确保对象属性使用唯一标识符,进而用作非字符串形式的对象属性。保证不会发生属性命名冲突。
语法:let sym = Symbol(description);
description字符串作为对sym的描述,将来可以通过这个字符串来调试代码,但是它与符号定义或标识完全无关
symbol没有字面量语法,(这也是他们发挥作用的关键)。按照规范,只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性
Symbol()函数不能与new关键字一起作为构造函数使用。这样做是为了避免创建符号包装对象。 像Boolean、String或Number那样,他们都支持构造函数且可用于初始化包含原始值的包装对象
let str = new String();
console.log(typeof str); // 'object'
let sym = new Symbol(); // TypeError: Symbol is not a constructor
// 如果确实想使用Symbol包装对象,可以借用Object函数
let mySym = Symbol();
let myWrapperSym = Object(mySym);
console.log(typeof myWrapperSym); // 'object'
- 使用全局Symbol注册表
如果运行时的不同部分需要共享和重用Symbol实例,可以用一个字符串作为键,在全局Symbol注册表中创建并重用Symbol
let globalSymbol = Symbol.for('foo');
console.log(typeof globalSymbol);
Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的symbol,会生成一个新Symbol实例并添加到注册表中,发现存在与该字符串对应的symbol,就会返回对应实例
全局注册表中的Symbol必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串。此外,注册表中使用的键同时也会被用作符号描述
Symbol.keyFor()用于查询全局注册表
let globalSymbol = Symbol.for('foo');
console.log(typeof globalSymbol);
let localSymbol = Symbol('foo');
console.log(globalSymbol === localSymbol); // false
console.log(Symbol.keyFor(globalSymbol)); // foo
- 获取属性数组
Object.getOwnPropertySymbols() 返回对象实例的symbol属性数组。
Object.getOwnPropertyNames() 返回对象实例的常规属性数组。
这两个方法的返回值彼此互斥
Object.getOwnPropertyDescriptors() 会返回同时包含常规和符号属性描述符的对象
Reflect.ownKeys() 会返回两种类型的键
let s1 = Symbol('foo');
let s2 = Symbol('bar');
let o = {
[s1]: 'foo val',
[s2]: 'bar val',
baz: 'baz val',
qux: 'qux val'
};
console.log(Object.getOwnPropertySymbols(o)); // [Symbol(foo), Symbol(bar)]
console.log(Object.getOwnPropertyNames(o)); // ['baz', 'qux']
console.log(Object.getOwnPropertyDescriptors(o)); // {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}
console.log(Reflect.ownKeys(o)); // ['baz', 'qux', Symbol(foo), Symbol(bar)]
- 常用内置符号(well-known symbol),即Symbol常量
用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为
这些内置常量最重要的用途之一是重新定义它们,从而改变原生结构的行为
比如for-of循环会在相关对象上使用Symbol.iterator属性,那么就可以通过在自定义对象上重新定义Symbol.iterator的值,来改变for-of在迭代该对象时的行为
所有内置Symbol属性都是不可写、不可枚举、不可配置的。在提到ECMAScript规范时,经常会引用符号在规范中的名称,前缀为@@。比如@@iterator指的就是Symbol.iterator
-
Symbol.iterator
"一个方法,该方法返回对象默认的迭代器。由for-of语句使用"。换句话说,这个符号标识实现迭代器API的函数
循环时,它们会调用以Symbol.iterator为键的函数,并默认这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象是实现该API的Generator:
class Foo{ *[Symbol.iterator]() {} } let f = new Foo(); console.log(f[Symbol.iterator]()); // Generator {<suspended>}技术上,这个由
Symbol.iterator函数生成的对象应该通过其next()方法陆续返回值。可以通过显式地调用next()方法返回,也可以隐式地通过生成器函数返回:class Emitter{ constructor(max) { this.max = max; this.idx = 0; } *[Symbol.iterator]() { while(this.idx < this.max) { yield this.idx++; } } } function count() { let emitter = new Emitter(5); for(const x of emitter) { console.log(x); } } count(); // 0 1 2 3 4 -
Symbol.asyncIterator
Symbol.asyncIterator是ES2018规范定义的,使用时需要考虑其兼容性
“一个方法,该方法返回对象默认的AsyncIterator。由for-await-of语句使用”。换句话说,这个符号表示实现异步迭代器API的函数
很多时候,返回的对象是实现该API的AsyncGenerator
class Foo{ constructor(max) { this.max = max; this.asyncIdx = 0; } async *[Symbol.asyncIterator]() { while(this.asyncIdx < this.max) { yield new Promise(resolve => resolve(this.asyncIdx++)); } } } let f = new Foo(); console.log(f[Symbol.asyncIterator]()); // AsyncGenerator {<suspended>} // 可通过该对象的next()方法陆续返回Promise实例,支持显示调用和隐式调用 async function asyncCount() { let emitter = new Foo(5); for await(const x of emitter) { console.log(x); } } asyncCount(); // 0 1 2 3 4 -
Symbol.hasInstance
“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用”
在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系
function Foo() {} let f = new Foo(); console.log(f instanceof Foo); // true console.log(Foo[Symbol.hasInstance](f)); // true class Bar{} class Baz extends Bar{ static [Symbol.hasInstance]() { return false; } } let b = new Baz(); console.log(Baz[Symbol.hasInstance](b)); // true console.log(b instanceof Bar); // true console.log(Baz[Symbol.hasInstance](b)); // false console.log(b instance Baz); // false -
Symbol.isConcatSpreadable
“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素,false或假值会导致整个对象被追加到数组末尾”
覆盖Symbol.isConcatSpreadable的值可以修改这个行为
let initial = ['foo']; let array = ['bar']; console.log(array[Symbol.isConcatSpreadable]); // undefined console.log(initial.concat(array)); // ['foo', 'bar'] array[Symbol.isConcatSpreadable] = false; console.log(initial.concat(array)); // ['foo', Array(1)] array[Symbol.isConcatSpreadable] = true; console.log(initial.concat(array)); // ['foo', 'bar'] -
Symbol.search
“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由String.prototype.search()方法使用”。
String.prototype.search()方法会使用以Symbol.search为键的函数来对正则表达式求值
正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
console.log(RegExp.prototype[Symbol.search]); // ƒ [Symbol.search]() { [native code] } console.log('foobar'.search(/bar/)); // 3可以通过重新定义Symbol.search函数以取代默认对正则表达式求值的行为。
class FooSearcher{ static [Symbol.search](target) { return target.indexOf('foo'); } } console.log('foobaz'.search(FooSearcher)); // 0 class StringSearcher{ constructor(str) { this.str = str; } [Symbol.search](target) { return target.indexOf(this.str); } } console.log('foobar'.search(new StringSearcher('foo'))); // 0 -
Symbol.replace
“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()方法使用”
同Symbol.search相似
class FooReplacer{ static [Symbol.replace](target, replacement) { return target.split('foo').join(replactment); } } -
Symbol.match
同Symbol.search相似
class FooMatcher{ static [Symbol.match](target) { return target.includes('foo'); } } -
Symbol.species
"一个函数值,该函数作为创建派生对象的构造函数"。用于对内置类型实例方法的返回值暴露实例化派生对象的方法
class Bar extends Array {} class Baz extends Array { // 用Symbol.species定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义 static get [Symbol.species]() { return Array; } } let bar = new Bar(); console.log(bar instanceof Array); // true console.log(bar instanceif Bar); // true bar = bar.concat('bar'); console.log(bar instanceof Array); // true console.log(bar instanceof Bar); // true let baz = new Baz(); console.log(baz instanceof Array); // true console.log(baz instanceof Baz); // true baz = baz.concat('baz'); console.log(baz instanceof Array); // true console.log(baz instanceof Baz); // false -
Symbol.split
"一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由String.prototype.split()方法使用"
console.log(RegExp.prototype[Symbol.split]); // ƒ [Symbol.split]() { [native code] } console.log('foobarbaz'.split(/bar/)); // ['foo', 'baz']给这个方法传入非正则表达式值会导致该值被转换为RegExp对象。可以通过重新定义Symbol.split函数来改变默认行为
class FooSplitter{ static [Symbol.split](target) { return target.split('foo'); } } console.log('barfoobaz'.split(FooSplitter)); // ['bar', 'baz'] -
Symbol.toPrimitive
“一个方法,该方法将对象转换为相应的原始值”。根据提供给这个函数的参数(string、number或defalut),可以控制返回的原始值:
class Bar{ constructor() { this[Symbol.toPrimitive] = function(hint) { switch(hint) { case 'number': return 3; case 'string': return 'string bar'; case 'default': return 'default bar'; } } } } let bar = new Bar(); console.log(3 + bar); // "3default bar" console.log(3 - bar); // 0 console.log(String(bar)); // "string bar" -
Symbol.toStringTag
作为一个属性表示 “一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法Object.prototype.toString()使用。”
let s = new Set(); console.log(s.toString()); // [object Set] console.log(s[Symbol.toStringTag]); // Set class Foo{} let f = new Foo(); console.log(f.toString()); // [object Object] console.log(f[Symbol.toStringTag]); // undefined class Bar{ constructor() { this[Symbol.toStringTag] = 'Bar'; } } let b = new Bar(); console.log(b.toString()); // [object Bar] console.log(b[Symbol.toStringTag]); // Bar -
Symbol.unscopables
不推荐使用with,因此也不推荐使用Symbol.unscopables
作为一个属性表示 “一个对象,该对象所有的 以及继承的属性,都会从关联对象的with环境绑定中排除”。设置这个symbol值为true,就可以阻止该属性出现在with环境绑定中
let o = {foo: 'bar'}; with(o) { console.log(foo); // bar } o[Symbol.unscopables] = { foo: true } with(o) { console.log(foo); // ReferenceError }
BigInt
js提供的一种表示大于 2^53-1 整数的方法
2^53-1原本是js中可以用Number表示的最大数字
BigInt可以表示任意大的整数。在一个整数字面量后面加n来表示一个BigInt整数
与Number不同:
- 不能用于Math对象中的方法
- 不能和任何Number实例混合运算
- 两者必须转成同一种类型(转换时要小心,因为BigInt变量在转换成Number变量时可能会丢失精度)
- BigInt目前不支持单目(+)运算符(为了兼容ASM.JS)
- 当使用/操作符时,带小数的运算会被取整
BigInt和Number不严格相等,但是宽松相等
console.log(0n === 0) // false
console.log(on == 0); // true