ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript でのこのバインディング メソッドの概要

JavaScript でのこのバインディング メソッドの概要

angryTom
angryTom転載
2019-11-29 15:42:212143ブラウズ

最近、js の基礎知識を復習するために「あなたの知らない JS」シリーズを読み直しましたが、これは常に最優先事項なので、これに関する関連知識を体系的にまとめてみることにしました。将来的には自分にとっても便利です。

JavaScript でのこのバインディング メソッドの概要

#これは 4 つのバインディング ルールです。

1. デフォルトのバインディング

これは、最も一般的なタイプの関数呼び出し: スタンドアロン関数呼び出し (つまり、関数は何も変更せずに関数参照を使用して直接呼び出されます)。このルールは、他のルールを適用できない場合のデフォルトのルールであると考えてください。

デフォルトのバインドは、

非厳密モードでは window を指し、厳密モードでは 未定義を指します。たとえば、次の関数 foo は window# を指します。 ## 非厳格モード:

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

[関連コースの推奨事項:

JavaScript ビデオ チュートリアル]

ここでの foo() メソッドのこれはウィンドウを指しているため、

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 の this は obj を指します。

また、obj には foo が参照として追加されていますが、obj で直接定義しても、最初に定義してから参照属性として追加しても、厳密には foo は obj に属さないので、" 「have」と「include」は分かりやすくするために引用符で囲みます。

一般的な暗黙的呼び出しシナリオ:

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"

Display binding

Use callメソッド、applyメソッド、bindメソッドでバインディング関数のthisの値を指定することができ、このバインディングメソッドをディスプレイバインディングと呼びます。

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) の後、新しいインスタンス オブジェクト バーが作成され、新しいオブジェクト バーがバインドされます。関数内で this を foo にするため、 this.a = a を実行した後、 a は実際には bar.a に割り当てられます。

Priority

通常の状況では、this のバインディングは上記4つの拘束ルールにおいて、それらが同時に出現する場合、どのような順番で方向性を判断すればよいのでしょうか?具体的なルールは次のとおりです。

関数は新しい (新しいバインディング) で呼び出されますか?そうである場合、これは新しく作成されたオブジェクト (var bar = new foo()) にバインドされます。

関数は呼び出し、適用 (明示的バインディング)、またはハード バインディングを通じて呼び出されますか?そうである場合、これは指定されたオブジェクトにバインドされます ( var bar = foo.call(obj2) )。

関数はコンテキスト オブジェクト (暗黙的なバインディング) で呼び出されますか?そうである場合、これはそのコンテキスト オブジェクトにバインドされます。 (var bar = obj1.foo())

どちらでもない場合は、デフォルトのバインディングを使用します。厳密モードの場合は、未定義にバインドされ、それ以外の場合はグローバル オブジェクトにバインドされます。 (var bar = foo())

バインディング例外

1. call、appy、bind、null パラメータで渡す明示的なバインディング メソッドを使用する、または未定義の場合がコンテキストとして使用されている場合、関数呼び出しでは引き続きデフォルトのバインディングが使用されます

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

どのような状況でコンテキストを null として渡す必要がありますか?

1. バインド関数を使用してカリー化を実装します

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

実際、上記の 2 つの使用シナリオでは、call/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(&#39;sven&#39;);  // B {name: "luo"}
console.log( b.getName() ); // 输出:  &#39;luo&#39;

执行new B('seven')后会返回一个新对象b,并且B函数中的this会绑定到新对象b上,B的函数体内执行A.apply(this.arguments)也就是执行b.name = name;这个时候b的值就是{name:'luo'},所以b.getName()就能输出'luo'啦~

实际在业务使用中,逻辑会更复杂一些,但是万变不离其宗,都按照上面写的规则来代入就好了

本文来自 js教程 栏目,欢迎学习!  

以上がJavaScript でのこのバインディング メソッドの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。