共计 7031 个字符,预计需要花费 18 分钟才能阅读完成。
前言
随着 JavaScript 越来越风行,越来越多地开发者开始接触并应用 JavaScript。
同时我也发现,有不少开发者对于 JavaScript 最根本的原始值和包装对象都没有很清晰的了解。
那么本篇文章,就由渣皮来给大家具体介绍一下它们。
🧐 话不多说,Let’s go!
注释
原始类型 (Primitive types)
原始类型也被称为“根本类型”。
目前在 JavaScript 中有以下几种原始类型:
string
(字符串)number
(数字)boolean
(布尔)null
(空)undefined
(未定义)bigint
(大整数,ES6)symbol
(标记?ES6)
📝 如下:
typeof 'chenpipi'; // "string"
typeof 12345; // "number"
typeof true; // "boolean"
typeof null; // "object"
typeof undefined; // "undefined"
typeof 12345n; // "bigint"
typeof Symbol(); // "symbol"
💡 特地留神
typeof null
尽管返回"object"
,然而这不代表null
就是对象,这其实是 JavaScript 的一个 Bug,且从 JavaScript 诞生以来便如此。在 JavaScript 最后的实现中,JavaScript 中的值是由一个示意类型的标签和理论数据值示意的。对象的类型标签是 0。因为
null
代表的是空指针(大多数平台下值为 0x00),因而,null
的类型标签是 0,typeof null
也因而返回"object"
。The history of“typeof null”:https://2ality.com/2013/10/typeof-null.html
原始值 (Primitive values)
原始值也就是原始类型的值(数据)。
A primitive value is data that is not an object and has no methods.
原始值是一种没有任何办法的非对象数据。
也就是说,string
、number
和 boolean
等原始类型的值自身是没有任何属性和办法的。
😨 这个时候嗅觉敏锐的小伙伴是不是曾经察觉到有什么不对劲了?
是孜然!我加了孜然!(手动狗头并划掉)
🤓 这里有一个十分有意思的点,然而在探讨这个问题之前,先让咱们意识下包装对象。
包装对象 (Wrapper objects)
除了 null
和 undefined
外的原始类型都有其相应的包装对象:
String
(字符串)Number
(数字)Boolean
(布尔)BigInt
(大整数,ES6)Symbol
(标记?ES6)
对象 (Object)
对象是援用类型。
首先,包装对象自身是一个对象,也是函数。
String instanceof Object; // true
String instanceof Function; // true
构造函数 (Constructor)
实例 (Instance)
其中 String
、Number
和 Boolean
均反对应用 new
运算符来创立对应的 包装对象实例。
📝 例如 String
的申明(节选):
interface StringConstructor {new(value?: any): String;
(value?: any): string;
readonly prototype: String;
}
declare var String: StringConstructor;
📝 应用 new
运算符失去的数据是对象(Object):
// 字符串
typeof 'pp'; // "string"
typeof new String('pp'); // "object"
new String() instanceof Object; // true
// 数字
typeof 123; // "number"
typeof new Number(123); // "object"
new Number() instanceof Object; // true
// 布尔
typeof true; // "boolean"
typeof new Boolean(true); // "object"
new Boolean() instanceof Object; // true
📝 咱们能够调用包装对象实例的 valueOf()
函数来获取其原始值:
// 字符串
let s = new String('pp');
s.valueOf(); // "pp"
typeof s.valueOf(); // "string"
// 数字
let n = new Number(123);
n.valueOf(); // 123
typeof n.valueOf(); // "number"
// 布尔
let b = new Boolean(true);
b.valueOf(); // true
typeof b.valueOf(); // "boolean"
“异类”(Attention)
而 BigInt
和 Symbol
都属于“不残缺的类”,不反对 new
运算符。
📝 例如 BigInt
的申明(节选):
interface BigIntConstructor {(value?: any): bigint;
readonly prototype: BigInt;
}
declare var BigInt: BigIntConstructor;
能够看到 BigInt
的申明中没有 new
运算符相干函数。
一般函数 (Function)
包装对象也能够作为 一般函数 来应用。
其中 String()
、Number()
和 Boolean()
函数都能够用来对任意类型的数据进行显式类型转换。
另外 Object()
函数也可用于显式类型转换,但本文不再开展。
String
📝 示例代码:
typeof String(); // "string"
String(); // ""String('pp'); //"pp"String(123); //"123"String(true); //"true"String(false); //"false"String(null); //"null"String(undefined); //"undefined"String([]); //""
String({}); // "[object Object]"
💡 小贴士 1
当咱们应用
String()
函数来转换对象时,JavaScript 会先拜访对象上的toString()
函数,如果没有实现,则会顺着原型链向上查找。🌰 举个栗子:执行
String({toString() {return 'pp';} })
返回的后果是"pp"
,并非"[object Object]"
。所以
String()
函数并不可能用来判断一个值是否为对象(会翻车)。💡 小贴士 2
罕用的判断对象的形式为
Object.prototype.toString({}) === '[object Object]'
。🌰 举个栗子:执行
Object.prototype.toString({toString() {return 'pp';} })
返回的是"[object Object]"
。
Number
📝 示例代码:
typeof Number(); // "number"
Number(); // 0
Number(''); // 0
Number('pp'); // NaN
Number(123); // 123
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
Number([]); // 0
Number({}); // NaN
💡 小贴士
对于
Number()
函数来说,可能最实用的转换就是将true
和false
转换为1
和0
吧。
Boolean
📝 示例代码:
typeof Boolean(); // "boolean"
Boolean(); // false
Boolean(''); // false
Boolean('pp'); // true
Boolean(0); // false
Boolean(1); // true
Boolean(null); // false
Boolean(undefined); // false
Boolean([]); // true
Boolean({}); // true
💡 小贴士
某些状况下,咱们会在数据中应用
0
和1
来示意虚实状态,此时就能够应用Boolean()
进行状态的判断。
BigInt
BigInt()
函数用于将整数转换为大整数。
该函数承受一个整数作为参数,传入参数若为浮点数或任何非数字类型数据都会报错。
📝 示例代码:
BigInt(123); // 123n
BigInt(123n); // 123n
typeof 123n; // "bigint"
typeof BigInt(123); // "bigint"
BigInt & Number
须要留神的是,BigInt
和 Number
是不严格相等(宽松相等)的。
📝 示例代码:
123n === 123; // false
123n == 123; // true
Symbol
Symbol()
函数用于创立一个 symbol
类型的值。
该函数承受一个字符串作为描述符(参数),如果传入其余类型的值则会被转换为字符串(除了 undefined
)。
留神,每一个 symbol
值都是举世无双的,即便它们的描述符都是一样的。
且 symbol
类型的数据只能通过 Symbol()
函数来创立。
📝 示例代码:
// 前面的返回值是 Devtools 模仿进去的,并非理论值
Symbol('pp'); // Symbol(pp)
Symbol(123); // Symbol(123)
Symbol(null); // Symbol(null)
Symbol({}); // Symbol([object Object])
// 类型
typeof Symbol('pp'); // "symbol"
Symbol('pp') === Symbol('pp'); // false
// 描述符
Symbol('pp').description; // "pp"
Symbol(123).description; // "123"
Symbol({}).description; // "[object Object]"
Symbol().description; // undefined
Symbol(undefined).description; // undefined
原始值不是对象 (Primitive not Object)
🎃 有意思的来了~
没有属性和办法 (No properties, no functions)
本文后面有提到:「原始值是一种没有任何办法的非对象数据。」
咱们都晓得对象(Object)上能够有属性和办法。
然而字符串不是对象,所以你不能给字符串减少属性。
📝 做个小试验:
let a = 'chenpipi';
console.log(a.length); // 8
// 尝试减少新的属性
a.name = '吴彦祖';
console.log(a.name); // undefined
// 尝试批改已有的属性
typeof a.slice; // "function"
a.slice = null;
typeof a.slice; // "function"
🎬 渣皮小剧场
此时一位头铁的小伙伴应用了反驳技能。
渣皮你别在这忽悠人了,我平时写 Bug 哦不写代码的时候明明能够调用到字符串、数字和布尔值上的办法!
📝 比方上面这段代码,可能失常执行并失去合乎预期的后果:
// 字符串
let s = 'chenpipi';
s.toUpperCase(); // "CHENPIPI"
'ChenPiPi'.slice(4); // "PiPi"
// 数字
let n = 123;
n.toString(); // "123"
(123.45).toFixed(2); // "123.5"
// 布尔值
let b = true;
b.toString(); // "true"
false.toString(); // "false"
💡 无用小常识
有没有发现,数字的字面量前面不能间接调用函数?例如执行
123.toString()
会报 SyntaxError(语法错误)。这是因为数字(浮点数)自身会用到小数点
.
,而调用函数也须要用小数点,这时就呈现了歧义(字符串和布尔值就没有这种懊恼)。对于这种状况,咱们能够应用括号
()
将数字包裹起来,如(123).toString()
;或者应用两个间断的小数点..
来调用函数,如123..toString()
。
🤔 奇了怪了
既然字符串不是对象,那么为什么字符串会有属性和办法呢?
转念一想,数字就是数字,数字身上怎么会有办法呢?
这的确不合乎逻辑,然而这又与理论相矛盾。
咋回事呢???
替身使者 (I can’t translate this)
答案揭晓~
😎 暗中操作
以字符串(string
)为例,当咱们在代码中读取字符串的属性或者办法时,JavaScript 会静默地执行上面的操作:
- 将字符串通过
new String()
的形式来创立一个长期的包装对象实例; - 通过创立的对象来执行咱们的代码逻辑(读取属性或执行函数);
- 长期对象不再应用,能够被销毁。
📝 如上面的栗子:
let a = 'chenpipi';
console.log(a); // "chenpipi"
// ------------------------------
let b1 = a.length;
console.log(b1); // 8
// 下面的代码相当于:let b2 = (new String(a)).length;
console.log(b2); // 8
// ------------------------------
let c1 = a.toUpperCase();
console.log(c1); // "CHENPIPI"
// 下面的代码相当于:let c2 = (new String(a)).toUpperCase();
console.log(c2); // "CHENPIPI"
数字(number
)和布尔值(boolean
)同理,但数字通过 new Number()
来创立长期对象,而布尔值则通过 new Boolean()
来创立。
📝 除了下面的例子,最无力的证实,就是他们的构造函数:
'chenpipi'.constructor === String; // true
(12345).constructor === Number; // true
true.constructor === Boolean; // true
这一切都是 JavaScript 在暗中实现的,且过程中产生的长期对象都是一次性的(用完就丢)。
😮 原来如此
芜湖,这么一来就说得通了!
这也就能解释为什么咱们可能拜访字符串上的属性和办法,却不能减少或批改属性。
那是因为咱们实际操作的指标其实是 JavaScript 创立的长期对象,而并非字符串自身!
所以咱们的减少或批改操作实际上是失效了的,只不过是在 长期对象上失效了!
📝 就像这样:
// 代码中:let a = 'chenpipi';
a.name = '吴彦祖';
console.log(a.name); // undefined
// 相当于:let a = 'chenpipi';
(new String(a)).name = '吴彦祖';
console.log(a.name); // undefined
// 相当于:let a = 'chenpipi';
let temp = new String(a);
temp.name = '吴彦祖';
console.log(a.name); // undefined
总结 (Summary)
🎉 以上,就是本篇文章的全部内容了。
最初咱们来总结一下:
- 少数原始类型都有相应的包装对象;
- 有些包装对象能够被
new
,有些不行; - 包装对象个别被用来进行显式的类型转换;
- 对象上有属性和办法;
- 原始值上没有属性和办法;
- 原始值上也不能有属性和办法;
- 但咱们能够像操作对象一样来操作原始值;
- 这是因为 JavaScript 在执行代码的时候偷偷搞小动作;
- JavaScript 会用长期的包装对象来替原始值执行操作。
咱们平时写代码的时候不太会留神到这件事,实际上这些也不会影响到咱们写代码。
所以,这篇文章不就白看啦?
🙉 是,也不全是~
知己知彼,屡战屡败。
学会以上这些无用小常识,也算是对 JavaScript 有了更深的了解了吧,至多还能用来吹牛皮(手动狗头~)。
相干材料
《JavaScript 高级程序设计(第 4 版)》
《JavaScript 权威指南(第 6 版)》
Primitive – MDN:https://developer.mozilla.org/en-US/docs/Glossary/Primitive
The history of“typeof null”:https://2ality.com/2013/10/typeof-null.html
传送门
微信推文版本
集体博客:菜鸟小栈
开源主页:陈皮皮
Eazax Cocos 游戏开发工具包
更多分享
《Cocos Creator 性能优化:DrawCall》
《在 Cocos Creator 里画个炫酷的雷达图》
《用 Shader 写个完满的波浪》
《在 Cocos Creator 中优雅且高效地治理弹窗》
《Cocos Creator 源码解读:引擎启动与主循环》
《JavaScript 内存详解 & 剖析指南》
《Cocos Creator 编辑器扩大:Quick Finder》
公众号
菜鸟小栈
😺 我是陈皮皮,一个还在一直学习的游戏开发者,一个酷爱分享的 Cocos Star Writer。
🎨 这是我的集体公众号,专一但不仅限于游戏开发和前端技术分享。
💖 每一篇原创都十分用心,你的关注就是我原创的能源!
Input and output.