این متن نت برداری بسیار خلاصه شده ی من از کتاب YDKJS است. درباره خط به خط این متن به تفصیل در کتاب پرداخته شده. من از نوشتن جزئیات در متن پرهیز کرده ام. به علاوه به طور خاص مفاهیمی را "یادداشت" کرده ام که برایم جدید بودند.
بخش کتاب اول و دوم از ویراست دوم(YDKJSY) و باقی از ویراست یک(YDKJS) هستند.
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 درمیاید.
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 انجام میدهند.
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 در "هربار" فراخوانی فانکشن(از نو) تعیین میشود.
این فصل توضیحی درباره ی کتاب های بعدی است.
اسکوپ در جاواسکریپت اشاره به current context of code دارد. مجموعه ی مشخصی از قوانین که دسترسی به متغیر ها را تعیین میکنند.
Compiled vs. Interpreted
کامپایلیشنِ کُد مجموعه مراحلی است که متن کُد را پردازش کرده و دستورالعمل قابل فهمی برای کامپیوتر از آن میسازد.
Compiling Code
در تئوری کلاسیک کامپایلر، یک برنامه توسط کامپایلر در ۳ مرحله پردازش میشود:
Required: Two Phases
به ساده ترین بیان پردازش یک برنامه ی جاواسکریپتی در دو فاز انجام میشود ۱- parsing/compilation و ۲- execution.
رفتار compile-then-execute در جاواسکریپت را میتوان از ویژگی های syntax errors و early errors و hoisting در آن نتیجه گرفت.
Lexical Scope
در زمان کامپایل مشخص میشود... ایده ی اصلی این است که lexical scope تماماً توسط مکان declaration ِ function ها، block ها و variable ها نسبت به هم کنترل میشود.
اسکوپ ها در زمان کامپایل تعریف میشوند اما در زمان اجرا(هربار که نیاز به اجرای یک اسکوپ باشد) ساخته میشوند.
A Conversation Among Friends
Engine(انجین), Compiler(کامپایلر), Scope Manger(اسکوپ منجر):
“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 دسترسی به متغیر ها را با جهت گیری ای به سمت بالا و بیرون(به سمت خارج اسکوپ) کنترل میکند.
Why Global Scope?
در کتاب اول فصل دوم گفته شد که تنها راهی که تک تک js. فایل ها باهم مانند یک برنامه کار میکنند از طریق به اشتراک گذاشتن state شان توسط "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("return this"))();
تا اینجا دیدیم برای دسترسی به آبجکت گلوبال از window یا self یا global و یا new Function استفاده میکنیم. اما راه دیگری وجود دارد که درواقع راهِ(بالاخره) استاندارد شده رفرنس داشتن به global scope obj است. این رفرنس در ES2020 تعریف شد: globalThis.
When Can I Use a Variable?
هر identifier ای در ابتدای اسکوپی که در آن تعریف شده است با هربار ورود به اسکوپ ساخته میشود(hoisting). hoisting خود درون function scope یا global scope اتفاق میافتد(هم variable hoisting و هم function hoisting به این گونه هستند).
Hoisting: Declaration vs. Expression
تنها در formal function declaration است که function hoisting صدق میکند. برای مثال به کُد زیر توجه کنید:
greeting(); // TypeError var greeting = function greeting() { console.log("Hello!"); };
در اینجا درواقع variable hoisting است که رخ میدهد و نه function hoisting.
Re-declaration?
به طور کلی در یک اسکوپ var ها(متغیر های تعریف شده با var) re-declare نمیشوند(تمام declaration ها hoist میشوند). و re-declare کردن let و const سینتکس ارور میدهد.
loops
تمام قوانین scope ها بر هر scope instance اعمال میشوند. با هر ورود به اسکوپ همه چیز reset میشود.
هر iteration از حلقه یک scope instance جدید است.
در مهندسی نرمافزار قانون بنیادین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) در بیاورید.
کلوژر مشخصاً از ویژگی های function هاست. کلوژر درواقع "وصل بودن"(live link) به متغیر هاییست که function به آنها به هرنحوی انتیاج دارد. این live link به این معناست که متغیر ها میتوانند توسط فانکشنی که روی آنها closure دارد خوانده ویا حتی نوشته(update) شوند.
به بیان دیگر closure یعنی که تابع هرچیزی را که لازم دارد(چه در اسکوپ خودش یا در اسکوپ بیرونیش) به یاد داشته باشد و بتواند در هر شاخه ای از scope chain (حتی خیلی دور از جایی که فانکشن تعریف شده!) آنهارا بخواند یا آپدیت کند.
اگر همه ی live link هایی که به یک متغیر که در closure تابع یا توابعی قرار دارد از بین بروند(دیگر هیچکدام از توابع به آن احتیاج نداشه باشند)، آنگاه garbage collect میشود.
کلوژر per variable انجام میشود.
Encapsulation and Least Exposure (POLE)
اینکپسولیشن از قوانین بسیار مهم برای سازماندهی کُد است(معمولاً برای OOP اما گستردگی آن از OOP عبورمیکند). ایده کاهش دسترسی با هرچه بیشتر private کردن اجزا است. هرچه private نشود public خواهد شد.
What Is a Module?
ماژول کالکشنی از data و function های مرتبط است که در آن جزئیاتی private و جزئیاتی public هستند(public API).
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 کرد.
Confusions
معمولاً دو معنا از this برداشت میشود که هردو غلط اند:
توجه کنیم که به هیچ عنوان نمیتوان از this برای دسترسی به چیزی که درون lexical scope هست استفاده کرد.
What's this?
بایند شدن this در موقع کامپایل اتفاق نمیافتد بلکه بایند شدنش یک runtime binding است. this binding یک فانشکن، هیچ ربطی به این ندارد که فانشکن کجا declare شده باشد بلکه تماماً مربوط میشود به اینکه فانشکن چطور call شده است که به call-site ِ آن برمیگردد(فصل بعد..).
Call-site
برای اینکه ببنیم this به کجا رفرنس میدهد call-stack را دنبال کنیم. call-site ِ مد نظر ما به آخرین فانکشن(در انتهای) call-stack مربوط میشود، دقیقن قبل از call شدنش. در واقع call-site تنها جایی است که در this binding اهمیت دارد.
Nothing But Rules
دو حالت اول به دلایلی میتوانند شدیدن گیج کننده باشند و طوری خلاف انتظارمان رفتار کنند(درواقع به نحوی 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
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
پراپرتی های یک "پراپرتی دیسکریپتور":
Immutability
JavaScript "Classes"
در جاواسکریپت برای پشتیبانی از OOP و class oriented software design مفهموم کلاس fake شده است.
Class Mechanics - Building
کلاس یک طرح و دستورالعمل است. برای اینکه یک object داشته باشیم(با ویژگی های مشخص شده در این 'طرح') باید از آن instance بگیریم. این instance یک آبجکت است که تمام ویژگی های کلاس در آن "کپی" شده.
Mixins
در جاواسکریپت هنگام instantiation و inherit کردن، مکانیزم آبجکت ها به طور اتوماتیک "کپی " انجام نمیدهد. اصلا در درواقع کلاسی در جاواسکریپت نیست که ازش instance بگیریم، تنها آبجکت های به هم لینک شده داریم. برای fake کردن "کپی" در جاواسکریپت، از mixin استفاده میشود:
مثال بسیار ساده شده از 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 = "Hello World" this.count = this.count ? this.count + 1 : 1; } }; var Another = { cool: function() { // --------> implicit mixin of `Something` to `Another` <-------- Something.cool.call( this ); } };
روش implicit mixin شکننده است و معمولاً منجر به کُدِ "سخت تر قابل فهم" میشود.
[[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 برخورد کنیم.
ساختار 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 است.
متغیر ها تایپ ندارند. مقادیر(value) درونشان تایپ دارند.
Arrays
در مقایسه با زبان های statically-typed، آرایه ی جاواسکریپت فقط یک ظرف برای نگه داشتن مقادیری با هر type ای هستند. یک کلکسیونِ index شده از مقادیر مختلف با هر تایپی.
Number
در جاواسکریپت تنها یک مقدار عددی وجود دارد: number. این تایپ هم شامل integer و هم floating point است. همچنین مقادیر خاصی مانند NaN(بهتر است "invalid number" بشناسیمش تا "not a number")، Infinity، -Infinity+ و 0- را نیز شامل میشود.
Boxing Wrappers
خود جاواسکریپت مقادیر primitive (مانند "some string") را در wrapper object مربوطش بسته بندی میکند و به این نحو به متود ها و پراپرتی های آن دسترسی خواهیم داشت(length. و ()toUpperCase. و ...).
Unboxing
برای خارج کردن مقدار primitive از wrapper object ِ آن، میتوانیم از متود ()valueOf و یا implicit unboxing استفاده کنیم:
var a = new String( "abc" ); a.valueOf(); // "abc" --> valueOf() method // OR a + "" // "abc" --> implicit unboxing
(نکته: به طور کلی بهتر است از فُرمِ constructor برای ساختن native ها استفاده نکنیم، مگر در مورد Error و Date)
Converting Values
جاواسکریپت coercion همیشه به یکی از مقادیر scalar primitives(مانند number، string، boolean) ختم میشود. هیچگاه چیزی به object یا function یا array که مقادیر complex هستند coerce نمیشود.
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] ); // "[1,null,null,4]" JSON.stringify( { a:2, b:function(){} } ); // "{"a":2}"
برای 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 احتمالاً به هیچ مشکلی نخواهیم خورد):
"0" == false; // true false == 0; // true false == "" // true false == []; // true "" == 0; // true "" == []; // true 0 == []; // true
قطعاً از == false بپرهیزید.
Safely Using Implicit Coercion
به طور کلی اگر جوابتان به این دو سوال منفی بود، میتواند موقعیت خوبی برای استفاده از == (درواقع استفاده از coercion) باشد.
(در هر یک از موارد اگر جواب بله است، از == استفاده نکنید)
*توجه: Explicit Coercion کُدی است که به وضوح یک value به type ِ مشخصی(از هر تایپی که داشته) درمی آید. برای Explicit Coercion به طور کلی میتوانیم از توابع constructor ِمقادیر native ِ زبان استفاده کنیم. مانند (someValue)Boolean.
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 میشوند.
به طور کلی قرار است فاصله زمانی(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 طوری جا کنند که انگار "همزمان" اجرا میشوند.
بنیادی ترین الگوی 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) گاهی ممکن است یکی از حالات زیر رخ دهد:
مشکلاتی که در قسمت 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 کردن نیستند.
Breaking Run-to-Completion
جنریتور های یک نوع function اند که برعکسِ توابع عادی، به صورت run to completion رفتار نمیکنند. و (به طرز عجیبی!) میتوانند در یک نقطه pause شوند، state ِ خود را به یاد داشته باشند، و بعد هرگاه بخواهیم از همان نقطه ی توقف دوباره resume کنند!
Input and Output
توسط کلمه ی کلیدی yield متوقف شده و یک output برمیگداند. و از بیرون توسط (some_input)next. ورودی بگیرد و ادامه پیدا کند. (درواقع یک تابع با ورودی و خروجی(دوطرفه) به تعداد دلخواه)
---
مزیت اصلی generator این است که به کنترل جریان async مان کمک میکند و شکلی synchronous به آن میدهد. درواقع مشکل "درک کردنِ پیچیدگیِ" callback ها را تا حدی حل میکند.
به مسائل بهینه سازی و پرفورمنس در کنترل و جابه جایی داده میپردازد و Web Worker ها، SIMD، و همینطور asm.js(به عنوان ابزاری برای آپتیمیزیشن برنامه ی JS مان[http://asmjs.org]) معرفی میکند.
به جزئیات بنچمارکینگ و مقایسه ی پرفورمنس کُد و اینکه چطور اصلاً باید به موضوع نگاه کنیم میپردازد و همچنین اشاره ای به ریزه کاری های محاسباتی بنچمارکینگ میکند.
همینطور که بهتر است به جای استفاده از logic ِ احتمالاً اشتباه خودمان(مگر واقعا اینکاره باشیم!) از Benchmark.js استفاده کنیم[https://benchmarkjs.com].
یک نکته ی قابل توجه: وسواسی که بسیاری از برنامه نویس ها در باره ی بهینه سازی کدشان و "سریع" تر کردن آن با اهمیت دادن به جزئیات microperformance (مانند اینکه "آیا a++ سریع تر است یا ++a ؟")، وسواسِ بسیار اشتباهی است و معمولاً فقط منجر به اتلاف زمان و پیچیده شدن کُد برای خواننده های بعدی(یا حتی خودشان) میشود.
اضافه میشود...