JavaScript -- 이 자세한 설명

이 블로그는 이 관련 문제에 대한 요약본인 "JavaScript You Don't Know" 책을 기반으로 합니다.

1. 1장: 이에 대해

1. 두 가지 오해

● 오해 1: 이것은 함수 자체를 가리킨다.

함수의 this는 함수 자체가 아닐 수도 있는 함수 호출의 컨텍스트에 따라 결정됩니다.

예를 들어

function a(){
    console.log(this);
    console.log(this.a);
}
a()
console.log('分*************界****************线');
a.bind(a)()

위의 예는 a() 함수의 this가 window를 가리키고, bind() 함수에 묶인 this가 a 함수 자체를 가리키고 있어 오해를 불러일으키는 좋은 예입니다.

● 오해 2: 이것은 함수의 범위를 가리킵니다.

이것의 방향은 함수의 실행 순서에 따라 동적으로 결정되며, 함수 범위는 코드가 작성된 순서(어휘 범위)에 따라 결정됩니다. 어휘 범위란 무엇입니까? 내 이전 기사를 참조하십시오

예를 들어

var a = 1;
function foo() {
    var a = 2;
    this.bar()
}
function bar() {
    console.log(this.a);
}
foo();  // 1

2. 이것이 정확히 무엇입니까?

this는 작성 시간이 아니라 런타임에 바인딩되며 해당 컨텍스트는 함수가 호출될 때 다양한 조건에 따라 달라집니다. this의 바인딩은 함수가 선언된 위치와는 아무런 관련이 없으며 함수가 호출되는 방식만 관련이 있습니다.

함수가 호출되면 컨텍스트 객체가 생성되는데, 이 컨텍스트 객체는 함수의 호출 스택, 함수의 메소드, 함수의 매개변수 등의 정보를 포함하며 이는 함수 컨텍스트의 속성이다.

2. 2장: 이에 대한 종합적인 분석

1. 전화 위치

함수 호출 스택은 함수가 중첩되어 호출될 때 함수가 스택 형태로 저장됨을 의미합니다. 함수의 호출 위치는 현재 실행 중인 함수의 이전 호출입니다.

2. 구속력 있는 규칙

(1) 기본 바인딩

기본 바인딩은 다른 바인딩 규칙이 만족되지 않을 때 기본 바인딩 규칙을 활성화하는 것입니다.

예를 들어

function a() {
    console.log(this); // window
}
a()

참고: 엄격 모드('use strict')가 함수 내에서 또는 전역적으로 활성화되면 기본 바인딩이 창에 바인딩되지 않고 정의되지 않음에 바인딩됩니다.

예를 들어

function foo1() {
    "use strict";
    console.log(this); // undefined
}
foo1(); 

"use strict";
function foo2() {
    console.log(this); // window
}
foo2(); 

(2) 암시적 바인딩

암시적 바인딩은 함수가 호출되는 범위를 말하며 함수의 this는 범위의 this를 가리키며 현재 함수를 호출하는 이전 호출 스택이라고도 할 수 있습니다.

예를 들어

function foo() {
    console.log(this.a); // 1,this指向 obj2,foo函数的调用栈是 obj2
    console.log(a) // 3,词法作用域(只与函数书写位置有关),函数书写在全局作用域
}
var a = 3;
var obj2 = {
    a: 1,
    foo: foo
};
var obj1 = {
    a: 2,
    obj2: obj2
};
obj1.obj2.foo(); 

사실 암시적 바인딩과 기본 바인딩의 원리는 동일하며, 기본 바인딩은 전역 범위에서 f()를 호출하는데, 실제로는 window.f()입니다.

암묵적 손실 문제

암시적으로 바인딩된 함수를 참조하여 바인딩된 개체를 잃어버릴 수 있으며 이로 인해 창 또는 정의되지 않음(엄격 모드)을 가리킬 수 있습니다.

예를 들어(할당으로 인해 발생)

function a(){
    console.log(this.num);
}
var obj = {
    num:1,
    a:a
}
var num = 2;
var b = obj.a; // 赋值方式
b(); // 2

