JavaScript에서 호출, 적용 및 바인딩은 Function 개체와 함께 제공되는 세 가지 메서드입니다. 이 세 가지 메서드의 주요 기능은 함수에서 this 포인터를 변경하여 '꽃을 차지하는' 효과를 얻는 것입니다. 나무를 제거하는 것입니다. 이 문서에서는 이러한 세 가지 방법을 자세히 설명하고 몇 가지 일반적인 애플리케이션 시나리오를 나열합니다.
전화(thisArgs [,args...])
이 메소드는 thisArgs 매개변수와 매개변수 목록을 전달할 수 있습니다. thisArgs는 런타임 시 함수의 호출자, 즉 함수의 this 객체를 지정하고 매개변수 목록은 호출하는 함수에 전달됩니다. thisArgs 값에는 다음과 같은 네 가지 상황이 있습니다.
(1) 함수에서 창 개체를 가리키는 this를 전달하거나 null, 정의되지 않은 값을 전달하지 마세요
(2) 다른 함수의 함수 이름을 전달하고 함수의 this는 이 함수에 대한 참조를 가리킵니다.
(3) 문자열, 숫자 또는 부울 유형과 같은 기본 유형을 전달합니다. 이는 함수에서 문자열, 숫자, 부울과 같은 해당 패키징 객체를 가리킵니다.
(4) 객체를 전달하고 함수의 this가 이 객체를 가리킵니다
function a(){ console.log(this); //输出函数a中的this对象 } function b(){} //定义函数b var obj = {name:'onepixel'}; //定义对象obj a.call(); //window a.call(null); //window a.call(undefined);//window a.call(1); //Number a.call(''); //String a.call(true); //Boolean a.call(b);// function b(){} a.call(obj); //Object
이것이 호출의 핵심 기능입니다. 객체에 대해 정의되지 않은 메소드를 호출할 수 있으며, 이 메소드를 사용하면 객체의 속성에 액세스할 수 있습니다. 먼저 간단한 예를 살펴보세요.
var a = { name:'onepixel', //定义a的属性 say:function(){ //定义a的方法 console.log("Hi,I'm function a!"); } }; function b(name){ console.log("Post params: "+ name); console.log("I'm "+ this.name); this.say(); } b.call(a,'test'); >> Post params: test I'm onepixel I'm function a!
b.call이 실행되면 'test'라는 문자열이 함수 b에 매개변수로 전달됩니다. call의 함수로 인해 함수 b의 this는 객체 a를 가리키므로 객체에서 함수 b를 호출하는 것과 동일합니다. a. 실제로 b는 a에 정의되어 있지 않습니다.
적용(thisArgs[,args[]])
apply와 call의 유일한 차이점은 두 번째 매개변수가 전달되는 방식이지만 apply의 두 번째 매개변수는 배열이어야 하고 call을 사용하면 매개변수 목록을 전달할 수 있습니다. Apply가 매개변수 배열을 수신하더라도 호출 함수에 전달될 때 매개변수 목록의 형태로 전달된다는 점에 주목할 가치가 있습니다. 간단한 예를 살펴보겠습니다.
function b(x,y,z){ console.log(x,y,z); } b.apply(null,[1,2,3]); // 1 2 3
이 적용 기능은 매우 중요합니다. 다음 적용 시나리오에서 이 기능을 언급하겠습니다.
바인드(thisArgs [,args...])
bind는 ES5의 새로운 메소드입니다. 매개변수는 call과 유사하지만 call/apply와는 크게 다릅니다. 즉, call 또는 apply를 호출하면 해당 함수가 자동으로 실행되지만, 바인딩은 해당 함수를 실행하지 않습니다. . 함수는 함수에 대한 참조를 반환합니다. 언뜻 보면 바인드가 호출/적용보다 뒤처져 있는 것 같은데, ES5에서 바인드를 도입하는 이유는 무엇입니까?
사실 ES5에서 바인드를 도입한 진짜 목적은 호출/적용의 단점을 보완하기 위한 것입니다. 호출/적용은 대상 함수를 자동으로 실행하므로 이벤트 바인딩 기능에서는 사용할 수 없습니다. 함수가 필요하지 않습니다. 수동으로 실행하고 이벤트가 트리거될 때 내부적으로 JS에 의해 자동으로 실행됩니다. 함수를 변경하는 동안 Bind는 대상 함수를 자동으로 실행하지 않으므로 위의 문제를 완벽하게 해결할 수 있습니다.
var obj = {name:'onepixel'}; /** * 给document添加click事件监听,并绑定onClick函数 * 通过bind方法设置onClick的this为obj,并传递参数p1,p2 */ document.addEventListener('click',onClick.bind(obj,'p1','p2'),false); //当点击网页时触发并执行 function onClick(a,b){ console.log( this.name, //onepixel a, //p1 b //p2 ) }
웹 페이지를 클릭하면 onClick이 실행되어 onepixel p1 p2가 출력되는데 이는 onClick에서 obj 객체로 바인드되어 변경되었음을 나타냅니다. 바인드에 대한 심층적인 이해를 돕기 위해, 바인드의 폴리필 구현을 살펴보겠습니다.
if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, //this在这里指向的是目标函数 fBound = function () { return fToBind.apply( //如果外部执行var obj = new fBound(),则将obj作为最终的this,放弃使用oThis this instanceof fToBind ? this //此时的this就是new出的obj : oThis || this, //如果传递的oThis无效,就将fBound的调用者作为this //将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递 aArgs.concat(Array.prototype.slice.call(arguments))); }; //将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用 fBound.prototype = this.prototype; //返回fBond的引用,由外部按需调用 return fBound; }; }
적용 시나리오 1: 상속
우리 모두 알고 있듯이 JavaScript에는 Java, C#과 같은 고급 언어에는 확장 키워드가 없으므로 JS에는 상속 개념이 없습니다. 상속이 필요한 경우 호출 및 적용을 통해 이 기능을 구현할 수 있습니다. :
function Animal(name,weight){ this.name = name; this.weight = weight; } function Cat(){ Animal.call(this,'cat','50'); //Animal.apply(this,['cat','50']); this.say = function(){ console.log("I am " + this.name+",my weight is " + this.weight); } } var cat = new Cat(); cat.say();//I am cat,my weight is 50
当通过new运算符产生了cat时,Cat中的this就指向了cat对象,而继承的关键是在于Cat中执行了Animal.call(this,'cat','50') 这句话,在call中将this作为thisArgs参数传递,于是Animal方法中的this就指向了Cat中的this,而cat中的this指向的是cat对象,所以Animal中的this指向的就是cat对象,在Animal中定义了name和weight属性,就相当于在cat中定义了这些属性,因此cat对象便拥有了Animal中定义的属性,从而达到了继承的目的。
应用场景二:移花接木
在讲下面的内容之前,我们首先来认识一下JavaScript中的一个非标准专业术语:ArrayLike(类数组/伪数组)
ArrayLike 对象即拥有数组的一部分行为,在DOM中早已表现出来,而jQuery的崛起让ArrayLike在JavaScript中大放异彩。ArrayLike对象的精妙在于它和JS原生的Array类似,但是它是自由构建的,它来自开发者对JavaScript对象的扩展,也就是说:对于它的原型(prototype)我们可以自由定义,而不会污染到JS原生的Array。
ArrayLike对象在JS中被广泛使用,比如DOM中的NodeList, 函数中的arguments都是类数组对象,这些对象像数组一样存储着每一个元素,但它没有操作数组的方法,而我们可以通过call将数组的某些方法`移接`到ArrayLike对象,从而达到操作其元素的目的。比如我们可以这样遍历函数中的arguments:
function test(){ //检测arguments是否为Array的实例 console.log( arguments instanceof Array, //false Array.isArray(arguments) //false ); //判断arguments是否有forEach方法 console.log(arguments.forEach); //undefined // 将数组中的forEach应用到arguments上 Array.prototype.forEach.call(arguments,function(item){ console.log(item); // 1 2 3 4 }); } test(1,2,3,4);
除此之外,对于apply而言,我们上面提到了它独有的一个特性,即apply接收的是数组,在传递给调用函数的时候是以参数列表传递的。 这个特性让apply看起来比call 略胜一筹,比如有这样一个场景:给定一个数组[1,3,4,7],然后求数组中的最大元素,而你知道,数组中并没有获取最大值的方法,一般情况下,你需要通过编写代码来实现。而我们知道,Math对象中有一个获取最大值的方法,即Math.max(), max方法需要传递一个参数列表,然后返回这些参数中的最大值。而apply不仅可以将Math对象的max方法应用到其他对象上,还可以将一个数组转化为参数列表传递给max,看代码就能一目了然:
var arr = [2,3,1,5,4]; Math.max.apply(null,arr); // 5
以上便是call和apply比较经典的几个应用场景,熟练掌握这些技巧,并把这些特性应用到你的实际项目中,会使你的代码看起来更加耐人寻味!