typeof、instanceof等这些原理你知道嘛?

1,281 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

首先

  • 如何判断变量是不是数组类型?
  • 为什么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

盗图

image.png

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。

image.png

这个图是盗来的,想了解更多的可以看这个原理参考的博客

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()来判断的。

image.png

结论

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()未改变。

最后,菜鸟的我写的有不对的地方希望大佬们指教,抱拳。