前言
本文,便是一段专为探索与驾驭JavaScript类型判断艺术量身定制的奇幻旅程。我们将携手踏入这片既熟悉又时常令人困惑的领域,揭开每种类型背后的秘密面纱,掌握那些在编码征途中不可或缺的“辨形术”。从朴素的typeof咒语,到进阶的instanceof与构造函数比对,再到ES6新晋贵族Array.isArray与Object.prototype.toString.call的精妙运用,每一式每一招都蕴含着转变代码世界的无限可能。
正文
数据类型
原始类型
- 数值Number
- 字符串String
- 布尔值Boolean
- Null
- Undefined
- Symbol
- BigInt
引用类型
- 对象Object
- 函数Function
- 数组Array
- Date
定义上面提到的所有数据类型
// 原始数据类型
let s = '123';
let n = 123;
let f = true;
let u = undefined;
let nul = null;
let sy = Symbol(123);
let b = BigInt(123);
// 引用类型
let obj = {};
let arr = [];
let fn = function () {};
let date = new Date();// date类型对象(特殊对象)
typeof
console.log(typeof(s));
console.log(typeof(n));
console.log(typeof(f));
console.log(typeof(u));
console.log(typeof(sy));
console.log(typeof(nul));// object --> 二进制前三位为0
console.log(typeof(obj));// object
console.log(typeof(arr));// object
console.log(typeof(fn));
console.log(typeof(date));// object
typeof结论
- 可以判断除 null 之外的所有原始类型
- 除了function其他所有的引用类型都会判断为object
- typeof是通过将值转换为二进制后判断其二进制前三位是否为0,是则为object
instanceof
console.log(obj instanceof Object);
console.log(arr instanceof Array);
console.log(obj instanceof Object);
console.log(date instanceof Date);
console.log(s instanceof String);// false
面试官叫你手写instanceof
- 首先我们要知道
arr.__proto__ = Array.prototype;Array.prototype.__proto__ = Object.prototype; - 一直往上找原型,直到满足
=L.__proto__ === R.prototype,同时将L.__proto__赋值给L
function myinstanceof(L, R) {
while (L !== null) {
if (L.__proto__ === R.prototype){
return true;
}
L = L.__proto__;
}
return false;
}
console.log(myinstanceof([], Array));// true
console.log(myinstanceof([], Object));// true
console.log(myinstanceof({}, Array));// fals
instanceof结论
- 只能判断引用类型
- 通过原型链查找来判断类型
Object.prototype.toString.call
Object.prototype.toString(this),该方法只能判断对象类型
var a = {}
var b = new Date();
var c = function(){};
var d = [];
var e = 123;
console.log(Object.prototype.toString(a));
console.log(Object.prototype.toString(b));
console.log(Object.prototype.toString(c));
console.log(Object.prototype.toString(d));
console.log(Object.prototype.toString(e));
步骤 1.如果 this 值未定义, 返回“[对象未定义]” 2. 如果 此值为 null, 返回“[object Null]” 3. 将 O 作为 ToObject(this) 的执行结果 4. 定义一个class作为内部属性 [[class]] 的值 5. 返回由 "[object" 和 class 和 "]" 组成的字符串
Object.prototype.toString.call(xxx)
用来获取对象类型的标准且可靠的方法,利用了Object.prototype.toString方法,并通过.call()方法改变其内部的this绑定,使其能够应用于任何对象,从而判断该对象的具体类型
var a = {}
var b = new Date();
var c = function(){};
var d = [];
var e = 123;
console.log(Object.prototype.toString.call(a));
console.log(Object.prototype.toString.call(b));
console.log(Object.prototype.toString.call(c));
console.log(Object.prototype.toString.call(d));
console.log(Object.prototype.toString.call(e));
手写call方法(改变任意函数的调用上下文(即函数内部的this值))
- 类型检查: 首先,通过
typeof this !== 'function'检查调用mycall方法的主体(即this指向的对象)是否为函数类型。如果不是函数,则抛出一个类型错误(TypeError),提示调用者这不是一个函数。 - 创建唯一标识符: 使用
Symbol创建一个独一无二的属性名fn。这样做可以防止与对象中已存在的属性名冲突,保证临时添加的函数不会覆盖原有的属性。 - 将函数绑定到目标对象: 将当前函数(
this,即想要改变调用上下文的函数)赋值给context对象上的fn属性。这样,context对象就暂时拥有了这个函数,可以通过context[fn]来调用它。 - 调用函数并利用隐式绑定: 接下来,通过
context[fn]()执行这个函数。由于是在context对象上调用的,根据JavaScript的函数调用规则,这里的this会被绑定到context上,实现了上下文的改变。 - 清理: 最后,通过
delete context[fn]删除之前添加到context对象上的临时函数,以保持原对象的清洁,不留下任何副作用。
Function.prototype.mycall = function(context) {
// 调用的哥们是不是函数体
if (typeof this !== 'function' ) {
return new TypeError(this + 'is not a function');
}
// this里面的this => context
const fn = Symbol('key');
context[fn] = this;// 让对象拥有该函数 {Symbol('key'): foo}
context[fn]();// 触发了隐式绑定
delete context[fn];
}
应用
var obj = {
a:1
}
function foo() {
console.log(this.a);
}
foo.mycall(obj);
Array.isArray
判断某个变量是否为数组类型的一个内置函数
let a = [];
let b = {};
let c = 123;
console.log(Array.isArray(a));
console.log(Array.isArray(b));
console.log(Array.isArray(c));