JS 基础: typeof instanceof 类型检查

news/2024/7/19 14:38:52 标签: js, prototype, object, 测试类型

JS 基础: typeof & instanceof 类型检查

文章目录

  • JS 基础: typeof & instanceof 类型检查
    • 简介
    • 参考
  • 正文
    • `typeof` 关键字
      • 字面量类型判断
      • 内置函数类型判断
      • `typeof` 小结
      • 应用
    • `instanceof` 关键字
      • Prototype Chain 原型链回顾
        • 内置函数的原型链
        • 属性规则:`prototype`、`__proto__`
      • `instanceof` 实例
      • 添加新类型
      • 完善 Integer
        • 一般版本
        • Currying 科里化定义
  • 结语

简介

写过几篇有关 JS 语言特性的博客,也研究过一些 ES6 相关的新特性。但是现在回头来看看我真的够了解 JS 里面的类型检查吗?由于 JS 属于弱类型动态语言,平常都用 varletconst 声明变量就完事了,但是若我们想加强代码的健壮性和可用性,势必要在方法前对于不同的参数进行类型检查,并根据不同类型有相应的处理。好讲白了就是使用 typeofinstanceof 两个关键字就对了,马上来看看它们到底在干嘛。

参考

JS中typeof与instanceof的区别https://www.cnblogs.com/Trr-984688199/p/6180040.html
js中typeof与instanceof用法https://www.cnblogs.com/double405/p/5326311.html

正文

typeof 关键字

先来看看 typeof 这个关键字,比较简单。

