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

2022-04-15 0 801
目录
  • 前言
  • 正文
    • 原始类型 (Primitive types)
      • 原始值 (Primitive values)
    • 包装对象 (Wrapper objects)
      • 对象 (Object)
      • 构造函数 (Constructor)
      • 普通函数 (Function)
    • 原始值不是对象 (Primitive not Object)
      • 没有属性和方法 (No properties, no functions)
      • 替身使者 (I can’t translate this)
  • 总结 (Summary)

    前言

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

    免责声明:
    1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

    2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
    如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
    如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

    NICE源码网 JavaScript JavaScript原始值与包装对象的详细介绍 https://www.niceym.com/26650.html