前言

随着 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原始值与包装对象内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

JavaScript原始值与包装对象的详细介绍的更多相关文章

  1. html5 拖拽及用 js 实现拖拽功能的示例代码

    这篇文章主要介绍了html5 拖拽及用 js 实现拖拽,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  4. Swift与Js通过WebView交互

    开发环境:Swfit2.3XCode8.2基础概念jscontext,jscontext是代表JS的执行环境,通过-evaluateScript:方法就可以执行一JS代码JSValue,JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等JSExport,JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用Swif

  5. JSCore swift

    如果双方相互引用,会造成循环引用,而导致内存泄露。以上是Jscore的基本使用,比较简单

  6. Swift WKWebView的js调用swift

    最近项目需求,需要用到JavaScriptCore和WebKit,但是网上的资源有限,而且比较杂,都是一个博客复制另外一个博客,都没有去实际敲代码验证,下面给大家分享一下我的学习过程。

  7. Swift WKWebView的swift调用js

    不多说,直接上代码:在html里面要添加的的代码,显示swift传过去的参数:这样就实现了swift给js传参数和调用!

  8. 在 Swift 專案中使用 Javascript:編寫一個將 Markdown 轉為 HTML 的編輯器

    你有強烈的好奇心,希望在你的iOS專案中使用JavaScript。jscontext中的所有值都是JSValue對象,JSValue類用於表示任意類型的JavaScript值。因此,我們既需要寫Swift代碼也要寫JavaScript代碼。此外,我們還會在JavaScript中按照這個類的定義來創建一個對象并對其屬性進行賦值。從Swift中呼叫JavaScript就如介紹中所言,JavaScriptCore中最主要的角色就是jscontext類。一個jscontext對象是位於JavaScript環境和本

  9. swift - WKWebView JS 交互

    本文介绍WKWebView怎么与js交互,至于怎么用WKWebView这里就不介绍了HTML代码APP调JS代码结果JS给APP传参数首先注册你需要监听的js方法名2.继承WKScriptMessageHandler并重写userContentController方法,在该方法里接收JS传来的参数3.结果

  10. swift 开发UIWebView跟JS的交互

    前言作为小白的我,才开始入门IOS,选择了swift来进行入门学习,学习做着公司一个简单的小小项目,该项目需要进行跟H5进行交互,然后我就开始研究了UIWebView的使用,其实基本原理跟Android的一样,因为我是Android开发的,所以就顺水推舟了。))//这里设置你需要加载的地址}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()//disposeofanyresourcesthatcanberecreate

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部