이 예에서는 바인딩 개체 obj가 손실됩니다. b가 a를 가리키기 때문에(b는 a에 대한 참조를 보유) b() 호출은 obj와 연결되지 않으며 obj는 사과를 파는 행상인( a 참조) 과 같고 행상인은 사과를 b에게 주었으므로 b는 다음을 수행할 있습니다 . 행상인이 당신의 손 (이는 obj를 가리킴)을 사용하여 먹이를 주는 대신 직접 당신 의 손 (이는 창을 가리킴) 을 사용하여 잡고 먹습니다.

예를 들어(콜백으로 인해 발생)

function a(){
    console.log(this.num);
}
var obj = {
    num:1,
    a:a
}
function f(fn){
    fn();
}
var num = 2;
f(obj.a); // 2
f(obj.a()); // 1 TypeError , 注意此处并不是隐式丢失,而是隐式绑定 + 语法错误(1不是函数)

콜백이 이 바인딩 개체를 잃는 문제는 할당 손실과 유사합니다. 함수가 매개 변수를 전달할 때 실제로는 할당(fn = obj.a)이기 때문입니다.

JS 내장 함수 및 자체 정의 함수는 setTimeout 함수와 같은 문제를 잃게 됩니다.

function a(){
    console.log(this.num);
}
var obj = {
    num:1,
    a:a
}
function f(fn){
    fn();
}
var num = 2;
setTimeout(f(obj.a),1000) // 2

요컨대: 암시적 손실을 유발하는 콜백 및 할당은 상대적으로 일반적이며 이는 매우 비우호적이므로 해결해야 합니다. 이 문제는 나중에 이것을 수정 하면 해결됩니다 .

(3) 명시적 바인딩

디스플레이 바인딩은 JS에서 제공하는 call(), apply() 및 bind() 메소드를 사용하여 이것을 객체에 수동으로 바인딩하는 것입니다.

예를 들어

function a(){
    console.log(this.num);
}
obj = {
    num:1
}
var num = 2;
a(); // 2
a.call(obj); // 1
a.apply(obj); // 1
a.bind(obj)(); // 1

암시적 손실 해결(하드 바인딩) : 하드 바인딩은 함수를 호출할 때 이를 수동으로 변경하는 것입니다.

암묵적 손실의 예

function a(){
    console.log(this.num);
}
var obj = {
    num:1,
    a:a
}
function f(fn){
    // fn(); // 原来的写法
    // 硬绑定
    fn.call(obj)
}
var num = 2;
f(obj.a); // 1

(4) 새로운 바인딩

먼저 오해를 명확히 하십시오. JS의 new는 다른 클래스 언어와 동일합니다.

사실 JS의 new는 다른 클래스 언어와 다릅니다. Java와 같은 전통적인 클래스 지향 언어에서는 new 연산자를 사용하여 클래스를 초기화할 때 클래스 내의 생성자가 호출됩니다. JS의 new 연산자는 사용할 때 Java와 다르지 않지만 메커니즘은 다릅니다. JS의 경우 생성자는 new 연산자를 사용할 때 호출되는 함수일 뿐 특정 클래스에 속하지도 않고 클래스를 인스턴스화하지도 않으며 new 연산자가 호출하는 일반적인 함수일 뿐입니다.

모든 함수는 new로 호출할 수 있으며, 이런 함수 호출을 "생성자 호출"이라고 합니다.

new를 사용하여 함수를 호출하면 다음 단계를 따릅니다 .

① 새로운 객체를 생성합니다.

② 이 새 개체는 "프로토타입 연결"로 실행됩니다.

③ 새 개체는 함수 호출의 this에 바인딩됩니다.

④ 함수가 다른 객체를 반환하지 않으면 새 표현식은 새 객체를 반환합니다.

예를 들어

function Foo(a) {
    this.num = a;
    this.that = this;
    console.log(this);
}
var bar = new Foo(1); // bar就是默认返回的新对象
console.log(Foo); 
console.log(bar.num); // 1,新对象绑定函数调用的this
console.log(bar); 
console.log(bar === bar.that); // 函数中的this,就是新对象bar

 3. 우선순위