稍微用过或是看过 JS(没看过猪跑至少吃过猪肉(bushi),都知道在 JS 中有九个基本类型(算上 undefined 和 Symbol):number、boolean、string、array、object、function、null、undefined、Symbol(ES6 新增);同时也存在一些内置函数作为构造函数:Number、Boolean、String、Array、Object、Symbol 等。

typeof 关键字是一个一元操作符(加不加括号没差,语法解析正确就行),会返回参数变量的"类型"的字符串,然而这个类型可能有一点 bug,跟你想象中的不太一样。其实也没啥秘密,直接来看输出:

字面量类型判断

第一波先给出直接判断字面量的结果:

js">console.log('----- literal value -----')
console.log(`typeof 1: ${typeof 1}`) // 数字字面量
console.log(`typeof '1': ${typeof '1'}`) // 字符串字面量
console.log(`typeof true: ${typeof true}`) // 布尔值字面量
console.log(`typeof []: ${typeof []}`) // 数组字面量
console.log(`typeof {}: ${typeof {}}`) // 对象字面量
console.log(`typeof function () {}: ${typeof function () {}}`) // 方法字面量
console.log(`typeof () => {}: ${typeof (() => {})}`) // 箭头函数字面量
console.log(`typeof null: ${typeof null}`) // null
console.log(`typeof undefined: ${typeof undefined}`) // defined
  • 输出结果
----- literal value -----
typeof 1: number
typeof '1': string
typeof true: boolean
typeof []: object
typeof {}: object
typeof function () {}: function
typeof () => {}: function
typeof null: object
typeof undefined: undefined

从输出我们可以看到几个特点(以下说明皆为个人理解和记忆方式,不一定正确hhh):

  • undefined 自成一类
  • null 空指针,所以被归类为对象 object
  • [] 数组:这个就比较特别了,可能是因为历史缘故,又或是 JS 引擎内部保存并解析数组的方式造成的,由于除了基本类型之外 JS 所有东西都能被视为对象 object,数组便能够被视为拥有键 0 , 1 , 2 , . . . 0, 1, 2, ... 0,1,2,... 的一个特殊对象(还有一种对象叫做类数组对象(Array-like Object),这又是另一个故事了…),总而言之结论就是 typeof [] = "object",记住就对了hhh

内置函数类型判断

前面提过,JS 也为这些基本类型提供内置函数(built-in function),然而这个内置函数同时能够作为构造函数,也能够作为一般函数使用,这时候构建出来的值的类型就大不相同了:

js">console.log('----- use constructor -----')
console.log(`typeof Number(1): ${typeof Number(1)}`)
console.log(`typeof new Number(1): ${typeof new Number(1)}`)
console.log(`typeof String('1'): ${typeof String('1')}`)
console.log(`typeof new String('1'): ${typeof new String('1')}`)
console.log(`typeof Boolean(true): ${typeof Boolean(true)}`)
console.log(`typeof new Boolean(true): ${typeof new Boolean(true)}`)
console.log(`typeof Array(10): ${typeof Array(10)}`)
console.log(`typeof new Array(10): ${typeof new Array(10)}`)
console.log(`typeof Object({}): ${typeof Object({})}`)
console.log(`typeof new Object({}): ${typeof new Object({})}`)
  • 输出
----- use constructor -----
typeof Number(1): number
typeof new Number(1): object
typeof String('1'): string
typeof new String('1'): object
typeof Boolean(true): boolean
typeof new Boolean(true): object
typeof Array(10): object
typeof new Array(10): object
typeof Object({}): object
typeof new Object({}): object

我们可以看到如果将内置函数作为一般函数使用(例如 Number(xxx)),这时候构建出来的值相当于一个字面量,然而作为构造函数构造出来的仅仅是不同类型的另一个对象而已(这里指的是对象的类型变量本身的类型必为对象 object

typeof 小结

这边给出关于 typeof 关键字的小结:

  • typeof 用于判断变量的类型,而不是对象的类型
  • undefined 自成一类:undefined 类型
  • nullarray 会被判断成对象 object

应用

由于 typeof 不能很好的识别数组和对象的差别,也看不出对象所属的类型(原型,下面会提到),所以 typeof 通常用于下面几种场景:

  • 判断未定义 undefined:当然其实用 === undefined 也行,所以用处不大
  • 判断对象 object:如果是对象才有能够访问的属性,也就是决定有没有属性能访问(属性访问符 .)
  • 判断函数 function:用于确定回调函数是否正确传入

instanceof 关键字

看完发现,typeof 好像有点废啊,尤其到了大家都在用 OOP 的时代,还不能判断对象的类型那可不行,因此我们就要请出第二个关键字 instanceof(是的没错 Java 里面也有一个,但是作用和含义大不相同:Java 使用的是基于类型的继承结构,而 JS 则是基于原型链,两者差了大概八千里远)

Prototype Chain 原型链回顾

在正式使用 instanceof 关键字之前,我们先来回顾以下关于 JS 原型链的知识(也可以参考我之前写的 JS 基礎:Prototype Chain 原型鏈)

JS 的对象类型是基于原型链,这边就不再展开解说,我们的重点摆在内置函数之间的继承(原型)关系

内置函数的原型链

前面我们提到各个类型的内置函数,同时就是构造函数,同时就是类型(划重点,这个真的很重要)!

原型链的类型信息就是指当前对象的原型对象(proto),我们可以通过非正式但是通用的属性 __proto__ 来访问得到:

js">const nc = Number
const sc = String
const bc = Boolean
const ac = Array
const oc = Object

console.log(nc)
console.log(nc.prototype)
console.log(nc.prototype.__proto__)
console.log(sc)
console.log(sc.prototype)
console.log(sc.prototype.__proto__)
console.log(bc)
console.log(bc.prototype)
console.log(bc.prototype.__proto__)
console.log(ac)
console.log(ac.prototype)
console.log(ac.prototype.__proto__)
console.log(oc)
console.log(oc.prototype)
console.log(oc.prototype.__proto__)

console.log('----- compare -----')
console.log(nc.prototype.__proto__ === sc.prototype.__proto__)
console.log(nc.prototype.__proto__ === bc.prototype.__proto__)
console.log(nc.prototype.__proto__ === ac.prototype.__proto__)
console.log(nc.prototype.__proto__ === oc.prototype)

这边我们使用浏览器的输出能够看得更清楚:

我们可以看到各个内置函数的原型链,输出太乱了,画张图吧(上一篇 里有完整的大图,这边重画一次)

prototype__proto___175">属性规则:prototype__proto__

我们可以看到通常是构造函数的 prototype 指向原型对象,对象实例(可能同时作为某个对象的原型)则透过 __proto__ 指向它的原型对象,所有对象的原型链顶部必定存在 Object.prototype 指向的对象,而这个对象的 __proto__ 属性则为 null

instanceof 实例

讲了半天的原型链,现在回到我们的主题 instanceof 关键字。

instanceof 的关键字检查的便是当前对象的原型链上是否存在指定类型(原型对象)

好看起来有点难懂,首先我们透过上一篇明确所谓的对象类型,便是原型链上的一个个对象,instanceof 则会为我们检查这个链上有没有目标类型(一个原型对象),使用语法如下:

js"><object> instanceof <constructor>

注意这边的第二个参数不是直接传入那个对象,而是某个构造函数,所以 instanceof 关键字实际上做的事应该这样说:

检查原型对象 <constructor>.prototype 是否存在于 <object> 的原型链上

说了半天越说越糊涂了,赶紧来看代码hhh

js">const n = new Number(1)

console.log(n)
console.log(n.__proto__)
console.log(n.__proto__.__proto__)
console.log(n.__proto__.__proto__.__proto__)

console.log(`1 instanceof Number: ${1 instanceof Number}`)
console.log(`n instanceof Number: ${n instanceof Number}`)
  • 输出

和我们预想的一样:字面量 1 不是对象,所以啥也不是,必须为 false;而 n 使用 Number 构造出来的对象,所以它的原型链应该为:

n -> Number.prototype -> Object.prototype -> null

最后一句 n instanceof Number 会查找 Number.prototype 是否在 n 的原型链上,第一个就是所以返回 true

添加新类型

看过使用内置函数创建的数字(Number)对象,接下来我们自己写一个整数类型(Integer) 来加深我们对于 instanceof 的应用

prototype_225">直接指向 Number.prototype

首先我们第一个想到的就是,直接写一个构造函数,然后把它的原型变成 Number.prototype 不就成了吗,马上来试试:

js">function Integer (num) {
  this.value = Math.floor(num)
}

Integer.prototype = Number.prototype

const n = new Number(1)
const i = new Integer(123)
console.log(i)
console.log('----- Integer.prototype = Number.prototype -----')
console.log(`i instanceof Number: ${i instanceof Number}`)
console.log(`i instanceof Integer: ${i instanceof Integer}`)
console.log(`n instanceof Number: ${n instanceof Number}`)
console.log(`n instanceof Integer: ${n instanceof Integer}`)
  • 输出
----- Integer.prototype = Number.prototype -----
i instanceof Number: true
i instanceof Integer: true
n instanceof Number: true
n instanceof Integer: true

咦?好像有个小 bug,n 怎么也变成 Integer 类型了?

这是因为我们直接写了 Integer.prototype = Number.prototype,也就是说我们只是创建了 Number 类型的另一个构造函数 Integer,也就是说原型链张成下面这样:

下面我们来看看真正的继承

继承 Number

上面的实现其实不太对,Integer 应该作为 Number 的子类(使用 is a 来判断:一个 Integer 是 Number,而一个 Number 不一定是 Integer)

也就是说我们应该创建一个新类型(再次强调,类型就是原型对象),我们应该要将原型链做成下面这样:

js">function Integer (num) {
  this.value = Math.floor(num)
}
const IntegerProto = new Number()
Integer.prototype = IntegerProto

