百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

今天我们就来搞懂call()源码instanceof源码和类型转换

yuyutoo 2024-10-12 01:48 5 浏览 0 评论

前言

面试官:你能实现一下call()源码吗? 轻松学习JavaScript类型转换和call()方法源码以及instanceof源码

又来上干货啦!!

今天我们来学习JavaScript中的类型转换和call方法源码!

正文

JavaScript中的数据类型

要进入今天的学习,我们要先总结一下JavaScript中各种数据类型!

基本数据类型
let str = 'hello'//string
let str2 = 'hello'//string
let num = 12//number
let flag = false //boolean
let und = undefined //undefined
let nul = null //null
let big  = 1232n //big integer  big number用于存2**53-1以下或者2**-53以上
let s = Symbol('hello') //Symbol不参与逻辑运算

引用数据类型
let obj = {}//对象
let arr = []//数组
let fn = function (){}//函数
let date = new Date()//时间
let regex = /模式/;//正则

以上就是我们目前总结的数据类型!

这其中,我觉得有必要介绍的就是big integer和symbol了

对于big integer,顾名思义,它就是大数的代表!一般我们的数字只能运算2^-53~2^53-1之间的数据,而大数,则是用来运算超过这段区间的值!解决大家遇见的精度问题!相信大家很容易理解!

对于symbol呢,每个通过 Symbol 创建的值都是唯一的,即使创建时使用相同的描述字符串。这也是为了避免我们在项目当中遇到与其他人变量名相同,导致功能出现问题的情况,每一个由Symbol创建的值都是独一无二的!

接下来,我们步入我们的正题!数据类型判断

typeof()方法

再JavaScript中,typeof()方法目前能判断所有的基本数据类型,null除外,这是一个历史遗留问题,我们稍后介绍。

对于引用类型,typeof()方法又不那么管用了,它会将所有的引用类型都判断为Object,但是函数除外,typeof能判断出函数的数据类型,这里为什么,我们不予探究!

接下来,我们就应用上述的变量,来使用tyoeof()方法进行判断!

基本数据类型

console.log(typeof(str));//typeof str 能得到String
console.log(typeof(num));//number
console.log(typeof(flag));//boolean
console.log(typeof(und));//undefined
console.log(typeof(nul));//object这是js中的bug,历史遗留问题
console.log(typeof(big));//bigint
console.log(typeof(s));//symbol 
输出:
string
number
boolean
undefined
object
bigint
symbol

可以看到,typeof()方法能够判断所有的基本数据类型!但是null除外!为什么呢?那我们就要聊聊当初设计师们是如何设计出typeof()方法。

在当时,设计typeof()方法的时候,设计师们想到利用二进制的前三位进行判断,只要前三位为0,就都是Object因为,基本数据类型大多前三位有值嘛,于是得到所有人的一致认可!但是,之后设计null的时候,设计师们觉得啊,既然是空值,那null的二进制就全是0好了,就此,人们就发现typeof()判断null值的时候!就是产生了一个BUG,会被识别为Object,时至今日,这个BUG仍然存在!

听了上面所说!你也就知道了typeof()判断引用类型的结果!我们来看看!

引用类型

console.log(typeof(obj));//object
console.log(typeof(arr));//object 在typeof的眼里,所以引用类型都是对象object 判断不了引用类型
console.log(typeof(fn));//function typeof只能判断function
console.log(typeof(date));//object
console.log(typeof(regex));//object
输出:
console.log(typeof(obj));//object
console.log(typeof(arr));//object 在typeof的眼里,所以引用类型都是对象object 判断不了引用类型
console.log(typeof(fn));//function typeof只能判断function
console.log(typeof(date));//object
console.log(typeof(regex));//object

对于引用类型,typeof()又只能准确判断function,这点大家记住就好!

你又问了!不是能判断Object嘛?这一点,你可以认为它能判断Object也能认为是运气好,刚好撞中了!小编就以后者为主了!这一点仁者见仁智者见智!大家不必纠结!

介绍到这里,大家稍加理解,就能学会typeof()这个方法的原理了,接下来我们开始介绍另外一种方法!

instanceof判断

在 JavaScript 中,instanceof 是一个运算符,用于检测对象是否是某个构造函数(或者其原型链上)的实例。

instanceof会顺着隐式原型往上找,直到找到了 obj.__proto__===Object.prototype一步步找obj.__proto__.__proto__===Object.prototype一步一步下去,找到了就返回true,没有找到就返回false

它的语法如下:

object instanceof constructor
  • object:要检测的对象。
  • constructor:要检测的构造函数(类型)。

instanceof 运算符返回一个布尔值,如果 object 是 constructor 的实例,返回 true;否则返回 false。

instanceof 运算符的原理是这样:

  1. 检查对象的原型链: instanceof 首先检查对象(左操作数)的 [[Prototype]] 链,即原型链,原型链大家可以参考:【面试】网易:所有的对象最终都会继承自Object.prototype 吗?搞懂原型原来这么简单!! - 掘金 (juejin.cn)
  2. 匹配构造函数的原型: 然后,它检查构造函数的 prototype 属性是否出现在对象的原型链上的任何位置(会顺着原型链不断查找)。
  3. 返回布尔值: 如果找到匹配,instanceof 返回 true,表示对象是构造函数的实例。如果在整个原型链上都找不到匹配,返回 false。