신규 > 표시 > 암시적 > 기본값

예를 들어(디스플레이 > 암시적)

function a(){
    console.log(this.num);
}
var obj = {
    a:a,
    num:1
}
var num = 2;
obj.a(); // 1
obj.a.call(window); // 2 显示绑定比隐式绑定优先

예를 들어(신규 > 암시적)

function a(n){
    this.num = n;
}
var obj = {
    a:a,
}
var num = 2;
obj.a(1);
console.log(obj.num); // 1

var b = new obj.a(3); // new 绑定比隐式绑定优先。因为 b.num的值为3,并没有受到 obj的影响
console.log(obj.num);// 1
console.log(b.num); // 3

예를 들어(신규 > 명시적)

function a(n){
    this.num = n;
}
var obj = {
    a:a,
}
var num = 2;
var bar = a.bind(obj);
bar(1);
console.log(obj.num); // 1

var b = new bar(3); // new比显示绑定优先。因为new bar(3)没有受到a.bind(obj)中obj的影响
                    // b.num的结果是3
console.log(obj.num);// 1
console.log(b.num); // 3

4. 바인딩 예외

(1) 이것을 무시했다

디스플레이 바인딩 메서드 call(), apply(), bind()를 사용하여 이를 수동으로 수정하는 경우 정의되지 않거나 null이 전달되거나 아무것도 전달되지 않으면 기본 바인딩이 창에 바인딩하는 데 사용됩니다(엄격하지 않은 모드). .

function a(){
    console.log(this.num);
}
var num = 1;
var obj = {
    num:2,
    a:a
}
a.call(obj); // 2
obj.a.call(undefined); // 1 因为显示绑定优先于隐式绑定,
                       // 但是显示绑定又指定this为undefined,多以使用默认绑定(window)
a.call(null); // 1 
a.call(); // 1 
a.apply(null); // 1
a.bind(undefined)(); // 1

(2) 보다 안전함(묵시적 손실 문제도 해결할 수 있음)

이를 무시하기 위해 항상 null 또는 undefined를 사용하면 일부 타사 라이브러리의 함수에서 사용하는 것과 같은 일부 문제가 발생할 수 있으며 기본적으로 window(비엄격)에 바인딩되어 오류가 발생합니다.

따라서 더 안전한 방법은 특별한 빈 개체를 만들고 모든 함수의 this를 이 빈 개체로 제한하는 것입니다. 그러면 전역 개체에 영향을 미치지 않습니다. Object.create(null)을 사용하여 {}보다 비어 있는 객체를 생성하고 Object.create(null)에 의해 생성된 객체는 Object.prototype에 의해 위임되지 않습니다.

예를 들어 (null 사용의 단점)

function foo(argu) {
    console.log(this.num,argu);
}
var num = 1;
var obj = {
    num:2,
    foo:foo
}
foo.call(null,'null'); // 1 'null' , this是window
setTimeout(obj.foo('setTimeout'),100); // 2 'setTimeout' , 注意this是obj,因为直接执行了
setTimeout(obj.foo,100); // 1 undefined , this是window

예를 들어 (Object.create(null) 사용)

function foo(argu) {
    console.log(this.num,argu);
}
var num = 1;
var obj = {
    num:2,
    foo:foo
}
var ø = Object.create(null);
foo.call(ø,'ø'); // undefined 'ø' , this是 {}
setTimeout(obj.foo.bind(ø,'ø'),100); // undefined 'ø' , this是 {}
setTimeout(obj.foo.call(ø,'ø'),100); // undefined 'ø' , this是 {}

(3) 소프트 바인딩

하드 바인딩을 사용하는 경우 하드 바인딩(이 문제를 해결하기 위해 명시적 바인딩 사용)을 사용하면 암시적 바인딩 및 기타 명시적 바인딩이 실패하기 때문에 함수의 유연성이 크게 감소합니다.

