1.3.2 web-Js基础-JavaScript基础数据类型

169 阅读13分钟

JavaScript基础数据类型

1. 值类型与引用类型

1.1 数据类型分类

  • 基本数据类型:String Boolean Number Symbol(ES6新增) Undefined Null
  • 复杂数据类型:Object
  • 基本数据类型中有两个为特殊数据类型: null undefined
  • 常见内置对象:Boolean Number String Array Math Date RegExp Function...

Symbol(ECMAScript 6 新定义数据类型)暂时不涉及

1.2 数据访问

  • 基本数据类型在栈空间存储,访问方式是按值访问。基本类型(值类型)在调用函数的时候,传递的是值。
  • 引用类型的对象在堆中存储,地址在栈中存储。引用类型,在函数调用的时候,传递的是地址(引用)。

具体如下图所示:

js数据类型-1.png

function addTen (num) {
  num += 10;
  return num;
}
const count = 20;
const result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30

function setName (obj) {
  obj.name = 'liam';
  // eslint-disable-next-line no-new-object
  obj = new Object();
  obj.name = 'tom';
}
// eslint-disable-next-line no-new-object
const person = new Object();
person.name = 'liam1';
setName(person);
console.log(person.name); // "liam"

2. 数据类型判断

2.1 typeof

typeof操作符是检测基本类型的最佳工具

返回结果含义
undefined未定义
boolean布尔值
string字符串
number数值
object对象或null
function函数
对未初始化的变量执行typeof操作符会返回undefined值,而对未声明的变量执行typeof操作符同样也会返回undefined
let test; // 这个变量声明之后默认取得了 undefined 值
// var age // 下面这个变量并没有声明
console.log(typeof test); // "undefined"
console.log(typeof age); // "undefined"

typeof原理:不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息

  • 000: 对象
  • 010: 浮点数
  • 100:字符串
  • 110:布尔
  • 1:整数

null:null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"

undefined:用 −2^30 整数来表示(一个超出整型范围的数)

ES6 的 typeof 是如何对待函数和对象类型的:

  • 如果一个对象(Object)没有实现 [[Call]] 内部方法,那么它就返回 object
  • 如果一个对象(Object)实现了 [[Call]] 内部方法,那么它就返回 function

一个对象如果支持了内部的 [[Call]] 方法,那么它就可以被调用,就变成了函数,所以叫做函数对象

注意首字母为小写字母 例如: typeof 'abc'=== 'string'

2.2 Instanceof

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上

所以instanceof可以用于判断typeof返回object的引用类型存储值的类型

JavaScript 中一切皆对象,如下代码第3行返回false,其实问题的关键在于typeof str返回string并不是object

const str = 'This is a simple string';
const newStr = new String('String created with constructor');
str instanceof String; // 返回 false, 检查原型链会找到 undefined
newStr instanceof String; // 返回 true

这样 Boolean Number是一样的逻辑。还剩下两种基本类型:Null Undefined undefined 当我们对变量只声明没有初始化时,输出为 undefined,typeof undefined 返回的是 undefined 也不是 object 类型,所以 undefined 并不是任何对象的实例。

null 表示的是空对象,虽然 typeof null 是 object,但是 null 和 undefined 一样并没有任何属性和方法,在 instanceof 定义中也有判断,如果类型不是 object(这个类型判断并不是跟 typeof 返回值一样),就返回 false。

js数据类型转存失败,建议直接上传图片文件

使用 instanceof 就是确定原型和实例之间的关系

const Fn = function () {};
const f1 = new Fn();
console.log(f1 instanceof Fn); // true
console.log(f1 instanceof Function); // false
console.log(f1 instanceof Object); // true

instanceof 还可以在继承关系中用来判断一个实例是否属于它的父类型

// 判断 f2 是否是 Fn2 类的实例 , 并且是否是其父类型的实例
function Father () {}
function Son () {}
Son.prototype = new Father(); // JavaScript 原型继承
const f = new Son();
console.log(f instanceof Son); // true
console.log(f instanceof Father); // true

2.3 instanceof 运算符代码

