JavaScript原始值与包装对象的详细介绍


Posted in Javascript onMay 11, 2021

前言

随着 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 会静默地执行下面的操作:

  1. 将字符串通过 new String() 的方式来创建一个临时的包装对象实例;
  2. 通过创建的对象来执行我们的代码逻辑(读取属性或执行函数);
  3. 临时对象不再使用,可以被销毁。

? 如下面的栗子:

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)

? 以上,就是本篇文章的全部内容了。

最后我们来总结一下:

  1. 多数原始类型都有相应的包装对象;
  2. 有些包装对象可以被 new,有些不行;
  3. 包装对象一般被用来进行显式的类型转换;
  4. 对象上有属性和方法;
  5. 原始值上没有属性和方法;
  6. 原始值上也不能有属性和方法;
  7. 但我们可以像操作对象一样来操作原始值;
  8. 这是因为 JavaScript 在执行代码的时候偷偷搞小动作;
  9. 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

到此这篇关于JavaScript原始值与包装对象的文章就介绍到这了,更多相关JS原始值与包装对象内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
jquery浏览器滚动加载技术实现方案
Jun 03 Javascript
javascript学习笔记(二)数组和对象部分
Sep 30 Javascript
jQuery实现统计输入文字个数的方法
Mar 11 Javascript
在JavaScript中访问字符串的子串
Jul 07 Javascript
JavaScript中的Function函数
Aug 27 Javascript
五种js判断是否为整数类型方式
Dec 03 Javascript
探究JavaScript函数式编程的乐趣
Dec 14 Javascript
微信小程序开发之Tabbar实例详解
Jan 09 Javascript
jQuery实现的简单排序功能示例【冒泡排序】
Jan 13 Javascript
Js利用prototype自定义数组方法示例
Oct 20 Javascript
AngularJS自定义表单验证功能实例详解
Aug 24 Javascript
Angular6 正则表达式允许输入部分中文字符
Sep 10 Javascript
vue组件的路由高亮问题解决方法
原生Js 实现的简单无缝滚动轮播图的示例代码
May 10 #Javascript
Angular性能优化之第三方组件和懒加载技术
Vue通过懒加载提升页面响应速度
如何开发一个渐进式Web应用程序PWA
Html5生成验证码的示例代码
May 10 #Javascript
Vue详细的入门笔记
You might like
PHP isset()与empty()的使用区别详解
2010/08/29 PHP
thinkphp配置连接数据库技巧
2014/12/02 PHP
php实现根据IP地址获取其所在省市的方法
2015/04/30 PHP
php array_values 返回数组的值实例详解
2016/11/17 PHP
PHP7导出Excel报ERR_EMPTY_RESPONSE解决方法
2019/04/16 PHP
网站被黑的假象--ARP欺骗之页面中加入一段js
2007/05/16 Javascript
javascript 拖动表格行实现代码
2011/05/05 Javascript
Web Inspector:关于在 Sublime Text 中调试Js的介绍
2013/04/18 Javascript
JQuery验证jsp页面属性是否为空(实例代码)
2013/11/08 Javascript
jQuery中removeProp()方法用法实例
2015/01/05 Javascript
jQuery实现新消息闪烁标题提示的方法
2015/03/11 Javascript
JS实现从表格中动态删除指定行的方法
2015/03/31 Javascript
使用JQuery中的trim()方法去掉前后空格
2016/09/16 Javascript
ES6通过babel转码使用webpack使用import关键字
2016/12/13 Javascript
jQuery 常见小例汇总
2016/12/14 Javascript
Node.js readline模块与util模块的使用
2018/03/01 Javascript
vue 项目中使用Loading组件的示例代码
2018/08/31 Javascript
layui自定义工具栏的方法
2019/09/19 Javascript
python用ConfigObj读写配置文件的实现代码
2013/03/04 Python
详解在Python中处理异常的教程
2015/05/24 Python
Python Sqlite3以字典形式返回查询结果的实现方法
2016/10/03 Python
Python调用adb命令实现对多台设备同时进行reboot的方法
2018/10/15 Python
python聚类算法解决方案(rest接口/mpp数据库/json数据/下载图片及数据)
2019/08/28 Python
基于Python执行dos命令并获取输出的结果
2019/12/30 Python
Python sklearn库实现PCA教程(以鸢尾花分类为例)
2020/02/24 Python
python 瀑布线指标编写实例
2020/06/03 Python
全球知名旅游社区法国站点:TripAdvisor法国
2016/08/03 全球购物
Lululemon英国官网:加拿大瑜伽服装品牌
2019/01/14 全球购物
大学生实习期自我评价范文
2013/10/03 职场文书
彩色的非洲教学反思
2014/02/18 职场文书
2014民事授权委托书范本
2014/09/29 职场文书
2014法院四风问题对照检查材料思想汇报
2014/10/04 职场文书
煤矿安全生产工作总结
2015/08/13 职场文书
企业愿景口号
2015/12/25 职场文书
Centos7中MySQL数据库使用mysqldump进行每日自动备份的编写
2021/08/02 MySQL
Nginx配置根据url参数重定向
2022/04/11 Servers