Bagaimana kata kunci "ini" berfungsi dalam suatu fungsi?

248

Saya baru saja menemukan situasi yang menarik dalam JavaScript. Saya memiliki kelas dengan metode yang mendefinisikan beberapa objek menggunakan notasi objek-literal. Di dalam benda-benda itu, thispointer sedang digunakan. Dari perilaku program, saya telah menyimpulkan bahwa thispointer merujuk ke kelas di mana metode itu dipanggil, dan bukan objek yang dibuat oleh literal.

Ini tampaknya sewenang-wenang, meskipun itu adalah cara yang saya harapkan akan berhasil. Apakah perilaku ini didefinisikan? Apakah ini lintas browser aman? Adakah alasan yang melatarbelakangi mengapa hal itu berada di luar "spesifikasi kata" (misalnya, apakah ini merupakan konsekuensi dari beberapa keputusan / filosofi desain yang lebih luas)? Contoh kode yang diturunkan:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}
rmeador
sumber
itu memang terjadi ketika saya melakukan ini juga var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons
lihat posting ini . Memiliki beberapa penjelasan yang baik tentang berbagai penggunaan dan perilaku kata kunci ini.
Love Hasija

Jawaban:

558

Dibatalkan dari pos saya yang lain, ini lebih dari yang ingin Anda ketahui tentang ini .

Sebelum saya mulai, inilah hal paling penting untuk diingat tentang Javascript, dan untuk mengulangi diri Anda ketika itu tidak masuk akal. Javascript tidak memiliki kelas (ES6 classadalah gula sintaksis ). Jika sesuatu terlihat seperti kelas, itu trik yang cerdas. Javascript memiliki objek dan fungsi . (Itu tidak 100% akurat, fungsi hanyalah objek, tetapi kadang-kadang bisa berguna untuk menganggapnya sebagai hal yang terpisah)

The ini variabel melekat fungsi. Setiap kali Anda menjalankan fungsi, ini diberi nilai tertentu, tergantung pada bagaimana Anda menjalankan fungsi. Ini sering disebut pola doa.

Ada empat cara untuk menjalankan fungsi dalam javascript. Anda dapat memanggil fungsi sebagai metode , sebagai fungsi , sebagai konstruktor , dan dengan menerapkan .

Sebagai Metode

Metode adalah fungsi yang dilampirkan ke objek

var foo = {};
foo.someMethod = function(){
    alert(this);
}

Ketika dipanggil sebagai metode, ini akan terikat ke objek fungsi / metode adalah bagian dari. Dalam contoh ini, ini akan terikat ke foo.

Sebagai Fungsi

Jika Anda memiliki fungsi yang berdiri sendiri, variabel ini akan terikat ke objek "global", hampir selalu objek jendela dalam konteks browser.

 var foo = function(){
    alert(this);
 }
 foo();

Ini mungkin yang membuat Anda tersandung , tetapi jangan merasa buruk. Banyak orang menganggap ini keputusan desain yang buruk. Karena panggilan balik dipanggil sebagai fungsi dan bukan sebagai metode, itu sebabnya Anda melihat apa yang tampak sebagai perilaku yang tidak konsisten.

Banyak orang mengatasi masalah dengan melakukan sesuatu seperti, um, ini

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Anda mendefinisikan variabel yang yang menunjuk ke ini . Penutupan (topik semua itu sendiri) terus yang di sekitar, jadi jika Anda menelepon bar sebagai callback, masih memiliki referensi.

CATATAN: Dalam use strictmode jika digunakan sebagai fungsi, thistidak terikat ke global. (Yaitu undefined).

Sebagai Konstruktor

Anda juga dapat menjalankan fungsi sebagai konstruktor. Berdasarkan konvensi penamaan yang Anda gunakan (TestObject) ini juga mungkin apa yang Anda lakukan dan apa yang membuat Anda tersandung .

Anda memanggil fungsi sebagai Konstruktor dengan kata kunci baru.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

Ketika dipanggil sebagai konstruktor, Obyek baru akan dibuat, dan ini akan terikat ke objek itu. Sekali lagi, jika Anda memiliki fungsi dalam dan mereka digunakan sebagai callback, Anda akan memanggil mereka sebagai fungsi, dan ini akan terikat pada objek global. Gunakan var itu = trik / pola ini.