const n = new Number(1)
const j = new Integer(456)
console.log('----- Integer.prototype = IntegerProto -----')
console.log(`j instanceof Number: ${j instanceof Number}`)
console.log(`j instanceof Integer: ${j instanceof Integer}`)
console.log(`n instanceof Number: ${n instanceof Number}`)
console.log(`n instanceof Integer: ${n instanceof Integer}`)
  • 输出
----- Integer.prototype = IntegerProto -----
j instanceof Number: true
j instanceof Integer: true
n instanceof Number: true
n instanceof Integer: false

好的大功告成啦!我们成功继承了 Number,并区别出 Number 和 Integer 的类层次关系了(j 是 Integer 也是 Number;n 是 Number 但不是 Integer)

完善 Integer

最后的最后我们完善以下我们的 Integer 类型,并在其中加入类型判断,使其能够接受 (1) 一般的数字也能够接受 (2) Integer 类型的对象作为参数

一般版本

js">function Integer (num) {
  this.value = Math.floor(num)
}

Integer.prototype = new Number() // 继承 Number

Integer.prototype.add = function (num) {
  // 传入参数为 number
  if (typeof num === 'number') {
      num = Math.floor(num)
      this.value = this.value + num
  // 传入为 Integer 实例
  } else if (num instanceof Integer) {
    this.value = this.value + num.value
  // 不接受其他类型
  } else {
    throw new TypeError('expect number or Integer for function add')
  }
  console.log(`add ${num} => ${this.value}`)
  return this
}

