由一道阿里面试题聊聊JS中的包装类

249 阅读7分钟

细说JS中的包装类

前言

阿里面试,想必大家都有所耳闻,问题往往只有短短几行代码,却暗含玄机,如果基础不打扎实,大概率会在一些意想不到的地方栽跟头。唯有一点一点夯实基础,才能 千磨万击还坚劲,任尔东西南北风

正文

今天就笔者昨天看到的一道阿里面试题来聊一聊JS中 包装类 的概念。话不多说,先上题目:

var str = 'abc'
str += 1
var test = typeof(str)
if (test.length == 6) {
    test.sign = 'typeof的返回结果可能是String'
}
console.log(test.sign)
多思考多学习,进步才能更快。先独立思考几分钟,有想法后再来看答案哟~ 右滑下方进度条即可查看 --> 答案:undefined

本题其实就三个考点:

① 在四则运算中,string类型number类型相加时,number类型会转换为string类型,v8引擎会对转换后的二者进行拼接处理。
因此str += 1的结果会是'abc1'

typeof用于判断JS中的数据类型,对于string、number、boolean、undefinedtypeof返回的均为其自身数据类型string、number、boolean、undefined
因此var test = typeof(str) <==> var test = 'string'

延伸一下typeof

在简单数据类型 string、number、boolean、undefined、nullnull是比较特殊的,它转为机器语言后是一串0,而typeof在区分简单数据类型和引用数据类型时,是看其转为机器语言后的前三位,引用数据类型前三位是000,typeof由此判断它需要返回object,这个判断的原则就导致了typeof会误以为null也是引用数据类型而返回object

③ 在JS中简单数据类型是不能具有属性和方法的,属性和方法只有引用数据类型(对象)才能拥有。

那为什么我们又常说字符串string它自带length属性呢?

这就涉及到标题中提及的包装类的概念了,如果你看完①、②还没有想明白为什么答案是undefined,那你不妨先跟着笔者来了解一下JS中包装类的概念。

一、构造函数

也许有的朋友已经开始烦躁了,你不是说要带我了解包装类的概念么?怎么讲起构造函数了?

别急,包装类其实与构造函数的内部实现原理有关,我们首先得先聊明白构造函数内部是如何实现的。

1.构造函数是什么?

构造函数其实又分为系统自带的构造函数人为定义的构造函数
前者如:Number函数、String函数、Boolean函数等,由系统自带,可以直接调用
借助系统自带的构造函数创建对象示例:

let num = new Number(123)
console.log(num)   // [Number: 123]
let str = new String('123')
console.log(str)   // [String: 123]
let bl = new Boolean(true)
console.log(bl)   // [Boolean: true]

后者需要人为定义和创建
借助人为定义的构造函数创建对象示例:

function Bag(brand, color) {
    this.brand = brand
    this.color = color
}
var myBag = new Bag('爱马仕', red)
console.log(myBag)   // Bag {brand: '爱马仕', color: 'red'}

此处笔者定义的构造函数Bag()好像没有return任何值,但在借助构造函数Bag()创建的对象myBag中却出现了具体的对象。欸,这是为什么?这就涉及构造函数的内部实现原理了,我们继续往下看。

2.构造函数 内部实现原理

其实刚刚笔者创建的构造函数内部隐式的创建了一个this对象,然后才执行函数体中的代码,向this对象中传值,最后又隐式的返回this对象

因此,借助构造函数Bag()创建的对象myBag其实就是此处隐式返回的this对象,所以console.log(myBag)结果会如上例所示。

什么隐式创建?什么隐式返回?this我好像听说过,但你这又是在弄啥嘞?能不能讲清楚了啊喂

其实,代码化构造函数的实现原理就会像下面这样:

function Bag(brand, color) {
    // 隐式创建this对象
    var this = {  
        brand: ''
        color: ''
    }
    // 函数体执行
    this.brand = brand
    this.color = color
    // 隐式返回this对象
    return this
}
var myBag = new Bag('爱马仕', red)
console.log(myBag)   // Bag {brand: '爱马仕', color: 'red'}

这样代码化一下是不是就好理解了?

