Javascript 基本数据类型

212 阅读15分钟

基本数据类型(原始值/原始数据类型): 无方法、无对象、无法修改的数据

当前Javascript中有7种基本数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6)、BigInt(ES10)

无法修改
// 示例
let str = 'hello';  // 初始化时, str的值为 'hello'
str = 'hi';         // 赋值其实修改的是变量存储的值,'hello'本身并没有改变,只是str存储的值变成了 'hi'
str.toUpperCase();; // 转换成大写, 返回HI,但是不影响原本的str
console.log(str);   // hi 


let num = 1024;
num * 2;
console.log(num); // 1024

// 结论:基本数据类型无法修改,你可以修改的只是存储在某个变量上的值
// 你对数字、字符串做操作,本质上返回的也是一个新的数字、字符串,不会改变原来的数字、字符串,其他基本数据类型同理
构造函数
// 基本数据类型除了null和undefined,都存在其构造函数
// 构造函数主要有两种用法
// 1. 创建包装对象 - 装箱
// 加上new关键字,创建对应的包装对象,其valueOf方法返回基本数据类型
// 演示String,其他几个同理,Symbol不能使用new关键字
const strObj = new String('String-Type'); 
console.log(strObj);           // [String: 'String-Type']
console.log(typeof strObj);    // object, 包装对象,顾名思义,是对象类型
// 将包装对象转成基本数据类型 - 拆箱
console.log(strObj.valueOf()); // String-Type

// 前面说到过,基本数据类型没有方法,那为什么我们可以使用方法呢?
// 答:本质上是JS引擎将基本数据类型转换成了对应的包装对象
console.log('String-Type'.toLowerCase());  // string-type
// 开发者使用的时候是感知不到的,相当于
console.log(new String('String-Type').toLowerCase()); // string-type

// 由于null和undefined没有构造函数,无法装箱,所以它们是不能调用属性和方法的

// 2. 转换数据类型 - 显式类型转换
// JS引擎自动转换是隐式类型转换
// 当构造函数不使用new关键字时,它就变成了转换函数,用于转换数据类型
console.log(String(1024));   // '1024', 等价于 '' + 1024

console.log(Boolean(''));    // false,  等价于 !!''

console.log(Number('!'));    // NaN,    等价于 +'!'

console.log(Symbol('s'));    // Symbol(s)

console.log(BigInt('1024')); // 1024n

string

String(字符串)代表0或多个16位Unicode字符序列,可以使用单引号('')、双引号("")和反引号(``)表示。

const ssx = '孙尚香';
const mk = "马可波罗";
const gsl = `公孙离`;
字符字面量
字面量含义
\n换行
\r回车
\t制表符
\b退格
\f换页,其实就是换行,然后前面填充空格至上一行的长度
\\反斜杠(\)
\'单引号,字符串以单引号表示时使用,如 'He\'s code is nice'
\"双引号,字符串以双引号表示时使用,如 "He said \"Hi\""
\`反引号,字符串以反引号表示时使用,如 `He said \`Hi\``
转换成字符串
// 1. 使用 '' + 
console.log('' + 1024);        // '1024'

// 2. 使用构造函数
console.log(String(1024));     // '1024' 

// 3. toString方法,null和undefined没有
console.log(1024..toString()); // '1024' ,为什么是..,因为数字存在小数点,用于区分
模版字符串

ES6新增了模版字符串,其具有可以换行以及使用变量的能力,使用 ``定义。

// 1. 跨行定义字符串, ""和''则不行
const str = `
  line 1,
  line 2,
`;

// 2. 在``中使用变量,需要使用${}包裹
const date = 1024;
console.log(`${date}是程序员节!`); // 1024是程序员节!
// 相当于
console.log(date + '是程序员节!'); // 1024是程序员节!
// 和字符串拼接相比,模板字符串更加简洁
字符串常用方法

以下演示的是最基本的用法,详细用法需参考 MDN - String

// chatAt(index) 获取字符串index位置的字符
console.log('Easy'.charAt(0)); // E 

// charCodeAt(index) 获取字符串index位置字符对应的Unicode值
console.log('Hard'.charCodeAt(0)); // 72,  H对应的是72

// concat(str) 拼接字符串,可接受多个字符串
console.log('Learn '.concat('String ', '!')); // Learn String !

// startsWith(str) 是否以str开头,返回值是 true/false
console.log('Learn String!'.startsWith('Learn'));       // true

// endsWith(str) 是否以str结尾,返回值是 true/false
console.log('Learn String!'.endsWith('!'));       // true