我们来几个案例分析一下:

代码中仍然是拿到刚刚的变量。

console.log(obj instanceof Object);//判断obj是不是隶属于object
console.log(arr instanceof Array);
console.log(fn instanceof Function);
console.log(date instanceof Date);
console.log(str instanceof String);//判断不了原始类型
输出
true
true
true
true
false

我们会发现instanceof判断不了原始数据类型,这是为什么呢?这是因为instanceof 是基于对象的原型链进行检查的。原始数据类型(例如字符串、数字、布尔等)并不是对象,它们没有自己的原型链,因此无法通过 instanceof 来判断。

instanceof 主要用于检查对象是否是某个构造函数的实例,它在检查原型链时查找构造函数的 prototype 属性。对于原始数据类型,因为它们不是对象,也就没有 prototype 属性,所以 instanceof 检查不会得到期望的结果。

当然,它能不能判断数组是不是对象呢?能!

console.log(arr instanceof Object)
输出:true

为什么能?因为所有的引用类型都有一个共同的祖先Object,所有instanceof在寻找原型链中,也一定能找到这样一个Objcetprotype属性,所以也能判断!

接下来,我们可以实现一下instanceof的源码来帮助我们更好地理解:

function instanceOF(L,R){

    let left = L.__proto__
    let right = R.prototype
    while(left!=null){
        if(left===right){return true}
        left = left.__proto__
    }
    return false
}

以上就我们写的源码了!我们具体是如何实现的呢?其实就是根据instanceof的原理:一步一步查找原型链进行判断。

所以,在我们自己写的源码当中,我们写了一个函数

这个函数有两个参数L代表我们要判断的对象,R代表我们要判断的类型

  1. 我们在函数体当中定义两个变量left和right
  2. left指向我们要判断的对象的隐式原型__proto__,right指向判断类型的构造函数的原型prototype
  3. 接下来,我们定义一个while循环,当我们的left不为null持续循环。
  4. 在循环体当中判断left是否等于right,是则返回true,不是,则让我们的left顺着原型链往下找!
  5. 直到找到匹配的值返回true或者没找到直到null结束循环则返回false

这就是我们构造函数的编写原理了,让我们来验证一下是否成功!

console.log(instanceOF([],Object))
console.log(instanceOF('',Array))
输出:
true
false

经过多个案例,也是证明,我们写的函数没有问题!到这里,大家好好理解一下,也就能搞懂了!

接下来是:

Array.isArray()

Array.isArray(arr)函数自带的方法,用于判断是否为数组,数组独有。

只能判断是否为数组!

console.log(Array.isArray([]));
console.log(Array.isArray({}));
输出:
true
false

这个我们一笔带过就好啦!接下来是我们的重头戏!

Object.prototype.toString.call(xxx)

这里,我们就不得不提一嘴!Object.prototype.toString()

这个方法在官方文档Annotated ES5 :Annotated ES5

是这样介绍的:

什么?你看不懂?来翻译一下!

如果this值为undefined,返回"[object Undefined]"。 如果this值为null,返回"[object Null]"。 将O作为ToObject(this)的执行结果 让class成为 O 内部属性[[Class]]的值 返回由三个字符串"[object "、 class 和 "]" 三部分拼接而成的字符串。

什么,中文也看不明白?那我们之间上案例好了!

console.log(Object.prototype.toString('12'))
输出
[object Object]

咦,这也不对啊?这是因为toString()不接收值,也就意味其中你输入任何参数都没效果,

toString的运行原理就是,先判断调用它的那个变量的类型,然后再把它转换为字符串!

Object.prototype.toString()输出[object Object]是官方规定的输出类型,我们不做过多探究!

在官方文档的解释,其实我们可以通过修改String函数this的指向来改变它的结果!

于是Object.prototype.toString.call(xxx)方法应运而生!

我们来看看这个案例:

console.log(
    Object.prototype.toString.call(123)
);
console.log(
    Object.prototype.toString.call('dd')
);
console.log(
    Object.prototype.toString.call(undefined)
);
console.log(
    Object.prototype.toString.call(null)
);
console.log(
    Object.prototype.toString.call({})
);
console.log(
    Object.prototype.toString.call([])
);
console.log(
    Object.prototype.toString.call(new Date())
);
console.log(
    Object.prototype.toString.call(function() {})
);
输出:
[object Number]
[object String]
[object Undefined]
[object Null]
[object Object]
[object Array]
[object Date]
[object Function]

我们就找到了一个能够判断所有数据类型的方法!但是!这样的输出结果,可能并不是我们想要的?

于是我们可以通过这样一个操作来实现!只获取它的数据类型!

function isType(s) {
    return Object.prototype.toString.call(s).slice(8,-1)

};
console.log(isType('1455'))
输出:String

这样,就获取了我们想要的结果!其中slice(8,-1)表示从下标8开始到倒数第二个!-1表示的是到倒数第二个结束!

