浅析JavaScript中的装箱和拆箱

  在javascript中有两种数据类型:

  基本类型:字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol

  引用类型:对象(Object)、数组(Array)、函数(Function)

  在 JavaScript 中,有四个基本的包装类型 String、Number、Boolean、Symbol。

一、装箱操作

  所谓的装箱,是指将基本数据类型转换为对应的引用类型的操作。

  装箱分隐式装箱和显式装箱两种装箱方式。

1、隐式装箱

  先说「隐式装箱」,隐式装箱是由「引擎自动执行」的。

let web = 123;

  基本类型是不能添加属性和方法的,添加会报错。

let web = 'Javascript';
web.subText = 'JavaScriptSub';
web.subTextFn = function(){
  console.log('JavascriptSubTextFn');
};
console.log(web.subText);//undefined
console.log(web.subTextFn());//Uncaught TypeError: web.subTextFn is not a function

  那为什么普通字符串类型可以调用方法呢,比如str.substring()、str.indexOf()等,我们接着往下看:

2、那装箱都做了什么?

  在读取值的时候,引擎会创建一个基本类型所对应的「包装类型的对象」,见下图。

  对于隐式装箱的执行步骤,我们看下面的代码:

var s1 = 'call_me_R'; // 隐式装箱
var s2 = s1.substring(2);

  上面代码的执行步骤其实是这样的:

(1)先创建String类型的一个实例;

(2)在实例中调用制定的方法;

(3)销毁这个实例。

  上面的三个步骤转换为代码,如下:

// 1
var s1 = new String('call_me_R');
// 2
var s2 = s1.substring(2);
// 3
s1 = null;

  所以,我们在基本类型值上可以使用方法(比如string的substring等),是因为有「隐式装箱」操作。

  隐式装箱当读取一个基本类型值时,后台会创建一个该基本类型所对应的基本包装类型对象。在这个基本类型的对象上调用方法,其实就是在这个基本类型对象上调用方法。这个基本包装类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立即被销毁。这也是在基本类型上添加属性和方法会不识别或报错的原因了。

3、显式装箱

  装箱的另一种方式是显式装箱,这个就比较好理解了,这是通过基本包装类型对象对基本类型进行显式装箱,如下:

var name = new String('call_me_R');

  显式装箱的操纵可以对new出来的对象进行属性和方法的添加啦,因为通过new操作符创建的引用类型的实例,在执行流离开当前作用域之前一直保留在内存中

var objStr = new String('call_me_R');
objStr.job = 'frontend engineer';
objStr.sayHi = function(){
    console.log('hello kitty');
}
console.log(objStr.job); // frontend engineer
objStr.sayHi(); // hello kitty

  显式装箱可以添加属性和方法的,隐式装箱是不能添加属性和方法的。

二、箱操作

  拆箱就和装箱相反了,拆箱是指把引用类型转换成基本的数据类型。通常通过引用类型的valueOf()和toString()方法来实现。

  引用类型的值转成基本类型的值就是拆箱。拆箱必须要提两个方法 toString() 和 valueOf() ,toString() 返回字符串,valueOf() 返回对象本身。

1、在下面的代码中,留意下valueOf()和toString()返回值的区别:

var objNum = new Number(64);
var objStr = new String('64');
console.log(typeof objNum); // object
console.log(typeof objStr); // object
// 拆箱
console.log(typeof objNum.valueOf()); // 64  number 基本的数字类型,想要的
console.log(typeof objNum.toString()); // '64'  string 基本的字符类型,不想要的
console.log(typeof objStr.valueOf()); // '64'   string 基本的数据类型,不想要的
console.log(typeof objStr.toString()); // '64'  string 基本的数据类型,想要的

2、更改对象的 toString() 和 valueOf() 两个方法:

//更改对象的 toString() 和 valueOf() 两个方法:
var web = {
  'name':'html',
  valueOf: () => {
    console.log("valueOf");
  },
  toString: () => {
    console.log("toString");
  }
}
console.log(String(web))//toString undefined
console.log(Number(web))//valueOf NaN
var ss = {'web': 22}
String(ss)
//"[object Object]"
ss.toString()
//"[object Object]"
ss.valueOf()
//{web: 22}
Number(ss)
//NaN

  执行的逻辑是什么?为什么有时候先走 valueOf() ?为什么有时候先执行 toString()?

  对象中有 toPrimitive 方法,此方法提供了用于将对象强制转换为基元并替换 toString() 和 valueOf() 方法的通用接口。

  运行方法时,判断 PreferredType 的值是哪种类型:

  如果是 Number:

(1)如果是基本类型,按原样返回。

(2)否则,输入是一个对象,调用 obj.valueOf() 方法,如果结果是原始的,则将其返回。

(3)否则,调用 obj.toString() 方法,如果结果是原始数据,则将其返回。

(4)否则,抛出 TypeError

  如果为 String:那需要交换 2 和3 的执行顺序。

  如果没有 PreferredType ,对于 Date 的实例将其设置为 String。

  对于所有的其他值,设置为 Number。

 参考资料:

https://juejin.cn/post/6844903859765133320

https://mp.weixin.qq.com/s/QDHJBCP9E8GyKPlMOExwAA

posted @ 2021-02-24 17:49  古兰精  阅读(1211)  评论(0编辑  收藏  举报