// includes(str) 是否包含返回str, true/false
console.log('Learn String!'.includes('String')); // true

// indexOf(str) 左->右,返回找到的第一个索引值,不存在返回 -1 
console.log('Learn String!'.indexOf('String'));     // 6

// lastIndexOf(str) 右边->左,返回找到的第一个索引值,不存在返回 -1 
console.log('Learn String!'.lastIndexOf('String')); // 6


// padStart(length,padString)和padEnd(length,padString)
// padStart左侧填充,padEnd右侧填充,将字符串填充到length长度
console.log('Me'.padStart(5,'-'));  // ---Me
console.log('Me'.padEnd(5,'-'));    // Me---

// repeat(count)  重复字符串cout次
console.log('.'.repeat(3)); // ...

// replace(oldStr,newStr) 替换字符串,支持正则表达式
console.log('This is easy'.replace('easy', 'hard')); // This is hard

// slice(start,end) 提取start~end部分的字符串作为新字符串返回,不影响原本的字符串
console.log('Easy Hard'.slice(0,4)); // Easy

// split(str) 以str对原字符串进行切割放入数组并返回
console.log('abcde'.split('')); // [ 'a', 'b', 'c', 'd', 'e' ]
// 以空格切割成数组更推荐使用[...'']或Array.form('')
console.log('😊'.length);      // 2,可以看出,emoji表情占两个字符长度 
console.log('😊'.split(''));   // [ '�', '�' ],以''切割,😊会被切割成两个字符放入数组
console.log([...'😊']);        // ['😊'],正常转换成数组
console.log(Array.form('😊')); // ['😊'],正常转换成数组

// substring(start,end) 返回索引值 start~end的字符串,不包含索引为end到字符
console.log('How are you?'.substring(4,7)); // are

// toLocaleLowerCase()转小写,toLocaleUpperCase()转大些
console.log('Nice'.toLocaleLowerCase()); // nice
console.log('Nice'.toLocaleUpperCase()); // NICE


// trim()去除两侧空格,trimStart()去除左侧空格,trimEnd()去除右侧空格
console.log('  Me  '.trim());     // Me
console.log('   Me'.trimStart()); // Me
console.log('Me   '.trimEnd());   // Me

number

数值类型,用于表示数字,包括整数和浮点数,能够准确表示的整数范围在-2*53到2*53之间

数字字面量
let one = 1;           // 十进制 1
let three =  0b11;     // 二进制 3, 0b开头
let nine = 011;        // 八进制 9, 0开头
let twenty_six = 0x1A; // 十六进制 26, 0x开头
// 十六进制:0 1 2 3 4 5 6 7 8 9 A B C D E F 分别是 0 到 15

let percent90 = 0.9;   // 浮点数 
数字常量和方法

以下演示的是最基本的用法,详细用法需参考 MDN - Number

// Number常量

console.log(Number.NaN); // NaN

// Number.MIN_SAFE_INTEGER 代表在 JavaScript中最小的安全的integer型数字 (-(2**53 - 1))
console.log(Number.MIN_SAFE_INTEGER === -(2 ** 53 - 1)); // true

// Number.MIN_VALUE 属性表示在 JavaScript 中所能表示的最小的正值
console.log(Number.MIN_VALUE);  // 5e-324

// Number.MAX_SAFE_INTEGER 常量表示在 JavaScript 中最大的安全整数,其值为2的53次方-1
console.log(Number.MAX_SAFE_INTEGER === 2 ** 53 - 1); // true, 

// MAX_VALUE属性值接近于1.79e+308,大于MAX_VALUE就是Infinity
console.log(Number.MAX_VALUE);  // 约等于 1.79e+308

console.log(1/0);  // Infinity
console.log(1/-0); // -Infinity
console.log(+Infinity); // Infinity,正无穷大
console.log(-Infinity); // -Infinity,负无穷大
console.log(Number.POSITIVE_INFINITY);  // Infinity,正无穷大
console.log(Number.NEGATIVE_INFINITY);  // -Infinity,负无穷大

// Number方法

// 1. Number.isNaN 判断值是否是NaN
console.log(Number.isNaN(NaN)); // true
// 如果不使用方法,那么可以判断
const n = NaN;
console.log(n!==n); // true, NaN是唯一一个不等于自身的值

// 2. Number.isFinite 是否是有穷数
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite(1024));     // true

// 3. Number.isInteger 是否是整数
console.log(Number.isInteger(1024)); // true
console.log(Number.isInteger(0.99)); // false

// 4. Number.parseInt和Number.parseFloat
// 全局对象上也有parseInt和parseFloat,并且和Number上的是相等的
console.log(Number.parseInt === parseInt); // true

