رضا فتحی
رضا فتحی
خواندن ۱۵ دقیقه·۳ سال پیش

نت برداری شخصی از کتاب You Don't Know JS

این متن نت برداری بسیار خلاصه شده ی من از کتاب YDKJS است. درباره خط به خط این متن به تفصیل در کتاب پرداخته شده. من از نوشتن جزئیات در متن پرهیز کرده ام. به علاوه به طور خاص مفاهیمی را "یادداشت" کرده ام که برایم جدید بودند.

بخش کتاب اول و دوم از ویراست دوم(YDKJSY) و باقی از ویراست یک(YDKJS) هستند.


کتاب اول - Get Started

فصل ۱ - ?What Is JavaScript

Many Faces

در جاواسکریپت شما میتوانید از الگو های مختلف استفاده کنید و JS کاملا این انعطاف پذیری(قدرتمند) در استایل‌ کُد نویسی را در اختیار شما قرار میدهد که پارادایم شیئ گرا(OOP)، فانکشنال(FP)، پروسیجرال(Procedural)، و یا ترکیبی از آنها! و یا حتی هیچکدام را استفاده کنید.

Backward & Forward

جاواسکریپت backward-compatible است و نه forward-compatible. این بدان معنی است اگر کُد جاواسکریپتبهبهبه شما امروز معتبر باشد، تا همیشه معتبر است.

Jumping the Gaps

اینکه جاواسکریپت forward-compatible نیست، یعنی فیچر های جدیدتر زبان(مانند سینتکس جدید) در بروزر های قدیمی تعریف نشده و برنامه ی "جدید"ِ جاواسکریپت شما در این محیط های قدیمی اجرا نمیشوند. برای حل این مشکل از ابزار های traspilation استفاده میکنیم.

Filling the Gaps

اگر مسئله ی اضافه شدن API های جدید به زبان(که در محیط های قدیمی نیستند) باشد، راه حل polyfill ("shim" هم گفته میشود) کردن است.


What's in an Interpretation? - Is JS an interpreted language? Or is it compiled?

اجرای خط به خط و مستقیم در زبان های تفسیری(اسکریپتی)
اجرای خط به خط و مستقیم در زبان های تفسیری(اسکریپتی)
مراحل پیش از اجرا و اجر در جاوا اسکریپ
مراحل پیش از اجرا و اجر در جاوا اسکریپ

مرحله ی parsing تنها در زبان هایی است که کامپایل میشود. جاوا اسکریپت یک compiled language است.


Strictly Speaking

مود استریکت(strict mode - ES5) در JS میتواند یا برای یک فایل و یا برای یک فانکشن ( function scope) فعال شود.
استریکت مود بسیار خوب است ازین جهت که به سمت بهتر کُد زدن "مجبور" میکند. با اینحال آنرا default mode نمیکنند زیرا کُد های قدیمتر احتمالا کار نخواهد کرد و backward-compatible رعایت نمیشود.

تقریبا هر کُد JS بعد از traspilation به strict mode درمیاید.



فصل ۲ - Surveying JS

Each File is a Program

دلیل اصلی که این موضوع اهمیت دارد به ارور هندلینگ در جاواسکریپت مربوط میشود. در JS وجود ارور در یک فایل لزوماً منجر به کار نکردن باقی بخش های پروژه نمیشودDD.
تنها راهی که تک تک js. فایل ها باهم مانند یک برنامه کار میکنند از طریق به اشتراک گذاشتن state شان توسط "global scope" است.

Functions

در جاواسکریپت فانکشن ها value هستند، و به همین دلیل هم هست که JS برنامه نویسی فانکشنال را پشتیبانی میکند.

Comparison

عملگر === در ۲ مورد کاملا درست(طبق انتظار ما) خروجی نمیدهد:

NaN === NaN; // false 0 === -0; // false

دقیق ترین راه مقایسه متود (..)Object.is است که در موارد بالا هم طبق انتظارمان خروجی میدهد.

Coercive Comparisons

عملگر های === و == اگر تایپِ value ها یکسان باشد دقیقن مانند هم عمل میکنند.

مانند == عملگرهای < ، > ، <= و >= نیز در صورت مغایرت تایپِ value ها coercion انجام میدهند.


فصل ۳ - Digging to the Roots of JS

Iteration - iterator pattern:بهبه

یک رویکرد استاندارد شده برای مصرف(consumption) داده از یک سورس به صورت چانک چانک است(one chunk at a time).


Iteratables

هر value که بتوان روی آن حلقه زد: strings, arrays, maps, sets.


Consuming Iterators(the protocol)

طبق آن یک iterator instance از یک iterable ساخته میشود، و همان instance (برای iteration) مصرف میشود.


Closure

از رفتار های مختص به function ها است.


Prototype

در طبیعت object هاست.


this Keyword

در جاواسکریپت اینکه funciton ها به چی چیز دسترسی دارند به scope آنها مربوط میشود (function's scope). اما یک ویژگی دیگر در function های جاواسکریپت هست که این دسترسی را تعیین میکند و که execution context آن است(function's execution context). Execution context یک فانکشن از طریق this آن در اختیارش قرار میگیرد.

اسکوپ(scope) ثابت(static) است.
ولی execution context داینامیک(dynamic) بوده و این بستگی دارد که function چگونه call شده است. this در "هربار" فراخوانی فانکشن(از نو) تعیین میشود.



