探析JS中的对象和包装类

136 阅读5分钟

前置知识

JavaScript 中的数据类型可以分为两大类:原始类型和引用类型

原始类型:也叫基本数据类型,有七种,分别是

  • string
  • number:包括整数和浮点数,以及特殊值如 NaN(Not-a-Number)
  • boolean
  • undefined
  • null
  • bigInt:用于表示整数,其长度没有上限
  • symbol:一种唯一的、不可变的数据类型

引用类型:也叫复杂类型,主要为对象,其中包括数组、函数体、日期、正则对象等

对象

对象属性的基本操作

var obj = {
    name: 'qing',
    age: 18,
    sex: 'girl',
}
console.log(obj)  // 查

以上述代码为例,介绍基于对象的增删改:

①增:

  • obj.abc = 'student' 以 . 的方式添加属性,默认直接以abc字符串为属性名添加到obj上

  • obj[abc] = 'student' 以[ ]的方式添加属性,就是以 变量abc为key的值 为属性添加到obj上

  • obj['abc'] = 'student'等同于第一种obj.abc = 'student',直接以abc字符串为属性名添加到obj上

② 删:delete obj.a

③ 改:obj.age = 20 与增是相同的手段:对象上已存在该属性就是改,不存在就是增

创建对象的方式

创建对象字面量:直接在一个大括号 {} 中定义对象的属性和方法。本文第一段代码就是通过字面量直接创建对象

调用系统自带的构造函数 new Object():例var obj = new Object()

调用自定义的构造函数:例如下面这段代码就是自定义了一个People构造函数

function People(name, age) {  // 可以向构造函数传参指定对象的属性
    this.name = name
    this.age = age 
}
let p1 = new People('hh', 18) // 实例对象

你有没有感到很神奇:如果这行代码右边如果没有new,只有一个People的函数调用,那变量p1接收到的就是一个undefined,因为People这个函数中并没有return返回值;而现在有了new,p1就接收到的就是一个对象

所以new究竟干了一些什么事情?让我们接下来探讨一下——

new 关键字

  1. new 会在构造函数中创建一个对象
  2. 将构造函数的this指向创建的那个对象
  3. 执行函数中的逻辑代码 (相当于往对象上添加属性)
  4. 让对象的隐式原型等于构造函数的显式原型
  5. 返回this对象

所以new一个构造函数可以浅显一点理解成下列代码:

function Person() {  
    var obj = {}  
    Person.call(obj)
    this.name = 'xxx'
    this.age = 18
    this._proto_ = Person.prototype
    return obj
}

let p = Person('qing', 20)
console.log(p);   // { name: 'xxx', age: 18 }

于是你理解了:new一个构造函数会得到一个对象。那么请看下面两段代码:

var num = new Number(123)
console.log(num * 2); // 246
var str = 'abcd'
console.log(str.length);  // 4

这里好似产生了两个悖论:①既然num是一个对象,对象怎么能乘以2得出结果246呢?②str是一个字符串,原始类型数据又如何能通过 . 调用length方法呢

事实上,第一段的确得到了一个实例对象,但是当参与到算术运算时,v8就会把num看成一个原始类型数据;而第二段代码var str = 'abcd'通过字面量创建对象与调用系统自带的构造函数new String()无异,当要调用length方法时,在v8(JS引擎)看来str就是一个对象

所以一个东西创建出来可以被当成原始值来用,也可以当成对象来用,取决于你怎么用它。探究背后的原因,就要来聊聊包装类——

包装类

包装类是用于将原始数据类型封装成对象的一组特殊类。JS中有三种主要的包装类,分别对应于三种基本数据类型:Number, String, 和 Boolean。当需要将一个基本类型作为对象使用时,JS引擎会自动创建一个对应的包装类实例,这样就可以在基本类型上使用对象的属性和方法了

包装类的过程

var num = 123
num.abc = 'hello'
console.log(num.abc);   // undefined

属性和方法是对象独有的,原始类型数据是不能拥有属性和方法的。而上面这段代码一是给num安排属性不报错,二是访问该属性时也不报错、但是访问结果undefined。你是否又对此感到疑惑?

其实这里发生了一个隐式过程叫做类的包装,过程如下:

new Number(123).abc = 'hello'
delete new Number(123).abc
console.log(num.abc); 

通俗点讲,通过字面量创建的num 直接被v8引擎看做成一个由new构造函数创建的对象,等到要访问新增的abc属性时v8才会意识到用户定义的是一个自变量,原来是它自己一厢情愿将num当成一个对象的,所以他就又删除了这个所谓对象上的新增属性abc,最后再打印。访问一个对象身上不存在的属性,结果就是undefined

同理,下面这段代码要改数组的长度能改的动,但是改字符串的改不动,是因为字符串的执行了包装类的过程

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr.length = 2
console.log(arr); // [ 1, 2 ]

var str = 'abcde'
str.length = 2
console.log(str); // abcde
// 字符串的包装类过程
new String('abcde').length = 2
delete new String('abcde').length
new String('abcde').length   // 最后一步又会去执行一遍

阿里面试题

最后我们以一道阿里面试题收尾,来测测自己对 对象和包装类知识点 的掌握程度吧

var str = 'abc'
str += 1
var test = typeof(str)
if (test.length == 6) {
  test.sign = 'typeof 的返回结果是String'
}
console.log(test.sign); 

解析见阿里面试题:有关对象和包装类 - 掘金 (juejin.cn)一文

认真读完后相信你已经对js的对象和包装类知识点有了更加深刻的理解,阅读过程中若发现有任何不足或亮点,欢迎大家在评论区指出交流,我们共同进步~