// 开头的空格会被忽略,将开头开始合法的字符串转换成数字,如果从头到尾都不合法,则返回NaN
// 第二个参数可以指定进制,默认10进制
// Number.parseInt 将字符串转换成整数,如果无法转换则返回NaN
console.log(Number.parseInt('3.14 Code'));   // 3
// Number.parseFloat 将字符串转换成整数,如果无法转换则返回NaN
console.log(Number.parseFloat('3.14 Code')); // 3.14
// 转换成数字优先使用parseInt和parseFloat,而不是Number

// 5. toFixed()方法 转换成保留几位小数的字符串
console.log(1024..toFixed(2)); // '1024.00' 

boolean

布尔值有两个字面量,分别是true(真)和false(假),用于表达真假

布尔字面量
const win = true;
const lose = false; 

// 布尔值区分大小写,只有全小写的true/false才是布尔值
// True,False这种都不是布尔值,而是标识符
truthy(真值)和falsy(假值)

和布尔值不同,真值表示会被转换成true的值,而falsy则是会被转换成false的值 目前的falsy值有undefined、null、''、0、NaN以及false本身,除去falsy值,其他都是真值

注意: 任何值都有对应的布尔值,所以!!和Boolean对任何值进行转换都是可以的

// 可以通过Boolean()或者!!将值转换成true或者false
console.log(Boolean(undefined)); // false
console.log(!!undefined);  // false
 
// 真/假值在JS中使用非常频繁,常见有
if(true){console.log('真值')} // if条件判断,真值时才会执行代码块中的代码
const source = true && 80;   // 逻辑运算符 && 和 ||
true ? '真': '假'             // 三元运算符

undefined

undefined表示声明未定义的变量/参数的初始值,undefined类型只有一个值undefined

undefined是全局对象的属性,同时也是一个字面量

// 基本使用
let u1; // 默认没有进行赋值就是undefined 
let u2 = undefined; // 手动赋值undefined,不推荐

// 运算符时提及,void运算符返回undefined,平时不一定要这样写,但是如果看到要知道什么意思
let v = void 0;

// 访问对象上没有属性/方法
const obj = {};
console.log(obj.name); // undefined

// 函数参数没有传值
function test(num) {
  console.log(num);
}
test(); // undefined

// 函数没有return,默认返回undefined
function test(){}
console.log(test()); // undefined 

null

null类型只有一个值null,是一个字面量,指代一个空指针,即未设置值的对象

let obj = null; 
obj = {};

// 需要注意一点,也是面试常问
console.log(typeof null); // 'object'
// 原因
// 在JS中, 值是有一个表示类型的标签和实际值组成,对象的标签为000
// null表示空指针、对象无引用,其值全为0,标签也是0 (早期设计,现在无法修复)
// 而typeof是根据标签来判断的,所以typeof null返回 'object'
null 和 undefined 异同
// 相同点:
// 1. 都是falsy 
console.log(!!null); // false
console.log(!!undefined); // false

// 2. 在JS,null和undefined非严格相等
console.log(null == undefined); // true
console.log(null === undefined); // false

// 3. 无妨访问属性和方法,因为无构造函数
// 前情回顾
// 3.1 null和undefined是没有构造函数的
// 3.2 基本数据类型是没有属性和方法的
// 3.3 基本数据类型能调用属性和方法是因为JS引擎使用构造函数将其转换成包装对象
// 综上所述,null和undefined没有属性和方法,不能访问,否则 类型异常:无法从null/undefined获取属性 
console.log(undefined.title); // TypeError:Cannot read property 'title' of undefined
console.log(null.title); // TypeError:Cannot read property 'title' of null


// 不同点:
// 1. 含义不同
// null表示空指针、对象无引用地址,而undefined表示缺少值、未被定义的值

// 2. 转换成数字时,null转换成0,而undefined转换成NaN
console.log(+null); // 0
console.log(+undefined); // NaN

// 3. null是一个字面量(值),而undefined既是全局的一个属性,也是字面量
// 可以通过赋值检验
null = {}; // SyntaxError:Invalid left-hand side in assignment - 语法异常:等号左侧不合法
undefined = {}; // 正常运行,不过undefined无法被修改

// 4. 是否会赋值默认值
// null不会赋默认值,而undefined会赋默认值,因为默认不传递参数时,就是undefined
function test(num = 1024){ // 1024是默认值,当num为undefined时赋值
  console.log(num);
}

test(null); // null
test(undefined); // 1024

Symbol