فصل ۴ - The Bigger Picture

این فصل توضیحی درباره ی کتاب های بعدی است.




کتاب دوم - Scope & Closure

فصل ۱ - ?What's the Scope

اسکوپ در جاواسکریپت اشاره به current context of code دارد. مجموعه ی مشخصی از قوانین که دسترسی به متغیر ها را تعیین میکنند.

Compiled vs. Interpreted

کامپایلیشنِ کُد مجموعه مراحلی است که متن کُد را پردازش کرده و دستورالعمل قابل فهمی برای کامپیوتر از آن میسازد.

Compiling Code

در تئوری کلاسیک کامپایلر، یک برنامه توسط کامپایلر در ۳ مرحله پردازش میشود:

  1. توکنایزینگ یا نشانه گذاری کردن (Tokenizing/Lexing): شکستن رشته کاراکتر ها به تکه(chunk)های معنا دار که آنها را توکن(token) مینامیم. برای مثال var a = 2 به 2 و = و a و var توکنایز میشود.
  2. تجزیه کردن (Parsing): از آرایه ای از token ها AST میسازد(Abstract Syntax Tree).
  3. تولید کُد (Code Generation): تبدیل AST به کُد قابل اجرا.

Required: Two Phases

به ساده ترین بیان پردازش یک برنامه ی جاواسکریپتی در دو فاز انجام میشود ۱- parsing/compilation و ۲- execution.
رفتار compile-then-execute در جاواسکریپت را میتوان از ویژگی های syntax errors و early errors و hoisting در آن نتیجه گرفت.

Lexical Scope

در زمان کامپایل مشخص میشود... ایده ی اصلی این است که lexical scope تماماً توسط مکان declaration ِ function ها، block ها و variable ها نسبت به هم کنترل میشود.
اسکوپ ها در زمان کامپایل تعریف میشوند اما در زمان اجرا(هربار که نیاز به اجرای یک اسکوپ باشد) ساخته میشوند.


فصل ۲ - Illustrating Lexical Scope

  • هر متغیر در اسکوپ مشخصی تعریف میشود.
  • هر رفرنس به متغیری در اسکوپی که آن متغیر declare شده است ظاهر میشود و یا اینکه میتواند در اسکوپ های nested داخل آن ظاهر شود.
  • اسکوپ variable ها در زمان کامپایل تعریف میشود و به این طریق اطلاعات مربوط به آن(برای lookup کردن متغیر) در زمان اجرا آماده ی استفاده است.

A Conversation Among Friends

Engine(انجین), Compiler(کامپایلر), Scope Manger(اسکوپ منجر):

  • انجین مسئول صفر تا صد compilation و execution است.
  • کامپایلر parsing و code-generation را هندل میکند.
  • کار اسکوپ منجر جمع کردن و نگه داری یک lookup list از declared identifiers ها و همینطور اعمال کردن مجموعه ی قوانینی رویشان که دسترسی(accessibility) به آنها را مشخص میکند.


فصل ۳ - The Scope Chain

“Lookup” Is (Mostly) Conceptual

همانطور که در فصل قبل گفته شد اطلاعات مربوط به scope (برای variable lookups) در زمان اجرا در دسترسِ Engine هست و این مزیت اصلی lexical scope برای بهینه سازی در performance محسوب میشود.

Global Unshadowing Trick

کار خوبی نیست و نباید متغیر گلوبالی که به آن احتیاج داریم را shadow کنیم که نیاز به unshadow کردنش داشته باشیم. زیرا این کار میتواند درک کُدمان را سخت تر کند.

Illegal Shadowing

به طور خلاصه، let همیشه میتواند اسکوپ خارجی(block یا function) را shadow کند، ولی var تنها میتواند اسکوپ خارجی را shadow کند اگر var درون فانکشن(function-scoped only) باشد.(درواقع یک function
boundary بین اسکوپ var و اسکوپ خارجی آن وجود داشته باشد)

Backing Out

با تعریف هر فانکشن یک اسکوپ جدید بوجود میاید. موضع گیری تودرتوی(nested) اسکوپ ها یک سلسله(hierarchy) از آنها در سراسر برنامه بوجود میاورد که به آن scope chain گفته میشود. scope chain دسترسی به متغیر ها را با جهت گیری ای به سمت بالا و بیرون(به سمت خارج اسکوپ) کنترل میکند.


فصل ۴ - Around the Global Scope

Why Global Scope?

در کتاب اول فصل دوم گفته شد که تنها راهی که تک تک js. فایل ها باهم مانند یک برنامه کار میکنند از طریق به اشتراک گذاشتن state شان توسط "global scope" است. درواقع ۳ راه ِ اصلی برای شکل گیری یک برنامه از این تک فایل ها وجود دارد:

  1. مستقیماً استفاده از ES modules. ماجول ها توسط سیستم import/export (رفرنس به ماجول های دیگر) همکاری میکنند.
  2. استفاده از یک bundler در build process که همه ی فایل هارا concatenate میکند.
  3. در غیر اینصورت global scope تنها راه است.


Where Exactly is this Global Scope?

Browser “Window”, DOM Globals, Web Workers(global obj reference using self keyword), Developer Tools Console/REPL, ES Modules (ESM), Node, & Global This.

