온라인에 글이 많지만 대부분이 복사해서 붙여넣기해서 이해하기 어렵습니다. 이 글을 통해 적용, 호출, 바인드에 대한 이해가 확실히 향상되고 그 중 일부를 나열할 수 있기를 바랍니다. 내 기억을 깊게하는 데 놀라운 용도로 사용됩니다.
적용, 호출
JavaScript에서는 함수 실행 시 컨텍스트를 변경하기 위해 호출과 적용이 모두 존재합니다. 함수 본문 내에서 this의 포인터를 변경하십시오.
JavaScript의 주요 특징은 함수에 "정의 컨텍스트"와 "런타임 컨텍스트" 및 "컨텍스트가 변경될 수 있다"는 개념이 있다는 것입니다.
밤나무부터 시작해 보겠습니다.
function fruits() {} fruits.prototype = { color: "red", say: function() { console.log("My color is " + this.color); } } var apple = new fruits; apple.say(); //My color is red
하지만 바나나 = {color : "yellow"} 객체가 있고 이에 대한 say 메소드를 재정의하고 싶지 않은 경우 , 그런 다음 Apple의 say 메소드를 사용하여 Or Apply를 호출할 수 있습니다.
banana = { color: "yellow" } apple.say.call(banana); //My color is yellow apple.say.apply(banana); //My color is yellow
따라서 객체에 특정 메소드가 없을 때(바나나는 이 밤나무에는 say 메소드가 있습니다. 그러나 다른 것들도 있습니다(이 밤나무에는 apple에 say 메소드가 있습니다). call 또는 apply를 사용하여 다른 객체 메소드와 함께 작동할 수 있습니다.
적용과 호출의 차이점
적용과 호출의 경우 기능은 완전히 동일하지만 매개변수를 받아들이는 방식이 다릅니다. 예를 들어 다음과 같이 정의된 함수가 있습니다.
var func = function(arg1, arg2) { };
은 다음과 같이 호출할 수 있습니다.
func.call(this, arg1, arg2); func.apply(this, [arg1, arg2])
여기서 지정하려는 컨텍스트는 다음과 같습니다. 무엇이든 가능 JavaScript 객체(JavaScript의 모든 것은 객체임), 호출은 매개변수를 순서대로 전달해야 하는 반면 Apply는 매개변수를 배열에 넣습니다.
JavaScript에서는 함수의 매개변수 개수가 정해져 있지 않으므로 조건을 적용하려면 매개변수 개수를 확실히 알 수 있을 때 call을 사용하세요.
확실하지 않은 경우 적용을 사용한 다음 매개변수를 배열에 푸시하고 전달하세요. 매개변수 개수가 불확실한 경우 인수 배열을 통해 함수 내에서 모든 매개변수를 탐색할 수 있습니다.
메모리를 통합하고 심화하기 위한 몇 가지 일반적인 용도는 다음과 같습니다.
1. 배열 사이에 추가
var array1 = [12 , "foo" , {name "Joe"} , -2458]; var array2 = ["Doe" , 555 , 100]; Array.prototype.push.apply(array1, array2); /* array1 值为 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
2. 최대 합계를 구합니다. 배열 최소값
var numbers = [5, 458 , 120 , -215 ]; var maxInNumbers = Math.max.apply(Math, numbers), //458 maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
number 자체에는 max 메소드가 없지만 Math에는 있으므로 해당 메소드를 호출 또는 적용과 함께 사용할 수 있습니다.
3. 배열인지 확인합니다(toString() 메서드가 재정의되지 않은 경우)
functionisArray(obj){ returnObject.prototype.toString.call(obj) === '[object Array]' ; }
4. 클래스에 배열 메서드를 사용합니다. (의사) 배열
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
자바스크립트에는 의사 배열이라는 객체 구조가 있습니다. 더 특별한 것은 인수 객체와 getElementsByTagName, document.childNodes 등과 같은 호출입니다. 그들이 반환하는 NodeList 객체는 의사 배열입니다. Array의 push, pop 및 기타 메소드는 적용할 수 없습니다.
그러나 Array.prototype.slice.call을 사용하여 실제 배열의 length 속성을 가진 객체로 변환하면 domNodes가 Array 아래의 모든 메소드를 적용할 수 있습니다.
지원 및 전화 사용에 대한 심층적 이해
지원 및 전화 사용에 대한 심층적 이해를 위해 면접 질문을 빌려 보겠습니다.
console.log 메서드를 프록시할 수 있도록 로그 메서드를 정의합니다. 일반적인 해결 방법은 다음과 같습니다.
function log(msg) { console.log(msg); } log(1); //1 log(1,2); //1
위 방법은 가장 기본적인 요구 사항을 해결할 수 있지만, pass 입력 매개변수의 개수가 불확실할 경우 위의 방법은 실패하게 되며, 전달되는 매개변수의 개수가 불확실하므로 Apply를 사용하는 방법이 가장 좋습니다.
function log(){ console.log.apply(console, arguments); }; log(1); //1 log(1,2); //1 2
다음 요구 사항은 각 로그 메시지에 "(app)" 접두사를 추가하는 것입니다. 예:
log("hello world"); //(app)hello world
어떻게 하면 좀 더 우아하게 할 수 있을까요? 이때 인수 매개변수가 의사 배열이라고 생각하고 Array.prototype.slice.call을 통해 표준 배열로 변환한 후 배열 메소드인 unshift를 사용하면 됩니다. :
function log(){ var args = Array.prototype.slice.call(arguments); args.unshift('(app)'); console.log.apply(console, args); };
bind
说完了 apply 和 call ,再来说说bind。bind() 方法与 apply 和 call 很相似,也是可以改变函数体内 this 的指向。
MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
直接来看看具体如何使用,在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它。 像这样:
var foo = { bar : 1, eventBind: function(){ var _this = this; $('.someClass').on('click',function(event) { /* Act on the event */ console.log(_this.bar); //1 }); } }
由于 Javascript 特有的机制,上下文环境在 eventBind:function(){ } 过渡到 $('.someClass').on('click',function(event) { }) 发生了改变,上述使用变量保存 this 这些方式都是有用的,也没有什么问题。当然使用 bind() 可以更加优雅的解决这个问题:
var foo = { bar : 1, eventBind: function(){ $('.someClass').on('click',function(event) { /* Act on the event */ console.log(this.bar); //1 }.bind(this)); } }
在上述代码里,bind() 创建了一个函数,当这个click事件绑定在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。因此,这里我们传入想要的上下文 this(其实就是 foo ),到 bind() 函数中。然后,当回调函数被执行的时候, this 便指向 foo 对象。再来一个简单的栗子:
var bar = function(){ console.log(this.x); } var foo = { x:3 } bar(); // undefined var func = bar.bind(foo); func(); // 3
这里我们创建了一个新的函数 func,当使用 bind() 创建一个绑定函数之后,它被执行的时候,它的 this 会被设置成 foo , 而不是像我们调用 bar() 时的全局作用域。
有个有趣的问题,如果连续 bind() 两次,亦或者是连续 bind() 三次那么输出的值是什么呢?像这样:
var bar = function(){ console.log(this.x); } var foo = { x:3 } var sed = { x:4 } var func = bar.bind(foo).bind(sed); func(); //? var fiv = { x:5 } var func = bar.bind(foo).bind(sed).bind(fiv); func(); //?
答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
apply、call、bind比较
那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个栗子:
var obj = { x: 81, }; var foo = { getX: function() { return this.x; } } console.log(foo.getX.bind(obj)()); //81 console.log(foo.getX.call(obj)); //81 console.log(foo.getX.apply(obj)); //81
三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。
也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。
再总结一下:
apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用后续参数传参;
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。