symbol是ES6新增的基本数据类型,表示唯一不重复的值, 使用Symbol函数创建Symbol类型的值,主要用来解决JS对象属性名都是字符串、容易出现属性名冲突的问题 symbol接收字符串,非字符串的值会先被转换成字符串,再创建Symbol值

基本使用

// 1. 基本使用
const s1 = Symbol();
const s2 = Symbol('des'); // 接受一个描述信息
console.log(s2.description); //des, 获取Symbol的描述信息,ES2019新增

// 2. Symbol每次调用都会创建一个新的Symbol,描述只是利于区分,描述相同也不是同一个值
const foo1 = Symbol('foo');
const foo2 = Symbol('foo');
console.log(foo1 === foo2); // false

// Symbol不是一个完整的构造函数,不支持new关键字,所以也没有对应的包装对象
const info = new Symbol(); // TypeError: Symbol is not a constructor - 类型异常: Symbol不是一个构造函数

Symbol类型转换

// 1. 使用构造函数是显式类型转换,使用运算符进行运算时,是隐式类型转换
// Symbol只能
// 2. Symbol只能显式转换成 String和Boolean
console.log(String(Symbol()));  // 'Symbol()'
console.log(Boolean(Symbol())); // true
console.log(Number(Symbol()));  // TypeError: Cannot convert a Symbol value to a number - 类型异常: 无法将Symbol转换成number

作为属性名

Symbol的主要用途是作为对象的key,避免属性名冲突,一般搭配[]使用,需要理解对象 - Object

// 1. symbol作为key的设置和读取 
let s1 = Symbol();
let s2 = Symbol();
let s3 = Symbol();
// 使用[]将symbol作为key
let obj = { [s1]: 'Symbol - 1',  s1: 'String' };
obj[s2] = 'Symbol - 2';

// 使用Object.defineProperty设置
Object.defineProperty(obj, s3, { value: 'Symbol - 3' });

// 获取的话也使用[]获取
console.log(obj.s1);  // String,使用.运算符获取的是key为's1'的字符串对应的值 
console.log(obj[s1]); // Symbol - 1
console.log(obj[s2]); // Symbol - 2

// 注意
// 1. Symbol作为属性名无法通过Object.getOwnPropertyNames获取
console.log(Object.getOwnPropertyNames(obj)); // []

// 2. for .. in .. 遍历时,获取不到symbol的key
for (const key in obj){
  console.log(key); // s1
}

// 3. 可以通过Object.getOwnPropertySymbols获取
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(), Symbol(), Symbol() ]

Symbol.keyFor和Symbol.for

有时我们也需要使用到相同的Symbol,JS也为我们提供了相应的机制

JS存在一个全局symbol注册表,Symbol.for方法会在注册表中进行注册,如果注册表中已经存在该描述注册的Symbol,则直接使用,否则会创建。

Symbol.keyFor 获取全局symbol注册表中的symbol对应的描述,如果不在注册表中,则返回undefined

// 1. Symbol.for
const s = Symbol('des'); // 每次Symbol调用都会创建一个新的symbol
const s1 = Symbol.for('des'); // 使用des创建一个Symbol并登记到注册表
const s2 = Symbol.for('des'); // des创建的symbol已经存在,无需创建直接使用,所以 s1 === s2

console.log(s === s1);  // false
console.log(s1 === s2); // true

//总结:使用Symbol.for并且描述相同的Symbol是同一个symbol

// 2. Symbol.keyFor 获取注册表中的symbol对应的描述
const sDes = Symbol.keyFor(s);
console.log(sDes); // undefined, Symbol()的方式不会在注册表登记,所以返回undefined

const s1Des = Symbol.keyFor(s1);
console.log(s1Des); // des

实用内置Symbol

ECMAScript除了可以自定义Symbol以外,还提供了内置Symbol值,以Symbol的静态属性形式存在,以下列举部分比较有意思的Symbol值,详细可参考 MDN - Symbol

Symbol.hasInstance

判断对象是否是构造函数的实例,作用于构造函数

class Arr{
  static [Symbol.hasInstance](instance){
    // instance是接收的实例
    return false; 
  }
}

const arr = new Arr();
// arr是Arr构造函数new出来的,正常来说arr instanceof Arr应该是true
// 但是我们通过 Symbol.hasInstance改变的判断结果,所以返回了false
console.log(arr instanceof Arr); // false
Symbol.toPrimitive

在对象转换成基本数据类型时调用,可以控制对象转换成基本数据类型的结果,转换成boolean不会触发,优先级Symbol.toPrimitive > valueOf > toString

