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 API来处理不同的浏览器事件
Dec 09 Javascript
两个多选select(multiple左右)添加、删除选项和取值实例
May 12 Javascript
js怎么判断flash swf文件是否加载完毕
Aug 14 Javascript
jquery和js实现对div的隐藏和显示方法
Sep 26 Javascript
jQuery制作仿Mac Lion OS滚动条效果
Feb 10 Javascript
jQuery插件jPaginate实现无刷新分页
May 04 Javascript
使用JS 插件qrcode.js生成二维码功能
Feb 20 Javascript
Bootstrap 表单验证formValidation 实现表单动态验证功能
May 17 Javascript
Node接收电子邮件的实例代码
Jul 21 Javascript
详解vue-cil和webpack中本地静态图片的路径问题解决方案
Sep 27 Javascript
JS事件流与事件处理程序实例分析
Aug 16 Javascript
JavaScript 浏览器对象模型BOM原理与常见用法实例分析
Dec 16 Javascript
vue组件的路由高亮问题解决方法
原生Js 实现的简单无缝滚动轮播图的示例代码
May 10 #Javascript
Angular性能优化之第三方组件和懒加载技术
Vue通过懒加载提升页面响应速度
如何开发一个渐进式Web应用程序PWA
Html5生成验证码的示例代码
May 10 #Javascript
Vue详细的入门笔记
You might like
PHP开发负载均衡指南
2010/07/17 PHP
php面向对象中static静态属性和静态方法的调用
2015/02/08 PHP
php使用APC实现实时上传进度条功能
2015/10/26 PHP
php实现面包屑导航例子分享
2015/12/19 PHP
laravel ORM 只开启created_at的几种方法总结
2018/01/29 PHP
才发现的超链接js导致网页中GIF动画停止的解决方法
2007/11/02 Javascript
JavaScript中的几个关键概念的理解-原型链的构建
2011/05/12 Javascript
javascript中全局对象的parseInt()方法使用介绍
2013/12/19 Javascript
JavaScript动态修改网页元素内容的方法
2015/03/21 Javascript
html、css和jquery相结合实现简单的进度条效果实例代码
2016/10/24 Javascript
如何解决jQuery EasyUI 已打开Tab重新加载问题
2016/12/19 Javascript
js中setTimeout的妙用--防止循环超时
2017/03/06 Javascript
解析NodeJS异步I/O的实现
2017/04/13 NodeJs
jquery实现图片放大点击切换
2017/06/06 jQuery
vue项目部署到Apache服务器中遇到的问题解决
2018/08/24 Javascript
vue v-for 使用问题整理小结
2019/08/04 Javascript
解决Vue的文本编辑器 vue-quill-editor 小图标样式排布错乱问题
2020/08/03 Javascript
python 切片和range()用法说明
2013/03/24 Python
python使用socket远程连接错误处理方法
2015/04/29 Python
python Django模板的使用方法
2016/01/14 Python
13个最常用的Python深度学习库介绍
2017/10/28 Python
Python开发之Nginx+uWSGI+virtualenv多项目部署教程
2019/05/13 Python
python中比较两个列表的实例方法
2019/07/04 Python
OpenCV 边缘检测
2019/07/10 Python
Python Django简单实现session登录注销过程详解
2019/08/06 Python
Python json模块与jsonpath模块区别详解
2020/03/05 Python
html5 浏览器支持 如何让所有的浏览器都支持HTML5标签样式
2012/12/07 HTML / CSS
梵蒂冈和罗马卡:Omnia Card Pass
2018/02/10 全球购物
Sunglass Hut巴西网上商店:男女太阳镜
2020/10/04 全球购物
警察思想汇报
2014/01/04 职场文书
幼儿园运动会入场词
2014/02/10 职场文书
简洁的英文求职信范文
2014/05/03 职场文书
2014年学习全国道德模范事迹思想汇报
2014/09/15 职场文书
学生抄作业检讨书(2篇)
2014/10/17 职场文书
借条如何写
2015/05/26 职场文书
深入理解CSS 中 transform matrix矩阵变换问题
2021/08/30 HTML / CSS