Javascript基础数据类型

372 阅读12分钟

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

包含两个逻辑值:truefalse

显示转换: 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