Beberapa orang berpikir konstruktor / kata kunci baru adalah tulang yang dilemparkan ke Jawa / programmer OOP tradisional sebagai cara untuk membuat sesuatu yang mirip dengan kelas.

Dengan Metode Terapkan

Akhirnya, setiap fungsi memiliki metode (ya, fungsi adalah objek dalam Javascript) bernama "apply". Terapkan memungkinkan Anda menentukan apa nilai ini akan, dan juga memungkinkan Anda mengirimkan array argumen. Ini contoh yang tidak berguna.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
Alan Storm
sumber
8
Catatan: Dalam mode ketat , thisakan undefineduntuk pemanggilan fungsi.
Miscreant
1
Deklarasi fungsi, mis. function myfunction () {}, adalah kasus khusus "sebagai metode" di mana "ini" adalah ruang lingkup global (jendela).
richard
1
@ Richard: Kecuali dalam mode ketat, dan thistidak ada hubungannya dengan ruang lingkup. Maksud Anda objek global .
TJ Crowder
@ alan-storm. Dalam kasus "Sebagai konstruktor" this.confusing = 'hell yeah';sama dengan var confusing = 'hell yeah';? Jadi keduanya akan memungkinkan myObject.confusing? Akan lebih baik jika tidak hanya agar Anda dapat menggunakan thisuntuk membuat properti dan variabel lain untuk pekerjaan internal.
wunth
Tapi sekali lagi saya kira hal-hal yang dapat dilakukan di luar fungsi dan nilai diteruskan ke konstruktor: function Foo(thought){ this.confusing = thought; }dan kemudianvar myObject = new Foo("hell yeah");
wunth
35

Panggilan fungsi

Fungsi hanyalah jenis Objek.

Semua objek fungsi memiliki panggilan dan menerapkan metode yang mengeksekusi objek fungsi mereka dipanggil.

Saat dipanggil, argumen pertama ke metode ini menentukan objek yang akan direferensikan oleh thiskata kunci selama eksekusi Fungsi - apakah itu nullatau undefined, objek global window, digunakan untuk this.

Dengan demikian, memanggil Fungsi ...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... dengan tanda kurung - foo()- sama dengan foo.call(undefined)atau foo.apply(undefined), yang secara efektif sama dengan foo.call(window)atau foo.apply(window).

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Argumen tambahan untuk callditeruskan sebagai argumen untuk panggilan fungsi, sedangkan argumen tambahan tunggal untuk applydapat menentukan argumen untuk panggilan fungsi sebagai objek seperti Array.

Dengan demikian, foo(1, 2, 3)sama dengan foo.call(null, 1, 2, 3)atau foo.apply(null, [1, 2, 3]).

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Jika suatu fungsi adalah properti dari suatu objek ...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... mengakses referensi ke Fungsi melalui objek dan memanggilnya dengan tanda kurung - obj.foo()- sama dengan foo.call(obj)atau foo.apply(obj).

Namun, fungsi yang dipegang sebagai properti objek tidak "terikat" pada objek tersebut. Seperti yang dapat Anda lihat dalam definisi di objatas, karena Fungsi hanyalah jenis Objek, mereka dapat dirujuk (dan dengan demikian dapat diteruskan dengan referensi ke Panggilan fungsi atau dikembalikan dengan referensi dari Panggilan fungsi). Ketika referensi ke Fungsi dilewatkan, tidak ada informasi tambahan tentang di mana ia lulus dari dilakukan dengan itu, yang mengapa terjadi berikut:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

Panggilan ke referensi Fungsi kami baz,, tidak memberikan konteks apa pun untuk panggilan tersebut, jadi itu sama dengan baz.call(undefined), jadi pada thisakhirnya merujuk window. Jika kita ingin baztahu bahwa itu milik obj, kita perlu entah bagaimana memberikan informasi itu ketika bazdipanggil, yang merupakan tempat argumen pertama callatau applydan penutupan ikut bermain.

