자바스크립트를 일상적으로 사용하거나 프론트엔드 면접 과정에서 나타나는 비율이 매우 높습니다. 이는 의심할 여지없이 이것의 중요성을 보여줍니다. 그러나 이는 매우 유연하기 때문에 많은 사람들이 이 동작을 이해하기 어렵게 만듭니다. 이 기사는 이것이 왜 필요한지부터 시작하여 이에 대한 6가지 규칙을 요약하여 귀하의 혼란에 답하는 데 도움이 되기를 바랍니다.
소개
이 매개변수는 실제로 개발 중에 수동으로 전달될 수도 있고 JS 또는 세 번째 세 당사자로부터 수신됩니다.
이 매개변수는 일반적으로 함수가 실행될 때 "소유자"를 가리킵니다. 이 메커니즘은 기능 설계를 더욱 간결하고 재사용 가능하게 만들 수 있습니다.
함수가 실행될 때 바인딩됩니다. 총 6가지 바인딩 규칙이 있습니다.
●new 바인딩: new 키워드를 사용하여 객체를 생성할 때 this가 바인딩됩니다. 생성된 개체에 대해
● 명시적 바인딩: 호출, 적용 또는 바인드 메서드를 사용하여 명시적으로 바인딩하는 경우 첫 번째 매개변수입니다.
●암시적 바인딩: 객체에 함수가 실행되면 시스템이 이를 객체에 암시적으로 바인딩합니다.
●기본 바인딩: 함수가 독립적으로 실행될 때 엄격 모드에서 this의 기본 바인딩 값은 정의되지 않고, 그렇지 않으면 전역 개체입니다.
●화살표 함수 바인딩: 화살표 함수를 사용할 때 this의 바인딩 값은 외부 일반 함수(또는 전역 개체 자체)의 this와 동일합니다.
●시스템 또는 제3자 바인딩: 시스템이나 제3자가 제공하는 인터페이스에 함수가 매개변수로 전달되면 전달된 함수의 해당 함수가 시스템 또는 제3자에 의해 바인딩됩니다.
이
이 메커니즘의 역할은 객체를 암시적으로 전달하는 우아한 방법을 제공하여 기능 설계를 더욱 간결하고 재사용 가능하게 합니다.
클릭하면 배경이 빨간색으로 바뀌는 두 개의 버튼에 대한 다음 예를 살펴보세요.
function changeBackgroundColor(ele) { ele.style.backgroundColor = 'red'; } btn1.addEventListener('click',function () { changeBackgroundColor(btn1); }); btn2.addEventListener('click',function () { changeBackgroundColor(btn2); });
여기서는 클릭한 요소를 명시적으로changeBackgroundColor 함수에 전달합니다. 그러나 실제로는 암시적으로 컨텍스트를 전달하는 이 기능을 활용하여 현재 클릭된 요소를 함수에서 직접 가져올 수 있습니다. 다음과 같습니다:
function changeBackgroundColor() { this.style.backgroundColor = 'red'; } btn1.addEventListener('click',changeBackgroundColor); btn2.addEventListener('click',changeBackgroundColor);
첫 번째 예에서는 클릭한 요소가 이 형식 매개변수인 ele로 대체됩니다. 두 번째 예에서는 특수 키워드 this로 대체되었습니다. 이는 형식 매개변수와 유사한 기능을 가지고 있으며, 본질적으로 객체에 대한 참조이며, 수동으로 값을 전송할 필요가 없으므로 사용이 더 간단하고 편리하다는 것이 특징입니다.
6가지 규칙
실제로 이것이 가리키는 대상이 무엇인지 가장 혼란스럽습니다. 이 문서에서는 6가지 유형의 시나리오를 분류하고 이에 대한 6가지 바인딩 규칙을 요약합니다.
1.new 바인딩
new를 사용하여 객체를 생성할 때 클래스의 this를 참조합니다. 무엇입니까?
class Person { constructor(name){ this.name = name; } getThis(){ return this } } const xiaoMing = new Person("小明"); console.log(xiaoMing.getThis() === xiaoMing); // true console.log(xiaoMing.getThis() === Person); // false console.log(xiaoMing.name === "小明"); // true
위의 예에서 Person 클래스는 ES6 구문을 사용하여 생성되었습니다. new 키워드를 사용하여 개체를 생성하는 과정에서 이는 시스템에 의해 생성된 개체, 즉 xiaoMing에 자동으로 바인딩됩니다.
규칙 1: new 키워드를 사용하여 객체를 생성할 때 생성된 객체에 바인딩됩니다.
2. 명시적 바인딩
시나리오 2, 호출, 적용 및 바인딩 메서드를 사용하여 이 매개변수를 명시적으로 바인딩합니다.
call을 예로 들어 보겠습니다. call 메소드에 전달된 첫 번째 매개변수는 이것이 참조하는 객체입니다.
function foo() { console.log( this === obj ); // true console.log( this.a === 2 ); // true}const obj = { a: 2}; foo.call( obj );
명시적 전달의 경우 이것이 가리키는 객체가 분명하며 이는 호출, 적용 또는 바인드 메소드의 첫 번째 매개변수입니다.
규칙 2: 호출, 적용 또는 바인드 메소드를 사용하여 명시적으로 바인딩하는 경우 이것이 첫 번째 매개변수입니다.
3. 암시적 바인딩
명시적 바인딩과 암시적 바인딩의 차이점은 개발자의 몫입니다. 암시적으로 바인딩되면 함수나 메서드에 "소유자"가 있고 이 "소유자"는 직접 호출되는 함수나 메서드 개체를 나타냅니다.
예제 1
먼저 가장 간단한 예를 살펴보겠습니다.
function bar() { console.log( this === obj ); }const obj = { foo: function () { console.log( this === obj ); }, bar: bar }; obj.foo(); // trueobj.bar(); // true
foo 함수는 obj 객체에 직접 걸려 있고, bar 함수는 외부에 정의된 후 obj 객체에 걸려 있습니다. 함수가 어디에 정의되어 있든 상관없이 함수가 최종적으로 호출될 때 함수의 "소유자"는 obj입니다. 따라서 이는 함수가 호출될 때 "소유자" obj를 가리킵니다.
예제 2
더 깊은 이해를 위해 새로운 객체에 기능을 재할당하는 경우를 생각해 보겠습니다.
function bar() { console.log( this === obj1 ); // false console.log( this === obj2 ); // true}const obj1 = { foo: function () { console.log( this === obj1 ); // false console.log( this === obj2 ); // true }, bar: bar };const obj2 = { foo: obj1.foo, bar: obj1.bar }; obj2.foo(); obj2.bar();
이 예에서는 obj1의 foo 및 bar 메소드가 obj2에 할당됩니다. 함수가 호출되면 "소유자"는 obj1이 아니라 obj2입니다. 따라서 이것은 obj2를 가리킵니다.
예 3
객체는 여러 수준에 중첩될 수 있습니다. 이 경우 함수가 실행될 때 함수의 "소유자"는 누구입니까?
const obj1 = { obj2: { foo: function foo() { console.log( this === obj1 ); // false console.log( this === obj1.obj2 ); // true } } }; obj1.obj2.foo()
foo 메서드/함수의 직접 호출자는 obj1이 아니라 obj2입니다. 따라서 함수의 "소유자"는 가장 가까운 직접 호출자를 가리킵니다.
예시 4
如果一个方法/函数,在它的直接对象上调用执行,又同时执行了 call 方法,那么它是属于隐式绑定还是显式绑定呢?
const obj1 = { a: 1, foo: function () { console.log(this === obj1); // false console.log(this === obj2); // true console.log(this.a === 2); // true } };const obj2 = { a: 2}; obj1.foo.call(obj2); // true
由上,可以看出,如果显式绑定存在,它就不可能属于隐式绑定。
规则三:如果函数是挂在对象上执行的,这个时候系统会隐式的将 this 绑定为函数执行时的“拥有者”。
4.默认绑定
前一小段,讨论了函数作为对象的方法执行时的情况。本小段,要讨论的是,函数独立执行的情况。
在函数直接调用的情况下,this 绑定的行为,称之为默认绑定。
例一
为了简单起见,先讨论在浏览器的非严格模式的下绑定行为。
function foo() { console.log( this === window); // true} foo();
在上面的例子中,系统将 window 默认地绑定到函数的 this 上。
例二
在这里,先介绍一种我们可能会在代码中见到的显式绑定 null 的写法。
function foo() { console.log( this == window ); // true} foo.apply(null);
将例一默认绑定的情况,改为了显式绑定 null 的情况。
在实际开发中,我们可能会用到 apply 方法,并在第一个参数传入 null 值,第二个参数传入数组的方式来传递数组类型的参数。这是一种传统的写法,当然现在可以用 ES6 的写法来代替,但是这不在本文的讨论范围内。
在本例最需要关注的是,this 竟然指向的 window 而不是 null。个人测试的结果是,在函数独立调用时,或者显式调用,传入的值为 null 和 undefined 的情况下,会将 window 默认绑定到 this 上。
在函数多次调用,形成了一个调用栈的情况下,默认绑定的规则也是成立的。
例三
接着,探讨下严格模式下,this 的默认绑定的值。
"use strict"; function foo() { console.log( this === undefined ); } foo(); // true foo.call(undefined); // true foo.call(null); // false
在严格模式下,this 的默认绑定的值为 undefined。
规则四:在函数独立执行的情况下,严格模式 this 的默认绑定值为 undefined,否则默认绑定的值为 window。
5.箭头函数绑定
箭头函数实际上,只是一个语法糖,实际上箭头函数中的 this 实际上是其外层函数(或者 window/global 本身)中的 this。
// ES6 function foo() { setTimeout(() => { console.log(this === obj); // true }, 100); } const obj = { a : 1 } foo.call(obj); // ES5 function foo() { var _this = this; setTimeout(function () { console.log(_this === obj); // true }, 100); } var obj = { a : 1 } foo.call(obj);
规则五:使用箭头函数时,this 的绑定值和其外层的普通函数(或者 window/global 本身) this 绑定值相同。
6.系统或第三方绑定
在 JavaScript 中,函数是第一公民,可以将函数以值的方式,传入任何系统或者第三方提供的函数中。现在讨论,最后一种情况。当将函数作为值,传入系统函数或者第三方函数中时,this 究竟是如何绑定的。
我们在文章一开始提到的,两个按钮例子,系统自动将 this 绑定为点击的按钮。
function changeBackgroundColor() { console.log(this === btn1); // true} btn1.addEventListener('click',changeBackgroundColor);
接着测试系统提供的 setTimeout 接口在浏览器和 node 中绑定行为。
// 浏览器 setTimeout(function () { console.log(this === window); // true },0) // node setTimeout(function () { console.log(this === global); // false console.log(this); // Timeout },0)
很神奇的是,setTimeout 在 node 和浏览器中的绑定行为不一致。如果我们将 node 的中的 this 打印出来,会发现它绑定是一个 Timeout 对象。
如果是第三发提供的接口,情况会更加复杂。因为在其内部,会将什么值绑定到传入的函数的 this 上,事先是不知道的,除非查看文档或者源码。
系统或者第三方,在其内部,可能会使用前面的五种规则一种或多种规则,对传入函数的 this 进行绑定。所以,规则六,实际上一条在由前五条规则上衍生出来的规则。
规则六:调用系统或者第三方提供的接口时,传入函数中的 this 是由系统或者第三方绑定的。