前言
今天给大家讲讲包装类这一概念,当然,不会仅仅只有干货,后面还带一道大厂面试题哦,荤素结合,主打的就是一个均衡。
正文
在讲包装类之前呢我们先来看看数据类型的种类,其实就两大类了,基本数据类型也叫原始数据类型,另一种就是引用数据类型也叫复杂数据类型。
/// 原始数据类型
let a = 'hello'
let b = 123
let c = true
let u = undefined
let n = null
// 引用类型(复杂类型)对象
let obj = {
name:''
}
原始数据类型就这五种:
整型(Integer Types):包括byte、short、int、long四种类型,分别表示不同范围的整数值。
浮点型(Floating-Point Types):包括float和double两种类型,用于表示带有小数部分的数值。
字符型(Character Type):表示单个字符,使用char类型。
布尔型(Boolean Type):表示逻辑值,只有两个取值true和false,使用boolean类型。
空类型(Void Type):表示没有值,只用于方法的返回类型,使用void关键字。
引用类型包括类(Class)、接口(Interface)、数组(Array)等。通过使用关键字new来创建引用类型的对象,并使用变量来引用这些对象。引用类型的变量实际上存储的是对象的内存地址,通过该地址可以访问对象的属性和方法。
知道了这些还是远远不够的,我们还要进一步去探究对象和构造函数的奥秘。
对象
这个对象可不是那个“对象”哦,这对象是一个具体的实体,具有特定的属性(数据)和行为(方法),对象也是类的实例化,通过类来定义对象的属性和行为。举个例子,在现实世界中,我们可以将对象看作是具体的事物或实体,比如一辆汽车、一个人或一个银行账户等。每个对象都有自己的状态(属性)和行为(方法)。例如,一辆汽车的属性可以包括颜色、品牌和速度,它的行为可以包括启动、加速和刹车等操作。
并且对象是储存在堆中而不是词法环境中,原因有以下几种:
动态分配:对象在程序运行时动态分配内存,因此需要在堆中进行存储。词法环境是在编译时确定的静态结构,无法进行动态分配。
对象的生命周期:对象的生命周期可以很长,可能跨越多个词法环境的执行。将对象存储在堆中可以保证对象在不同的词法环境中都能够访问到。
共享和共享引用:堆中的对象可以被多个词法环境引用,实现对象的共享。如果对象存储在词法环境中,那么每个词法环境都会有一份对象的拷贝,导致资源的浪费和不一致性。
垃圾回收:堆中的对象可以被垃圾回收器进行管理和回收,释放不再使用的对象的内存。如果对象存储在词法环境中,无法进行自动的垃圾回收。
构造函数
构造函数是用来构造对象的。构造函数是一个特殊的方法,它与类同名,并且没有返回类型。当我们创建一个类的对象时,会调用该类的构造函数来初始化对象的状态。构造函数在对象创建时自动调用,用于执行必要的初始化操作,例如为对象的属性赋初值或执行其他必要的设置。通过构造函数,我们可以确保对象在创建后处于有效的状态,并且可以使用对象的属性和方法。
那我们能想到几种创建对象的方法呢?
1.var obj = {} //对象字面量 | 对象直接量
2.let obj = new Object() // 构造函数(创建对象)
3. 自定义构造函数
4. Object.create()
构造函数可以像工厂一样批量化创建对象
function Person(name,age,sex){
this.name = name
this.age = age
this.sex = sex
// let this = {
// name: name,
// age: age,
// sex: sex
// }
// return this
}
let p = new Person('小黄',18,'boy')
console.log(p)
---------------------------------------------------
function Person(name, age, sex){
var that = {}
that.name = name
that.age = age
that.sex = sex
return that
}
let p1 = Person('小王',18)
let p2 = Person('小红',19)
console.log(p1)
console.log(p2)
而new 的过程就相当于下面几步
- 隐式创建一个this = {}
- 执行函数中的 this.xxx = xxx
- 隐式的返回 this
包装类
好了,铺垫了这么久,重头戏来了,先聊一下什么是包装类吧,包装类是指Java中的一组类,用于将基本数据类型转换为对象。每个基本数据类型都有对应的包装类,例如Integer、Float、Boolean等。包装类提供了一些方法,可以在基本数据类型和对象之间进行转换,以及进行一些常见的操作,例如比较大小、计算等。包装类还可以用于在集合类中存储基本数据类型的值。
原始值是不能拥有属性和方法的,属性和方法是对象独有的。这句话对不对?大家想想看,答案是对的,那大家再来看看下面三行代码
var num = 123
num.abc = 'hello'
console.log(num.abc)
这个输出的是undefined,诶,问题就来了,为什么没报错而是输出undefined呢,不是说原始属性不能有属性和方法吗,那为什么num.abc是undefined,因为这里存在一个隐式包装类的概念,隐式包装类是指当我们在对基本类型使用属性或方法时,JavaScript会自动将其转换为对应的包装对象,以便我们可以访问其属性和方法。当我们访问基本类型的属性或方法时,JavaScript会创建一个临时的包装对象,然后再调用对应的属性或方法。
var num = new Number(123)
num.abc = 'hello'
delete num.abc
运行num.abc时就相当于运行了这么一段代码,在console识别时存在但是会删除,所以会输出undefined。
好了,现在让我们看看这道大厂面试题:
var str = 'abc'
str += 1 //'abc1'
var test = typeof(str)
if(test.length == 6){
test.sign = 'typeOf的返回结果可能为String'
}
console.log(test.sign)
输出结果是什么呢?输出的是undefined,
var str = 'abc'
str += 1 //'abc1'
var test = typeof(str) //test = String
if(test.length == 6){ // new String(test).length
test.sign = 'typeOf的返回结果可能为String'
// new String(test).sign = 'typeOf的返回结果可能为String'
// delete
}
console.log(test.sign) // new String(test).sign
前面什么str += 1都是没啥用的,用来迷惑的,结果就是看test.sign,执行test.sign = 'typeOf的返回结果可能为String'时,相当于new String(test).sign = 'typeOf的返回结果可能为String',然后delete,而最后console.log(test.sign)相当于打印new String(test).sign,但因为这是没有值的,又重复一遍隐式包装类,所以输出undefined。