در باره ی...

ES Module (ESM):

رفتار اسکوپ بیرونی فایل(top-level scope) را تغیر داده است. ESM اصرار به کمینه کردن درگیری با global scope دارد. در اجرای یک module هر ماجول دیگری که احتیاج باشد از طریق گلوبال اسکوپ import میشود. به همین دلیل کمتر دیده میشود که مستقیماً از global scope یا global object استفاده شود.

Node:

با همه ی js. فایل ها مانند module برخورد میکند(ES module یا CommonJS module). تاثیر کاربردی این مسئله آن است که در top level ِ برنامه ی node ِ شما، هیچگاه global scope قرار ندارد.

نود، یک ماجول(فایل) را چیزی شبیه به کُد زیر میبیند. انگار محتوای فایل درون یک wrapper function قرار گیرد:

function Module(module,require,__dirname,...) { // actual file content... }

برای استفاده ی مستقیم از global object در نود، از کلمه(keyword) global استفاده میکنیم.

Global This:

یک trick دیگر برای گرفتن رفرنس به گلوبال آبجکت استفاده از new Function است. مانند کُد زیر:

const theGlobalScopeObject = (new Function(&quotreturn this&quot))();


تا اینجا دیدیم برای دسترسی به آبجکت گلوبال از window یا self یا global و یا new Function استفاده میکنیم. اما راه دیگری وجود دارد که درواقع راهِ(بالاخره) استاندارد شده رفرنس داشتن به global scope obj است. این رفرنس در ES2020 تعریف شد: globalThis.


فصل ۵ - The (Not So) Secret Lifecycle of Variables

When Can I Use a Variable?

هر identifier ای در ابتدای اسکوپی که در آن تعریف شده است با هربار ورود به اسکوپ ساخته میشود(hoisting). hoisting خود درون function scope یا global scope اتفاق میافتد(هم variable hoisting و هم function hoisting به این گونه هستند).

  1. "بالابردن" فانکشن ها(function hoisting): نام تابع در ابتدای اسکوپ( ِدر بردارنده ی آن) تعریف میشود و با مقدار "reference به آن تابع" initialize میشود. به این طریق در سرتاسر scope قابل فراخوانی است.
  2. "بالابردن" متغیر ها(variable hoisting):
    - var: همه ی identifier های تعریف با آن در ابتدای اسکوپ تعریف شده با undefined به عنوان مقدار
    اولیه initialize میشوند. در سرتاسر اسکوپ قابل دسترسی اند.
    - let یا const: مانند var "بالا" برده میشوند اما به مقداری initialize نمیشوند. و درواقع تا زمانی که execution دقیقن به خطی که متغیر توسط let یا const تعریف شده است نرسد، نمیتوان آنرا خواند یا مقدار دهی کرد(به این وقفه که در استفاده از let و const وجود دارد Temporal Dead Zone یا TDZ گفته میشود).

Hoisting: Declaration vs. Expression

تنها در formal function declaration است که function hoisting صدق میکند. برای مثال به کُد زیر توجه کنید:

greeting(); // TypeError var greeting = function greeting() { console.log(&quotHello!&quot); };

در اینجا درواقع variable hoisting است که رخ میدهد و نه function hoisting.

Re-declaration?

به طور کلی در یک اسکوپ var ها(متغیر های تعریف شده با var) re-declare نمیشوند(تمام declaration ها hoist میشوند). و re-declare کردن let و const سینتکس ارور میدهد.

loops

تمام قوانین scope ها بر هر scope instance اعمال میشوند. با هر ورود به اسکوپ همه چیز reset میشود.
هر iteration از حلقه یک scope instance جدید است.


فصل ۶ - Limiting Scope Exposure

در مهندسی نرمافزار قانون بنیادینPOLP ــــمعمولاً در زمینه ی امنیت نرم افزار("The Principle of Least Privilege")ــــ بیان میکند که:

اجزای یک سیستم باید با کمترین امتیاز(least privilege)، کمترین دسترسی(least access)، و کمترین در معرض گذاری(least exposure) کار کنند.

ازین قاعده قانونِ دیگری مشتق میشود. POLE که همان "The Principle of Least Exposure" است که بر function/variable scoping اعمال میشود. به بیان ساده میگوید:

همه چیز را به خصوصی ترین حالت ممکن(as private as possible) در بیاورید.


فصل ۷ - Using Closures

کلوژر مشخصاً از ویژگی های function هاست. کلوژر درواقع "وصل بودن"(live link) به متغیر هاییست که function به آنها به هرنحوی انتیاج دارد. این live link به این معناست که متغیر ها میتوانند توسط فانکشنی که روی آنها closure دارد خوانده ویا حتی نوشته(update) شوند.
به بیان دیگر closure یعنی که تابع هرچیزی را که لازم دارد(چه در اسکوپ خودش یا در اسکوپ بیرونیش) به یاد داشته باشد و بتواند در هر شاخه ای از scope chain (حتی خیلی دور از جایی که فانکشن تعریف شده!) آنهارا بخواند یا آپدیت کند.

اگر همه ی live link هایی که به یک متغیر که در closure تابع یا توابعی قرار دارد از بین بروند(دیگر هیچکدام از توابع به آن احتیاج نداشه باشند)، آنگاه garbage collect میشود.

کلوژر per variable انجام میشود.


فصل ۸ - The Module Pattern

Encapsulation and Least Exposure (POLE)

اینکپسولیشن از قوانین بسیار مهم برای سازماندهی کُد است(معمولاً برای OOP اما گستردگی آن از OOP عبورمیکند). ایده کاهش دسترسی با هرچه بیشتر private کردن اجزا است. هرچه private نشود public خواهد شد.

What Is a Module?

ماژول کالکشنی از data و function های مرتبط است که در آن جزئیاتی private و جزئیاتی public هستند(public API).

  1. باید data وجود داشته باشد وگرنه ماژول نیست(تنها method برای ماژول بودن کافی نیست).
  2. باید بخش private وجود داشته باشد وگرنه ماژول نیست.
  3. باید public API وجود داشته باشد وگرنه ماژول نیست.(این public API به حداقل یک تابع رفرنس دارد که روی (حداقل) یک بخشِ private از ماژول closure دارد)


Node CommonJS Modules

فایل-based هستند، یک ماژول برای هر فایل.

برای default export کردن در آن پیشنهاد YDKJSY این است که به جای

// don't do this (better not) module.exports = { // ..exports.. };

از

// better approach Object.assign(module.exports,{ // ..exports.. });

استفاده کنیم. (توضیحات در فصل ۸ بخش Node CommonJS Modules)

ماژول های CommonJS مانند IIFE ها singleton هستند.


Modern ES Modules (ESM)

بسیار مشابه CommonJS است (file-based, singleton, everything-private-by-default)... اما تفاوت مهمی که داردند، ESM به طور پیشفرض در strict-mode است و به هیچ طریقی نمیتوان آنرا non-strict-mode کرد.




کتاب سوم - this & Object Prototype

فصل ۱ - ?this Or That

Confusions

معمولاً دو معنا از this برداشت میشود که هردو غلط اند:

  1. خودش(itself): یعنی this اشاره به خود فانشکن دارد.(function itself) ×
  2. اسکوپش(it's scope): اشاره به اسکوپِ فانشکن دارد. (function's scope) ×
    (در هردو مورد منظور از فانکشن، فانشکنی است که this در آن بکار رفته)

توجه کنیم که به هیچ عنوان نمیتوان از this برای دسترسی به چیزی که درون lexical scope هست استفاده کرد.

What's this?

بایند شدن this در موقع کامپایل اتفاق نمیافتد بلکه بایند شدنش یک runtime binding است. this binding یک فانشکن، هیچ ربطی به این ندارد که فانشکن کجا declare شده باشد بلکه تماماً مربوط میشود به اینکه فانشکن چطور call شده است که به call-site ِ آن برمیگردد(فصل بعد..).


فصل ۲ - !this All Makes Sense Now

Call-site

برای اینکه ببنیم this به کجا رفرنس میدهد call-stack را دنبال کنیم. call-site ِ مد نظر ما به آخرین فانکشن(در انتهای) call-stack مربوط میشود، دقیقن قبل از call شدنش. در واقع call-site تنها جایی است که در this binding اهمیت دارد.


Nothing But Rules

  1. Default Binding
  2. Implicit Binding
  3. Explicit Binding - Hard Binding
  4. new Binding

دو حالت اول به دلایلی میتوانند شدیدن گیج کننده باشند و طوری خلاف انتظارمان رفتار کنند(درواقع به نحوی call-site جایی باشد که نمیخواهیم - برای مثال در دیفالت بایندینگ global obj را this شود(مثلاً اگر call-site در strict-mode باشد this ِدیفالت undefined است) یا در implicit بایندینگ call-site را در حین assignment های بعدی یا پاس دادن callback فانکشنها گم کنیم).

بایند کردن اجباری(Explicit) یک آبجکت به عنوان this؛ توابع (..)call و (..)apply. مثال:

function myFunction() { console.log( this.a ); } var obj = { a: 2 }; myFunction.call( obj ); // obj is forced to be 'this' for our function call

اما این حالت هم کاملاً مشکل را حل نمیکند و this binding میتواند دوباره گم شود.

راه حل Hard binding است. هارد بایندینگ به طور کلی اینطور کار میکند که function call به همراه this binding ِ آن درون یک "تابع دیگر" قرار میگیرد(wrap میشود - که درواقع "تابع دیگر" روی this object کلوژِر دارد). سپس هرگاه این wrapper function(همان "تابع دیگر") را فراخوانی کنیم، call-site درون آن خواهد بود.
Hard binding بسیار رایج شد و در ES5 تابع (..)bind مخصوصن به همین منظور تعریف شد.

اگر فانکشنی را با new فراخوانی کنیم، ابتدا یک object جدید ساخته میشود(Prototype-linked است)، سپس همان آبجکت به عنوان this binding استفاده میشود، نهایتا همان آبجکت(یا آبجکتی که تابعِ کال شده برمیگرداند) برگردانده میشود.

Everything In Order

اولویت: با new سپس explicit بعد implicit نهایتا default است.


Lexical this - (arrow function & self = this)

function foo() { return (a) => { // `this` here is lexically adopted from `foo()` console.log( this.a ); }; } var myDesiredThisObj = { a: 2 }; var someOtherObj = { a: 3 }; var bar = foo.call( myDesiredThisObj ); // <------------ *** bar.call( someOtherObj ); // 2, not 3!

در ***‌ foo را فراخوانی کرده و object ای که میخواهیم به عنوان this بایند کنیم به آن پاس میدهیم و خروجی فانکشنی خواهد بود بایند شده به this ِ مورد نظره ما. arrow function جایگزینی برای استفاده از (..)bind است که درواقع مکانیزم this را خنثی و به lexical scope تکیه میکند.

و در مورد self = this:

function foo() { var self = this; // <-------- lexical capture of `this` setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2


فصل ۳ - Objects

Duplicating Objects

برای deep کپی کردن object ها در جاواسکریپت اگر آبجکت JSON-safe باشد(serializable به یک JSON string و دوباره deserializable به یک آبجکت با همان ساختار و مقدار ها) یک روش استاندارد این است:

var newObj = JSON.parse( JSON.stringify( someObj ) );

برای shallow کپی کردن object ها تابع (..)Object.assign در ES6 تعریف شد:

var newObj = Object.assign( {}, myObject );


Property Descriptors

(..)Object.getOwnPropertyDescriptor و (..)Object.defineProperty

پراپرتی های یک "پراپرتی دیسکریپتور":

  • value
  • writable
  • configurable
  • enumerable


Immutability

  • Object Constant: Object.defineProperty(..) --> writable:false & configurable:false
  • Prevent Extensions: Object.preventExtensions(..)
  • Seal: Object.seal(..)
  • Freeze: Object.freeze(..)


فصل ۴ - Mixing (Up) "Class" Objects

JavaScript "Classes"

در جاواسکریپت برای پشتیبانی از OOP و class oriented software design مفهموم کلاس fake شده است.

Class Mechanics - Building

کلاس یک طرح و دستورالعمل است. برای اینکه یک object داشته باشیم(با ویژگی های مشخص شده در این 'طرح') باید از آن instance بگیریم. این instance یک آبجکت است که تمام ویژگی های کلاس در آن "کپی" شده.

Mixins

در جاواسکریپت هنگام instantiation و inherit کردن، مکانیزم آبجکت ها به طور اتوماتیک "کپی " انجام نمیدهد. اصلا در درواقع کلاسی در جاواسکریپت نیست که ازش instance بگیریم، تنها آبجکت های به هم لینک شده داریم. برای fake کردن "کپی" در جاواسکریپت، از mixin استفاده میشود:

  • Explicit Mixins
  • Implicit Mixins

مثال بسیار ساده شده از explicit mixin (دیگر اثری از class نیست):

// vastly simplified `mixin(..)` example: function mixin( sourceObj, targetObj ) { for (var key in sourceObj) { // only copy if not already present if (!(key in targetObj)) { targetObj[key] = sourceObj[key]; } } return targetObj; }

در اینجا پراپرتی های یک آبجکت explicitly کپی میشوند(shared reference (برای آبجکت ها و فانکشن هایشان) همچنان بین sourceObj و targetObj وجود دارد). explicit mixin را تنها زمانی بکار ببریم که readability کُد را افزایش میدهد و از آن بپرهیزیم زمانی که وابستگی غیر ضروری بین object ها وجود میاورد و درک کُد را سخت میکند.

مثال implicit mixins(شبیه و مرتبط با explicit pseudo-polymorphism):

var Something = { cool: function() { this.greeting = &quotHello World&quot this.count = this.count ? this.count + 1 : 1; } }; var Another = { cool: function() { // --------> implicit mixin of `Something` to `Another` <-------- Something.cool.call( this ); } };

روش implicit mixin شکننده است و معمولاً منجر به کُدِ "سخت تر قابل فهم" میشود.


فصل ۵ - Prototypes

[[Prototype]]

یک property ِ اینترنال در آبجکت های JS است، که صرفا یک reference است به یک آبجکت دیگر. تقریبا به prototype ِ همه ی آبجکت ها مقداری غیر-null در زمان ساخته شدنشان داده میشود.

میتواند از تابع (..)Object.create برای prototype linked کردن دو آبجکت استفاده کرد:

var anotherObject = { a: 2 }; // create an object linked to `anotherObject` var myObject = Object.create( anotherObject ); myObject.a; // 2


Object.prototype

هر prototype chain ِ نرمالی، به built-in آبجکت ِ Object.prototype ختم میشود.


"Class"

در هنگام instantiate کردن کلاس ها در JS کار به اینجا ختم میشود که ۲ آبجکت داریم(یکی خودِ‌ کلاس و دیگری اینستنسِ آن) که به هم link شده اند...

وقتی حرف از class میشود، جاواسکریپ فقط object دارد.

Constructor Or Call?

تابع constructor در پروتوتایپِ فانکشن ها وجود دارد(func.prototype.constructor). اگر یک تابع را به همراه new فراخوانی کنیم، آن تابع به عنوان constructor کال میشود(کانستراکتورش کال میشود) و در نتیجه یک object جدید برمیگرداند. میتوان گفت در جاواسکریپ constructor، هر تابعی است که با new فراخوانی شده باشد.


Constructor Redux

به طور کلی بهتر است در کُدمان به constructor-reference زیاد تکیه نکنیم زیرا در شرایطی ممکن است (با تغییری در prototype) به جایی point کند که فانشکن مورد نظرمان نیست.


"(Prototypal) Inheritance"

در ES6 تابع(helper) کمکی (..)Object.setPrototypeOf به عنوان روش استاندارد (و قابل پیشبینی) برای لینک کردن object به زبان اضافه شد.

Object.setPrototypeOf( Bar.prototype, Foo.prototype );

*‌ رایج ترین راه برای لینک کردن دو آبجکت به یکدیگر استفاده از new است.


Inspecting "Class" Relationships

anObject instanceof someFunction; // true/false

سوالی که instanceof جواب میدهد این است: آیا در سرتاسر زنجیره ی [[Prototype]] ِ anObject، اثری از someFunction.prototype هست؟

بهتر است با [[Prototype]] برای خوانایی بهترِ کُد، read-only برخورد کنیم.


فصل ۶ - Behavior Delegation

ساختار class oriented را میتوانیم با behavior delegation جاگزین کنیم که در آن به جای درگیر شدن با سلسله مراتب class ها و ساختار شدیدن وابسته ی آنها، میتوانیم "رفتار" را بین دو object به اشتراک بگذاریم. در behavior delegation دو آبجکت با هم جفت میشوند و رفتارهایی را به اشتراک میگذارند.

- در class oriented رابطه مادر و فرزندی
- در behavior delegation رابطه جفت بودن

طبیعتِ [[Prototype]] است که behavior delegation کند و نه اینکه در class hierarchy قرار بگیرد.

استفاده از ـــتنهاــــ object ها در طراحیِ کُدمان منجر به سینتکسِ ساده تر میشود و همینطور میتواند باعث معماری ساده تری نیز شود.

کُد استایلِ OLOO(objects-linked-to-other-objects) مستقیماً object ها را میسازد و مستقیماً به یکدیگر متصل میکند بدون نیاز به class. این استایل بر اساس behavior delegation است.






کتاب چهارم - Types & Grammar

فصل ۱ - Types

متغیر ها تایپ ندارند. مقادیر(value) درونشان تایپ دارند.

فصل ۲ - Values

Arrays

در مقایسه با زبان های statically-typed، آرایه ی جاواسکریپت فقط یک ظرف برای نگه داشتن مقادیری با هر type ای هستند. یک کلکسیونِ index شده از مقادیر مختلف با هر تایپی.

Number

در جاواسکریپت تنها یک مقدار عددی وجود دارد: number. این تایپ هم شامل integer و هم floating point است. همچنین مقادیر خاصی مانند NaN(بهتر است "invalid number" بشناسیمش تا "not a number")، Infinity، -Infinity+ و 0- را نیز شامل میشود.

فصل ۳ - Natives

Boxing Wrappers

خود جاواسکریپت مقادیر primitive (مانند "some string") را در wrapper object مربوطش بسته بندی میکند و به این نحو به متود ها و پراپرتی های آن دسترسی خواهیم داشت(length. و ()toUpperCase. و ...).

Unboxing

برای خارج کردن مقدار primitive از wrapper object ِ آن، میتوانیم از متود ()valueOf و یا implicit unboxing استفاده کنیم:

var a = new String( &quotabc&quot ); a.valueOf(); // &quotabc&quot --> valueOf() method // OR a + &quot&quot // &quotabc&quot --> implicit unboxing


(نکته: به طور کلی بهتر است از فُرمِ constructor برای ساختن native ها استفاده نکنیم، مگر در مورد Error و Date)

فصل ۴ - Coercion

Converting Values

جاواسکریپت coercion همیشه به یکی از مقادیر scalar primitives(مانند number، string، boolean) ختم میشود. هیچگاه چیزی به object یا function یا array که مقادیر complex هستند coerce نمیشود.

Abstract Value Operations:

ToString

مقادیر built-in primitive به طور عادی استریگ میشوند.

null => “null”

true => “true”

undefined => “undefined”

اعداد نیز همینطور استرینگ میشوند ولی اعداد بسیار بزرگ وبسیار کوچک در حالت توانی استرینگ میشوند.

JSON Stringification

برای serialize کردن یک مقدار به یک string (که JSON-compatible) باید آن مقدار JSON-safe باشد. مواردی که JSON-safe نیستند: undefined، ×function، ×object× و symbol×.

تابع (..)JSON.stringify اگر با مقادیرِ function, undefined یا symbol برخورد کند آنها را نادیده میگیرد. اگر این مقادیر درون یک array باشند به جای آنها null میگذارد(که اندیس حفظ شود). اگر به عنوان یک پراپرتی از آبجکتی باشند، مقدار حاصل execute شدنشان را قرار میدهد. مثال:

JSON.stringify( undefined ); // undefined JSON.stringify( function(){} ); // undefined JSON.stringify( [1,undefined,function(){},4] ); // &quot[1,null,null,4]&quot JSON.stringify( { a:2, b:function(){} } ); // &quot{&quota&quot:2}&quot

برای JSON Stringification ِیک مقدار که JSON-safe نیست ابتدا باید (..)toJSON آن را اجرا کنیم(اگر این متود برای آبجکتمان وجود ندارد آنرا میسازیم). (..)toJSON مقدار را به مقداری قابل سریالایز شدن تبدیل میکند. سپس (..)JSON.stringify اش میکنیم.


ToNumber

true -> 1 , false -> 0 , undefined -> NaN , null -> 0 , “” -> 0 , [] -> 0, …


ToBoolean - Falsy & Truthy

به جز "" همه ی string ها truthy هستند.

لیست falsy ها: undefined ، null ، false ، +0 ، -0 ، NaN و "".


تمام object ها طبق اسپسیفیکیشنِ جاواسکریپت truthy هستند. مگر در موارد خاصی که مقادیرِ object مانندی که توسط browser ها تولید شده اند(مانند document.all) که falsy-object میباشند.


Sanity Check

استفاده ی درست از coercion میتواند به تمیز تر شدن کُدمان کمک کند و اگر آنرا بشناسیم.
(در کتاب جزئیات بسیاری از موارد گمراه کننده و عجیب درcoercion و توضیحِ اینکه دقیقاً دارد چه اتفاقی میافتد آمده که از گفتنشان صرف نظر کردم)

لیستی از استفاده ی بد از coercion(اگر ازین موارد بپرهیزیم در استفاده از coercion احتمالاً به هیچ مشکلی نخواهیم خورد):

&quot0&quot == false; // true false == 0; // true false == &quot&quot // true false == []; // true &quot&quot == 0; // true &quot&quot == []; // true 0 == []; // true

قطعاً از == false بپرهیزید.

Safely Using Implicit Coercion

به طور کلی اگر جوابتان به این دو سوال منفی بود، میتواند موقعیت خوبی برای استفاده از == (درواقع استفاده از coercion) باشد.

  1. آیا ممکن از یکی از دو طرف مقایسه true یا false باشند؟
  2. آیا ممکن است یکی از دو طرف مقایسه [] یا "" یا 0 باشد؟

(در هر یک از موارد اگر جواب بله است، از == استفاده نکنید)


*توجه: Explicit Coercion کُدی است که به وضوح یک value به type ِ مشخصی(از هر تایپی که داشته) درمی آید. برای Explicit Coercion به طور کلی میتوانیم از توابع constructor ِمقادیر native ِ زبان استفاده کنیم. مانند (someValue)Boolean.


فصل ۶ - Grammar

Statement Completion Values

یک حقیقت درباره ی گرامر JS این است که تمامی statement ها "ارزش انتهایی(completion value)" دارند. (حتی اگر این value فقط undefined باشد)

راه ساده برای دیدن این CV ها این است که در دِولوپر کنسولِ یک بروزر statement ای وارد کنیم، و completion value ی آن برگردانده میشود.

labels

در جاواسکریپت میتوان از continue و break مانند goto استفاده کرد. به طوری که در جلوی آنها برچسبی که میخواهیم به آن پرش کنیم را بگذاریم. مانند کُد زیر:

// `foo` labeled-loop foo: for (var i=0; i<4; i++) { for (var j=0; j<4; j++) { if (j == i) { // jump to the next iteration of // the `foo` labeled-loop continue foo; } } }


else if and optional blocks

در گرامر جاواسکریپت else if وجود ندارد. یعنی اگر بنویسیم:

if (a) { // .. } else if (b) { // <-- چیزیکه فکر میکنید نیست(احتمالاً) // .. } else { // .. }

در واقع نوشته ایم:

if (a) { // .. } else { // <-- چیزیکه در واقع اتفاق میافتد if (b) { // .. } else { // .. } }


Tighter Binding

اولویت && از || و || از : ? بیشتر است.

ASI (Automatic Semicolon Insertion)

یک مکانیزم built-in در جاواسکریپت برای اصلاح ارور های هنگام parse کردن است(parser-error-correction) که در شرایط(و در مکانهای) مشخصی ; قرار میدهد؛: ? وقتی که ; قطعا لازم باشد، وقتی ; (از قلم!)افتاده باشد، و وقتی که قرار دادن ; ارورِ پارسِر را فیکس میکند.


try..finally

اگر درون بلاک finally از return استفاده شود، return های قبلی override میشوند.




کتاب پنجم - Async & Performance

فصل ۱ - Asynchrony: Now & Later

به طور کلی قرار است فاصله زمانی(gap ِ انتظار) بین اجرای دو قسمت از برنامه که یکی now و دیگری later انجام خواهد شد مدیریت شود.

A Program in Chunks - Event Loop

یک برنامه ی جاواسکریپت قطعه قطعه(chunk chunk) است. تنها یک قطعه now و باقی later انجام میشوند. در واقع one chunk at a time انجام می شوند. بعضی ازین قطعه ها که قرار است engine را معتل خودشان کنند در قالب function هایی که callback functions مینامیم در یک صف به اسم event loop چیشده(schedule) میشوند و engine به ادامه ی اجرا(اگر کاری برای انجام دادن داشته باشد) مشغول میشود.

*در JS هیچ دو کاری موازی انجام نمیشوند(فقط پشتسر هم..).

Jobs

لایه ای دیگر روی event queue در ES6 تعریف شد به اسم job queue. این job ها مانند event ها later انجام میشوند با این تفاوت که تضمین میشود آنها later ولی as soon as possible انجام شوند.

Concurrency

در JS همزمانی به این معناست که چند event chain خود را در event queue طوری جا کنند که انگار "همزمان" اجرا میشوند.


فصل ۲ - Callbacks

بنیادی ترین الگوی async در جاواسکریپ callback ها هستند. ولی callback ها با توجه به پیچیده شدن کاربردِ JS کافی نیستند و اصلی ترین مشکلمان با آنها وقتی است که callback hell بوجود بیاید(جزئیات نسبتاً زیادی در کتاب توضیح داده شده). روش هایی برای جلوگیری از callback hell مانند Promise ها (ES6) و الگوی Node style(همان error-first-style) معرفی شده اند.

*یک نکته ی بسیار مهم این ست که: همیشه callback هارا به صورت async بخوانیم(تحت هر شرایطی!). میتوان (به طور غیر به صرفه ای) تابعِ (..)asyncify ای ساخت تا مطمئن شویم آنها را async فرامیخوانیم.

Trust Issues

زمانی که یک callback function را پاس میدهیم(معمولا به یک third party utility) گاهی ممکن است یکی از حالات زیر رخ دهد:

  • فانکشن خیلی زود call شود.
  • فانکشن خیلی دیر(یا هرگز) call شود.
  • فانکشن تعداد دفعات زیاد یا کم call شود.
  • موفق با pass شدن از طریق environment یا parameter های لازم نشود.
  • ارور ها(یا اکسپشن ها) خورده شوند(دریافتشان نکنیم).

فصل ۳ - Promises

مشکلاتی که در قسمت Trust Issues ِ بخش قبل گفته شد را حل میکنند(در بخش Promise Trust ِ فصل ۳ توضیح داده شده که چطور). callback ها همچنان وجود دارند اما مکانیزم promise طوری است که مشکلات(inversion of control) استفاده ی مستقیم از callback ها را برطرف میکند.

درواقع یک promise "قولی" است مبنی بر اینکه در آینده مقداری دریافت میکنیم یا از ناموفق بودن عملیات خبر داده میشویم. قولی برای یک future value. این مقدار(بالاخره) به دستمان میرسد و به زمان بستگی ندارد.

مقداری که از یک promise میگیریم(هرموقه که resolve شود) immutable بوده و هرزمان میتوانیم دوباره از آن استفاده کنیم.

مقداری که به (..)Promise.resolve پاس میدهیم unwrap میشود و یک promise ِ نُرمال از آن برگردانده میشود(اگر یک promise به آن بدهیم خوده همان promise را برمیگرداند).

هندل کردن Error در promise ها به دقت بیشتری احتیاج دارد ممکن است به راحتی اروری از دستمان در برود(uncaught). (بخشِ Pit of Despair و Error Handling و Uncaught Handling همین فصل)

همچنین با promise ها کد زدن async ِ مان زنجیروار میشود(step های async که پشت سر هم میایند) و به درک و maintain کردن کُد کمک میکند.

نکته ی دیگر اینکه promise ها قابل cancel کردن نیستند.


فصل ۴ - Generators

Breaking Run-to-Completion

جنریتور های یک نوع function اند که برعکسِ توابع عادی، به صورت run to completion رفتار نمیکنند. و (به طرز عجیبی!) میتوانند در یک نقطه pause شوند، state ِ خود را به یاد داشته باشند، و بعد هرگاه بخواهیم از همان نقطه ی توقف دوباره resume کنند!

Input and Output

توسط کلمه ی کلیدی yield متوقف شده و یک output برمیگداند. و از بیرون توسط (some_input)next. ورودی بگیرد و ادامه پیدا کند. (درواقع یک تابع با ورودی و خروجی(دوطرفه) به تعداد دلخواه)

---

مزیت اصلی generator این است که به کنترل جریان async مان کمک میکند و شکلی synchronous به آن میدهد. درواقع مشکل "درک کردنِ پیچیدگیِ" callback ها را تا حدی حل میکند.


فصل ۵ - Program Performance

به مسائل بهینه سازی و پرفورمنس در کنترل و جابه جایی داده میپردازد و Web Worker ها، SIMD، و همینطور asm.js(به عنوان ابزاری برای آپتیمیزیشن برنامه ی JS مان[http://asmjs.org]) معرفی میکند.


فصل ۶ - Benchmarking & Tuning

به جزئیات بنچمارکینگ و مقایسه ی پرفورمنس کُد و اینکه چطور اصلاً باید به موضوع نگاه کنیم میپردازد و همچنین اشاره ای به ریزه کاری های محاسباتی بنچمارکینگ میکند.
همینطور که بهتر است به جای استفاده از logic ِ احتمالاً اشتباه خودمان(مگر واقعا اینکاره باشیم!) از Benchmark.js استفاده کنیم[https://benchmarkjs.com].

یک نکته ی قابل توجه: وسواسی که بسیاری از برنامه نویس ها در باره ی بهینه سازی کدشان و "سریع" تر کردن آن با اهمیت دادن به جزئیات microperformance (مانند اینکه "آیا a++ سریع تر است یا ++a ؟")، وسواسِ بسیار اشتباهی است و معمولاً فقط منجر به اتلاف زمان و پیچیده شدن کُد برای خواننده های بعدی(یا حتی خودشان) میشود.




کتاب ششم - ES6 & Beyond

اضافه میشود...

جاوا اسکریپتjsyou don t know js yetsummaryydkjs
شاید از این پست‌ها خوشتان بیاید