function instanceOfNew(L, R) { // L 表示左表达式,R 表示右表达式
  const O = R.prototype; // 取 R 的显示原型
  L = L.__proto__; // 取 L 的隐式原型
  while (true) {
    if (L === null) return false;
    if (O === L) { // 这里重点:当 O 严格等于 L 时,返回 true
      return true;
    }
    L = L.__proto__;
  }
}

2.4 constructor

constructor 属性返回对创建此对象的数组函数的引用。

object.constructor 其中object 是一个对象或函数的名称。 constructor 属性是每个具有原型的对象的原型成员。 这包括除 Global 和 Math 对象之外的所有内部 JavaScript 对象。

'abc'.constructor === String; // true
(123).constructor === Number; // true
true.constructor === Boolean; // true
[1, 2].constructor === Array; // true
({ name: 'liam' }).constructor === Object; // true
(() => {}).constructor === Function; // true
new Date().constructor === Date; // true
(/[a-z]/).constructor === RegExp; // true

js数据类型转存失败,建议直接上传图片文件

下面是使用new创建的基础数据类型

const str = new String('abc');
str.constructor === String; // true

在继承关系中用来判断会出现错误

function Father () {}
function Son () {}
Son.prototype = new Father(); // JavaScript 原型继承
const f = new Son();
console.log(f.constructor === Son); // false
console.log(f.constructor === Father); // true

解决construtor的问题通常是让对象的constructor手动指向自己:

function Father () {}
function Son () {}
Son.prototype = new Father(); // JavaScript 原型继承
const f = new Son();
f.constructor = Son; // 将自己的类赋值给对象的constructor属性
console.log(f.constructor === Son); // true
console.log(f.constructor === Father); // 基类不会报true了

2.5 Object.prototype.toString.call(obj)

Object.prototype.toString.call() 最准确最常用的方式。首先获取Object原型上的toString方法,让方法执行,让toString方法中的this指向第一个参数的值。

Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call('abc'); // "[object String]"
Object.prototype.toString.call([]); // "[object Array]"
// eslint-disable-next-line no-new-func
Object.prototype.toString.call(new Function()); // "[object Function]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(new RegExp()); // "[object RegExp]"
Object.prototype.toString.call(new Error()); // "[object Error]"
Object.prototype.toString.call(document); // "[object HTMLDocument]"
Object.prototype.toString.call(window); // "[object Window]" window是全局对象global的引用
// 使用new创建的基础数据类型对象
const str = new String();
Object.prototype.toString.call(str); // "[object String]"

3. 数据类型转换

3.1 基础介绍

js在一些操作符的作用下会进行隐式转换,涉及隐式转换最多的两个运算符 +==,下面来介绍一下。

  • + 可能是字符串拼接操作,也可能是数字相加
  • ==不同于===存在数据类型转换
  • - * % / > <等其他操作符只会针对number类型,所以其都是把其他类型转换成数字类型
  • && || !与或非操作符是进行布尔运算的,所以会把他们转换成布尔值

可以看一下下面的例子,下文中会依次介绍

'123' - 1; // 122
'123a' - 1; // NaN
`123${ 1}` // 1231
`${NaN }123a`; // NaN123a
Number(null); // 0
null == 0; // false
null >= 0; // true
2 == true; // false
'' == 0; // true

隐式转换中主要涉及到三种转换:

含义函数名称
将值转为原始值ToPrimitive()
将值转为数字ToNumber()
将值转为字符串ToString()
将值转为布尔值ToBoolean()

3.2 类型转换涉及函数

3.2.1 通过ToNumber将值转换为数字
参数类型转换结果举例
UndefinedNaNundefined +0 // NaN
Null+0null >=0 // true
Booleantrue转换1,false转换为+0true + 1 // 2
String将字符串中内容转化为数字,转化失败返回NaN'123'-1 // 122
Object先用ToPrimitive(obj, Number)转换为原始值,再用ToNumber转换为数字[2]-1 // 1

''空字符串转换为数字为 0

3.2.2 通过ToString将值转换为字符串
参数类型转换结果举例
Undefined'undefined'undefined + '123' // undefined123
Null'null'null + '123' // null123
Boolean转换为'true' 或 'false''false' + '123' // false123
Number数字转换字符串-17.5 + '123' // -17.5123
Object先用ToPrimitive(obj, String)转换为原始值,再用ToString转换为数字[key='name',1]+'123a' // name,1123a
3.2.3 通过ToBoolean将值转换为布尔值
参数类型转换结果举例
Undefinedfalse!!undefined // false
Nullfalse!!null // false
Number0、NaN为false,其他为true!!0 // false
String空字符串(长度为0)为false,其他为true!!'' // false
Objecttrue!![] !!{} // true