其中null和undefined为什么会输出null和undefined其实这是官方定死的,我们不用过多纠结!

为什么我们在Object.prototype.toString()加一个.call就能达到这样的效果呢???

根据官方文档,我们可以通过修改this的值,来改变对应的输出结果!

我们要如何去改变this的值呢?这里我已经在前文介绍过:OpenAI见了也皱眉?JS的this关键字,十分钟带你跨过大山! - 掘金 (juejin.cn)

大家可以前往学习一下,我们在这里利用的就是call()达成显式绑定的条件来改变this的指向从而达到我们的目标!

但是其实,call()利用的还是隐式绑定来达成效果的!

接下来就来到了我们今天的重头戏!

call()源码的实现!

我们先解释一下call()的原理!

call()的原理
 {
     fn:foo
 }
 obj.fn()
 delete obj.fn()

上面其实就是对call()原理的解释,我们来口头描述一下!

call()方法其实就通过现在先把前面的函数体挂在它传入的对象当中!

然后立马用这个对象调用这个函数体。(这里其实就利用隐式绑定修改this的指向)

紧接着又偷偷摸摸给你把对象当中的这个函数体给删除掉!

我们要实现这样一个call()源码!,我们按照这个思路来写即可!

我们就先上源代码!

Function.prototype.myCall = function(context){
    //this是这个函数,隐式绑定
    if(typeof this!='function'){//或者条件里面写this instanceof Function
        throw new TypeError('myCall is not a function')//效果和return一样,后续的逻辑不会执行
    }
    //获取实参
    //类数组不能用数组的方法,只要下标
    //Array.from(类数组)把类数组转成一个数组
    // let arge = Array.from(arguments).slice(1)//从下标一 切完最后所有,不影响原数组,得到新数组,
    let arge = [...arguments].slice(1)
    context.fn = this
    console.log(this);
    //let res = context.fn(...arge)如果有返回值,就return res
    let res = context.fn(...arge)//触发隐式绑定规则  会给对象赛东西 (...arge)结构数组
    delete context.fn
    return res
}
foo.myCall(obj,1,2);

这其实就是一个call()源码了!我们来给大家分析一下!

注意这里的点

例如:foo.myCall(obj,1,2);

我们所说的函数体就是foo,我们所说的对象就是obj,1,2就是传的实参!

arguments 所有的函数都有这个关键字 是所有参数的统称 它是一个类数组

  1. 我们定义了一个myCall函数,其中传入一个形参context
  2. 在函数体内,我们第一步用this判断调用myCall的数据是不是一个函数!如果不是则抛出一个typeError的异常!因为我们调用call方法的必须是一个函数,通过修改函数里面的this所以我们要加上一个这样的判断!
  3. 紧接着!我们用一个变量let arge = [...arguments].slice(1)来存储函数当中可能传过来的实参!因为我们无法确定函数体是否有实参传过来!arguments是一个类数组,我们用一个新的参数let arge = [...arguments].slice(1) 这样,我们会先解构类数组,再把解构后的一个元素去掉,存入到arge中。
  4. 接下来,我们用context.fn = this这个相当于在对象添加一个属性,属性名为fn,值为调用myCall函数体,这样就在对象中引用了这个函数体,形成了一个隐式绑定!
  5. 然后我们let res = context.fn(...arge)这里相当于对函数体进行调用!触发隐式绑定规则,同时给对象赛(...arge)结构数组
  6. 然后我们再把这个对象当中引用的函数体删除掉!
  7. 最后返回传过来的实参!

接下来我们验证一下!!

var obj = {
    a:1,
}
function foo(num1,num2){
    console.log(this.a,num1,num2);
}

Function.prototype.myCall = function(context){
    if(typeof this!='function'){
        throw new TypeError('myCall is not a function')
    }
    let arge = [...arguments].slice(1)

    context.fn = this
    let res = context.fn(...arge)
    delete context.fn
    return res
}

foo.myCall(obj,1,2);
输出:
1 1 2

输出结果没有问题!!

这样我们就实现了call()方法的源码!!

最后

到这里,我们今天的干货就讲完啦!!

大家有不懂的地方欢迎评论留言!也欢迎大佬对我指正!

点个小小的赞鼓励支持一下吧!


原文链接:https://juejin.cn/post/7300207920284631081

相关推荐

Mysql和Oracle实现序列自增(oracle创建序列的sql)

Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...

关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)

概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...

MySQL CREATE TABLE 简单设计模板交流

推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....

mysql学习9:创建数据库(mysql5.5创建数据库)

前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...

MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别

执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...

Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出

在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...

美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍

多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...

汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)

全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...

【推荐】ProAc Response系列扬声器逐个看

有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...

#本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱

作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...

专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)

Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...

常用半导体中英对照表(建议收藏)(半导体英文术语)

作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...

Fyne Audio F502SP 2.5音路低音反射式落地音箱评测

FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...

有哪些免费的内存数据库(In-Memory Database)

以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...

RazorSQL Mac版(SQL数据库查询工具)

RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...

取消回复欢迎 发表评论: