今天我们就来搞懂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 运算符的原理是这样:
- 检查对象的原型链: instanceof 首先检查对象(左操作数)的 [[Prototype]] 链,即原型链,原型链大家可以参考:【面试】网易:所有的对象最终都会继承自Object.prototype 吗?搞懂原型原来这么简单!! - 掘金 (juejin.cn)
- 匹配构造函数的原型: 然后,它检查构造函数的 prototype 属性是否出现在对象的原型链上的任何位置(会顺着原型链不断查找)。
- 返回布尔值: 如果找到匹配,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在寻找原型链中,也一定能找到这样一个Objcet的protype属性,所以也能判断!
接下来,我们可以实现一下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代表我们要判断的类型
- 我们在函数体当中定义两个变量left和right
- left指向我们要判断的对象的隐式原型__proto__,right指向判断类型的构造函数的原型prototype
- 接下来,我们定义一个while循环,当我们的left不为null持续循环。
- 在循环体当中判断left是否等于right,是则返回true,不是,则让我们的left顺着原型链往下找!
- 直到找到匹配的值返回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 所有的函数都有这个关键字 是所有参数的统称 它是一个类数组
- 我们定义了一个myCall函数,其中传入一个形参context
- 在函数体内,我们第一步用this判断调用myCall的数据是不是一个函数!如果不是则抛出一个typeError的异常!因为我们调用call方法的必须是一个函数,通过修改函数里面的this所以我们要加上一个这样的判断!
- 紧接着!我们用一个变量let arge = [...arguments].slice(1)来存储函数当中可能传过来的实参!因为我们无法确定函数体是否有实参传过来!arguments是一个类数组,我们用一个新的参数let arge = [...arguments].slice(1) 这样,我们会先解构类数组,再把解构后的一个元素去掉,存入到arge中。
- 接下来,我们用context.fn = this这个相当于在对象添加一个属性,属性名为fn,值为调用myCall函数体,这样就在对象中引用了这个函数体,形成了一个隐式绑定!
- 然后我们let res = context.fn(...arge)这里相当于对函数体进行调用!触发隐式绑定规则,同时给对象赛(...arge)结构数组
- 然后我们再把这个对象当中引用的函数体删除掉!
- 最后返回传过来的实参!
接下来我们验证一下!!
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,是一个使服务器能够识别用户使用的浏览器类型、版本以及运行浏览器的操作系统等信息的字符串。它作为浏览器请求头部信息的一部分发送给服务器,以便服务...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)