除了这六个值(undefined、null 、false、0、NaN、'')转换为false,其他都为true

3.2.4 通过ToPrimitive转换值

调用 ToPrimitive 函数将对象转化为原始类型。先看一下下面的内容,是不是头大,没关系,我们一点点讲解

[2, 3] - 1 // NaN
[] + [] // ""
{} - 1 // -1
({}) - 1 // NaN
{} + 1 // 1
1 + {} // 1[object Object]
[] + {} // [object Object]

ToPrimitive(input, hint)

参数名称参数含义
input将要转换的值
hint转换类型,有Number/String/default三个分类
3.2.4.1 如果hint被标记为Number
  1. input为原始值,直接返回input
  2. 否则,调用input对象的valueOf()方法,如果结果是原始类型,则返回这个原始值
  3. 否则,调用input对象的toString()方法,如果结果是原始类型,则返回这个原始值
  4. 否则,抛出TypeError异常
3.2.4.2 如果hint被标记为String
  1. input为原始值,直接返回input
  2. 否则,调用input对象的toString()方法,如果结果是原始类型,则返回这个原始值
  3. 否则,调用input对象的valueOf()方法,如果结果是原始类型,则返回这个原始值
  4. 否则,抛出TypeError异常
3.2.4.3 如果没有设置hint这个参数时,会按照如下来自动设置
  1. input对象为Date类型,则hint被设置为String
  2. 否则,hint被设置为Number
3.2.4.4 valueOf方法解析
  1. Number、Boolean、String这三种构造函数生成的基础值的对象形式,通过valueOf转换后会变成相应的原始值。
  2. Date这种特殊的对象,其原型Date.prototype上内置的valueOf函数将日期转换为日期的毫秒的形式的数值。
  3. 除此之外返回的都为this,即对象本身:(有问题欢迎告知)
const num = new Number('123');
num.valueOf(); // 123

const time = new Date();
time.valueOf(); // 1545879601992

// eslint-disable-next-line no-array-constructor
const arr = new Array();
arr.valueOf() === arr; // true
3.2.4.5 toString方法解析
  1. Number、Boolean、String、Array、Date、RegExp、Function这几种构造函数生成的对象,通过toString转换后会变成相应的字符串的形式,因为这些构造函数上封装了自己的toString方法。
  2. 除以上对象及其实例化对象之外,其他对象返回的都是该对象的类型,(有问题欢迎告知),都是继承的Object.prototype.toString方法。
// eslint-disable-next-line no-prototype-builtins
Date.prototype.hasOwnProperty('toString'); // true

// eslint-disable-next-line no-prototype-builtins
RegExp.prototype.hasOwnProperty('toString'); // true

// eslint-disable-next-line no-prototype-builtins
Function.prototype.hasOwnProperty('toString'); // true

const arr = new Array([1, 2, 3]);
arr.toString(); // '1,2,3'

const time = new Date();
time.toString(); // "Thu Dec 27 2018 11:06:31 GMT+0800 (中国标准时间)"

const func = function () {
  return '123';
};
func.toString(); // "function () { return '123'}"

// eslint-disable-next-line no-new-object
const obj = new Object({});
obj.toString(); // "[object Object]"

Math.toString(); // "[object Math]"

看一个经典的面试题:a == 1 && a == 2 && a == 3 什么情况下为 true ?

const a = {
  i: 1,
  toString () { // 或 valueOf
    a.i += 1;
    return a.i;
  },
};

// eslint-disable-next-line eqeqeq
console.log(a == 1 && a == 2 && a == 3); // true

a.i 最开始为1,执行 a == 1时,会调用 toString() 方法,得到 true 后,a.i 自增为 2,然后继续执行 a.i == 2,如此依次执行。 如果已经掌握了本文,那么不难知道,这里的 toString 也可以是 valueOf 或 ES6 中的 [Symbol.toPrimitive] 。

3.3 类型转换与操作符