Rantai ruang lingkup

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Ketika suatu Fungsi dieksekusi, ia menciptakan suatu lingkup baru dan memiliki referensi ke segala lingkup yang melampirkan. Ketika fungsi anonim dibuat dalam contoh di atas, ia memiliki referensi ke ruang lingkup itu dibuat, yang merupakan bindruang lingkup. Ini dikenal sebagai "penutupan."

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

Ketika Anda mencoba mengakses variabel ini, "rantai lingkup" ini berjalan untuk menemukan variabel dengan nama yang diberikan - jika cakupan saat ini tidak mengandung variabel, Anda melihat cakupan berikutnya dalam rantai, dan seterusnya hingga Anda mencapai ruang lingkup global. Ketika fungsi anonim dikembalikan dan bindselesai dieksekusi, fungsi anonim masih memiliki referensi ke bindruang lingkup, jadi bindruang lingkup tidak "hilang".

Dengan semua hal di atas, Anda sekarang harus dapat memahami bagaimana ruang lingkup bekerja dalam contoh berikut, dan mengapa teknik untuk melewatkan fungsi di sekitar "pra-terikat" dengan nilai tertentu thisakan memilikinya ketika disebut bekerja:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
Jonny Buchanan
sumber
"Ketika referensi ke suatu Fungsi diloloskan, tidak ada informasi tambahan tentang dari mana ia diloloskan dibawa bersamanya" terima kasih @insin untuk ini.
Alex Marandon
9

Apakah perilaku ini didefinisikan? Apakah ini lintas browser aman?

Iya. Dan ya.

Apakah ada alasan yang mendasari mengapa itu seperti itu ...

Arti thiscukup sederhana untuk disimpulkan:

  1. Jika thisdigunakan di dalam fungsi konstruktor, dan fungsi itu dipanggil dengan newkata kunci, thismerujuk ke objek yang akan dibuat. thisakan terus berarti objek bahkan dalam metode publik.
  2. Jika thisdigunakan di tempat lain, termasuk fungsi yang dilindungi bersarang , ini merujuk ke lingkup global (yang dalam hal browser adalah objek jendela).

Kasing kedua jelas cacat desain, tetapi cukup mudah untuk mengatasinya dengan menggunakan penutup.

Rakesh Pai
sumber
4

Dalam hal ini bagian dalam thisterikat ke objek global alih-alih ke thisvariabel fungsi luar. Begitulah cara bahasa dirancang.

Lihat "JavaScript: The Good Parts" oleh Douglas Crockford untuk penjelasan yang baik.

Santiago Cepas
sumber
4

Saya menemukan tutorial yang bagus tentang ECMAScript ini

Nilai ini adalah objek khusus yang terkait dengan konteks eksekusi. Oleh karena itu, dapat dinamai sebagai objek konteks (yaitu objek di mana konteks konteks eksekusi diaktifkan).

Objek apa pun dapat digunakan sebagai nilai konteks ini.

a nilai ini adalah properti dari konteks eksekusi, tetapi bukan properti dari objek variabel.

Fitur ini sangat penting, karena bertentangan dengan variabel, nilai ini tidak pernah berpartisipasi dalam proses resolusi pengidentifikasi. Yaitu ketika mengakses ini dalam kode, nilainya diambil langsung dari konteks eksekusi dan tanpa pencarian rantai lingkup. Nilai ini ditentukan hanya sekali ketika memasuki konteks.

Dalam konteks global, nilai ini adalah objek global itu sendiri (itu berarti, nilai ini di sini sama dengan objek variabel)

Dalam kasus konteks fungsi, nilai ini dalam setiap panggilan fungsi tunggal mungkin berbeda

Referensi Javascript-the-core dan Bab-3-ini

Damodaran
sumber
" Dalam konteks global, nilai ini adalah objek global itu sendiri (itu berarti, nilai ini di sini sama dengan objek variabel) ". The obyek global adalah bagian dari konteks eksekusi global, seperti yang (ES4) "variabel objek" dan merekam lingkungan ES5. Tetapi mereka adalah entitas yang berbeda dengan objek global (misalnya catatan lingkungan tidak dapat dirujuk secara langsung, itu dilarang oleh spec, tetapi objek global bisa).
RobG