#1. これについての簡単な説明
このキーワードは、JavaScript で最も複雑なメカニズムの 1 つです。これは、すべての関数のスコープ内で自動的に定義される非常に特殊なキーワードです。しかし、経験豊富な JavaScript 開発者でも、それが正確に何を指しているのかを理解するのは困難です。
十分に高度なテクノロジーは魔法と区別がつきません。 — Arthur C. Clarke
実際、JavaScript におけるこのメカニズムはそれほど高度ではありませんが、開発者は理解プロセスを複雑にする傾向があります。これはあなたにとって完全な魔法になります。
2. なぜこれを使用するのでしょうか?
const obj = { title: '掘金', reading() { console.log(this.title + ',一个帮助开发者成长的社区'); } }
これにより、オブジェクト参照を暗黙的に「渡す」ためのより洗練された方法が提供されるため、API をよりクリーンで再利用しやすいように設計できます。
使用パターンがますます複雑になるにつれて、コンテキスト オブジェクトを明示的に渡すとコードがますます乱雑になりますが、今回の場合は当てはまりません。オブジェクトとプロトタイプを紹介すると、関数が適切なコンテキスト オブジェクトを自動的に参照できることがいかに重要であるかが理解できるでしょう。
##3. これに関するよくある誤解
これは、関数自体を指していると理解するのが簡単です。 新しい JavaScript 開発者は、通常、関数はオブジェクトとみなされ (JavaScript のすべての関数はオブジェクトです)、関数を呼び出すときにそれらを使用できると考えます。 . 状態 (プロパティの値) を格納します。しかし、結果は通常彼らを驚かせます。たとえば、次のコード function foo() {
// 让新添加的 count + 1
this.count++
}
// 向函数对象 foo 添加了一个属性 count
foo.count = 0
foo()
console.log(foo.count); // 0
は問題ないように見えますが、最後の行に注目してください。
の出力は 0 であることがわかりますか? !
: なぜこのようなことが起こっているのでしょうか?関数オブジェクト foo に属性 count
を明確に追加し、関数内に this.count
も書きました。なぜ 0 になったのでしょう。
毛織物?
: this.count の this は foo 関数自体をまったく指していません が、グローバル
window# を指しています。 ####。さらに注意深く見ると、count 属性が
window に追加されており、その値は NaN であることがわかります。
(なぜこれが window を指すのかは後ほど説明します) したがって、これを単純に関数自体を指すと理解するのは間違いです。
これは実際には、関数が呼び出されたときに発生するバインディングであり、それが何を指すかは、関数が呼び出される場所によって完全に異なります。
4. このバインディング ルール
関数の実行中に、呼び出し位置が this のバインディング オブジェクトをどのように決定するかを見てみましょう。 発信場所を見つけて、次の 4 つのルールのどれを適用する必要があるかを決定します。まず、これら 4 つのルールをそれぞれ説明し、次に複数のルールが使用可能な場合にそれらのルールがどのように優先されるかを説明します。
4.1 デフォルトのバインディング最初に紹介するのは、最も一般的に使用されるタイプの関数呼び出し、独立関数呼び出しです。このルールは、他のルールを適用できない場合のデフォルトのルールであると考えてください。
function foo() { console.log(this.a) } var a = 2 foo() // 2
foo()
- が呼び出されると、
- this.a
がグローバル変数
foo()a
に解析されることがわかります。 。なぜ?この例では、this
のデフォルト バインディングが適用されて関数が呼び出されるため、
this はグローバル オブジェクトを指します。
それでは、デフォルトのバインディングがここに適用されていることをどのようにして知ることができるでしょうか?呼び出し場所を分析することで、
がどのように呼び出されるかを確認できます。コードでは、 - foo()
は、何も変更せずに関数参照を使用して直接呼び出されるため、デフォルトのバインディングのみが使用でき、他のルールは適用できません
に属しているため、上記の count コードで関数の - this
が
window
を指している理由も説明しています。独立した関数呼び出しに接続し、デフォルトのバインディングをトリガーして、グローバル ウィンドウをポイントします。 (ブラウザ内のグローバル オブジェクトは window オブジェクトであり、ノードは空のオブジェクト {} です)
デフォルトのバインド ルールにも属します:
関数呼び出しチェーン (関数が別の関数を呼び出す) -
関数をパラメータとして別の関数に渡す
- (拡張: strict モードが使用されている場合) (厳密モード)、デフォルト バインディングにグローバル オブジェクトを使用できないため、これは未定義にバインドされます:)
this
、すべて全体像を指し示します。4.2 隐式绑定
4.2.1 一般的对象调用
这一条需要考虑的规则是调用位置是否有上下文对象,或者说是通过某个对象发起的函数调用
function foo() { console.log(this.a) } const obj = { a: 2, foo: foo } // 通过 obj 对象调用 foo 函数 obj.foo() // 2
调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥 有”或者“包含”它。
foo()
被调用时,它的前面确实加上了对obj
的引用。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this
绑定到这个上下文对象。因为调用foo()
时this
被绑定到obj
上,因此this.a
和obj.a
是一样的。
4.2.2 对象属性引用链
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。举例来说:
function foo() { console.log(this.a) } var obj2 = { a: 2, foo: foo } var obj1 = { a: 1, obj2: obj2 } obj1.obj2.foo() // 2
最终 this
指向的是 obj2
4.2.3 隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上(取决于是否是严格模式)
第一种情况:将对象里的函数赋值给一个变量
function foo() { console.log(this.a) } var obj = { a: 2, foo: foo } var bar = obj.foo // 函数别名! var a = 'global' // a 是全局对象的属性 bar() // "global"
虽然 bar
是 obj.foo
的一个引用,但是实际上,它引用的是 foo
函数本身,因此此时的 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"
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一 个例子一样。
结论:隐式绑定的 this,指向调用函数的上下文对象。
4.3 显式绑定
4.3.1 使用 call(...) 和 apply(...)
如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?可以使用函数的 call(..)
和 apply(..)
方法
- JavaScript 提供的绝大多数函数以及你自 己创建的所有函数都可以使用
call(..)
和apply(..)
方法。
这两个方法是如何工作的呢?它们的第一个参数是一个对象,是给 this 准备的,接着在调用函数时将其绑定到 this。因为你可以直接指定 this 的绑定对象,因此我们称之为显式绑定。 思考以下代码:
function foo() { console.log(this.a) } var obj = { a: 2 } foo.call(obj) // 2
通过
foo.call(..)
,我们可以在调用foo
时强制把它的this
绑定到obj
上。如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..) 或者 new Number(..))。这通常被称为“装箱”。
从 this 绑定的角度来说,call(..) 和 apply(..) 是一样的,它们的区别体现在参数上:第一个参数是相同的,后面的参数,call为参数列表,apply为数组,他们内部的实现原理也不难理解,详细请看以下两个手写方法
4.3.2 硬绑定(一个函数总是显示的绑定到一个对象上)
由于硬绑定是一种非常常用的模式,所以 ES5 提供了内置的方法 Function.prototype.bind
, 它的用法如下
function foo(num) { console.log(this.a, num) return this.a + num } var obj = { a: 2 } // 调用 bind() 方法,返回一个函数 var bar = foo.bind(obj) var b = bar(3) // 2 3 console.log(b) // 5
调用 bind(...)
方法,会返回一个新函数,那么这个新函数的 this
,永远指向我们传入的obj
对象
关于 bind 方法的简单实现,可以前往:手写 bind 方法,超级详细 ⚡⚡⚡
4.3.3 API调用的 “上下文(内置函数)
第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和 bind(..)
一样,确保你的回调函数使用指定的 this
。例如:
(1)数组方法 forEach()
function foo(el) { console.log(el, this.id) } var obj = { id: 'bin' }; [1, 2, 3].forEach(foo, obj) // 输出: // 1 bin // 2 bin // 3 bin
- 调用 foo(..) 时把 this 绑定到 obj 身上
(2)setTimeout()
setTimeout(function() { console.log(this); // window }, 1000);
- 在使用 setTimeout 时会传入一个回调函数,而这个回调函数中的
this
一般指向window
,这个和 setTimeout 源码的内部调用有关,这个不再展开赘述
结论:显式绑定的 this,指向我们指定的绑定对象。
4.4 new 绑定
在 JavaScript 中,普通函数可以使用 new
操作符去调用,此时的普通函数则被称为 “构造函数”。没错,凡是由 new
操作符调用的函数,都称为 “构造函数”
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
在内存中创建一个新对象。
这个新对象内部的[[Prototype]] 特性被赋值为构造函数的 prototype 属性。
构造函数内部的this 被赋值为这个新对象(即this 指向新对象)。
执行构造函数内部的代码(给新对象添加属性)。
如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
function foo(a) { this.a = a } var bar = new foo(2) console.log(bar.a) // 2
使用 new
来调用 foo(..)
时,我们会构造一个新对象并把它绑定到 foo(..)
调用中的 this
上。new
是最后一种可以影响函数调用时 this
绑定行为的方法,我们称之为 new 绑定。
结论:new 绑定的 this,都指向通过 new 调用的函数的实例对象(就是该函数)
5. 绑定规则的优先级
现在我们已经了解了函数调用中 this 绑定的四条规则,你需要做的就是找到函数的调用位置并判断应当应用哪条规则。但是,如果某个调用位置可以应用多条规则呢?所以就需要有绑定规则的优先级。
它们之间的优先级关系为:
默认绑定
这里提前列出优先级,想看详细代码解析的可以往下看,也可以直接拖到最后面的例题部分
5.1 默认绑定的优先级最低
毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
5.2 隐式绑定和显式绑定的优先级比较
测试一下即可知道,有以下代码:
function foo() { console.log(this.a) } var obj1 = { a: 1, foo: foo } var obj2 = { a: 2, foo: foo } // 同时使用隐式绑定和显示绑定 obj1.foo.call(obj2) // 2
可以看到,输出的结果为 2,说明 foo
函数内 this
指向的是 obj2
,而 obj2 是通过显示绑定调用的,所以:显示绑定的优先级更高
5.3 隐式绑定和 new 绑定的优先级比较
有以下测试代码:
function foo() { console.log(this); } var obj = { title: "juejin", foo: foo } // 同时使用隐式绑定和new绑定 new obj.foo(); // foo对象
最后 foo
函数输出的 this
为 foo 对象,说明new绑定优先级更高(否则应该输出 obj 对象),所以:new 绑定的优先级更高
5.4 new 绑定和显示绑定的优先级比较
最后,new 绑定和显式绑定谁的优先级更高呢?
new绑定和call、apply是不允许同时使用的,只能和 bind 相比较,如下:
function foo() { console.log(this) } var obj = { title: "juejin" } var foo = new foo.call(obj); // 直接报错
但是 new 绑定可以和 bind 方法返回后的函数一起使用
function foo() { console.log(this); } var obj = { title: "juejin" } var bar = foo.bind(obj); var foo = new bar(); // foo 对象, 说明使用的是new绑定
最后 foo
函数输出的 this
为 foo 对象,说明new绑定优先级更高(否则应该输出 obj 对象),所以:new 绑定的优先级更高
优先级结论:默认绑定
6. 判断this
现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的 顺序来进行判断:
函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。 var bar = new foo()
函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。 var bar = foo.call(obj2)
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。 var bar = obj1.foo()
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定 到全局对象。 var bar = foo()
就是这样。对于正常的函数调用来说,理解了这些知识你就可以明白 this 的绑定原理了。 不过……凡事总有例外
7. 绑定例外
规则总有例外,这里也一样。在某些场景下 this 的绑定行为会出乎意料,你认为应当应用其他绑定规则时,实际上应用的可能是默认绑定规则。
7.1 箭头函数
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。
7.2 被忽略的this
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:
function foo() { console.log(this.a) } var a = 2 foo.call(null) // 2 foo.call(undefined) // 2 foo.bind(null)();
最后输出的结果都是 2,说明 this
指向的是全局 window
7.3 间接引用
另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的间接引用,在这种情况下,调用这个函数会应用默认绑定规则。 间接引用最容易在赋值时发生:
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()
。根据我们之前说过的,这里会应用默认绑定。
8. this 判断例题
请说出例题中的输出结果
8.1 例题一
var name = "window"; var person = { name: "person", sayName: function () { console.log(this.name); } }; function sayName() { var sss = person.sayName; sss(); person.sayName(); (person.sayName)(); (b = person.sayName)(); } sayName();
解析:
function sayName() { var sss = person.sayName; // 独立函数调用,没有和任何对象关联 sss(); // window // 关联 person.sayName(); // person (person.sayName)(); // person (b = person.sayName)(); // window }
8.2 例题二
var name = 'window' var person1 = { name: 'person1', foo1: function () { console.log(this.name) }, foo2: () => console.log(this.name), foo3: function () { return function () { console.log(this.name) } }, foo4: function () { return () => { console.log(this.name) } } } var person2 = { name: 'person2' } person1.foo1(); person1.foo1.call(person2); person1.foo2(); person1.foo2.call(person2); person1.foo3()(); person1.foo3.call(person2)(); person1.foo3().call(person2); person1.foo4()(); person1.foo4.call(person2)(); person1.foo4().call(person2);
解析:
// 隐式绑定,肯定是person1 person1.foo1(); // person1 // 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2 person1.foo1.call(person2); // person2 // foo2()是一个箭头函数,不适用所有的规则 person1.foo2() // window // foo2依然是箭头函数,不适用于显示绑定的规则 person1.foo2.call(person2) // window // 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window person1.foo3()() // window // foo3显示绑定到person2中 // 但是拿到的返回函数依然是在全局下调用,所以依然是window person1.foo3.call(person2)() // window // 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2 person1.foo3().call(person2) // person2 // foo4()的函数返回的是一个箭头函数 // 箭头函数的执行找上层作用域,是person1 person1.foo4()() // person1 // foo4()显示绑定到person2中,并且返回一个箭头函数 // 箭头函数找上层作用域,是person2 person1.foo4.call(person2)() // person2 // foo4返回的是箭头函数,箭头函数只看上层作用域 person1.foo4().call(person2) // person1
9. 总结
如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
由 new 调用?绑定到新创建的对象。
由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
由上下文对象调用?绑定到那个上下文对象。
默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
每文一句:如果把生活比喻为创作的意境,那么阅读就像阳光。
ok,本次的分享就到这里,如果本章内容对你有所帮助的话可以点赞+收藏,希望大家都能够有所收获。有任何疑问都可以在评论区留言,大家一起探讨、进步!
【推荐学习:javascript高级教程】
以上がこの4つの拘束ルールについて詳しく説明しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます

この記事では、許可によって保護されたバックエンドとのフロントエンド統合を示し、next.jsを使用して機能的なedtech SaaSアプリケーションを構築します。 FrontEndはユーザーのアクセス許可を取得してUIの可視性を制御し、APIリクエストがロールベースに付着することを保証します

JavaScriptは、現代のWeb開発のコア言語であり、その多様性と柔軟性に広く使用されています。 1)フロントエンド開発:DOM操作と最新のフレームワーク(React、Vue.JS、Angularなど)を通じて、動的なWebページとシングルページアプリケーションを構築します。 2)サーバー側の開発:node.jsは、非ブロッキングI/Oモデルを使用して、高い並行性とリアルタイムアプリケーションを処理します。 3)モバイルおよびデスクトップアプリケーション開発:クロスプラットフォーム開発は、反応および電子を通じて実現され、開発効率を向上させます。

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

Pythonはデータサイエンスや機械学習により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、簡潔な構文とリッチライブラリエコシステムで知られており、データ分析とWeb開発に適しています。 2。JavaScriptは、フロントエンド開発の中核です。 node.jsはサーバー側のプログラミングをサポートしており、フルスタック開発に適しています。

JavaScriptは、最新のブラウザにすでに組み込まれているため、インストールを必要としません。開始するには、テキストエディターとブラウザのみが必要です。 1)ブラウザ環境では、タグを介してHTMLファイルを埋め込んで実行します。 2)node.js環境では、node.jsをダウンロードしてインストールした後、コマンドラインを介してJavaScriptファイルを実行します。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

WebStorm Mac版
便利なJavaScript開発ツール

MantisBT
Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

メモ帳++7.3.1
使いやすく無料のコードエディター
