前端面试的那些事儿(1)~ JavaScript 原始数据类型

前言

自我总结面试常问的一些细节,方便不断回顾与补充。第一次发表文章,如有问题或不足之处望及时指出。

JavaScript 原始数据类型

1.1 基础数据类型

7大基础数据类型

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol
  • BigInt (Stage阶段)

1.2 boolean

只有true、false两个值

什么是falsy?

转换成false的值称为假值(falsy value),这7个值包括undefined、null、+0、-0、NaN、false、""(空字符串)

1.3 null 和 undefined

  • null值表示一个空对象指针
  • undefined是一个表示”无”的原始值

比较:

  • null == undefined // true
  • null === undefined // false

转为数值:

  • null -> 0
  • undefined -> NaN

null 是对象吗?

不是

typeof null 为什么是object?

原因是在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

为什么有些地方要使用 void 0 代替 undefined

undefined在JavaScript中并不属于保留字/关键字,因此在IE5.5~8中我们可以将其当作变量那样对其赋值,于是采用void方式获取undefined则成了通用方法。

1.4 number

javascript采用IEEE754格式来表示数字,不区分整数和浮点数,javascript中的所有数字都用浮点数值表示

不让除以 0 出错,而引入了无穷大的概念

  • Infinity,无穷大; => 1/+0 === Infinity
  • -Infinity,负无穷大。=> 1/-0 === -Infinity

number 中经典的 0.1 + 0.2 !=0.3

原因:

  1. 采用 IEEE754格式的语言都有该问题
  2. 计算机将 0.1 0.2 表示为二进制,然后相加之后再转换成10进制
  3. 问题就是转换成2进制时,0.1 在二进制中是无限循环的一些数字(很多十进制小数用二进制表示都是无限循环的)
  4. JS 采用的浮点数标准却会裁剪掉我们的数字
  5. 那么这些循环的数字被裁剪了,就会出现精度丢失的问题,也就造成了 0.1 不再是 0.1 了,而是变成了 0.100000000000000002
  6. 0.2 在二进制也是无限循环的,被裁剪后也失去了精度变成了 0.200000000000000002
  7. 因此结果是 0.1 + 0.2 === 0.30000000000000004

解决方案:

检查左右两边差的绝对值是否小于最小精度

Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON

Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

NaN

NaN(not a number)表示非数字,NaN与任何值都不相等,包括NaN本身,且任何涉及NaN的操作都会返回NaN

判断NaN更可靠的方法是,利用NaN是javascript之中唯一不等于自身的值这个特点,进行判断

function myIsNaN(value) {
  return value !== value;
}

1.5 string

字符串String类型是由引号括起来的一组由16位Unicode字符组成的字符序列

Note:现行的字符集国际标准,字符是以 Unicode 的方式表示的,每一个 Unicode 的码点表示一个字符,理论上,Unicode 的范围是无限的。UTF 是 Unicode 的编码方式,规定了码点在计算机中的表示方法,常见的有 UTF16 和 UTF8。 Unicode 的码点通常用 U+??? 来表示,其中 ??? 是十六进制的码点值。 0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)

JavaScript 字符串把每个 UTF16 单元当作一个字符来处理,所以处理非 BMP(超出 U+0000 - U+FFFF 范围)的字符时,你应该格外小心。

JavaScript 这个设计继承自 Java,最新标准中是这样解释的,这样设计是为了“性能和尽可能实现起来简单”。因为现实中很少用到 BMP 之外的字符。

JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

1.6 symbol

Symbol的作用非常的专一,换句话说其设计出来就只有一个目的——作为对象属性的唯一标识符,防止对象属性冲突发生。

除了自己创建的symbol,JavaScript还内建了一些在ECMAScript 5 之前没有暴露给开发者的symbol,它们代表了内部语言行为。

Symbol 函数比较特殊,直接用 new 调用它会抛出错误,但它仍然是 Symbol 对象的构造器。

Symbol.toPrimitive

JavaScript 对象转换到基本类型值时,会使用 ToPrimitive 算法,这是一个内部算法,是编程语言在内部执行时遵循的一套规则。

An object without Symbol.toPrimitive property.

var obj1 = {};
console.log(+obj1);     // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ''); // "[object Object]"

当我们创建一个普通对象时({} 或 new Object() 的方式等),对象上是不具备 [Symbol.toPrimitive] (方法)属性的。所以,对于普通对象的到基本类型值的运算,一般按照具体场景:

  1. hint 值为 "string" 时,先调用 toString,toString 如果返回一个基本类型值了,则返回、终止运算;否则接着调用 valueOf 方法。
  2. 否则先调用 valueOf,valueOf 如果返回一个基本类型值了,则返回、终止运算;
  3. 否则接着调用 toString 方法。

An object with Symbol.toPrimitive property.

var obj2 = {
  [Symbol.toPrimitive](hint) {
    if (hint == 'number') {
      return 10;
    }
    if (hint == 'string') {
      return 'hello';
    }
    return true;
  }
};

console.log(+obj2);     // 10        -- hint is "number"
console.log(`${obj2}`); // "hello"   -- hint is "string"
console.log(obj2 + ''); // "true"    -- hint is "default"

