typeof和instanceof原理

目录

  • JavaScript数据类型
  • typeof
  • JavaScript原型链
  • instanceof

JavaScript数据类型

JavaScript有八种内置类型前端

  • 空值(null)
  • 未定义(undefined)
  • 布尔值(boolean)
  • 数字(number)
  • 字符串(string)
  • 对象 (object)
  • 符号(symbol, ES6中新增)
  • 大整数(BigInt, ES2020 引入)

除对象外,其余统称为“基本类型”。es6

typeof null // 'object'
typeof undefined; // "undefined"
typeof false; // "boolean"
typeof 1; // "number"
typeof '1'; // "string"
typeof {}; // "object" 
typeof []; // "object" 
typeof new Date(); // "object"

typeof Symbol(); // "Symbol"
typeof 123n // 'bigint'
复制代码

这里的类型指的是值,变量是没有类型的,变量能够随时持有任何类型的值。JavaScript中变量是“弱类型”的,一个变量能够如今被赋值为 字符串类型,随后又被赋值为数字类型。数组

typeof是一个操做符而不是函数,用来检测给定变量的数据类型。浏览器

Symbol 是ES6中引入的一种原始数据类型,表示独一无二的值。BigInt(大整数)是 ES2020 引入的一种新的数据类型,用来解决 JavaScript中数字只能到 53 个二进制位(JavaScript 全部数字都保存成 64 位浮点数,大于这个范围的整数,没法精确表示的问题。(在日常的开发中,数据的id 通常用 string 表示的缘由)。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。 1234为普通整数,1234nBigInt。了解更多能够看 《ES6 入门教程》微信

typeof null 为何返回 'object',稍后会从JavaScript数据底层存储机制来解释。函数

还有一种状况学习

function foo() {};
typeof foo; // 'function'
复制代码

这样看来,function 也是JavaScript的一个内置类型。然而查阅规范,就会知道,它其实是 object 的一个"子类型"。具体来讲,函数是“可调用对象”,它有一个内部属性[[call]],该属性使其能够被调用。typeof 能够用来区分函数其余对象。ui

可是使用 typeof不能 判断对象具体是哪一种类型。全部typeof 返回值为 "object" 的对象(如数组,正则等)都包含一个内部属性 [[class]](咱们能够把它看作一个内部的分类)。这个属性没法直接访问,通常经过 Object.prototype.toString(...)来查看。this

Object.prototype.toString.call(new Date); // "[object Date]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(/reg/ig); // "[object RegExp]"
复制代码

instanceof 运算符也经常用来判断对象类型。用法: 左边的运算数是一个object,右边运算数是对象类的名字或者构造函数; 返回truefalsespa

[] instanceof Array; // true
[] instanceof Object; // true
[] instanceof RegExp; // false
new Date instanceof Date; // true
复制代码

instanceof 的内部机制是:检测构造函数的 prototype 属性是否出如今某个实例对象的原型链上。下面会详解介绍该部分。

typeof 原理

typeof原理: 不一样的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息

  • 000: 对象
  • 010: 浮点数
  • 100:字符串
  • 110: 布尔
  • 1: 整数

typeof null 为"object", 缘由是由于 不一样的对象在底层都表示为二进制,在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,天然前三位也是0,因此执行typeof时会返回"object"。 一个不恰当的例子,假设全部的Javascript对象都是16位的,也就是有16个0或1组成的序列,猜测以下:

Array: 1000100010001000
null:  0000000000000000

typeof []  // "object"
typeof null // "object"
复制代码

由于Array和null的前三位都是000。为何Array的前三位不是100?由于二进制中的“前”通常表明低位, 好比二进制00000011对应十进制数是3,它的前三位是011。

instanceof

要想从根本上理解,须要从两个方面入手:

  • 语言规范中是如何定义这个运算符的
  • JavaScript原型继承机制

通俗一些讲,instanceof 用来比较一个对象是否为某一个构造函数的实例。注意,instanceof运算符只能用于对象,不适用原始类型的值。

  1. 判断某个实例是否属于某种类型
function Foo() {};
Foo.prototype.message = ...;
const a = new Foo();
复制代码
  1. 也能够判断一个实例是不是其父类型或者祖先类型的实例。
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
复制代码

JavaScript原型链

理解原型

咱们建立的每一个函数都有一个 [prototype])属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。那么 prototype 就是调用 构造函数 而建立的那个对象实例的原型对象。使用原型对象的好处是可让全部对象实例共享它所包含的属性和方法。

function Person() {};
Person.prototype.name = 'kangkang';
Person.prototype.sayName = function() {
    console.log(this.name);
}

const person1 = new Person();
person1.sayName(); // 'kangkang'

const person2 = new Person();
person2.sayName(); // 'kangkang'

console.log(person1.sayName === person2.sayName);
// true
复制代码

构造函数,原型和实例的关系

  • 每一个构造函数都有一个原型对象
  • 原型对象都包含一个指向构造函数指针
  • 实例都包含一个指向原型对象指针

那么,假如咱们让原型对象等于另外一个类型实例,结果会怎么样? 显然,此时的原型对象将包含一个指向另外一个原型指针,相应地,另外一个原型中也包含着一个指向指向另外一个构造函数指针。假如另外一个原型又是另外一个类型实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

上面这段话有点绕,若是想不明白的话,这里能够停一下,读三篇,再结合咱们日常写代码使用过程当中的实际场景。

[[prototype]]机制

