小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
首先
- 如何判断变量是不是数组类型?
- 为什么typeof不行?
- instanceof为什么判断object也是true?
- 如何实现instanceof?
- toString为什么可以打印出[object Array],Array是怎么来的?
- toString为什么要使用call?
- toString 判断数组和isArray判断数组有啥区别?
原理
typeof
我们第一个认识判断类型的就是这个typeof。
但是:你知道typeof可以判断出哪几种类型呢?
答: object、function、number、symbol、string、undefined、boolean
知道了可以判断这些类型,但是typeof有一个一直历史还遗留的bug你知道是什么嘛?
console.log(typeof null); // object
原因:
js在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息。
000: 对象010: 浮点数100: 字符串110: 布尔1: 整数
对于null 和 undefined 是有点特殊的。
null的机器码均为0,所以它的低位3位也是000,因此就是object。
undefined:用 −2^30 整数来表示
但是这里又有一个问题。
console.log(null instanceof null); //Right-hand side of 'instanceof' is not an object
console.log(null instanceof Object) //false
这里我查了很多资料,也稍微看了v8的源码(就是随便点两下)然而我看不懂也找不到对应的位置。然后,我就猜测这个判断null不是Object,可能是instanceof是先000判断了null为object,但是instanceof又进行了二次判断null有没有object的那些基础的api(toString等),所以null不是Object。
这里还有疑问为什么typeof可以判断function就不能判断array其他的类型呢?
当然还是有二次判断了,那就是判断对象是否有实现了[[Call]]内部的方法,有就返回为function。参考文章
这里还有一个注意点
console.log(typeof Object); // function
console.log(typeof Array); // function
instanceof
instanceof:原理只有3个字原型链
function instanceofCode(left, right) {
var rightPrototype = right.prototype
while(true) {
if(left.__proto__ == rightPrototype) {
return true
}else if(left.__proto__ == null) { // 原型链的尽头就是null
return false
}
left = left.__proto__
}
}
var arr = []
console.log(instanceofCode(arr, Array));
对了instanceof还有个注意点:
console.log('1' instanceof String); // false
var string = new String(1)
console.log(string instanceof String); // true
value分为原始值和对象,前面'1'就是属于原始值,后面是经过new String处理,因此属于对象。
toString
在对一道常见的面试题:判断一个对象是数组有什么方法。
大家会想到Object.prototype.toString.call()
注意点:
console.log(Object.toString()); //function Object() { [native code] }
console.log(Object.prototype.toString); // [Function: toString]
原理:
Object.prototype.toString 返回的格式是 [object tag] 我们重点来说说这个tag是如何判断的。
es6之前是有一个[[class]]属性来表示不同的对象类型,内部属性(就像原型链全局找这个属性)
es6后把这个[[class]]改成了[[NativeBrand]]这个就根据枚举寻找对应的值更改tag。
这个图是盗来的,想了解更多的可以看这个原理参考的博客
null 和 undefined也是可以判断的。
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
当我们自己定义了一个对象,想要修改它内部的[[NativeBrand]]。
const obj = {
[Symbol.toStringTag]: 'obj'
}
console.log(Object.prototype.toString.call(obj)); //[object obj]
问:为什么要用call?
这是因为拿[[class]]和[[NativeBrand]]都是根据当前的this做判断的。
isArray
这个就是呼应前面的面试题判断是不是数组的方法。这个找了半天没找到缘由,来一个安慰的盲猜:那就是对比被判断的是不是拥有Array内部自身的所有api方法和属性。最后贴上看不懂v8--isArray的源码(路径: "src/builtins/array-isarray.tq")。
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
namespace runtime {
extern runtime ArrayIsArray(implicit context: Context)(JSAny): JSAny;
} // namespace runtime
namespace array {
// ES #sec-array.isarray
javascript builtin ArrayIsArray(js-implicit context: NativeContext)(arg: JSAny):
JSAny {
// 1. Return ? IsArray(arg).
typeswitch (arg) {
case (JSArray): {
return True;
}
case (JSProxy): {
// TODO(verwaest): Handle proxies in-place
return runtime::ArrayIsArray(arg);
}
case (JSAny): {
return False;
}
}
}
} // namespace array
根据之前toString我们是可以更改返回值的。
Array.prototype[Symbol.toStringTag] = 'arr'
const arr = new Array(1,2,3)
const arr1 = [1,2,3]
console.log(Object.prototype.toString.call(arr)); //[object arr]
console.log(Object.prototype.toString.call(arr1));// [object arr]
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(arr1)); // true
所以 Object.prototype.toString.call() 和 Array.isArray()的区别,当使用[Symbol.toStringTag]更改Object.prototype.toString.call()的tag的值就发生了变化,isArray的判断不会改变。最后,在vue2中源码是使用Array.isArray()来判断的。
结论
typeof可以检查出基本的数据类型,但是对object具体检测只能检查出function其他的都归
object。遗留历史feature:typeof null为object
instanceof 原理就是原型链一层层的往上找。
Object.prototype.toString.call()根据当前的this寻找[[NativeBrand]]内部属性。
isArray只是猜测未验证也不知道怎么验证(有知道的大佬求指教)。
Object.prototype.toString.call() 和 Array.isArray()的区别,当使用
[Symbol.toStringTag]更改Object.prototype.toString.call()的tag的值`,Array.isArray()未改变。
最后,菜鸟的我写的有不对的地方希望大佬们指教,抱拳。