Integer.prototype.sub = function (num) {
  if (typeof num === 'number') {
      num = Math.floor(num)
      this.value = this.value - num
  } else if (num instanceof Integer) {
    this.value = this.value - num.value
  } else {
    throw new TypeError('expect number or Integer for function sub')
  }
  console.log(`sub ${num} => ${this.value}`)
  return this
}

Integer.prototype.mul = function (num) {
  if (typeof num === 'number') {
      num = Math.floor(num)
      this.value = this.value * num
  } else if (num instanceof Integer) {
    this.value = this.value * num.value
  } else {
    throw new TypeError('expect number or Integer for function mul')
  }
  console.log(`mul ${num} => ${this.value}`)
  return this
}

Integer.prototype.div = function (num) {
  if (typeof num === 'number') {
    num = Math.floor(num)
    this.value = Math.floor(this.value / num)
  } else if (num instanceof Integer) {
    this.value = Math.floor(this.value / num.value)
  } else {
    throw new TypeError('expect number or Integer for function div')
  }
  console.log(`div ${num} => ${this.value}`)
  return this
}

Integer.prototype.toString = function () {
  return this.value
}

const i = new Integer(1)
console.log(`i = ${i}`)
console.log(`i instanceof Integer: ${i instanceof Integer}`)
console.log(`i instanceof Number: ${i instanceof Number}`)
i.add(15).sub(new Integer(4)).mul(5).div(new Integer(13.579))
  • 输出
i = 1
i instanceof Integer: true
i instanceof Number: true
add 15 => 16
sub 4 => 12
mul 5 => 60
div 13 => 4

Currying 科里化定义

我们发现上面的定义重复性相当高,因此我们就可以抽象出一个方法生成函数(借鉴 Currying 科里化的思想

js">function Integer (num) {
  this.value = Math.floor(num)
}

Integer.prototype = new Number() // 继承 Number

// 两个参数的操作抽象
Integer.prototype.binary_operation = function (name, calc) {
  return (num) => {
    if (typeof num === 'number') {
      num = Math.floor(num)
      this.value = Math.floor(calc(this.value, num))
    } else if (num instanceof Integer) {
      this.value = Math.floor(calc(this.value, num.value))
    } else {
      throw new TypeError('expect number or Integer for function add')
    }
    console.log(`${name} ${num} => ${this.value}`)
    return this
  }
}
// 科里化调用
Integer.prototype.add = function (num) { return this.binary_operation('add', (x, y) => x + y)(num) }
Integer.prototype.sub = function (num) { return this.binary_operation('sub', (x, y) => x - y)(num) }
Integer.prototype.mul = function (num) { return this.binary_operation('mul', (x, y) => x * y)(num) }
Integer.prototype.div = function (num) { return this.binary_operation('div', (x, y) => x / y)(num) }

