JavaScript 原始值与包装对象
前言
随着 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
传送门
更多分享
《Cocos Creator 编辑器扩展:Quick Finder》
公众号
菜鸟小栈
我是陈皮皮,一个还在不断学习的游戏开发者,一个热爱分享的 Cocos Star Writer。
这是我的个人公众号,专注但不仅限于游戏开发和前端技术分享。
每一篇原创都非常用心,你的关注就是我原创的动力!
Input and output.
JavaScript 原始值与包装对象的更多相关文章
- JavaScript中什么是包装对象?
存取字符串.数字或布尔值的属性时,创建的临时对象称为包装对象.包装对象只是偶尔用来区分字符串值和字符串对象.数字和数值对象以及布尔值和布尔对象.由于字符串.数字和布尔值的属性都是只读的,并且不能给它们 ...
- javascript深入浅出——学习笔记(包装对象和类型检测)
3包装对象:https://www.imooc.com/video/5676 当我们尝试把基本类型已对象的方式访问时,javascript会把该基本类型转换为对应的包装类型对象(临时对象),相当于ne ...
- javascript原始值和对象引用
一句话来说:原始值是不可变的,而对象引用是可变的. js中的原始值(undefined.null.布尔值.数字和字符串)与对象(包括数组和函数)有着本质的区别.原始值是不可更改的,任何方法都无法更改一 ...
- javascript原始值和引用值类型及区别
原始值和引用值类型及区别 首先,原始值和引用值类型都是js中的数据类型,为了充分利用存储空间,定义了不同的数据类型,而且js是弱类型,动态语言,数据类型可变. 原始值(简单数据类型) 存储在栈中的简单 ...
- 【JavaScript 从零开始】 数字 文本 包装对象
JavaScript中的算术运算 JavaScript 还自称更加复杂的算术运算,这些复杂的运算通过作为Math对象的属性定义的函数和常量来实现: Math.pow(2,53) //=>9007 ...
- 11 JavaScript Number原始值&对象&科学记数法&范围&进制转换&溢出Infinity&NaN
JavaScript Number对象 是经过封装的能处理数字值的对象 由Number()构造器创建 只有一种数字类型 可以使用也可以不使用小数点书写数字 JavaScript原始值与对象: 在Jav ...
- javascript类型系统——包装对象
× 目录 [1]定义 [2]生存期 [3]显式创建[4]转型函数[5]比较运算 前面的话 javascript对象是一种复合值,它是属性或已命名值的集合.通过'.'符号来引用属性值.当属性值是一个函数 ...
- 每日分享!~ vue JavaScript中为什么可以读取到字符串的长度!(包装对象)
首先需要知道什么是包装对象? 对象是JavaScript语言下最主要的数据类型,三种原始的值-----数值,字符串,布尔值,在一定条件下会自动的转为对象.也就是原始类型的包装对象: 也就是通过如下方式 ...
- JavaScript 基本包装类型,包装对象
前言 javascript对象是一种复合值,它是属性或已命名值的集合.通过'.'符号来引用属性值.当属性值是一个函数时,称其为方法.通过o.m()来调用对象o中的方法.我们发现,字符串也同样具有属性和 ...
随机推荐
- 写了一个 gorm 乐观锁插件
前言 最近在用 Go 写业务的时碰到了并发更新数据的场景,由于该业务并发度不高,只是为了防止出现并发时数据异常. 所以自然就想到了乐观锁的解决方案. 实现 乐观锁的实现比较简单,相信大部分有数据库使用 ...
- OpenGL 绘制你的 github skyline 模型
前言 好久没更新博客了,上一篇文章还是在去年6月份,乍一看居然快要过去一年了,不时还能看到粉丝数和排名在涨,可是却一直没有内容更新,怪不好意思的- -(主要是一直没想好要写些什么,中间也有过一些想法, ...
- Android学习之RecyclerView初探究
•RecyclerView基本用法 RecyclerView是新增的控件,为了让 RecyclerView 在所有 Android 版本上都能使用; Android 团队将 RecyclerView ...
- jenkins构建go及java项目
jenkins构建go及java项目 转载请注明出处https://www.cnblogs.com/funnyzpc/p/14554017.html 写在前面 jenkins作为java的好基友,经历 ...
- Ansible-Playbook中的变量使用
变量名:仅能由字母.数字和下划线组成,且只能以字母开头 变量来源: 1.ansible all -m setup 远程主机的所有变量都可直接调用 #显示所有变量 ansible all -m setu ...
- [.net] 关于Exception的几点思考和在项目中的使用(一)
本文链接 https://www.cnblogs.com/hubaijia/p/about-exceptions-1.html 关于exception的基本语法和作用,这里不再赘述,下面记录一下我在项 ...
- java面试-synchronized底层实现机制
一.synchronized的三种应用方式 1.修饰实例方法,锁是当前实例对象,进入同步代码前要获得当前实例的锁 /** * synchronized修饰实例方法,当前线程的锁是实例对象account ...
- 结合k8s和pipeline的流水线,并通过k8s接口完成镜像升级
现在这家单位的CICD比较的混乱,然后突发奇想,想改造下,于是就用pipeline做了一个简单的流水线,下面是关于它的一些介绍 写一个简单的流水线 大概就是这么个流程简单来说就是:拉代码---> ...
- 工作区和GOPATH
工作区和GOPATH 1.特性: 1.1.5版本的自举(即用 Go 语言编写程序来实现 Go 语言自身) 2.1.7版本的垃圾回收器 2.GOROOT.GOPATH 和 GOBIN GOROOT:Go ...
- try - with - resource
本文详细介绍了自 JDK 7 引入的 try-with-resources 语句的原理和用法,以及介绍了 JDK 9 对 try-with-resources 的改进,使得用户可以更加方便.简洁的使用 ...