读前必看
本次快文快答的主题是基本数据类型和类型检测。
在阅读本文之前,为了节约大家时间,可以先通过下方介绍了解一下鼠子的面试快问快答系列,如果觉得该系列不适合您或有什么不妥之处,欢迎给我留言。
Q1:谈谈基本数据类型
题目分析
首先基本数据类型有几种?5种?7种!
最常见的五种基本数据类型:string
、number
、boolean
、undefined
、null
以及ES6
后新增的symbol
ES10
草案提出的BigInt
但是这个题一般只是个引子,答这么多已经OK,但是为了防止面试官深挖,我们来对每个基本数据类型来加深一下理解。
加深理解
本节主要谈谈symbol
和BigInt
,剩下五种会在Q2中讲解。
symbol介绍与衍生问题
下面选取MDN的一段说明
我们可以把symbol
当成对象的属性名,避免覆盖对象原属性,因为每调用一次Symbol()
都会得到一个不重复的独一无二的值。
同时Symbol
提供了两个函数Symbol.for(key)
和Symbol.keyFor(sym)
分别用于在全局Symbol注册表进行注册和检索。
来做些简单的代码示例:
var sym1 = Symbol();
var sym2 = Symbol('foo');
var sym3 = Symbol('foo');
console.log(sym1 === sym2); // false
console.log(sym2 === sym3); // false 即使入参相同也是不同的值
var sym4 = Symbol.for('foo'); // 在全局注册表注册,key值为foo
var sym5 = Symbol.for('foo'); // 重复注册
console.log(sym2 === sym4); // false 入参都是foo,但是值不相等
console.log(sym4 === sym5); // true 重复注册会返回注册过的值
// 从全局注册表中根据symbol取键值
var key2 = Symbol.keyFor(sym2)
var key5 = Symbol.keyFor(sym5);
console.log(key2); // undefined
console.log(key5); // foo
衍生问题:symbol
有什么应用?
-
用来作常量
const CASE_1 = Symbol(); const CASE_2 = Symbol(); let type = CASE_1; switch( type) { case CASE_1: break case CASE_2: break default: }
好处在于,保证绝对不会污染其他常量。
-
用来作对象属性名
const FUNC = Symbol() let obj = { [FUNC]:function(){} }
好处在于,可以防止原有对象属性被覆盖。
但是值得一题的是,
symbol
命名的属性只能通过Object.getOwnPropertySymbols()
和Reflect.ownKeys()
来获取到。 -
用来作类的私有属性/方法
// 在文件a.js中 const PASSWORD = Symbol() class Login { constructor(username, password) { this.username = username this[PASSWORD] = password } checkPassword(pwd) { return this[PASSWORD] === pwd } } export default Login
//在文件 b.js 中 import Login from './a' const login = new Login('admin', '123456') login.checkPassword('123456') // true login.PASSWORD // 无法访问到 login[PASSWORD] // 无法访问到 login["PASSWORD"] // 无法访问到
-
特别的,在
window
嵌套的情况下(如iframe
),可用于共享传值// 外层 let gs1 = Symbol.for('global_symbol_1') //注册一个全局Symbol // iframe中 let gs2 = Symbol.for('global_symbol_1') //获取全局Symbol gs1 === gs2 // true
BigInt
BigInt
了解得不多,就简单理解成它的目的是支持比Number
数据类型支持的范围更大的整数值
因为BigInt
只是作为ES10
的草案,很多环境不支持,所以下面的问题也不再考虑它,大家作为前沿了解即可。
Q2:谈谈用typeof做类型检测
题目分析
这题坑不多,直接上代码片段看看结果。
var num = 1;
var str = '1';
var bool = true;
var a = undefined;
var b = null;
var symbol = Symbol();
console.log(typeof(num)); //"number"
console.log(typeof(str)); //"string"
console.log(typeof(bool)); //"boolean"
console.log(typeof(a)); //"undefined"
console.log(typeof(b)); //"object"
console.log(typeof(symbol));//"symbol"
在这里唯一不太符合直觉的就是null,它返回的是**“object”**。对此,MDN的解释为
加深理解
这个仅仅是个人的理解,为了方便记忆,我从语义上把null
看成是表示空对象,所以typeof null==='object'
是一个很好理解的事情。
Q3:如何检测引用类型
题目分析
一般Q1~Q3是这三个问题是一起抛出来的,引用类型当然指的就是在栈内存中用指针指向的存放在堆内存中的对象(当然js中没有明确使用指针这个概念,一般提及类似的概念会用reference即引用的说法)。
万物起源于Object
(除非你借助Object.create(null)
去创建一个原型对象为null
的对象)。
对于引用类型,我们最常见的手段是用instanceof
,从原型链的角度去进行判断,左侧一般是实例对象,右侧一般是构造函数,若构造函数的原型对象在实例对象的原型链上,那么就返回true
。
除此之外,我们也可以用Object.prototype.toString()
,这个函数也可以检测到很多内置对象,当然我们需要借助call
来显式绑定一下this
指向。
// 内置Date对象
console.log(new Date instanceof Date); // true
console.log(new Date instanceof Object); // true
console.log(Object.prototype.toString.call(new Date));//"[object Date]"
// 内置Function对象
console.log(new Function instanceof Function); // true
console.log(new Function instanceof Object); // true
console.log(Object.prototype.toString.call(new Function));//"[object Function]"
// 内置Array对象
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
console.log(Object.prototype.toString.call([]));//"[object Array]"
// 普通的Object
console.log({} instanceof Object); // true
console.log(Object.prototype.toString.call({}));//"[object Object]"
加深理解
特别地,在做类型检测的时候有两个比较特殊的对象需要我们关注下,一个是Function.prototype
即函数的原型对象,一个是Array.prototype
即数组的原型对象。
Function.prototype
特别之处在于,它可以用typeof
去检测。
console.log(typeof function(){});// "function"
Array.prototype
特别之处在于,它还可以用Array.isArray()
去判断,针对数组这个特殊的对象,我们下面的加餐环节还会进行具体的讲解。
console.log(Array.isArray([]));// true
白话总结版
对于引用类型,我们最常用instanceof
来进行检测,它是基于原型链的一种检测方法,同时我们也能用Object.prototype.toString()
函数去检测,一些比较特殊的对象比如函数,它可以用typeof
去检测,返回的是function
。数组的话,我更推荐用Array.isArray()
去判断。(具体原因可看下面加餐2)
加餐1:instanceof的实现
如何去实现instanceof
是面试的热门考题之一,它的实现也非常简单,让我们回忆一下MDN中对instanceof
的描述就找到思路。
思路很明确,沿着原型链上找就OK,下面我们用迭代的思想去实现(当然递归也可)。
function myInstanceof(object,constructor) {
const prototype = constructor.prototype;
let proto = object.__proto__;
while(true) {
if(proto === null) return false;
if(proto === prototype) return true;
proto = proto.__proto__;
}
}
加餐2:检测数组的多种方法与利弊
检测数组一共有三种方法,下面我将这三种方法都列出来。
console.log([] instanceof Array); //true
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Array.isArray([])); //true
先说一个结论,最佳实践是Array.isArray()
,但是你知道前两种方法的坑吗?
instanceof 检测Array的坑
这点其实在红宝书上有提到过,当页面中嵌套了iframe
的时候,假设我们把父级窗口中创建的一个数组array
,传到iframe
中后,在iframe
中用array instanceof Array
来判断时,返回的竟然是false
!不必惊讶,我们通过比较父级窗口和iframe
的数组原型对象就可以发现,其实它们虽然”内容“相同,但是它们其实存放在不同内存空间中,所以它们不是严格相等的。所以此Array
非彼Array
。
另外一个坑,提到的比较少,也是我偶然发现的,严格意义上来说也不算坑。其实instanceof
的行为是可以被重写的。
让我们来看看MDN上提供的例子
class Array1 {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof Array1);
// expected output: true
Symbol.hasInstance
作为一个静态方法,我们可以重写他的行为,使得我们的Array1
类在面对instanceof
检测时也能表现得和数组一样,虽然他的原型链上没有相应的数组原型对象。
下面我们尝试一下,能不能以此来改写数组的Symbol.hasInstance
规则,以此来欺骗instanceof
。
console.log(Array[Symbol.hasInstance]([])); //true
Array[Symbol.hasInstance] = function(){return false} //强行修改会怎么样
console.log(Array[Symbol.hasInstance]([])); //true 修改并没有成功
虽然我们没法直接修改Array
的行为,但是我们可以自己伪造一个对象去欺骗instanceof
,所以我勉强把它算作一个坑,大家可以了解一下。
Object.prototype.toString的坑
这个坑解释起来非常简单,仅仅是因为它有被重写的风险。
console.log(Object.prototype.toString.call([])); // "[object Array]"
Object.prototype.toString = function() {return "重写"};
console.log(Object.prototype.toString.call([])); // "重写"
加餐3:当Object.prototype.toString碰上基本数据类型
大家先看看下面的例子
console.log(Object.prototype.toString.call(null)); //"[object Null]"
console.log(Object.prototype.toString.call(undefined)); //"[object Undefined]"
console.log(Object.prototype.toString.call(1)); //"[object Number]"
console.log(Object.prototype.toString.call("str")); //"[object String]"
console.log(Object.prototype.toString.call(true)); //"[object Boolean]"
console.log(Object.prototype.toString.call(Symbol()));//"[object Symbol]"
我们发现,似乎它比typeof
更好用?连typeof
检测不出的null
它都能搞定?别着急下结论,让我们再举个例子。
console.log(Object.prototype.toString.call(new Number)); //"[object Number]"
console.log(Object.prototype.toString.call(new String)); //"[object String]"
console.log(Object.prototype.toString.call(new Boolean)); //"[object Boolean]"
对于number
、string
和boolean
来说,我们单靠Object.prototype.toString
是无法区分它们与Number
、String
、Boolean
对象区别的。
如果想更进一步了解Object.prototype.toString
的实现机理,推荐大家阅读下面的文章,本文就不再多多赘述。
【数据类型】JavaScript数据类型&聊聊Object.prototype.toString
加餐4:一种解决类型的检测的参考方案
下面,我将给出一种较为通用的解决类型检测的参考方案。该方案参考了type.js ,由颜海镜编写的用于判断数据类型的方法库。为了精简,我去掉了一些兼容性相关的代码,大家可以着重关注一下本文讲的一些知识点
function type(x) {
// 1. 在使用typeof检测时,先把特殊情况null检测了
if(x === null) {
return 'null';
}
const t = typeof x;
if(t !== 'object'){
return t;
}
// 2. 使用Object.prototype.toString检测,截取部分字符
const cls = Object.prototype.toString.call(x).slice(8, -1);
const clsLow = cls.toLowerCase();
if(clsLow !== 'object'){
// 区分Number Boolean String对象和number boolean string基本数据类型
// 即前者大写,后者小写
if (clsLow === 'number' || clsLow === 'boolean' || clsLow === 'string') {
return cls;
}
return clsLow;
}
// 3. 如果该对象的构造函数为Object,则直接返回‘object'
if(x.constructor === Object) {
return clsLow;
}
// 4. 处理Object.create(null)创建的没有原型对象的情况
if (x.__proto__ === null) {
return 'object'
}
// 5. 处理其他非内置对象
const cname = x.constructor.name;
if (typeof cname === 'string') {
return cname;
}
// 6. 未知的对象类型
return 'unknown';
}
测试一下
console.log(type(1)); //number
console.log(type(new Number)); //Number
console.log(type({})); //Object
console.log(Object.create(null)); //object
function Person(){}
console.log(type(new Person)); //Person
写在最后
至此,鼠子认为已经比较详尽的列出了与基本数据类型和类型检测相关的知识点,若有疏漏请大家提出来。本文将长期更新和补充类似问题,欢迎大家点赞收藏。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!