하드 바인딩은 암시적 손실 문제를 방지하기 위한 것입니다(매개 변수를 할당하고 전달할 때 bound this가 손실되고 this가 잘못된 창에 바인딩되거나 정의되지 않음). 암시적 손실 문제를 해결하는 또 다른 방법: 함수를 호출할 때 먼저 this를 확인하고, 그것이 window이거나 정의되지 않은 경우 this(대상 개체 this)를 수정합니다. 그렇지 않으면 변경되지 않습니다. 이것이 소프트 바인딩의 아이디어입니다.

아래 코드는 "JavaScript You Don't Know" 책에서 가져온 것입니다.

// 对softBind函数进行封装
if (!Function.prototype.softBind) {
    Function.prototype.softBind = function (obj) {
        var fn = this;
        // 捕获所有 curried 参数
        var curried = [].slice.call(arguments, 1); // 这行代码相当于arguments.slice(1),因为arguments是伪数组不是真正的数组,不能使用数组的slice方法,所以使用call(),将this绑定到数组,确保arguments可以使用数组的方法slice
        var bound = function () {
            return fn.apply(
                (!this || this === (window || global)) ? obj : this
                ,curried.concat.apply(curried, arguments) // 这行代码是将bound函数参数与softBind函数参数合并
                );
            };
            bound.prototype = Object.create(fn.prototype);
        return bound;
    };
}

function foo() {
    console.log("name: " + this.name);
}
var obj = { name: "obj" },obj2 = { name: "obj2" },obj3 = { name: "obj3" };
var fooOBJ = foo.softBind(obj);
fooOBJ(); // name: obj,证明解决隐式丢失问题,this不是window而是obj
fooOBJ.call(obj3); // name: obj3,证明解决硬绑定问题,可以更改this指向obj3
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2,bound函数this指向obj2
setTimeout(obj2.foo, 10);// name: obj,函数作为参数传递,this不是window,是初始化softBind函数时的obj

(4) 화살표 기능

화살표 함수는 ES에 새로 추가된 기능입니다. 화살표 함수는 위의 네 가지 바인딩 규칙을 사용하지 않습니다. 이것은 외부(함수 또는 전역) 범위에 따라 결정됩니다. 현재 어휘 범위에 따라 이것을 결정하십시오.

예를 들어

var obj1 = {
    num: 2,
    obj2: {
        num: 3,
        obj3: {
            num:4,
            a: () => {
                console.log(this.num);
            }
        }
    }
}
var num = 1;
obj1.obj2.obj3.a(); // 1
// window依次传递,最终obj3的this是window,则箭头函数的this也是window

화살표 함수의 this가 결정되면 수정할 수 없으며(new도 마찬가지임) 상위 범위의 this에 의해서만 결정됩니다.

예를 들어

function foo() {
   // 返回一个箭头函数
   return ()=>{
       //this 继承自 foo()
       console.log(this.a);
   };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var a = 4;
var bar = foo.call(obj1);
bar.call(obj2); // 2,obj2无法覆盖箭头函数的this(obj1)
bar(); // 2,window也无法覆盖箭头函数的this(obj1)

여전히 위의 코드는 화살표 기능을 일반 기능으로 바꾸십시오.

function foo() {
   // 返回一个箭头函数
   return function(){
       //this 继承自 foo()
       console.log(this.a);
   };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var a = 4;
var bar = foo.call(obj1);
bar.call(obj2); // 3,obj2覆盖普通函数的this(obj1)
bar(); // 4,window覆盖普通函数的this

ES6 이전에 that = this를 사용하면 화살표 기능의 기능을 구현할 수 있습니다.

function foo() {
   var that = this;
   // 返回一个箭头函数
   return function(){
       //this 继承自 foo()
       console.log(that.a);
   };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var a = 4;
var bar = foo.call(obj1); // 固定普通函数的this(obj1)
bar.call(obj2); // 2,普通函数的this已经被固定
bar(); // 2,

Supongo que te gusta

Origin blog.csdn.net/qq_48113035/article/details/123823275
Recomendado
Clasificación