Lexical Scope

ridwanf
4 min readAug 21, 2019

--

Sumber: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md

Pada post sebelumnya telah dijelaskan apa itu ‘Scope’. Ada 2 macam model scope yang sering digunakan. Yang pertama dana yang paling sering digunakan adalah Lexical Scope dan yang kedua adalah Dynamic Scope. Pada kali ini kita akan membahas tentang Lexical Scope.ime

Lex-time

Seperti yang telah kita pelajari pada post sebelum ini, Fase awal pada compiler standar disebuat lexing (tokenisasi). Ketika dipanggil, prosess lexing memecah sebuah string dari code kedalam kumpulan token yang memiliki arti sebagai hasilnya. Konsep ini akan kita gunakan sebagai dasar untuk mengerti apa itu lexical scrope dan darimana nama tersebut berasal.

Arti lexical scope dari sisi lain adalah scope yang didefinisikan pada saat lexical time. Dengan kata lain, lexical scope berasal pada saat dimana variabel dan blok dari scope dibuat.

mari kita lihat code dibawah

function foo(a) {

var b = a * 2;

function bar(c) {
console.log( a, b, c );
}

bar(b * 3);
}

foo( 2 ); // 2 4 12

ada tiga nested scope yang saling berkaitan pada code diatas, untuk lebih jelasnya bisa dilihat pada gambar dibawah.

Bubble 1 merupakan global scope, dan hanya memiliki 1 identifier didalamnya foo
Bubble 2
merupakan scope dari foo. yang didalamnya memiliki 3 identifier yaitu a, bar, dan b.
Bubble 3
merupakan scope dari bar, dan memilik 1 identifier c.

Look-ups

Pada contoh diatas, Engine mengeksekusi statement console.log(..) dan kemudian mencari 3 reverensi variabel yaitu a,b, dan c. Eksekusi contoh diatas dimulai dari scope bubble terdalam, scope dari bar(..). Didalam scope tersebut tidak terdapat variabel a, jadi engine akan naik satu level keluar scope bar(..) mencari scope bubble terdekat yaitu scope dari foo(..). Engine menemukan variabel a dan menggunakannya. Hal sama terjadi pada variabel b. Untuk variabel c, variabel ditemukan didalam scope bar(…).

Scope look-up berhenti ketika mendapatkan apa yang dicari. Identifier dengan nama yang sama dapat ditulis kembali pada bermacam layer dari nested scope, hal ini disebut shadowing

Global variable secara otomatis merupakan property dari global object (dalam hal ini window), jadi memungkinkan untuk mereferensikan sebuah global variable tidak secara langsung dengan nama lexical nya, tetapi hanya dengan referensi property dari global object.

window.a

Teknik ini memberikan akses pada global variable yang biasanya tidak dapat diakses ketika terkena shadowing.

Tak peduli dimana sebuah fungsi dipanggil atau bagaimana fungsi tersebut dipanggil, lexcical scope dari fungsi tersebut hanya akan didefinisikan dimana fungsi tersebut dideklarasikan.

Cheating Lexical

Jika lexical scope hanya didefiniskan pada saat fungsi tersebut dideklarasikan. Dapatkah kita memodifikasi sebuah lexical scope pada saat run-time ? Javascript mempunyai 2 buah mekanisme untuk memodifikasi lexical scope. Tetapi perlu diketahui bahwa 2 mekanisme ini merupakan bad practice pada code anda. Karena akan membuat performance code kita menjadi buruk. Adapun metode yang digunakan adalah eval dan with.

eval
Fungsi eval(…) javascript membutuhkan sebuah string sebagai sebuah argument dan memerlakukan string tersebut sebagai sebuah code. Mari kita lihat contoh code dibawah

function foo(str, a) {
eval( str ); // cheating!
console.log( a, b );
}

var b = 2;

foo( "var b = 3;", 1 ); // 1 3

String var b = 3 diperlakukan sebagai sebuah code oleh eval(..). Karena code tersebut maka dideklarasikannlah variabel baru b. Hal tersebut merubah lexical scope yang sudah ada dari foo(..). Tetapi apabila kita lihat code diatas membuat variabel b didalam foo(..) yang merupaka shadow dari variable b yang dideklarasikan pada scope luar (global).

Ketika console.log(..) dipanggil, terdapat variabel a dan b didalam scope foo(..) sehingga tidak mencari di scope luar sehingga akan menampilkan “1 3” dibanding “1 2”.

with
Cara lain untuk merubah lexical scope adalah dengan keyword with(deprecated). with merupakan cara cepat untuk membuat beberapa property references dari sebuah object tanpa mengulangi mereferensikan object tersebut. sebagai contoh

var obj = {
a: 1,
b: 2,
c: 3
};

// more "tedious" to repeat "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;

// "easier" short-hand
with (obj) {
a = 3;
b = 4;
c = 5;
}

contoh lain

function foo(obj) {
with (obj) {
a = 2;
}
}

var o1 = {
a: 3
};

var o2 = {
b: 3
};

foo( o1 );
console.log( o1.a ); // 2

foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 -- Oops, leaked global!

Pada contoh diatas terdapat dua object o1 dan o2. satu mempunyai property a, dan satu lagi tidak. Fungsi foo(..) mempunyai sebuah object reference obj sebagai sebuah argument, dan memanggilwith (obj) {..}. Didalam block with kita meng-assign a = 2.

Ketika kita pass o1, assignment a=2 menemukan property o1.a dan assign nilai 2. Tetapi ketika kita pass o2 kita tidak menemukan property o2.a sehingga akan menghasilkan undefined.

Tetapi ada satu hal yang aneh, ketika console.log(a) akan menghasilkan 2. Kenapa ini bisa terjadi. with statement memerlukan sebuah object yang mempunyai properties sebanyak nol atau lebih, dan memperlakukan object tersebut sebagai sebuah lexical scope yang berbeda dan properties dari object tersebut akan diperlakukan sebagai lexical identifier pada scope tersebut. Jadi ketika a = 2 dieksekusi akan menghasilkan variabel a pada global scope.

performance
Kedua metode eval(..) dan with mengubah lexical scope yang ada dengan membuat lexical scope baru pada saat runtime.
Jadi apa masalahnya? bukankah itu membuat code kita menjadi lebih flexibel. Jawabannya adalah tidak.
Javascript engine memiliki banyak performance optimizations yang terjadi ketika fase compilation. Namun ketika engine menemukan eval(..) atau with dalam code, engine akan mengasumsikan bahwa location identifier tersebut invalid, karena engine tidak mengetahui code apa yang kita berikan kepada eval(..) pada saat lexing time untuk merubah lexical scope. Dengan kata lain banyak optimisasi javascript yang tidak terjadi.

--

--