در فصل 2 به بررسی ویژگی های زبان js و برخی از الگو ها و رفتارهای زبان در سطح بالا (high level) پرداختیم در این فصل به بررسی برخی از ویژگی های سطح پایین تری (lower-level) از js میپردازیم .
برنامه ها اساسا برای پردازش داده ها و تصمیم گیری درباره داده ها ساخته میشن . الگو ها و روش هایی که برای نوشتن برنامه ها استفاده میکنیم تاثیر خیلی زیادی در خوانایی کد داره.
الگو های iterator یک روش استاندارد برای پردازش داده ها هستش. ایده این هستش که منبع داده تکرار بشه یعنی به تدریج پردازش داده انجام بشه و به جای اینکه کل داده یکباره پردازش روش انجام بشه، ابتدا قسمت اول پردازش بشه و سپس بخش بعدی و غیره.
ساختار داده ای رو تصور کنید که در پایگاه داده رابطه ای از کوئری SELECT برای نمایش داده استفاده میکنه ، به طور معمول نتایج به صورت row هایی سازماندهی میشن. اگه تعداد نتایج ما کم باشه میتونیم به طور همزمان کنترل کنیم و عملیات رو انجام بدیم ولی اگه تعداد نتایج ما مثلا 100 یا 1000 یا حتی بیشتر row باشه چطور ؟؟! اینجاست که باید از الگوهای تکرار کمک بگیریم و پردازش داده ها رو انجام بدیم.
از اونجایی که معمولا ما نمی دونیم تعداد داده ها ما چند تا هستش بنابراین استفاده از الگوهای تکرار تا رسیدن به انتهای مجموعه داده یا رسیدن به شرطی که برای پایان حلقه تکرار میذاریم منطقی تر هستش . از طرفی استفاده از این الگو باعث تمیزتر شدن کد و درک آسان تر کد هم میشه .
در ES6 پروتکل خاصی برای الگوهای تکرار به طور مستقیم تعریف شده . پروتکل متد next() رو تعریف میکنه که بعد از هربار فرخوانی iterator تا زمانی که شرط برقرار باشه نتیجه رو برمیگردونه.
علاوه بر next() در ES6 مکانیسم های دیگه ای هم برای انجام عمل تکرار وجود داره. یکی از این مکانیسم ها حلقه for ... of هستش. به کد زیر توجه کنین :
// given an iterator of some data source: var it = /* .. */; // loop over its results one at a time for (let val of it) { console.log(`Iterator value: ${ val }`); } // Iterator value: .. // Iterator value: .. // ..
مکانیسم دیگری که برای تکرار استفاده میشه ، عملگر ... هستش. این عملگر دو فرم داره : spread و rest که فرم spread یک iterator-consumer هستش. برای spread یک تکرار باید چیزی برای spread وجود داشته باشه. در js برای اینکار می تونیم از آرایه ها یا لیست آرگومان ها برای function call استفاده کنیم.
به کد زیر که یک array spread هست توجه کنین :
// spread an iterator into an array, // with each iterated value occupying // an array element position. var vals = [ ...it ];
و همچنین به function call spread :
// spread an iterator into a function, // call with each iterated value // occupying an argument position. doSomethingUseful( ...it );
در هر دو حالت فرم spread از ... برای تکرار استفاده کرده که مانند همون حلقه for ... of عمل میکنه.
پروتکل به طور خودکار یک نمونه تکرار شونده از یک تکرار کننده ایجاد میکنه و ازش استفاده میکنه یعنی یه تکرار میتونه بیشتر از یک بار استفاده شه و هربار یک نمونه تکرار شونده جدید ایجاد و مورد استفاده قرار میگیره.
در ES6 ساختار ها و مجموعه داده ها از جمله strings , arrays , maps , sets و موارد دیگر به عنوان iterables تعریف کرده.
در نظر بگیرین :
// an array is an iterable var arr = [ 10, 20, 30 ]; for (let val of arr) { console.log(`Array value: ${ val }`); } // Array value: 10 // Array value: 20 // Array value: 30
چون ارایه ها تکرار پذیر هستند میتونیم با استفاده از اپراتور ... spread کپی کنیم :
var arrCopy = [ ...arr ];
همچنین میتونیم کاراکتر ها رو به صورت یه رشته تکرار کنیم:
var greeting = "Hello world!" var chars = [ ...greeting ]; chars; // [ "H", "e", "l", "l", "o", " ", // "w", "o", "r", "l", "d", "!" ]
یک ساختار داده map از objects به عنوان key استفاده میکنه و یک value رو به اون object مرتبط میکنه. map تکرار متفاوتی با آنچه تا این جا بررسی کردیم داره چون این تکرار فقط از مقادیر نیست و به ورودی ها هم بستگی داره . یک ورودی که یه آرایه 2 بعدی که شامل یک key و یک value میشه هستش . به کد زیر توجه کنین :
// given two DOM elements, `btn1` and `btn2` var buttonNames = new Map(); buttonNames.set(btn1,"Button 1"); buttonNames.set(btn2,"Button 2"); for (let [btn,btnName] of buttonNames) { btn.addEventListener("click",function (){ console.log(`Clicked ${ btnName }`); }); }
در حلقه for ... of برای تکرار map از[btn,btnName] برای تجزیه هر جفت key/value استفاده میشه. (btn1 / "Button 1" و btn2 / "Button 2")
و اگه نیاز به index و value های یه آرایه در iteration داریم میتونیم از متد entries() استفاده کنیم.
به کد زیر توجه کنین:
var arr = [ 10, 20, 30 ]; for (let [idx,val] of arr.entries()) { console.log(`[${ idx }]: ${ val }`); } // [0]: 10 // [1]: 20 // [2]: 30
همین طور که دیدین built-in iterables در js دارای سه شکل iterator هستند :
1- keys-only (keys())
2- values-only (values())
3- entries (entries())
تعریفی که خود کتاب از closure میگه رو دقیقا براتون میذارم :
Closure is when a function remembers and continues to access variables from outside its scope, even when the function is executed in a different scope.
اینجا دو ویژگی هستش که اهمیت داره ، اول اینکه closure بخشی از ماهیت یک function هستش . دوم اینکه برای مشاهده یک closure باید یه function رو داخل یه scope متفاوت نسبت به جایی که در ابتدا اون function تعریف شده بود قرار بدین و کد رو اجرا کنین .
کد زیر رو در نظر بگیرین:
function greeting(msg) { return function who(name) { console.log(`${ msg }, ${ name }!`); }; } var hello = greeting("Hello"); var howdy = greeting("Howdy"); hello("Kyle"); // Hello, Kyle! hello("Sarah"); // Hello, Sarah! howdy("Grant"); // Howdy, Grant!
ابتدا یه function خارجی greeting() داریم که داخلش function who() تعریف شده. function داخلی امکان دسترسی به variable msg رو داره . وقتی function داخلی return میشه reference
داده میشه به hello() که در خارج از scope قرار داره. سپس بار دوم greeting() فراخوانی میشه و یک نمونه function جدید ازش ساخته میشه و که بعد از return function داخلی reference داده میشه به howdy.
زمانی که اجرای greeting () به پایان رسید تمام مقدار های اون به جز msg از بین میرن . پس فقط مقدار های hell و howdy و msg میمونن که خارج از scope بهشون امکان دسترسی وجود داره .
هنگام کار با asynchronous code مثل callback ها Closure رایج هستش . در نظر بگیرین:
function getSomeData(url) { ajax(url,function onResponse(resp){ console.log( `Response (from ${ url }): ${ resp }` ); }); } getSomeData("https://some.url/wherever"); // Response (from https://some.url/wherever): ...
عملگر داخلی onResponse روی url بسته شده است و تا زمانی که Ajax فراخوانی بشه و اجرا بشه اون رو حفظ میکنه. حتی اگه getSomeData بلافاصله تموم بشه متغیر پارامتر url رو تا زمانی که نیاز داره در closure حفظ میکنه.
به نکته ای که باید توجه کرد اینکه همیشه نیاز نیست scope بیرونی یه function باشه. چیزی که اهمیت داره اینکه در scope بیرونی حداقل یه variable وجود داره که از یک function داخلی قابل دسترس هستش. توجه کنین :
for (let [idx,btn] of buttons.entries()) { btn.addEventListener("click",function (){ console.log(`Clicked on button (${ idx })!`); }); }
چون این loop داره از let استفاده میکنه هر تکرار یه idx و btn ایجاد میکنه همچنین هربار یک تابع جدید هم ایجاد میکنه. چون مقدار idx رو در function داخلی حفظ میشه تا زمانی که عمل کلیک انجام بشه مقدار index رو پرینت میکنه.
مبحث Closure یکی از مباحث مهم در الگوهای برنامه نویسی در هر زبانی هستش به همین دلیل برای درک بهتر مفهوم در جلد 2 کتاب ( Scope & Closures) به این مبحث بیشتر پرداخته میشه و مورد بررسی قرار میگیره .
در پست بعدی درباره ادامه فصل که مباحث this
Keyword و Prototypes رو شامل میشه رو مورد بررسی قرار میدیم.