JavaScript有八种内置类型前端
除对象外,其余统称为“基本类型”。
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中变量是“弱类型”的,一个变量能够如今被赋值为 字符串类型,随后又被赋值为数字类型。es6
typeof
是一个操做符而不是函数,用来检测给定变量的数据类型。数组
Symbol
是ES6中引入的一种原始数据
类型,表示独一无二的值。BigInt(大整数)是 ES2020 引入的一种新的数据类型,用来解决 JavaScript中数字只能到 53 个二进制位(JavaScript 全部数字都保存成 64 位浮点数,大于这个范围的整数,没法精确表示的问题。(在日常的开发中,数据的id 通常用 string 表示的缘由)。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。1234
为普通整数,1234n
为BigInt
。了解更多能够看 《ES6 入门教程》
typeof null
为何返回 'object'
,稍后会从JavaScript数据底层存储机制来解释。浏览器
还有一种状况微信
function foo() {}; typeof foo; // 'function'
这样看来,function
也是JavaScript
的一个内置类型
。然而查阅规范,就会知道,它其实是 object
的一个"子类型"。具体来讲,函数是“可调用对象”,它有一个内部属性[[call]]
,该属性使其能够被调用。typeof
能够用来区分函数其余对象。函数
可是使用 typeof
不能 判断对象具体是哪一种类型。全部 typeof
返回值为 "object" 的对象(如数组,正则等)都包含一个内部属性 [[class]]
(咱们能够把它看作一个内部的分类)。这个属性没法直接访问,通常经过 Object.prototype.toString(...)
来查看。学习
Object.prototype.toString.call(new Date); // "[object Date]" Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call(/reg/ig); // "[object RegExp]"
instanceof
运算符也经常用来判断对象类型。用法: 左边的运算数是一个object
,右边运算数是对象类的名字或者构造函数; 返回true
或false
。this
[] instanceof Array; // true [] instanceof Object; // true [] instanceof RegExp; // false new Date instanceof Date; // true
instanceof
的内部机制是:检测构造函数的 prototype
属性是否出如今某个实例对象的原型链上。下面会详解介绍该部分。spa
typeof
原理: 不一样的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。prototype
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
用来比较一个对象是否为某一个构造函数的实例。注意,instanceof运算符只能用于对象,不适用原始类型的值。
实例
是否属于某种类型
function Foo() {}; Foo.prototype.message = ...; const a = new Foo();
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
咱们建立的每一个函数都有一个 [[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]]链最终都会执行内置的 Object.prototype
。因为全部的"普通"(内置,不是特定主机的扩展)对象都”源于“(或者说把[[prototype]] 链顶端设置为)这个Object.prototype
对象,因此说它包含JavaScript中许多通用的功能。好比说.toString()
和 .valueOf()
等等。
Object.prototype
是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]]
。
function Foo
就是一个方法,好比内置的 Array,String,或者自定义方法。function Object
就是 Object
function Function
就是 Function
__proto__
都是 Function.prototype
String
, Array
, Number
, Object
, Function
这些其实都是 functionfunction 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)
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
的原型链,直到找到 constructor
的 prototype
,若是查找失败,则会返回false
,告诉咱们,object
并不是是 constructor
的实例。
原型链这部分很很差理解,我基本上都是看完过几天就忘,因此要多看几遍多理解,花些时间搞明白,搞明白这部分。以后再看相关的东西,就很简单易懂。这部分是JavaScript很重要的核心。花几天时间反复看,弄明白了,之后理解不少问题都是简单的多。若是你发现我上面哪部分表述的不太准确,记得给我指出来,互相学习。这部分推荐好好看看《JavaScript高级程序设计(第3版)》第六章
的这部分,还有《你不知道的JavaScript(上卷)》第五章
关于这部份内容的讲解。
对象的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天。