二、包装类

1.什么是包装类?

在JS中,当我们去访问一个简单数据类型的属性和方法时,v8引擎会去借助系统内部的构造函数进行创建实例查找属性或调用方法操作完成后delete对应属性和方法的一个过程。这个过程其实就是我们所说的包装类

2.包装类过程

让我们通过下面三个示例来讲明白包装类的过程:

示例1:

var Person(name, age, sex) {
    name = '赵一',
    age = 22,
    sex = 'man'
}
console.log(a)     // 报错 a is not defined
delete Person.name  // 删除对象Person的name属性
console.log(Person.name)   // undefined
console.log(Person.height)  // undefined
console.log(Person.age)    // 22

访问一个不存在的变量a,报错a is not defined,这个很常见,不难理解。
但访问对象身上不存在的属性,值为什么会是undefined呢?
这就得衍伸到对象属性的查找了,对象属性的查找是由点运算符(.)引起的,这个运算负责在引用对象内部查找属性,假如未找到属性,那么就会返回undefined。

示例2:

var num = 4
num.len = 3
console.log(num.len)   // undefined

JS中万物皆对象,虽然说此处的变量num是简单数据类型中的number类型,按理说不该具有属性和方法。

但是在v8引擎执行这串代码的过程中其实发生了这样一个小插曲:num.len = 3让v8引擎迷惑了一下,你num不是number类型么,怎么还要我去找你的len属性赋值啊!于是v8引擎就会自作主张的去找系统内置的构造函数。

这时示例2中的代码在v8引擎的眼中是这样的:

var num = new Number(4) // 创建一个Number类型对象
new Number(4).len = 3 // 为这个对象实例的len属性赋值
delete Number(4).len // 销毁实例
console.log(num.len)  // new Number(4).len => undefined

先是创建了一个number类型的对象,然后再在这个对象实例上去找它的len属性赋值为3;然后v8引擎开始发现不对劲了,它试图亡羊补牢一下,连忙销毁掉这个实例。在它销毁完成后才走到第三行console.log(num.len),此时也就相当于在这个对象中去找它的len属性,既然已经delete掉了,那肯定找不到啊,于是返回undefined

示例3:

let str = 'abcd'  // let str = new String('abcd')
str.length = 2  // new String('abcd').length = 2 => new String('ab')
// delete new String('ab').length
console.log(str.length) // new String('abcd').length => 4

回看面试题

回到开篇的这个问题:

③ 在JS中简单数据类型是不能具有属性和方法的,属性和方法只有引用数据类型(对象)才能拥有。
那为什么我们又常说字符串string它自带length属性呢?

现在你是不是就能回答上来了?
其实访问stringlength属性就是在执行包装类的过程,具体的步骤拆解就像上面的示例3一样。

理解了包装类,让我们再回头看看这道阿里面试题:

var str = 'abc'
str += 1        // str = 'abc1'
var test = typeof(str)   // test = 'string' 如果到这一步没跟上,回头再看看开篇
if (test.length == 6) {
    test.sign = 'typeof的返回结果可能是String'
}
console.log(test.sign)  // undefined

为什么最后结果是undefined呢?
①变量test是什么类型?string类型
②string类型是不是简单数据类型?当然是
③那访问它的sign方法是不是又得借助系统内部的构造函数了?当然
④类包装过程哪三步? 创建实例查找属性和调用方法操作完成后delete对应属性和方法

本题的第三个关键点流程如下:

test.length <==> new String('string').length => 6 => 进入if语句
test.sign <==> new String('string').sign 
new String('string').sign = 'typeof的返回结果可能是String'
delete new String('string').sign  注意:sign属性操作完就deleteconsole.log(test.sign) <==> console.log(new String(string).sign) => undefined
这当然是找不到sign属性的,于是返回undefined

结语

以上就是由一道字节面试题到JS中包装类的介绍。
看完本文如果觉得有收获的话,不妨点个赞支持一下笔者。
觉得写的不错,但又还有些懵的朋友不妨收藏一下,下次想起再回看一下,印象会更深刻哟~
文章可能有一些错漏,欢迎评论指出和讨论。