文章目录
- js 类型判断
- Object.prototype.toString.call 方法的理解与实现
- 一、对Object.prototype.toString.call方法的理解
- 二、Object.prototype.toString.call方法的实现原理
- 三、简单的模拟实现示例
- Object.prototype上的toString方法 理解应用
- 补充(symbol/BigInt)
- NaN or Number.isNaN
- JSON
js 类型判断
- JavaScript数据类型概述
- JavaScript中有两种数据类型:基本数据类型(也叫基础类型)和复合数据类型(也叫引用类型)。基本数据类型的值是不可变的,而复合数据类型的值是可变的,因为它们存储的是引用地址,通过引用可以修改对象的内容。
- 基础类型及判断方法
- Number(数字)类型
- 包括整数和浮点数,例如
1
,3.14
等。 - 判断方法:
- 使用
typeof
操作符,typeof 1
返回'number'
,typeof 3.14
也返回'number'
。但是typeof NaN
也返回'number'
,需要注意NaN
是一个特殊的数字值,表示“不是一个数字”,但它的数据类型仍然是Number
。
- 使用
- 包括整数和浮点数,例如
- String(字符串)类型
- 由零个或多个字符组成,用单引号或双引号括起来,如
'hello'
,"world"
。 - 判断方法:
typeof 'hello'
返回'string'
,typeof "world"
也返回'string'
。
- 由零个或多个字符组成,用单引号或双引号括起来,如
- Boolean(布尔)类型
- 只有两个值
true
和false
,用于逻辑判断。 - 判断方法:
typeof true
返回'boolean'
,typeof false
返回'boolean'
。
- 只有两个值
- null类型
- 表示一个空值,它是一个特殊的关键字,只有一个值
null
。 - 判断方法:
typeof null
返回'object'
(这是JavaScript中的一个历史遗留问题),要准确判断是否为null
可以使用value === null
(value
为要判断的变量)。
- 表示一个空值,它是一个特殊的关键字,只有一个值
- undefined类型
- 当一个变量声明但未赋值时,它的值是
undefined
。 - 判断方法:
typeof undefined
返回'undefined'
。
- 当一个变量声明但未赋值时,它的值是
- Number(数字)类型
- 复合类型及判断方法
- Object(对象)类型
- 包括普通对象(
{}
)、数组([]
)、函数(function(){}
)等。 - 判断方法:
- 对于普通对象,可以使用
typeof
操作符,typeof {}
返回'object'
。但是对于数组和函数,typeof
操作符不能准确判断。对于数组,Array.isArray([])
返回true
,可以准确判断是否为数组。对于函数,function isFunction(value) { return typeof value === 'function'; }
,使用这样的自定义函数可以判断是否为函数。例如isFunction(function(){})
返回true
。
- 对于普通对象,可以使用
- 包括普通对象(
- Array(数组)类型
- 是一种特殊的对象,用于存储多个值,如
[1, 2, 3]
。 - 判断方法:
- 除了上面提到的
Array.isArray
方法外,还可以通过Object.prototype.toString.call([1,2,3])
返回'[object Array]'
来判断。这种方法可以在一些不支持Array.isArray
的旧环境中使用。
- 除了上面提到的
- 是一种特殊的对象,用于存储多个值,如
- Function(函数)类型
- 函数是一种特殊的对象,可以被调用执行一段代码,如
function add(a,b) { return a + b; }
。 - 判断方法:
- 除了自定义
isFunction
函数外,还可以通过Object.prototype.toString.call(add)
返回'[object Function]'
来判断。
- 除了自定义
- 函数是一种特殊的对象,可以被调用执行一段代码,如
- Object(对象)类型
在JavaScript中,准确判断数据类型对于编写健壮的代码非常重要,特别是在处理不同类型的数据进行不同操作的场景下,要根据具体情况选择合适的判断方法。
Object.prototype.toString.call 方法的理解与实现
一、对Object.prototype.toString.call方法的理解
-
本质
Object.prototype.toString.call()
是JavaScript中用于获取对象内部[[Class]]
属性值(在ES6规范之前,类似于对象的类型标识,现在更准确地说是对象的规范类型标签)的一种方法。它返回一个形如"[object <类型名称>]"
的字符串,其中<类型名称>
就是对象的具体类型标识。
-
用途
- 准确判断数据类型:在JavaScript中,
typeof
操作符虽然可以判断一些基本数据类型,但对于像数组、正则表达式等特殊对象类型的判断并不准确。例如,typeof []
返回"object"
,而Object.prototype.toString.call([])
则能准确返回"[object Array]"
,从而清晰地表明该对象是数组类型。同样,对于函数、日期对象等,它也能准确给出其类型标识,如Object.prototype.toString.call(function() {})
返回"[object Function]"
,Object.prototype.toString.call(new Date())
返回"[object Date]"
等。 - 区分不同类型的对象:当需要针对不同类型的对象执行不同的操作时,通过该方法可以精确地确定对象类型,进而编写更具针对性和健壮性的代码。比如在一个处理多种数据类型的函数中,先判断传入数据的准确类型,再根据类型进行相应的处理。
- 准确判断数据类型:在JavaScript中,
二、Object.prototype.toString.call方法的实现原理
-
toString方法的原生行为
- 在JavaScript中,每个对象都继承自
Object.prototype
,而Object.prototype
上定义了toString
方法。当直接在一个对象上调用toString
方法(如myObject.toString()
)时,其默认行为是返回一个字符串,这个字符串通常是"[object Object]"
,除非该对象自己重写了toString
方法来返回其他有意义的值。
- 在JavaScript中,每个对象都继承自
-
call方法的作用
call
是函数的一个方法,它允许我们改变函数内部的this
指向,并立即执行该函数。当我们使用Object.prototype.toString.call(obj)
时,实际上是将obj
作为this
传递给了Object.prototype.toString
函数。这样做的目的是为了让toString
函数在判断类型时,是基于我们传入的具体对象obj
来获取其内部的[[Class]]
属性值,而不是基于Object.prototype
自身(因为直接调用Object.prototype.toString()
会返回"[object Object]"
,这不是我们想要的针对具体对象的类型判断结果)。
三、简单的模拟实现示例
以下是一个简单的模拟实现Object.prototype.toString.call
方法功能的示例代码:
function customToStringCall(obj) {
var toString = Object.prototype.toString;
return toString.call(obj);
}
// 测试示例
var arr = [1, 2, 3];
var func = function() {};
var date = new Date();
console.log(customToStringCall(arr)); // "[object Array]"
console.log(customToStringCall(func)); // "[object Function]"
console.log(customToStringCall(date)); // "[object Date]"
在这个示例中,我们定义了一个函数customToStringCall
,它接受一个对象作为参数。在函数内部,我们先获取了Object.prototype
上的toString
方法,然后通过call
方法将传入的对象作为this
传递给toString
方法,从而实现了类似Object.prototype.toString.call
的功能,能够准确判断对象的类型并返回相应的类型标识字符串。
需要注意的是,这只是一个简单的模拟实现,实际的JavaScript引擎内部对于Object.prototype.toString.call
的实现要复杂得多,涉及到更多关于对象模型、类型系统等方面的底层机制。但通过这个模拟实现,可以帮助我们更好地理解其工作原理。
Object.prototype上的toString方法 理解应用
以下是一个简单的示例来展示Object.prototype
上的toString
方法的基本使用情况以及通过模拟它的部分行为来加深理解:
// 定义一个简单对象
let myObject = {
name: 'John',
age: 30
};
// 直接在对象上调用toString方法(这种情况下会返回默认格式的字符串)
let defaultToStringResult = myObject.toString();
console.log(defaultToStringResult);
// 输出: [object Object],因为没有重写toString方法,所以返回默认的表示形式
// 现在来模拟一下Object.prototype.toString方法的部分行为
// 定义一个函数来模拟获取对象类型标识的功能
function customToString(obj) {
if (obj === null) {
return "[object Null]";
} else if (obj === undefined) {
return "[object Undefined]";
} else {
let type = typeof obj;
if (type === 'object') {
if (Array.isArray(obj)) {
return "[object Array]";
} else if (obj instanceof Date) {
return "[object Date]";
} else if (obj instanceof Function) {
return "[object Function]";
} else {
return "[object Object]";
}
} else {
return `[object ${type}]`;
}
}
}
// 测试自定义的toString函数
console.log(customToString(myObject));
// 输出: [object Object],和直接调用myObject.toString()结果相同
let myArray = [1, 2, 3];
console.log(customToString(myArray));
// 输出: [object Array]
let myFunction = function() {};
console.log(customToString(myFunction));
// 输出: [object Function]
let myDate = new Date();
console.log(customToString(myDate));
// 输出: [object Date]
let myNullValue = null;
console.log(customToString(myNullValue));
// 输出: [object Null]
let myUndefinedValue = undefined;
console.log(customToString(myUndefinedValue));
// 输出: [object Undefined]
在上述代码中:
- 首先创建了一个普通对象
myObject
,并直接调用了它的toString
方法,得到了默认的[object Object]
结果。 - 然后定义了一个
customToString
函数来模拟Object.prototype.toString
方法获取对象类型标识的功能。这个函数通过判断对象的不同情况(是否为null
、undefined
,以及如果是object
类型再进一步判断是数组、日期、函数等具体类型)来返回相应的[object <类型>]
格式的字符串。 - 最后对不同类型的对象(普通对象、数组、函数、日期、
null
、undefined
)分别用自定义函数进行了测试,展示了不同情况下的输出结果,类似于Object.prototype.toString
方法在不同对象上的表现。
不过需要注意的是,这只是一个非常简化的模拟,实际的Object.prototype.toString
方法在JavaScript引擎内部的实现要复杂得多,涉及到更多关于对象模型、类型系统等方面的底层机制。
补充(symbol/BigInt)
- Symbol类型
- 基本概念:Symbol是ES6引入的一种新的原始数据类型,它的值是唯一的,用于创建对象的私有属性。Symbol值通过
Symbol()
函数生成,每次调用Symbol()
都会返回一个新的、独一无二的Symbol值。 - 应用场景和实例
- 对象属性名的唯一性:
- 在JavaScript中,对象的属性名通常是字符串。但有时候我们希望某些属性是独一无二的,这时候就可以使用Symbol。例如,我们有一个对象来存储用户的信息,其中包括一些公共属性和一些内部使用的属性。
let user = { name: 'John', age: 30 }; let internalProperty = Symbol('internal'); user[internalProperty] = 'This is an internal value'; console.log(user); // 输出: {name: "John", age: 30, Symbol(internal): "This is an internal value"}
- 在这个例子中,
internalProperty
是一个Symbol值,它被用作user
对象的属性名。这样可以确保这个属性不会与其他可能的属性名冲突,因为Symbol值是唯一的。
- 实现类的私有属性(在JavaScript的模拟意义上):
- 虽然JavaScript没有像传统面向对象语言那样严格的私有属性概念,但可以使用Symbol来模拟。例如,我们有一个简单的类(使用函数和原型来模拟类)来表示一个计数器。
let counterSymbol = Symbol('counter'); function Counter() { this[counterSymbol] = 0; } Counter.prototype.increment = function() { this[counterSymbol]++; }; Counter.prototype.getValue = function() { return this[counterSymbol]; }; let myCounter = new Counter(); myCounter.increment(); console.log(myCounter.getValue()); // 输出: 1
- 在这个
Counter
类中,counterSymbol
是一个Symbol值,用来存储计数器的当前值。这个值在外部无法直接访问,只能通过getValue
方法来获取,从而实现了一种模拟的私有属性效果。
- 避免命名冲突的全局常量池:
- 在一个大型的JavaScript应用中,可能会有多个模块或库使用相同的属性名。为了避免冲突,可以使用Symbol来创建全局常量池。例如,假设有两个模块,一个是用户认证模块,一个是用户权限模块。
// 用户认证模块 let AUTH_SYMBOL = Symbol('auth'); let userAuth = { [AUTH_SYMBOL]: { token: 'abc123', expires: '2024-12-31' } }; // 用户权限模块 let PERMISSION_SYMBOL = Symbol('permission'); let userPermission = { [PERMISSION_SYMBOL]: ['read', 'write'] };
- 这里使用Symbol来定义每个模块的关键属性,这样可以确保即使两个模块的属性在概念上可能不同,但不会因为使用了相同的字符串属性名而产生冲突。
- 对象属性名的唯一性:
- 基本概念:Symbol是ES6引入的一种新的原始数据类型,它的值是唯一的,用于创建对象的私有属性。Symbol值通过
- BigInt类型
- 基本概念:BigInt是ES11引入的数据类型,用于表示任意精度的整数。在JavaScript中,
Number
类型有一定的范围限制,对于非常大的整数会出现精度问题。BigInt可以处理这些超大型整数。 - 应用场景和实例
- 处理超大整数运算:
- 在密码学、金融计算等领域,经常需要处理非常大的整数。例如,在计算阶乘时,数字可能会迅速增长到超出
Number
类型的范围。
function factorial(n) { let result = BigInt(1); for (let i = BigInt(2); i <= BigInt(n); i++) { result *= i; } return result; } console.log(factorial(100)); // 输出一个非常大的整数,用BigInt表示,不会出现精度问题
- 这个例子中,通过使用BigInt类型,我们可以准确地计算出100的阶乘,而不会像使用
Number
类型那样因为精度问题得到错误的结果。
- 在密码学、金融计算等领域,经常需要处理非常大的整数。例如,在计算阶乘时,数字可能会迅速增长到超出
- 数据库中的大整数存储和运算(与后端交互):
- 当JavaScript应用与后端数据库交互时,数据库中可能存储了非常大的整数。例如,在一个存储全球用户编号的系统中,用户编号可能是一个巨大的整数。
// 假设从数据库获取了一个大整数的用户编号 let userIDFromDB = BigInt('123456789012345678901234567890'); console.log(userIDFromDB); // 可以对这个大整数进行各种运算,如加、减、乘、除等操作 let newID = userIDFromDB + BigInt(1); console.log(newID);
- 这里展示了如何从数据库获取一个大整数(以字符串形式存储,因为JavaScript的
Number
类型无法直接处理这么大的数字),并将其转换为BigInt类型,然后进行简单的加法运算。
- 高精度计算场景:
- 在一些科学计算或算法竞赛中,可能需要进行高精度的整数计算。例如,计算两个非常大的质数相乘。
let prime1 = BigInt('99999999999999999999'); let prime2 = BigInt('88888888888888888888'); let product = prime1 * prime2; console.log(product);
- 这个例子展示了使用BigInt类型来计算两个超大质数的乘积,确保计算结果的准确性。
- 处理超大整数运算:
- 基本概念:BigInt是ES11引入的数据类型,用于表示任意精度的整数。在JavaScript中,
NaN or Number.isNaN
isNaN
函数介绍- 基本定义:
isNaN()
是一个全局函数,用于判断一个值是否为NaN
(Not - a - Number)。它的参数会被强制转换为数字类型,然后判断转换后的结果是否为NaN
。 - 应用场景和原理:
- 当从用户输入或者其他数据源获取到一个可能是数字的值时,需要判断这个值是否为有效数字。例如,在一个表单验证的函数中,检查用户输入的是否是一个有效的数字。
function validateNumber(input) { return!isNaN(input); } console.log(validateNumber('123')); // true console.log(validateNumber('abc')); // false console.log(validateNumber(NaN)); // false
- 在这个例子中,
isNaN
函数会尝试将输入的input
值转换为数字。如果转换成功且不是NaN
,则返回false
,函数validateNumber
返回true
,表示是一个有效数字;如果转换后是NaN
,则返回true
,validateNumber
返回false
。 - 它也可以用于检查数学运算的结果是否为
NaN
。例如,在进行除法运算时,当除数为0时,结果是NaN
。
function divide(a, b) { let result = a / b; return!isNaN(result); } console.log(divide(10, 2)); // true console.log(divide(5, 0)); // false
- 基本定义:
Number.isNaN
函数介绍- 基本定义:
Number.isNaN()
是ES6新增的方法,它与isNaN
不同,不会对传入的参数进行类型转换,直接判断参数是否为NaN
。 - 应用场景和原理:
- 在需要精确判断一个值是否为
NaN
,而不希望有任何类型转换干扰的场景下使用。例如,在一个处理数字数组的函数中,要判断数组中的某个元素是否为NaN
。
function checkArrayForNaN(arr) { for (let i = 0; i < arr.length; i++) { if (Number.isNaN(arr[i])) { return true; } } return false; } let numberArray = [1, 2, NaN]; console.log(checkArrayForNaN(numberArray)); // true let stringArray = ['a', 'b', 'c']; console.log(checkArrayForNaN(stringArray)); // false
- 在这里,
Number.isNaN
直接判断数组元素是否为NaN
,不会对字符串等非数字类型进行转换后再判断,所以对于stringArray
,它不会产生误判。
- 在需要精确判断一个值是否为
- 基本定义:
- 两者的区别
- 类型转换方面:
isNaN
会对传入的参数进行类型转换,将其转换为数字类型后再判断是否为NaN
。例如,isNaN('abc')
会先尝试将'abc'
转换为数字(转换结果为NaN
),然后返回true
。Number.isNaN
不会进行类型转换,直接判断传入的参数是否是NaN
。所以Number.isNaN('abc')
返回false
,因为'abc'
不是NaN
。
- 应用场景方面:
isNaN
在一些不太严格的场景下使用,比如简单的用户输入验证,只要最终转换后不是NaN
就可以认为是有效的数字相关内容。Number.isNaN
用于更精确的判断,尤其是在处理已经确定是数字类型或者需要严格判断是否为NaN
的场景,如在数学计算后的结果检查或者处理纯数字数据结构中的元素判断。
- 类型转换方面:
JSON
- JSON的基本概念
- JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于JavaScript的语法,但可以被多种编程语言使用,用于在不同系统之间传递数据。JSON数据格式简洁、易于阅读和编写,并且易于机器解析和生成。
- 它主要由两种结构组成:对象(Object)和数组(Array),并且可以包含基本数据类型,如字符串(String)、数字(Number)、布尔值(Boolean)和特殊值(null)。
- JSON对象的结构和特点
- 对象结构
- JSON对象是一个无序的“键 - 值”对集合,用花括号
{}
包裹。键是一个字符串,必须用双引号""
括起来,值可以是任意合法的JSON数据类型,包括其他对象、数组、基本数据类型等。例如:
{ "name": "John", "age": 30, "city": "New York", "hobbies": ["reading", "traveling"], "address": { "street": "123 Main St", "zip": "10001" } }
- 在这个例子中,
name
、age
、city
等是对象的键,它们对应的"John"
、30
、"New York"
等是值。hobbies
是一个包含字符串的数组,address
是一个嵌套的对象。
- JSON对象是一个无序的“键 - 值”对集合,用花括号
- 特点
- 自我描述性:JSON对象的结构使得数据具有自我描述性。从键的名称可以大致了解对应值的含义。例如,通过
"age": 30
,很容易理解这个数据是关于某个人的年龄信息。 - 层次结构:可以通过嵌套对象和数组构建复杂的层次结构。如上述例子中的
address
对象嵌套在主对象中,用于存储更详细的地址信息;hobbies
数组用于存储多个兴趣爱好。
- 自我描述性:JSON对象的结构使得数据具有自我描述性。从键的名称可以大致了解对应值的含义。例如,通过
- 对象结构
- JSON数组的结构和特点
- 数组结构
- JSON数组是一个有序的值的集合,用方括号
[]
包裹,数组中的元素可以是任意合法的JSON数据类型,包括其他数组、对象、基本数据类型等。例如:
[ { "id": 1, "product": "Book", "price": 19.99 }, { "id": 2, "product": "Pen", "price": 1.99 } ]
- 这里是一个包含两个对象的数组,每个对象代表一个商品的信息,包括商品的
id
、product
名称和price
。
- JSON数组是一个有序的值的集合,用方括号
- 特点
- 有序性:与对象不同,数组中的元素是有序的。这使得它适用于表示按特定顺序排列的数据,如列表、序列等。在上面的商品列表例子中,可以通过索引来访问每个商品,如
array[0]
表示第一个商品对象,array[1]
表示第二个商品对象。 - 可扩展性:可以轻松地添加或删除元素来更新数据。例如,要添加一个新商品,只需将新的商品对象插入到数组中合适的位置即可。
- 有序性:与对象不同,数组中的元素是有序的。这使得它适用于表示按特定顺序排列的数据,如列表、序列等。在上面的商品列表例子中,可以通过索引来访问每个商品,如
- 数组结构
- JSON的应用场景
- 网络数据传输
- JSON是网络应用中最常用的数据传输格式之一。在前后端分离的架构中,后端服务器将数据以JSON格式发送给前端浏览器。例如,一个电商网站的后端API返回商品列表、用户订单等信息时,通常使用JSON格式。这样前端JavaScript代码可以轻松地解析这些数据并展示给用户。
- 配置文件
- 许多软件和应用使用JSON作为配置文件格式。它比传统的INI文件或XML文件更简洁,易于理解和编辑。例如,一个Node.js应用的配置文件可能如下所示:
{ "port": 3000, "database": { "host": "localhost", "user": "admin", "password": "123456", "database": "mydb" }, "logging": { "level": "info" } }
- 这个配置文件定义了应用运行的端口、数据库连接信息和日志记录级别等重要参数。
- 数据存储(与数据库交互)
- 一些数据库支持直接存储和读取JSON格式的数据。例如,MongoDB是一种流行的NoSQL数据库,它以BSON(Binary JSON)格式存储数据,与JSON非常相似。在这种数据库中,可以将复杂的数据结构,如包含嵌套对象和数组的用户文档,直接存储为JSON - like的数据格式,方便数据的存储和查询。
- 网络数据传输