浅析JS给原始数据类型加属性和方法为什么不报错
作者:全糖奶茶不加糖
前言
近日看到一道阿里前端面试题,由于不了解JS包装类的特性,果然不出意外的做错了。题目如下:
var str = 'abc' str += 1 var test = typeof (str) if (test.length == 6) { test.sign = 'typeOf的返回结果可能为String' } console.log(test.sign)
上述代码的输出结果是什么?报错?还是typeOf的返回结果可能为String?最终结果都不是上面两种,最后输出结果为undefined,为什么会这样呢?为什么彻底理解此类问题,我们有必要深入了解一下包装类。
我们都知道JavaScript中的数据类型分为两种,第一种是原始数据类型,另一种是引用数据类型(也称为复杂数据类型)。
原始数据类型
let a = 'hello' let b = 123 let c = true let u =undefined let n = null
原始数据类型主要分为六种,如上述代码所示,字符串类型、Number类型、Boolean类型、undefined类型、null类型。还有一种是ES6新增的Symbol类型(表示独一无二的值,Symbol最大的用途是用来定义对象的唯一属性名)
特点
这些原始数据类型都是不可变的,这意味着它们的值一旦被创建,就不能被修改,也意味着不能给原始数据类型加属性和方法,属性和方法是对象独有的。在JavaScript中,变量存储原始数据类型的值,而不是直接存储数据本身。原始数据类型的比较是基于它们的值,而不是引用,因此两个具有相同值的原始数据类型变量将被认为相等。
引用数据类型
在JavaScript中,除了原始数据类型(字符串、数字、布尔、未定义、空值和符号)之外,还有一种复杂的数据类型,被称为引用数据类型。引用数据类型是一种用于存储和处理更复杂数据结构的数据类型。它们不直接包含实际数据,而是存储对数据的引用。JavaScript中的引用数据类型包括对象(Object)、数组(Array)、函数(Function)、正则表达式(RegExp)、日期(Date)等。下面简单介绍一下部分引用数据类型。
对象
对象是JavaScript中最常见的引用数据类型。它是一个无序的数据集合,包含键值对。每个键值对的键是字符串,值可以是任意数据类型,包括原始数据类型和其他引用数据类型。对象用花括号 {}
定义。例如:
let person = { name : '小明', age: 18, hobby: basketball }
数组
数组是一种有序的集合,可以存储多个值,每个值可以是任意数据类型,包括原始数据类型和其他引用数据类型。数组用方括号 []
定义。例如:
let num =[1,2,3,4]
函数
函数是JavaScript中的一种对象,也是引用数据类型的一种。函数可以被定义、传递、赋值和作为参数传递给其他函数。函数用 function
关键字定义,如下:
function sayHello(){ console.log("Hello") }
正则表达式
正则表达式是一种用于匹配字符串模式的对象。它可以用于字符串的搜索、替换、分割等操作,提供了强大的文本处理功能。
let pattern = /[0-9]+/; // 匹配一个或多个数字
日期
日期对象用于处理日期和时间。它提供了各种方法来操作日期和时间,例如获取年、月、日、时、分、秒等信息,以及执行日期和时间的计算。
let nowDate = new Date();
引用数据类型在JavaScript中用于处理更复杂的数据结构和操作,它们提供了灵活性和功能性,使得JavaScript成为一种强大的编程语言。通过组合使用原始数据类型和引用数据类型,开发者可以处理各种类型的数据和问题。
介绍了一下JavaScript中的数据类型,接下来我们就可以讲解一下什么是包装类了。
在JavaScript中,包装类(Wrapper Objects)是一种特殊的对象,它们用来包装原始数据类型(如字符串、数字和布尔值),使其具备对象的方法和属性。这些包装类提供了对原始数据类型的操作,例如字符串的长度、数字的精度等。
我们在介绍原始数据类型的时候曾讲到过,原始数据类型都是不可变的,这意味着它们的值一旦被创建,就不能被修改,也意味着不能给原始数据类型加属性和方法,属性和方法是对象独有的 上述加粗字体这些规则的存在显然是必要的,正是有了这些规则,使得我们不能直接给原始数据随便添加属性和方法,这使得原始数据往往都是很简单的。而在我们的日常代码练习中,你一定有用到过一些原始类型数据自带的方法,比如字符串类型的数据往往都会自带一个length方法,它能够直接返回一个字符串的长度,由于上述规则的限定,显然我们不能直接修改字符串的长度。这确保的数据的完整性,使原始数据在创建后不会被无意或者恶意更改,尤其是在多线程或者并发编程环境中尤为重要。等等!我们刚刚不是讲到原始数据不能附加属性和方法吗?那字符串类型的length方法是哪来的呢?这就是我们要特别介绍的包装类。
包装类
包装类的设计正是为了给原始数据类型增加方法和属性。
在JavaScript引擎内部,会对原始数据类型进行了一些封装和包装,使其能够调用一些内置方法和属性。当你尝试访问原始数据类型的方法或属性时,JavaScript引擎会自动将原始数据类型包装成对应的包装对象,然后调用相应的方法或属性,然后再将包装对象销毁。这个过程被称为"自动包装"。例如,当你访问字符串的 length
属性时,JavaScript引擎会在内部将字符串包装成 String
对象,然后访问其 length
属性。这一切都是自动完成的,你无需手动创建包装对象。虽然原始数据类型不能附加自定义属性和方法,但这种自动包装机制使得它们可以调用一些内置的属性和方法,使其更方便。不过需要注意,包装对象在访问完成后会被销毁,所以无法在原始数据类型上保留自定义属性或方法。如果需要自定义属性和方法,通常会使用对象类型来实现。
了解了这些,我们再回到最初看到那道面试题,尝试着去分析它,相信问题就迎刃而解了。
var str = 'abc' str += 1 var test = typeof (str) if (test.length == 6) { test.sign = 'typeOf的返回结果可能为String' } console.log(test.sign)
分析题目
- str是一个字符串类型,当它和1相加时我们能够知道,字符串和其他东西相加会将它‘同化’最终也变为字符串,于是,我们得到得str变为'abc1'。
- typeof会读取到一个数据并返回它的类型,最终我们得到test被赋值为string.
- 由于string的长度为6,进入if判断语句。
- 但是test为字符串类型,为原始数据,它本身不能添加属性和方法,但由于包装类存在的缘故,test.sign = 'typeOf的返回结果可能为String' 会被执行为:new String(test).sign = 'typeOf的返回结果可能为String' 然后delete被销毁掉。
- 接着,我们退出if语句,控制台输出test.sign,由于我们之前创建的test.sign已经被销毁,但此时又读到了test.sign,JavaScript引擎又会生成 *new String(test).sign *
- 但由于 new String(test).sign并未被赋值,所以最终我们得到结果:undefined。
以上就是浅析JS给原始数据类型加属性和方法为什么不报错的详细内容,更多关于JS原始数据类型的资料请关注脚本之家其它相关文章!