const obj = {};
console.log(+obj);         // NaN
console.log(''+obj);       // [object Object]
console.log(String(obj)) ; // [object Object] 
console.log(`${obj}`);     // [object Object]

obj[Symbol.toPrimitive]=function(hint){
  // hint是会被转换成什么类型,共有3个取值
  // 通过+和Number()转换成number时,hint为number
  if(hint === 'number') return 1024;
  // 通过String()和模板字符串类型转换时,hint为string
  if(hint === 'string') return 'primitive';
  // 通过和字符串进行拼接 ''+ , hint为default
  if(hint === 'default') return true;
}


// +会先将对象转换成基本数据类型 - 默认是字符串
console.log(+obj);         // 1024, hint - number
console.log(Number(obj));  // 1024, hint - number
console.log(String(obj)) ; // primitive, hint - string
console.log(`${obj}`);     // primitive, hint - string
console.log(''+obj);       // true, hint - default
Symbol.toStringTag
// 调用toString()方法会打印出 [object tag]形式的字符串,Symbol.toStringTag就是这个tag
const obj = {};
console.log(obj.toString()); // [object Object]
obj[Symbol.toStringTag] = 'MyObject';
console.log(obj.toString()); //[object MyObject]
Symbol.iterator

为对象定义迭代器,可以使用for..of..遍历, 对象默认是不能遍历的,而数组和字符串是有默认的迭代器的,可参考迭代器 - iterator

const hero = { name: 'Marco', type: 'AD' };
// 为对象hero设置迭代器
hero[Symbol.iterator] = function *(){
  for (const key in hero) {
    yield hero[key];
  }
}
// 有了迭代器就可以被for..of..遍历了
for (const value of hero) {
  console.log(value);
}

for(const value of [1,2,3]){
  console.log(value); // 1 ,  2 , 3
}
Symbol.isConcatSpreadable

控制数组和类数组对象作为Array.prototype.concat()的参数时,是否可以展开

let oddNums = [];
const odd = [1,3,5,7];
odd[Symbol.isConcatSpreadable] = false;

oddNums = oddNums.concat(odd);  
console.log(oddNums);   // [ [1,3,5,7] ], odd没有被展开,整个数组作为一个值拼接到nums

let evenNums = [];
const even = [2,4,6,8];

evenNums = evenNums.concat(even); 
console.log(evenNums);  // [2,4,6,8], even被展开进行拼接

// 类数组对象,即有着和数组同样结构的对象,数字字符串的key以及length
// 默认情况下,类数组是不能展开的
const likeArrayObj = {
  0: '0',
  1: '1',
  a: '2',
  length: 2,
}

// 可以注释这行 来观察nums的值
likeArrayObj[Symbol.isConcatSpreadable] = true; 

let nums = [];
nums = nums.concat(likeArrayObj);

console.log(nums); // ['0', '1'],如果没有设置可以展开则是[ { '0': '0', '1': '1', length: 2 } ]

BigInt

BigInt是ES10新增的基本数据类型,可以表示的数字的范围没有限制,即任意大的数字都可以使用BigInt表示

基本使用
let code = 1024n; // 在数字后加n,就是BigIn的类型
let code2 = BigInt('1024'); // 使用构造函数将数字或者字符串数字转换成BigInt

// 注意:BitInt无法隐式类型转换成Number,但是可以隐式转换成String、Boolean
console.log('' + code); // 1024
console.log(!!code); // true
console.log(Number(code)); // 1024, Number()是显式转换
console.log(parseInt(1024n)); // BigInt可以通过parseInt转换成Number

// BigInt无法和number进行操作,因为它无法隐式转换成Number
console.log(+code); // TypeError:Cannot convert a BigInt value to a number - 类型异常: 无法将BigInt转换成Number
console.log(1024 + code); // TypeError:Cannot mix BigInt and other types, use explicit conversions - 类型异常: 不能将BigInt和其他类型混合使用,请使用显式转换

// BigInt和Symbol一样,构造函数(不完整)只具备转换类型的作用,不能使用new关键字新建实例
const bi = new BigInt(1024);  // TypeError:BigInt is not a constructor  - 类型异常: BigInt不是一个构造函数
比较

BigInt和number不能隐式转换,意味着不能直接进行运算,但是比较是可以直接进行比较的

console.log(1024n > 10); // true

// BigInt和Number不严格相等
console.log(0n == 0);    // true
console.log(0n === 0);   // false
无法序列化
const obj = {
  num: 1024n
}

console.log(JSON.stringify(obj)); // TypeError:Do not know how to serialize a BigInt - 类型错误: 不知道如何序列化BigInt