如果存在Symbol.toPrimitive则优先调用否则按照上面的规则处理。

ToPrimitive 算法在执行时,会被传递一个参数 hint,表示这是一个什么类型的运算(也可以叫运算的期望值),根据这个 hint 参数,ToPrimitive 算法来决定内部的执行逻辑。

hint 参数的取值只能是下列 3 者之一:

  • string
  • number
  • default

1.7 BigInt

目前还在stage阶段

  • BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。
  • 不能使用Number和BigInt操作数的混合执行算术运算,需要通过显式转换其中的一种类型。
  • 出于兼容性原因,不允许在BigInt上使用一元加号(+)运算符。

1.8 类型检查

有类型就势必会有类型检查

  • typeof 对于原始数据,都可以正确检测,但是null会显示object
  • typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true
  • so,使用instanceof判断类型就是胡扯,受限极大。
  • Object.prototype.toString.call(any anyType) // "[object anyType]" 这才是最准确的判断类型的方式

Object.prototype.toString

在早期版本的 JavaScript 中,“类”的定义是一个私有属性 [[class]],语言标准为内置类型诸如 Number、String、Date 等指定了[[class]]属性,以表示它们的类。语言使用者唯一可以访问[[class]]属性的方式是 Object.prototype.toString。

var o = new Object;
var n = new Number;
var s = new String;
var b = new Boolean;
var d = new Date;
var arg = function(){ return arguments }();
var r = new RegExp;
var f = new Function;
var arr = new Array;
var e = new Error;
console.log([o, n, s, b, d, arg, r, f, arr, e].map(v => Object.prototype.toString.call(v))); 
// ["[object Object]", "[object Number]", "[object String]", "[object Boolean]", "[object Date]", "[object Arguments]", "[object RegExp]", "[object Function]", "[object Array]", "[object Error]"]

在 ES5 开始,[[class]] 私有属性被 Symbol.toStringTag 代替,Object.prototype.toString 的意义从命名上不再跟 class 相关。我们甚至可以自定义 Object.prototype.toString 的行为,以下代码展示了使用 Symbol.toStringTag 来自定义 Object.prototype.toString 的行为:

var o = { [Symbol.toStringTag]: "MyObject" }
console.log(o + ""); // [object MyObject]

var d = new Date;
d[Symbol.toStringTag] = "custom"
Object.prototype.toString.call(d) // "[object custom]"

1.9 类型转换

JavaScript 中类型转换有三种情况

  1. 转换为布尔值
  2. 转换为数字
  3. 转换为字符串

转换成布尔值

方法:

  • !! 两次取反
  • if() 参数会自动转
  • Boolean() 构造函数转

规则:

  • number -> 除 0 -0 NaN 为false ,其它都为true
  • string -> 除 空字符串 为false , 其它都为true
  • undefined -> false
  • null -> false
  • object、function、array -> true

转换为数字

方法:

  • Number()
  • ParseInt()
  • ParseFloat()
  • 一元运算符 +'1' -'1'

规则:

  • string -> '1' => 1 'a' => NaN
  • array -> [] => 0 [1] => 1 [any] => NaN
  • null -> 0
  • 除数组的引用类型 -> NaN
  • Symbal -> 报错

面试题:

[] == ![]

分析:

  1. 0 == false
  2. false == false

转换为字符串

方法:

  • String()
  • toString()
  • ''+

规则:

  • number -> 转换成对应的字符串 1 =>'1'
  • Boolean -> 'true'
  • Object -> '[object,Object]'

[题1]:+ 'b'= NaN --> 'a'+NaN = 'aNaN'

'a' + + 'b' // -> "aNaN"

1.10 装箱操作与拆箱操作

  1. 原始值本身是没有方法可以调用的,除非它的包装类型
  2. 除了 null 和 undefined,所有的原始值都有等价的、由对象包装原始值的形式表达,通过各自的构造函数可以手动进行包装,系统也会进行隐式转换

装箱操作(原始类型转对象)

console.log("abc".charAt(0)); //a

. 运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

我们定义一个函数,函数里面只有 return this,然后我们调用函数的 call 方法到一个 Symbol 类型的值上,这样就会产生一个 symbolObject。

    var symbolObject = (function(){ return this; }).call(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

[注意] 装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。

    var symbolObject = Object(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

拆箱操作(对象转原始类型)

在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即拆箱转换)。

  • valueOf()方法返回指定对象的原始值,如果对象没有原始值,则valueOf将返回对象本身。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
  • toString()方法返回一个表示该对象的字符串。
var o = {
    valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}

o * 2
// valueOf
// toString
// TypeError

拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

到 String 的拆箱转换会优先调用 toString(这个不同浏览器还不一样,有的还是调用valueOf不纠结)。我们把刚才的运算从 o*2 换成 String(o),那么你会看到调用顺序就变了。

var o = {
    valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}

String(o)
// toString
// valueOf
// TypeError

在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。

var o = {
    valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}

o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}


console.log(o + "")
// toPrimitive
// hello
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343