Integer.prototype.toString = function () {
  return this.value
}

const i = new Integer(1)
console.log(`i = ${i}`)
console.log(`i instanceof Integer: ${i instanceof Integer}`)
console.log(`i instanceof Number: ${i instanceof Number}`)
i.add(15).sub(new Integer(4)).mul(5).div(new Integer(13.579))
  • 输出
i = 1
i instanceof Integer: true
i instanceof Number: true
add 15 => 16
sub 4 => 12
mul 5 => 60
div 13 => 4

结语

本篇本来就是想简单介绍以下 typeofinstanceof 的用法,贴几个输出就完事了hh,没想到突然脑洞大开加了一个 Integer 进去,也是加深对两个关键字的理解吧(确定馁hh)。


http://www.niftyadmin.cn/n/735465.html

相关文章

使用GPU进行深度学习训练时查看GPU状态

使用命令 nvidia-smi 实时查看GPU状态 $ watch -n 10 nvidia-smi # 每 10s 显示一次显存的情况参考链接&#xff1a;Linux下实时查看GPU状态

C++11-元组

范型元组 tuple 可以理解为是对老版本pair类模板的扩展&#xff0c;其中的元素个数不再限于两个&#xff0c;而且功能更加丰富 tuple:构造tuple对象所保存的只是构造实参的拷贝 make_tuple:构造tuple对象所保存的只是构造实参的拷贝 tie:通过tie构造的tuple对象&#xff0c;保存…

Linux基础(一)常用文件管理命令

笔记来源于y总的linux基础课 文件系统 /&#xff1a;根目录 bin&#xff1a;存储常用的可执行文件的命令 etc&#xff1a;配置文件 var&#xff1a;/log 中存储日志 lib&#xff1a;存储一些安装包和头文件 home&#xff1a;所有用户的家目录 proc&#xff1a;放一些进程相关…

JS 实战: Drag 点击拖曳效果

JS 实战: Drag 点击拖曳效果 文章目录JS 实战: Drag 点击拖曳效果简介参考正文项目结构 & 静态模版添加元素添加 position主要逻辑片段事件响应结构移动元素限制可移动范围最终版本结语简介 一直以来都觉得网页中的点击拖曳效果很酷&#xff0c;本篇就来尝试看看实现使用原…

Linux基础(二)tmux

蓝色 紫色 红色 tmux  [MacOS] 查看是否安装tmux   tmux -V 参考连接&#xff1a;Tmux的常用快捷键 启用鼠标控制 vim ~/.tmux.conf # 在该文件中写入 set-option -g mouse on # 退出vim # 在当前tmux pane中 ctrl b 之后输入 &#xff1a;再输入 source ~/.tmux.con…

协程

协程介绍 协程&#xff1a;是单线程下的并发&#xff0c;又称微线程&#xff0c;纤程。英文名Coroutine。一句话说明什么是线程&#xff1a;协程是一种用户态的轻量级线程&#xff0c;即协程是由用户程序自己控制调度的。、 需要强调的是&#xff1a; #1. python的线程属于内核…

JS 基础:一次搞懂 for、for...in、for...of 循环

JS 基础&#xff1a;一次搞懂 for、for…in、for…of 循环 文章目录JS 基础&#xff1a;一次搞懂 for、for...in、for...of 循环简介参考正文样例一般 for 循环Scope 作用域for...in 循环enumerable: false模仿 for...infor...in 小结for...of 循环Symbol.iteratorfor...of 小结…

Linux基础(三)Vim

蓝色 紫色 红色 Vim 一文精通 VIM -知乎 vi/vim -菜鸟教程 前言 因为我目前使用的远程连接服务器方式主要是VScodeSSH&#xff0c;所以用到Vim的情况不多&#xff0c;主要是用来修改部分配置文件&#xff0c;所以只需要会打开和编辑保存退出即可&#xff0c;因此这一部分先…