1.JS中call和apply的源码区别
2.如何理解和熟练运用js中的call及apply?
3.JS封装自己的call、apply和bind方法详解
4.JScall方法的源码简单模拟实现
JS中call和apply的区别
在JavaScript中,`apply`和`call`都是源码用于改变方法调用时的对象上下文,但它们之间存在一些关键的源码区别。
首先,源码我们来定义一下`apply`和`call`:
`apply`:主要应用于将一个方法应用于另一个对象。源码变色指标源码附图它接收两个参数,源码一个是源码用于替换当前对象的新对象,另一个是源码一个数组或arguments对象,包含要传递给方法的源码参数。
`call`:也用于改变方法调用时的源码对象上下文。它的源码调用方式类似于`apply`,但接收的源码参数是一个数组或arguments对象,以及一个表示新对象的源码参数。
共同点:它们都能让方法的源码执行环境从原始环境转变为指定的新对象。
不同点:`apply`接收的参数必须是一个数组或arguments对象,如果只传一个参数,这个参数也需要被封装为数组。如果未提供参数,suse源码将导致TypeError。而`call`则允许直接以参数列表形式传递参数,无需转换为数组。
简而言之,`apply`和`call`的功能相似,区别在于它们接收参数的方式不同,`apply`需要参数数组,而`call`允许直接传入参数列表。
举例说明:使用`call`调用方法时,sqllite 源码如`func.call(func1, var1, var2, var3)`,对应的`apply`写法为`func.apply(func1, [var1, var2, var3])`。可见,`call`是针对单一参数的调用,而`apply`则是针对参数数组的调用。
如何理解和熟练运用js中的call及apply?
理解与熟练运用JS中的call和apply,需先明确它们存在的原因。在JS的面向对象编程中,我们常遇到如下的kumpro源码定义:
function cat(){ } cat.prototype={ food:"fish", say: function(){ alert("I love "+this.food); } } var blackCat = new cat; blackCat.say();
但若我们有一个对象,如whiteDog = { food:"bone"},而它未定义say方法,可借助call或apply,用blackCat的say方法操作whiteDog:
blackCat.say.call(whiteDog);
这表明call和apply是为了动态改变this而产生的。当一个对象缺少某个方法,而其他对象有,我们能通过call或apply使用其他对象的方法。
call和apply在JavaScript中广泛使用。例如,使用document.getElementsByTagName选择的trilium源码DOM节点类似数组,但不支持如数组的push,pop等方法。我们可以通过:
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
使得domNodes能应用数组的全部方法。
总结而言,call和apply的主要用途在于动态改变this,或使类似数组的DOM节点具备数组方法。实际应用中,灵活运用这两者能解决许多问题,如在不同对象间重用方法或操作DOM节点。
JS封装自己的call、apply和bind方法详解
在封装之前我们先来复习一下this指向。
所谓的this其实可以理解成一根指针:其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,这就是精髓。最关键所在
this的四种指向:当this所在的函数被普通调用时,指向window,如果当前是严格模式,则指向undefined
当this所在当函数被以obj.fn()形式调用时,指向obj
当call,apply加入后,this的指向被改变了
此时控制台并没有代码输出,因为bind会重新生成并且返回一个函数,这个函数的this指向第一个参数
function a() { console.log(this.name); } const b = { name: "segmentFault" } a.bind(b, 1, 2, 3)此时输出segmentFault
正式开始自己实现call :
在函数原型上定义自己的myCall方法:
Function.prototype.myCall = function (context, ...arg) { const fn = Symbol('临时属性') context[fn] = this context[fn](...arg) delete context[fn] }
四行代码实现了简单的call,思路如下:
为了简化,今天都不做类型判断和错误边际处理,只把原理讲清楚。
自己实现apply:
//实现自己的myApply Function.prototype.myApply = function (context, arg) { const fn = Symbol('临时属性') context[fn] = this context[fn](...arg) delete context[fn] } const obj2 = { a: 1 } test.myApply(obj2, [2, 3, 4])
同理,只是apply传递的第二个参数是数组,这里我们只需要在调用时,将参数用...把数组展开即可
自己实现bind:
bind跟apply,call的本质区别,bind不会改变原函数的this指向,只会返回一个新的函数(我们想要的那个this指向),并且不会调用。但是apply和call会改变原函数的this指向并且直接调用
Function.prototype.myBind = function (objThis, ...params) { const thisFn = this; // 存储源函数以及上方的params(函数参数) // 对返回的函数 secondParams 二次传参 let fToBind = function (...secondParams) { console.log('secondParams',secondParams,...secondParams) const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用 const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上 return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果 }; fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind return fToBind; // 返回拷贝的函数 };
学习需要循序渐进,建议根据本文顺序去封装一遍,是比较轻松的,当然bind还需要判断是否是new调用.
JScall方法的简单模拟实现
记录一个知识点,共勉。1.call的简单使用在使用call方法是注意以下两点:
foo函数本身执行了
call改变了函数foo中this的指向,该例指向到obj
varobj={ value:1}functionfoo(){ console.log(this.value)}foo.call(obj)//.模拟实现原理varobj={ value:1,foo:function(){ console.log(this.value)}}//此时调用obj.foo也可以打印出value的值因而得到模拟的原理:
1.给对象obj赋值函数foo
2.调用这个函数
3.调用完后删除这个函数
obj.fn=fooobj.fn()deleteobj.fn3.第一步Function.prototype.call2=function(context){ context.fn=thiscontext.fn()deletecontext.fn}//使用varobj={ value:1}functionfoo(){ console.log(this.value)}foo.call2(obj)//.第二步call方法可接收不定量参数
1.在函数内部使用arguments可得到参数类数组
2.使用eval执行函数解决参数传递问题,内部会自动调用Array.toString()
Function.prototype.call2=function(context){ context.fn=thisconstargs=[]for(vari=1,len=arguments.length;i<len;i++){ args.push('arguments['+i+']');}eval('context.fn('+args+')')deletecontext.fn}//使用varobj={ value:1}functionfoo(name,age){ console.log(name)console.log(age)console.log(this.value)}foo.call2(obj,'Tom',)//Tom////.第三步1.call方法第一个参数可以传null,此时this指向window
2.第一个参数也可以传常量,使用Object包一层
Function.prototype.call2=function(context){ varcontext=context||windowcontext.fn=thisconstargs=[]for(vari=1,len=arguments.length;i<len;i++){ args.push('arguments['+i+']');}eval('context.fn('+args+')')deletecontext.fn}6.第四步call方法可以拥有返回值
Function.prototype.call2=function(context){ varcontext=context||windowcontext.fn=thisconstargs=[]for(vari=1,len=arguments.length;i<len;i++){ args.push('arguments['+i+']');}varres=eval('context.fn('+args+')')deletecontext.fnreturnres}varobj={ value:1}functionfoo(name,age){ return{ name,age,val:this.value}}console.log(foo.call(obj,'Tom',))//Object{ //name:'Tom',//age:,//value:1,//}7.apply方法的模拟实现apply的实现与call类似,唯一区别是apply接收的参数以数组的方式传入
Function.prototype.apply2=function(context,arr){ varcontext=context||windowcontext.fn=thisvarresif(!arr){ res=context.fn()}else{ varargs=[]for(vari=1,len=arguments.length;i<len;i++){ args.push('arguments['+i+']');}res=eval('context.fn('+args+')')}deletecontext.fnreturnres}最后这里只是简单的模拟实现,其中还有很多细节可以完善,例如对参数的类型判断,写法的优化等