[[prototype]]机制就是存在与对象中的一个内部连接,它会引用其余对象。 一般来讲,这个连接的做用是:若是在对象上没有找到须要的属性或者方法引用,引擎就会继续在 [[ptototype]]关联的对象上进行查找,同理,若是在后者中也没有找到须要的引用就会继续查找它的[[prototype]],以此类推。这一系列对象的连接被称为“原型链”。

可是哪里是 [[prototype]]的 ”尽头“呢?

全部普通的 [[prototype]]链最终都会执行内置的 Object.prototype。因为全部的"普通"(内置,不是特定主机的扩展)对象都”源于“(或者说把[[prototype]] 链顶端设置为)这个Object.prototype对象,因此说它包含JavaScript中许多通用的功能。好比说.toString().valueOf()等等

Object.ptototype是js原型链的最顶端,它的__proto__null(有__proto__属性,但值是 null,由于这是原型链的最顶端);

为何要这么设计?

最主要的就是节省内存,若是属性和方法定义在原型上,那么全部的实例对象就能共享。

__proto__

绝大多数(不是全部)浏览器也支持一种非标准的方法来访问内部的 [[prototype]]属性。

function Foo() {};
const a = new Foo();

a.__proto__ === Foo.prototype; // true
复制代码

这个奇怪的.__proto__属性“神奇地”引用了内部的[[prototype]]对象。若是你想直接查找(甚至能够直接经过.proto.proto ...来遍历)原型链的话,这个方法很是有用。

.construtor同样,__proto__实际上并不存在于你正在使用的对象(本例中是a)。实际上,它和其余的经常使用函数(.toString()、.isPrototypeOf(...),等等 同样,存在于内置的Object.prototype中。(它们是不可枚举的;

此外,.__proto__看起来很像一个属性,可是实际上它更像一个 getter/setter.__proto__的实现大体是这样的

Object.defineProperty(Object.prototype, "__proto__", {
    get: function() {
        return Object.getPrototypeOf(this);
    },
    // ES6中的Object.setPrototypeOf
    set: function(o) {
        Object.setPrototypeOf(this, o);
        return o;
    }
})
复制代码

所以,访问(获取值) a.__proto__时,其实是调用了 a.__proto__()(调用getter函数)。虽然getter函数存在于Object.prototype对象中,可是 它的 this 指向对象a,因此和object.getPrototypeOf(a)结果相同。

.__proto__是可设置属性,以前的代码中使用ES6的Object.setPrototypeOf(...)进行设置。然而,一般来讲你不须要修改已有对象的[[prototype]]

原型链

JavaScript原型链

    1. function Foo 就是一个方法,好比内置的 Array,String,或者自定义方法。
    1. function Object就是 Object
    1. function Function就是 Function
    1. 以上三个其实都是 function,因此他们的 __proto__都是 Function.prototype
    1. 记住 String, Array, Number,Object, Function这些其实都是 function 
function Foo() {};

console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true

console.log(Foo instanceof Foo); // false
console.log(Foo instanceof Object); // true
console.log(Foo instanceof Function); // true
复制代码

你们能够在控制台输出,能够直观的看到每一个步骤的输出,结合instanceof 的规范跟js原型链 加深理解。

回过头来再看instanceof

instanceof的语法:

object instanceof constructor
// 等同于
constructor.prototype.isPrototypeOf(object)
复制代码
  • object: 要检测的对象
  • constructor:某个构造函数

instanceof的代码实现。

function instanceof(L, R) { //L是表达式左边,R是表达式右边
    const O = R.prototype;
    L = L.__proto__;
    while(true) {
        if (L === null)
            return false;
        if (L === O) // 这里重点:当 L 严格等于 0 时,返回 true 
            return true;
        L = L.__proto__;
    }
}
复制代码

instanceof原理: 检测 constructor.prototype是否存在于参数 object的 原型链上。instanceof 查找的过程当中会遍历object的原型链,直到找到 constructorprototype ,若是查找失败,则会返回false,告诉咱们,object 并不是是 constructor 的实例。

原型链这部分很很差理解,我基本上都是看完过几天就忘,因此要多看几遍多理解,花些时间搞明白,搞明白这部分。以后再看相关的东西,就很简单易懂。这部分是JavaScript很重要的核心。花几天时间反复看,弄明白了,之后理解不少问题都是简单的多。若是你发现我上面哪部分表述的不太准确,记得给我指出来,互相学习。这部分推荐好好看看 《JavaScript高级程序设计(第3版)》第六章的这部分,还有 《你不知道的JavaScript(上卷)》第五章关于这部份内容的讲解。

Symbol.hasInstance

对象的Symbol.hasInstance属性,指向一个内部方法。当其余对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。好比,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true
复制代码

总结

看完以后,脑子里能够把上面的内容串一下;看看下面的几个问题你是否能够马上想出来

  • JavaScript有哪几种数据类型,都有哪些判断数据类型的操做,返回值是什么,原理是什么
  • typeof null 为何是 ”object“
  • 什么是原型,哪里是 [[prototype]]的 ”尽头“,为何要这么设计
  • JavaScript原型链的核心是什么
  • instanceof的原理是什么
  • Symbol.hasInstance又是什么(或者你本身实现一个instanceof

其余

最近发起了一个100天前端进阶计划,主要是深挖每一个知识点背后的原理,欢迎关注 微信公众号「牧码的星星」,咱们一块儿学习,打卡100天。

牧码的星星
相关文章
相关标签/搜索