操作符名称转换类型
++/--递增/递减Number
-/+一元加/减Number
-/*//减法/乘法/除法Number
!逻辑非Boolean
3.3.1 expr1 + expr2加法操作符
  • 如果expr1和expr2都是数字,执行常规的加法计算
  • 如果expr1和expr2不全是数字,则把对象、数值或布尔值等,调用它们的 toString()方法取得相应的字符串值。
  • 对于 undefined 和 null,则分别调用 String()函数并取得字符串"undefined"和"null",再将两个字符串拼接起来。
3.3.2 expr1 && expr2
  • 如果expr1 能转换成false则返回expr1,否则返回expr2。
  • 因此,与布尔值一起使用时,如果两个操作数都为true时&&返回true,否则返回false。
3.3.3 expr1 || expr2
  • 如果expr1能转换成true则返回expr1,否则返回expr2。
  • 因此,与布尔值一起使用时,如果任意一个操作数为true时,返回true。
3.3.4 小于(<)、大于(>)、小于等于(<=)和大于等于(>=)
  • 操作符都返回一个布尔值
  • 如果两个操作数都是数值,则执行数值比较。
  • 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值(可以通过字符串的 charCodeAt() 函数获取字符编码值)。
  • 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
  • 如果一个操作数是对象,则调用这个对象的 valueOf() 方法,用得到的结果按照前面的规则执行比较。如果对象没有valueOf()方法,则调用 toString()方法,并用得到的结果根据前面的规则执行比较。
  • 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。
const result1 = 5 - true; // 4,因为true被转换成了1
const result2 = NaN - 1; // NaN
const result3 = '23' < '3'; // true
const result4 = '23' < 3; // false
const result5 = 'a' < 3; // false

// eslint-disable-next-line use-isnan
const result6 = NaN < 3; // false
// eslint-disable-next-line use-isnan
const result7 = NaN >= 3; // false

// eslint-disable-next-line no-constant-binary-expression
console.log(0 || 'NaN'); // 返回 NaN
console.log(NaN || ''); // 返回 '' 空字符串
// eslint-disable-next-line no-constant-binary-expression
console.log(null || 'undefined'); // 返回 undefined

const obj = {};
// eslint-disable-next-line no-constant-binary-expression
console.log(0 && obj); // 返回 0
console.log(obj && 0); // 返回 0
3.3.5 expr1 == expr2
  • 如果一个值是null, 另一个是undefined,则它们相等
  • 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较
  • 如果其中一个值是true,则将其转换为1再进行比较。如果其中一个值是false,则将其转换为0再进行比较
  • 如果一个值是对象,另一个值是数字或字符串,则将对象转换为原始值,然后再进行比较。对象通过toString()方法或者valueOf()方法转换为原始值,JavaScript语言核心的内置类先尝试使用valueOf(),再尝试使用toString(),除了日期类,日期类只能使用toString()转换,那些不是JavaScript语言核心中的对象则通过各自的实现中定义的方法转换为原始值
5. 面试题目
const x = {};
const y = { key: 'y' };
const z = { key: 'z' };
x[y] = 'fatfish';
x[z] = 'medium';
console.log(x[y]);

// 使用一个对象作为属性键,最终会转换为[object Object]
x[y] = 'fatfish'; // x => { [object Object]: "fatfish" }
x[z] = 'medium'; // x => { [object Object]: "medium" }
console.log(x[y]); // medium
const arr = [1, 30, 4, 21, 100000];
console.log(arr.sort());

// 指定定义排序顺序的函数。如果省略,数组元素将转换为字符串,然后根据每个字符的 Unicode 值进行排序。
const fn = () => {
  // eslint-disable-next-line no-multi-assign
  let x = y = 1000;
  x += 1;
  return x;
};

fn();
console.log(typeof x);
console.log(typeof y);

// let x = y = 1000
// 这段代码等同于如下代码;
const x = 1000;
// 在这里,我们其实是声明了一个全局变量 y
y === 1000;

5. 参考文章

【JavaScript:Object.prototype.toString方法的原理】

【JavaScript instanceof 操作符】

你所忽略的js隐式转换

理清JS中等于(==)和全等(===)那些纠缠不清的关系

JavaScript高级程序设计(第3版)