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

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

yuyutoo 2024-10-12 01:48 2 浏览 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

相关推荐

Google Chrome 100 Beta发布 用户代理字符串作用开始逐渐降低

GoogleChrome和MozillaFirefox都在迅速接近100版本,这有可能破坏一些错误识别浏览器版本的网站(可能导致访问不正常,这有点类似于众所周知的千年虫)。两种浏览器都在研究可能的...

如何在Chrome,Edge,Safari和Firefox中更改用户代理

无论您是SEO,营销人员还是Web开发人员,通常都可能需要更改浏览器的用户代理以测试其他内容。例如,您正在运行特定于MAC-OS的活动。要确定您的广告系列是否正常运行并且未针对Linux用户,更改浏览...

Mozilla正在测试新的浏览器UserAgent

Mozilla最近发布了一个实验项目来测试3位数的UserAgent版本“Firefox/100.0”会不会让一些网站停止正常工作。浏览器UserAgent是一串字符串,里面包含了浏览器的软件信息,版...

爬虫与User-Agent

什么是User-Agent呢?User-Agent是一个特殊字符串头,被广泛用来标示浏览器客户端的信息,使得服务器能识别客户机使用的操作系统和版本,CPU类型,浏览器及版本,浏览器的渲染引擎,浏览器...

让你的浏览器充满魔性——User Agent Switche

对于前端人员,闲暇时就会研究各种插件,今天我就分享UserAgentSwitcher在Firefox和Chrome的使用情况。一、Firefox浏览器UserAgentSwitcher作为火...

亚马逊账号运营安全-浏览器指纹识别之User-Agent开篇

UA包含了一个约定的特征字符串。主要是面向受访问网络表明自己的操作系统,软件开发商,版本,应用类型等信息。这是一种主动暴露信息的方式。我们来看关于UA的简单语法定义:User-Agent:<p...

【每日学习】Python爬虫之伪装浏览器User-Agent

【主题】Python爬虫之伪装浏览器原理【分析】1.创建自定义请求对象的目的,对抗网站的反爬虫机制2.反爬虫机制1:判断用户是否是浏览器访问(User-Agent)3.对抗措施1:伪装浏览器进行访问【...

亚马逊账号运营安全-浏览器指纹识别之User-Agent二篇

大家好,上一篇亚马逊账号运营安全-浏览器指纹识别之User-Agent开篇为大家阐述了原理。下面是作者为大家整理的其他几个主流浏览器的UA配置。一下都是Windows1064X系统下整理。Chrom...

常见的爬虫UserAgent

通过前面的文章我们知道,UserAgent(用户代理)是HTTP请求的一部分,用于告诉服务器发起请求的客户端类型和属性等信息。同时,也了解了常见的UserAgent。...

HTTP请求头之User-Agent

什么是User-AgentUser-Agent中文名为用户代理,简称UA,...

你想不到的浏览器流氓史!那些奇怪的User-Agent,是这么来的...

平时我们用chrome浏览器做开发测试。Chrome的Useragent字段怎么这么奇怪?...

谷歌宣布 Chrome 将逐步停止支持 User Agent

谷歌近日宣布将放弃对Chrome浏览器中用户代理字符串(User-AgentString)的支持。取而代之的是,Chrome将提供一个名为“客户端提示(ClientHints)”的新API...

数据采集-用户代理(useragent)

UserAgent分类:PC端的UserAgent。移动端UserAgent。使用UserAgent的必要性:在写python网络爬虫程序的时候,经常需要修改UserAgent,有很多原因,罗列几个如...

如何获取当前浏览器的useragent

有时候,我们需要得到浏览器的useragent,从而再进行后面的一系列判断,返回不同的值。网上有说,在浏览器地址栏输入:javascript:alert(navigator.userAgent)这种方...

User Agent 解析:它是什么以及如何修改

什么是UserAgent?UserAgent,简称UA,是一个使服务器能够识别用户使用的浏览器类型、版本以及运行浏览器的操作系统等信息的字符串。它作为浏览器请求头部信息的一部分发送给服务器,以便服务...

取消回复欢迎 发表评论: