최근에 js에 대한 기본 지식을 복습하다가 "JS You Don't Know" 시리즈를 다시 읽었습니다. 이것이 항상 최우선 과제이므로 향후 복습을 용이하게 하기 위해 이와 관련된 지식을 체계적으로 요약하기로 결정했습니다. .
이 네 가지 바인딩 규칙
1. 기본 바인딩
가장 일반적으로 사용되는 함수 호출 유형입니다. 독립 함수 호출(즉, 함수 참조를 사용하지 않고 직접 함수를 만듭니다.) 수정)이라고 합니다. 다른 규칙을 적용할 수 없는 경우 이 규칙을 기본 규칙으로 생각하세요.
기본값은 비엄격 모드에서는 window를 가리키고 엄격 모드에서는 undefine을 가리킵니다. 예를 들어 비엄격 모드에서 다음 함수 foo는 다음과 같습니다.
var a = 2; function foo(){ var a = 3; console.log(this.a); } foo(); //2
[관련 과정 권장 사항: JavaScript 비디오 튜토리얼]
여기 foo() 메서드의 This는 창을 가리키므로 window.a = 2;
엄격 모드에서 this.는 정의되지 않음을 가리키므로 this.a에 액세스하면 오류가 보고됩니다.
var a = 2; function foo(){ "use strict"; var a = 3; console.log(this.a); } foo(); //Uncaught TypeError: Cannot read property 'a' of undefined
2. 숨겨진 암시적 바인딩
호출 위치에 컨텍스트 개체가 있거나 개체에 의해 "소유"되거나 "포함"된 경우 암시적 바인딩이 사용됩니다.
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
위의 예에서 foo는 obj.foo()를 통해 호출됩니다. 호출 위치는 obj 컨텍스트를 사용하여 함수를 참조하므로 foo의 이는 obj를 가리킵니다.
그리고 foo는 obj에 참조로 추가되는데, obj에 직접 정의하든지 먼저 정의한 후 참조 속성으로 추가하든 엄밀히 말하면 foo는 obj에 속하지 않으므로 "owned"와 " 위의 정의에서 "포함"은 이해를 돕기 위해 따옴표로 묶여 있습니다.
일반적인 암시적 호출 시나리오:
obj.fn(); arguments[i]();//其实就是将点的调用方式变为了[]调用 el.onClick(function(){console.log(this);//this指向el})
암시적 손실
먼저 코드 부분을 살펴보겠습니다.
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数别名! var a = "global"; // a 是全局对象的属性 bar(); // "global"
위 코드는 실제로 호출 메서드인 bar()에만 의존합니다. 이는 실제로 코드입니다. 데코레이트된 함수가 호출되므로 기본 바인딩이 적용됩니다.
암시적 손실을 유발하는 또 다른 매개변수 전달 방법이 있습니다. 원리는 실제로 위의 예와 동일합니다.
function foo() { console.log( this.a ); } function doFoo(fn) { // fn 其实引用的是foo fn(); // <-- 调用位置! } var obj = { a: 2, foo: foo }; var a = "global"; // a 是全局对象的属性 doFoo( obj.foo ); // "global"
디스플레이 바인딩
이 값을 지정하려면 호출, 적용 및 바인딩 메서드를 사용하세요. 이 바인딩 방법을 디스플레이 바인딩이라고 합니다.
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2
foo.call(obj)을 통해 호출 시 foo의 this가 obj에 바인딩되도록 강제할 수 있습니다.
새 바인딩
new 연산자는 인스턴스화인 "생성자" 인스턴스를 기반으로 새 객체를 생성할 수 있습니다. new의 과정은 다음과 같습니다:
● 완전히 새로운 객체를 생성(또는 구성)합니다.
● 이 새로운 개체는 [[ 프로토타입]]으로 연결됩니다.
● 이 새 객체는 함수 호출의 this에 바인딩됩니다.
● 함수가 다른 개체를 반환하지 않는 경우 새 표현식의 함수 호출은 자동으로 새 개체를 반환합니다.
new의 인스턴스화 프로세스를 명확히 한 후 다음 코드를 생각해 보세요.
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
new foo(2) 후에 새 인스턴스 객체 막대가 생성되고 새 객체 막대가 foo 함수에서 여기에 바인딩됩니다. 이것은 .a = a 이후에 실행되며 a는 실제로 bar.a
Priority
에 할당됩니다. 일반적으로 this의 바인딩은 위의 네 가지 바인딩 규칙을 기반으로 하므로 동시에 나타날 때 어떤 순서로 진행되나요? 구체적인 규칙은 다음과 같습니다.
new(새 바인딩)에서 함수가 호출됩니까? 그렇다면 새로 생성된 객체(var bar = new foo())에 바인딩됩니다.
호출을 통해 호출되는 함수는 적용(명시적 바인딩)인가요, 아니면 하드 바인딩인가요? 그렇다면 이는 지정된 객체( var bar = foo.call(obj2) )에 바인딩됩니다.
일부 컨텍스트 개체에서 함수가 호출됩니까(암시적으로 바인딩됨)? 그렇다면 이는 해당 컨텍스트 개체에 바인딩됩니다. ( var bar = obj1.foo() )
둘 다 아니라면 기본 바인딩을 사용하세요. 엄격 모드에 있으면 정의되지 않은 상태로 바인딩되고, 그렇지 않으면 전역 개체에 바인딩됩니다. (var bar = foo())
바인딩 예외
1. call, appy 및 바인딩과 같은 명시적 바인딩 방법을 사용하면 매개변수가 null로 전달되거나 컨텍스트로 정의되지 않은 경우 함수 호출은 계속해서 다음을 사용합니다. 기본 Binding
function foo() { console.log( this.a ); } var a = 2; foo.call( null ); // 2
어떤 상황에서 컨텍스트를 null로 전달해야 합니까?
1. 바인드 함수를 사용하여 currying
function foo(a,b) { console.log(a,b); } // 使用 bind(..) 进行柯里化 var bar = foo.bind( null, 2 ); bar( 3 ); // 2,3
2. Apply(..)를 사용하여 배열을 확장하고 이를 매개변수로 함수에 전달합니다.
function foo(a,b) { console.log(a,b); } // 把数组展开成参数 foo.apply( null, [2, 3] ); // 2,3
사실 위의 두 가지 사용 시나리오는 신경 쓰지 않습니다. /app/bind의 첫 번째 매개변수 값은 무엇입니까? 자리 표시자 값을 전달하고 싶습니다.
그러나 항상 null을 전달하면 추적하기 어려운 버그가 발생할 수 있습니다. 예를 들어 사용 중인 타사 라이브러리의 함수에 이 버그가 있으면 전역 개체에 잘못 바인딩되어 예측할 수 없는 버그가 발생할 수 있습니다. 결과(전역 변수 수정)
var a = 1;//全局变量 const Utils = { a: 2, changeA: function(a){ this.a = a; } } Utils.changeA(3); Utils.a //3 a //1 Utils.changeA.call(null,4); Utils.a //3 a //4,修改了全局变量a!
더 안전한 접근:
var o = Object.create(null); Utils.changeA.call(o,6); a //1, 全局变量没有修改 o.a // 6 改的是变量o
2.
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 (p.foo = o.foo)(); // 2
赋值表达式p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是foo() 而不是p.foo() 或者o.foo()。根据我们之前说过的,这里会应用默认绑定。
this词法(箭头函数)
上述的几种规则适用于所有的正常函数,但不包括ES6的箭头函数。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域(词法作用域)来决定this
function foo() { // 返回一个箭头函数 return (a) => { //this 继承自foo() console.log( this.a ); }; } var obj1 = { a:2 }; var obj2 = { a:3 }; var bar = foo.call( obj1 ); bar.call( obj2 ); // 2, 不是3 !
foo() 内部创建的箭头函数会捕获调用时foo() 的this。由于foo() 的this 绑定到obj1,bar(引用箭头函数)的this 也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)
几个例子加深理解
this的理论知识讲解得差不多了,来几个例子看看自己有没有理解全面:
1.经典面试题:以下输出结果是什么
var length = 10; function fn() { console.log(this.length); } var obj = { length: 5, method: function(fn) { fn(); arguments[0](); } }; obj.method(fn, 1);
obj中method方法里面调用了两次fn。第一次是直接调用的“裸露”的fn,因此fn()中this使用默认绑定,this.length为10.第二次调用时通过arguments0的方式调用的,arguments[0]其实指向的就是fn,但是是通过obj[fn]这种对象上下文的隐式绑定的,因此this指向arguments,而arguments只有一个一项(method中只有fn一个参数),因此arguments.length为1。因此打印的结果为:
10 1
2.以下输出什么
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = function () { return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); } }; obj.getAge();
答案是严格模式下会报错,非严格模式下输出NaN
原因也是因为在调用obj.getAge()后,getAge方法内的this使用隐式绑定。但是return fn()的时候用的是“裸露的fn”使用默认绑定,fn里面的this指向window或者undefined。
使用箭头函数来修正this的指向:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 25
使用箭头函数后,fn中的this在他的词法分析阶段就已经确定好了(即fn定义的时候),跟调用位置无关。fn的this指向外层的作用域(即getAge中的this)
3.以下输出为什么是'luo'
var A = function( name ){ this.name = name; }; var B = function(){ A.apply(this,arguments); }; B.prototype.getName = function(){ return this.name; }; var b=new B('sven'); // B {name: "luo"} console.log( b.getName() ); // 输出: 'luo'
执行new B('seven')后会返回一个新对象b,并且B函数中的this会绑定到新对象b上,B的函数体内执行A.apply(this.arguments)也就是执行b.name = name;这个时候b的值就是{name:'luo'},所以b.getName()就能输出'luo'啦~
实际在业务使用中,逻辑会更复杂一些,但是万变不离其宗,都按照上面写的规则来代入就好了
本文来自 js教程 栏目,欢迎学习!
위 내용은 JavaScript의 바인딩 방법 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!