关于OOP,我了解到的一件事是,对象可以具有表示简单数据的属性和方法,而不是值。这个概念看起来很基础,但是,当我尝试将它应用到JavaScript时,会出现一些明显的矛盾。此文正为了解什么是包装对象(Wrapper Object)以及它们与这些“不一致”有什么关系。
万物皆对象?
我们先来捋一捋JS中的数据类型,JS中的数据类型有下面几种
- Undefined
- Null
- Boolean
- Number
- String
- Symbol (ES6+)
- Object
前六种被称为基本数据类型,最后一种则是复杂数据类型,也就是对象类型。
困惑?
创建一个Object时存在一些方法,如toString
和hasOwnProperty
。它也可以有属性,像__proto__(虽然已经被废弃了)。
const obj = {}; console.log(obj.toString()); // [Object object] console.log(obj.hasOwnProperty('name')); // false console.log(obj.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, ...}
包括Array也是object,同样的存在一些方法例如push
和sort
,以及一些常用到的length
属性。
let t1 = [3,4,5,6]; t1.push(...[1,2,3,4]); console.log(t1.concat([233, 332])); // [3, 4, 5, 6, 1, 2, 3, 4, 233, 332] console.log(t1.length); // 8
String同样也存在一些方法例如indexOf
、substr
、replace
,以及length
属性。
const name = 'Mas0n Shi'; console.log(name.length); // 9 console.log(name.indexOf('S')); // 6 console.log(name.substr(3, 8)); //=> "0n Shi" console.log(name.replace('Mas0n', 'sunfish')); //=> "sunfish Shi"
因此,根据上面的推理,字符串似乎也是object?
错!
String是基本数据类型之一,因此String不是object。
数据类型?
与C语言类似,在ECMAScript中,变量可以存在两种类型的值,即原始值和引用值。
- 基本数据类型
总是通过值复制的方式来赋值/传递,包括Undefined、Null、Boolean、Number、String 和 Symbol。
- 复杂数据类型
对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。
// 基本数据类型 var t1 = 1; var t2 = t1; t1 = t2 + 1 console.log(t1); // 1 // 复杂数据类型 var a1 = []; var a2 = a1; a2.push(...[1,2,3,4]) console.log(a1); // [1, 2, 3, 4]
区别
复杂数据类型可以动态的添加属性和方法,而基本数据类型不行。
// 基本数据类型 var t1 = "hello"; t1.t = 10; console.log(t1.t); // undefinded // 复杂数据类型 var f1 = () => {}; f1.t = 10; console.log(f1.t); // 10
typeof null
console.log(typeof null); // object
为什么 typeof 运算符对于 null 值会返回 “Object”?
实际上这是 JavaScript 最初实现中的一个错误,然后被 ECMAScript 沿用了。现在,null 被认为是对象的占位符,从而解释了这一矛盾,但从技术上来说,它仍然是原始值。
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于
null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回"object"
。
包装对象(Wrapper Object)
Number、String、Boolean是三种原始类型。
在一定条件下,他们会自动转为对象,也就是原始类型的“包装对象”(Wrapper Object
)。
以下面的代码片段为例
const name = 'Mas0n Shi'; const arr = name.split(" "); console.log(arr); // [ 'Mas0n', 'Shi' ]
实际上,每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型的对象。
我们可以将name.split(" ")
的调用拆分成以下步骤:
- 创建 String 实例
- 在实例上调用指定的方法
- 销毁这个实例
用JS代码表示
let tmp = new String(name); const arr = tmp.split(" "); tmp = null;
从这里能够看到,一般的引用类型和包装类型唯一的区别就在于对象的生命周期。包装类型的对象生命周期很短,只有代码执行的一瞬间,然后就被销毁了,所以这也就是为什么我们不能在运行的时候为基本类型的值添加属性和方法。
这也解答了为什么'' instanceof String
的值为false
const name = 'Mas0n Shi'; const name2 = new String(name); console.log(name instanceof String); // false console.log(name2 instanceof String); // true
发表回复