عارفه حمیدی
عارفه حمیدی
خواندن ۱۷ دقیقه·۵ سال پیش

You Don't Know JS (Get Started-ch2 - part2)

در ادامه طبق روند فصل دوم کتاب جلو میریم و دیگر زمینه های اصلی زبان که باعث میشه بهتر بشناسیمش رو مورد بررسی قرار میدیم .

Functions

کلمه function در برنامه نویسی معنی های متنوعی داره. مثلا در دنیای Functional Programming ، شامل مجموعه ای از قوانین هستش که باید رعایت شه. در js ما باید برای function ها معنی وسیع تری رو در نظر بگیریم که بهش procedure میگن. یک procedure مجموعه ای از عبارات هستش که میتونه یک یا چندبار فراخوانی بشه همچنین ممکنه یک چند ورودی و یا یک یا چند خروجی هم داشته باشه.

تعریف function ها اینطور هستش:

function awesomeFunction(coolThings) { // .. return amazingStuff; }

به این function declaration میگیم . ارتباط بین شناسه awesomeFunction و مقدار function در مرحله کامپایل کد اتفاق میفته . در مقابل function declaration باید به function expression توجه کنیم که اینطور تعریف میشه:

// let awesomeFunction = .. // const awesomeFunction = .. var awesomeFunction = function(coolThings) { // .. return amazingStuff; };

این تابع یک expression هستش که به متغییر awesomeFunction اختصاص داده شده. برخلاف فرم function declaration در فرم function expression با identifier تا زمان runtime در ارتباط نیست.

ذکر این نکته اهمیت داره که در js توابع مقادیری هستند که میتونن اختصاص داده بشن یعنی توابع js نوعی خاصی از object value هستند .

توابع js میتونن ورودی پارامتر قبول کنند و همچنین باکلمه کلیدی return مقداری رو برگردونن. به کد زیر توجه کنین :

function greeting(myName) { return `Hello, ${ myName }!`; } var msg = greeting(&quotKyle&quot); console.log(msg); // Hello, Kyle!

در اینجا myName به عنوان پارامتر ورودی هستش که یک local variable هستش که داخل تابع عمل میکنه. همچنین با استفاده از return هم میتونیم یه مقدار رو برگردونیم و اگه نیازه مقدار بیشتری برگردونیم میتونیم از array یا object ها استفاده کنیم .

همچنین چون function ها value هستند بنابراین میتونیم به عنوان properties به object هم اختصاصشون بدیم . به کد زیر توجه کنین :

var whatToSay = { greeting() { console.log(&quotHello!&quot); }, question() { console.log(&quotWhat's your name?&quot); }, answer() { console.log(&quotMy name is Kyle.&quot); } }; whatToSay.greeting(); // Hello!

در این کد سه تابع (greeting, question, answer) داریم که به عنوان properties شی whatToSay هستند.

Comparisons

تصمیم گیری در برنامه ها نیاز به مقایسه value ها برای تعیین هویت و رابطه آنها با هم داره. js چندین مکانیسم برای امکان مقایسه داره، که با هم بررسیشون میکنیم.

Equal...ish

گاهی اوقات مقایسه برابری قصد تطابق دقیق رو داره اما در بعضی موارد هدف گسترده تر هستش و مقایسه در حد تطابق نزدیک یا مشابه هستش. بنابراین باید به تفاوت بین مقایسه برابری و مقایسه هم ارزی توجه کنیم.

حتما در کد js اپراتور === که بهش "triple-equals" میگن رو دیدین که برای "strict equality" تعریف شده. به این نکته کتاب توجه کنین:

"strict" means strict, as in narrow and exact.
Not exactly.

به مثال های زیر دقت کنید تا بهتر متوجه عملکرد این مقایسه بشین :

3 === 3.0; // true &quotyes&quot === &quotyes&quot // true null === null; // true false === false; // true 42 === &quot42&quot // false &quothello&quot === &quotHello&quot // false true === 1; // false 0 === null; // false &quot&quot === null; // false null === undefined; // false

همونطوری که میبنین مقایسه برابری با توجه به مقدار (value) و نوع (type) انجام شده . مثلا در خط 6 مقدار number 6 با مقدار string 6 رو مساوی در نظر نگرفته و مقدار false رو برگردونده.

دو مورد اشتباه هم برای === وجود داره که مقادیر NaN و -0 هستش:

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

در مورد مقدار NaN عملگر === به اشتباه میگه که دو مقدار NaN با هم برابر نیستن همچنین برای مقادیر 0 و -0 ولی نکته مهمی که وجود داره اینکه از مقدار -0 میتونین تو برنامه هاتون برای اینکه یه مقدار متمایز برای بررسی شرط داشته باشین استفاده کنین.

بهتر هستش برای جلوگیری از این اشتباه برای مقایسه NaN از Number.isNaN(..) و برای مقایسه -0 از Object.is(..) استفاده کنید که مقایسه دقیق تری هستش.

اما داستان وقتی پیچیده تر میشه که بخواییم object value ها رو مقایسه کنیم . توجه کنین :

[ 1, 2, 3 ] === [ 1, 2, 3 ]; // false { a: 42 } === { a: 42 } // false (x => x * 2) === (x => x * 2) // false

در js از === برای structural equality نمی تونیم استفاده کنیم پس می تونیم نتیجه بگیریم که === برای object تعریف نشده. ولی میتونیم برای هرobject یه refrence یا value تعریف کنیم و بهش ارجاع بدیم. کد زیر رو در نظر بگیرین :

var x = [ 1, 2, 3 ]; // assignment is by reference-copy, so // y references the *same* array as x, // not another copy of it. var y = x; y === x; // true y === [ 1, 2, 3 ]; // false x === [ 1, 2, 3 ]; // false

مقایسه مقدار x===y مقدار true رو برمیگردونه چون هردو یه مقدار بهشون reference شده اما در دوتا مقایسه بعدی چون بررسی ساختار وجود داره شکست میخوره.

Coercive Comparisons

اکثر نوشتار ها و گفتمان ها درباره js استفاده از == که بهش "loose equality" گفته میشه رو به دلیل طراحی و خطرناک بودنش از نظر رفع اشکال محکوم میکنن. حتی خود Brendan Eich از نحوه طراحی این به عنوان یه اشتباه بزرگ ابراز تاسف میکنه.

یک مشکل بزرگی که اکثرا فک میکنن وجود داره این هستش که تصور میکنن == بدون در نظر گرفتن type مقایسه رو انجام میده در حالی که هر دو اپراتور == و === ابتدا نوع رو بررسی میکنن اما == اجازه میده ابتدا تبدیل نوع انجام بشه و وقتی تبدیل شد و هر دو طرف مقایسه از نظر نوع یکسان شدند سپس همون کاری رو میکنه که === انجام میداد . بهتر اینکه به جای عبارت "loose equality" از عبارت "coercive equality" استفاده بشه.

کد زیر رو در نظر بگیرین:

42 == &quot42&quot // true 1 == true; // true

همونطور که توجه کردین == باعث میشه مقدار های غیر عددی "42" و true ابتدا به مقدار عددی تبدیل بشن و سپس مقایسه انجام بشه و در نتیجه true بهمون برگردونه.

همچنین از مقایسه های رابطه ای مثل < ، > ، =< و => می تونیم استفاده کنیم. این اپراتور ها هم مثل == اول نوع رو مقایسه میکنن و در صورت متفاوت بودن اجازه تبدیل نوع معمولا به اعداد رو میدن . توجه کنین :

var arr = [ &quot1&quot, &quot10&quot, &quot100&quot, &quot1000&quot ]; for (let i = 0; i < arr.length && arr[i] < 500; i++) { // will run 3 times }

مقایسه این جا اینطور انجام میشه :

1 < 500, 10 < 500, 100 < 500, and 1000 < 500

بعد از بررسی شرط چهارم false هستش بنابراین حلقه بعد از شرط سوم متوقف میشه.

ذکر این نکته اهمیت داره که در حالتی که یه طرف مقایسه ما عدد باشه نوع طرف دیگه به عدد تبدیل میشه وگرنه در نوع خودش باقی میمونه . توجه کنین :

var x = &quot10&quot var y = &quot9&quot x < y; // true, watch out!

مقایسه اینجا بین دو str هستش و بنابراین بر اساس حروف الفبا و ترتیب اعداد باید انجام بشه .

How We Organize in JS

دو الگوی اصلی سازمندهی کد که بطور گسترده در js استفاده میشه class ها و module ها هستن. از بعضی جهات این دو الگو بسیار از هم متفاوت هستن و از بعضی جهات هم فقط طرف های مختلف یک سکه هستن. برای اینکه مهارت بهتری در js بدست بیاریم نیازه که هر دو الگو رو کاملا درک کنیم و بدونیم در کجا نیازه از کدوم الگو استفاده کنیم.

Classes

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

کد زیر رو در نظر بگیرین :

class Page { constructor(text) { this.text = text; } print() { console.log(this.text); } } class Notebook { constructor() { this.pages = []; } addPage(text) { var page = new Page(text); this.pages.push(page); } print() { for (let page of this.pages) { page.print(); } } } var mathNotes = new Notebook(); mathNotes.addPage(&quotArithmetic: + - * / ...&quot); mathNotes.addPage(&quotTrigonometry: sin cos tan ...&quot); mathNotes.print(); // ..

در کلاس Page داده ورودی که به عنوان یک property هستش در this.text ذخیره شده و متد print() مقدار text رو در console نشون میده.

برای کلاس NoteBook هم داده ها array از Page ها هستند. متد addPage برای هر text یک new Page ایجاد میکنه و به ارایه pages اضافه میکنه. متد print هم همه صفحات موجود noteBook رو چاپ میکنه.

دستور mathNotes = new Notebook() یه نمونه جدید از کلاس NoteBook ایجاد میکنه و داخل کلاس NoteBook دستور page = new Page(text) یه نمونه جدید از کلاس Page رو ایجاد میکنه . متد های یک کلاس هم میتونیم اینطوری فراخوانی کنیم :

mathNotes.addPage(..)

Class Inheritance

یکی از ویژگی های طراحی "class-oriented" ، وراثت (inheritance) و چند ریختی (polymorphism) هستش .

کد زیر رو در نظر بگیرین:

class Publication { constructor(title,author,pubDate) { this.title = title; this.author = author; this.pubDate = pubDate; } print() { console.log(` Title: ${ this.title } By: ${ this.author } ${ this.pubDate } `); } } class Book extends Publication { constructor(bookDetails) { super( bookDetails.title, bookDetails.author, bookDetails.publishedOn ); this.publisher = bookDetails.publisher; this.ISBN = bookDetails.ISBN; } print() { super.print(); console.log(` Publisher: ${ this.publisher } ISBN: ${ this.ISBN } `); } } class BlogPost extends Publication { constructor(title,author,pubDate,URL) { super(title,author,pubDate); this.URL = URL; } print() { super.print(); console.log(this.URL); } }

کلاس Publication مجموعه ای از رفتارهای متداول رو که ممکن هستش یه Publication داشته باشه رو تعریف میکنه . دو کلاس Book و BlogPost هر دو extend شده از کلاس Publication هستن تا شامل ویژگی های Publication بشن . هر دو فراخوانی super(...) در هر constructor به کارهای کلاس پدر خودش میپردازه و سپس کارهای خاص تری رو بنا به نوع خودش انجام میده.

به استفاده از کلاس های فرزند توجه کنین :

var YDKJS = new Book({ title: &quotYou Don't Know JS&quot, author: &quotKyle Simpson&quot, publishedOn: &quotJune 2014&quot, publisher: &quotO'Reilly&quot, ISBN: &quot123456-789&quot }); YDKJS.print(); // Title: You Don't Know JS // By: Kyle Simpson // June 2014 // Publisher: O'Reilly // ISBN: 123456-789 var forAgainstLet = new BlogPost( &quotFor and against let&quot, &quotKyle Simpson&quot, &quotOctober 27, 2014&quot, &quothttps://davidwalsh.name/for-and-against-let&quot ); forAgainstLet.print(); // Title: For and against let // By: Kyle Simpson // October 27, 2014 // https://davidwalsh.name/for-and-against-let

توجه کنید که هر دوتا فراخوانی از متد print() کلاس های فرزند استفاده کردن و سپس هر کدوم به دستور super.print() رسیدن و با استفاده از وراثت print رو انجام دادن.

بنابراین میتونیم بگیم که وراثت یه ابزار قدرتمند برای سازماندهی دادها و رفتارها در کلاس ها هستش که اجازه میده کلاس های فرزند امکان دسترسی و استفاده از داده ها و رفتارهای کلاس های پدر رو داشته باشن .

Modules

الگو های module هم در واقع همون هدف class ها رو داره ، یعنی گروه بندی داده ها و رفتار ها با هم در واحد های منطقی. همچنین مانند کلاس ها به دلیل همکاری ماژول ها با هم می تونن به داده ها یا رفتارهای یکدیگر دسترسی داشته باشن. اما با هم تفاوت هایی هم دارند که از همه مهم تر syntax کاملا متفاوتشون هستش.

Classic Modules

از نشانه های یک classic module یک function خارجی هستش که instance ماژول رو با یک یا چند کار کرد در معرض استفاده قرار میده و میشه با استفاده ازش روی داده های داخلی ماژول کار کرد. بنابراین چون ماژول ها در این فرم عملا یک function هستند و فراخوانی اونها به عنوان instance تولید میشه توضیحات دیگر روی module factories هستش .

به فرم کلاسیک ماژول کدی که برای کلاس ها بررسی کردیم توجه کنین :

function Publication(title,author,pubDate) { var publicAPI = { print() { console.log(` Title: ${ title } By: ${ author } ${ pubDate } `); } }; return publicAPI; } function Book(bookDetails) { var pub = Publication( bookDetails.title, bookDetails.author, bookDetails.publishedOn ); var publicAPI = { print() { pub.print(); console.log(` Publisher: ${ bookDetails.publisher } ISBN: ${ bookDetails.ISBN } `); } }; return publicAPI; } function BlogPost(title,author,pubDate,URL) { var pub = Publication(title,author,pubDate); var publicAPI = { print() { pub.print(); console.log(URL); } }; return publicAPI; }

با مقایسه این کد و کد قبل که برای فرم کلاس ها بود میبینیم که شباهت ها بیشتر از تفاوت ها هستند.

در فرم کلاس ها داده ها و متد ها به عنوان یه object ذخیره میشیدن که با this. میتونستیم بهشون دسترسی داشته باشیم ولی در ماژول ها داده ها و متدها به عنوان identifier variables هستن که می تونیم ازشون استفاده کنیم و نیازی به this. نیستش. نحوه استفاده ازش رو ببینین :

var YDKJS = Book({ title: &quotYou Don't Know JS&quot, author: &quotKyle Simpson&quot, publishedOn: &quotJune 2014&quot, publisher: &quotO'Reilly&quot, ISBN: &quot123456-789&quot }); YDKJS.print(); // Title: You Don't Know JS // By: Kyle Simpson // June 2014 // Publisher: O'Reilly // ISBN: 123456-789 var forAgainstLet = BlogPost( &quotFor and against let&quot, &quotKyle Simpson&quot, &quotOctober 27, 2014&quot, &quothttps://davidwalsh.name/for-and-against-let&quot ); forAgainstLet.print(); // Title: For and against let // By: Kyle Simpson // October 27, 2014 // https://davidwalsh.name/for-and-against-let

همونطور که میبینین تنها تفاوت قابل توجه اینجا عدم استفاده از new هستش .

ES Modules

این ماژول ها در ES6 معرفی شدن و از نظر هدف مثل ماژول های کلاسیک هستند ولی در اجرا تفاوت زیادی دارند.

اول اینکه در این نوع ماژول function برای تعریف modules وجود نداره و هر file یک module هستش.

دوم اینکه شما به طور صریح با api ماژول ها تعامل ندارین بلکه از کلمه کلیدی export برای اضافه کردن داده یا متد به api عمومی استفاده میکنین . یعنی اگر چیزی در یک ماژول ایجاد شده ولی export نشده در داخل ماژول پنهان میمونه.

سوم اینکه می تونین یه ماژول رو داخل یه ماژول دیگه import کنین و از نمونه اون استفاده کنین. اگر ماژول شما نیاز به پشتیبانی از multiple instantiations داره ابتدا باید یه module-style factory function تعریف کنین و سپس روی اون ESM رو تعریف کنین . به مثال زیر توجه کنین تا نحوه استفاده براتون واضح تر بشه .

ابتدا فایل publication.js رو در نظر بگیرین :

function printDetails(title,author,pubDate) { console.log(` Title: ${ title } By: ${ author } ${ pubDate } `); } export function create(title,author,pubDate) { var publicAPI = { print() { printDetails(title,author,pubDate); } }; return publicAPI; }

برای import کردن و استفاده از این ماژول از ماژول blogpost.js استفاده میکنیم :

import { create as createPub } from &quotpublication.js" function printDetails(pub,URL) { pub.print(); console.log(URL); } export function create(title,author,pubDate,URL) { var pub = createPub(title,author,pubDate); var publicAPI = { print() { printDetails(pub,URL); } }; return publicAPI; }

و در نهایت برای استفاده از این ماژول import ش میکنیم داخل یه ESM دیگه ای مثل main.js:

import { create as newBlogPost } from &quotblogpost.js" var forAgainstLet = newBlogPost( &quotFor and against let&quot, &quotKyle Simpson&quot, &quotOctober 27, 2014&quot, &quothttps://davidwalsh.name/for-and-against-let&quot ); forAgainstLet.print(); // Title: For and against let // By: Kyle Simpson // October 27, 2014 // https://davidwalsh.name/for-and-against-let

همانطور که دیدین در صورت نیاز برای پشتیبانی از multiple-instantiation می تونین از ESM داخل classic modules استفاده کنین .



من تلاشم رو کردم که کدهایی که تو این فصل بود رو براتون بیارم و تا جایی که می تونم توضیح بدم ولی حتما حتما حتما کل فصل رو دقیق و کامل از خود کتاب بخونین ، چندین بار ...

خوشحال میشم نکته ای بود که به بهتر شدن مبحث کمک کنه رو بگین تا با هم ازش استفاده کنیم و بهتر یاد بگیریم .


jsfunctionclassmodule
شاید از این پست‌ها خوشتان بیاید