
جاوااسکریپت یه زبان dynamically typed هست — یعنی متغیرها به نوع خاصی محدود نیستن. این یه قدرت بزرگه، ولی همزمان یه منبع بزرگ باگ هم هست. مثلاً:
const possiblyNumeric = '203.45'; function setWidth(width) { width += 20; // انتظار داریم عدد بشه 223.45 applyWidth(width); } setWidth(possiblyNumeric); // => '203.4520' ← رشته!
چون یه operand رشتهست، + بهجای جمع، concatenate میکنه. همین یه خط ساده منشأ میلیونها ساعت debugging در دنیا بوده.
این فصل دو موضوع اصلی رو پوشش میده: تشخیص تایپ و تبدیل تایپ.
تشخیص تایپ (Detection)
قبل از اینکه بریم سراغ تکنیکها، یه سوال مهم: واقعاً چی رو میخوایم تشخیص بدیم؟
اغلب فکر میکنیم باید تایپ دقیق رو چک کنیم، ولی این میتونه کد رو بیدلیل rigid کنه. مثلاً این تابع:
function registerPeopleForMarathon(people) { if (Array.isArray(people)) { throw new Error('People is not an array'); } for (let person of people) { if (!(person instanceof Person)) { throw new Error('Each person should be an instance of Person'); } registerForMarathon(person.id, person.name); } }
این چکها بیشتر از اینکه کمک کنن، محدودیت میسازن. for...of روی هر iterable کار میکنه — نه فقط آرایه. و برای person هم، مهم اینه که id و name داشته باشه، نه اینکه حتماً instance از Person باشه.
رویکرد بهتر — duck typing:
function isIterable(obj) { return obj != null && typeof obj[Symbol.iterator] === 'function'; } function registerPeopleForMarathon(people) { if (!isIterable(people)) { throw new Error('People is not iterable'); } for (let person of people) { if (!person || !person.name || !person.id) { throw new Error('Each person should have a name and id'); } registerForMarathon(person.id, person.name); } }
اگه مثل اردک راه میره و مثل اردک قار قار میکنه، اردکه — لازم نیست شناسنامهاش رو ببینیم.
typeof
اولین ابزار تشخیص تایپ، typeof هست:
typeof 1; // => "number" typeof ''; // => "string" typeof {}; // => "object" typeof function(){}; // => "function" typeof undefined; // => "undefined" typeof Symbol(); // => "symbol" typeof 0n; // => "bigint" typeof true; // => "boolean"
ولی typeof یه دوست ناپایداره — دو تله بزرگ داره:
اول اینکه typeof null برابر "object" برمیگردونه — یه باگ تاریخی که هرگز درست نشده.
دوم اینکه بین انواع مختلف object فرق نمیذاره:
typeof []; // => "object" typeof RegExp(''); // => "object" typeof {};
تشخیص تایپهای مختلف
Boolean: معمولاً کافیه مستقیم توی شرط استفاده بشه. اگه میخواید مطمئن باشید دقیقاً true یا false هست:
if (isEnabled === true) {...}
Number: typeof درست کار میکنه، ولی NaN و Infinity هم "number" برمیگردونن. برای چک کردن عدد معتبر:
function isNormalNumber(n) { return typeof n === 'number' && isFinite(n); }
تفاوت isNaN و Number.isNaN رو هم بدونید:
isNaN('foo'); // => true (اول cast میکنه) Number.isNaN('foo'); // => false (بدون cast)
String: سادهترین تشخیص رو داره:
typeof 'hello'; // => "string"
برای چک کردن اینکه رشته غیرخالیه:
function isNonEmptyString(string) { return typeof string === 'string' && string.trim().length > 0; }
undefined: بهترین راه استفاده از typeof:
if (typeof value === 'undefined') {...}
یا از void 0 بهعنوان جایگزین مطمئن:
if (value === void 0) {...}
null: باید مستقیم چک بشه:
if (someValue === null) {...}
null یا undefined با هم: صریحترین راه:
if (value === null || value === undefined) {...}
یه راه کوتاهتر هم هست که محبوبه، ولی ممکنه نیت رو مبهم کنه:
if (value == null) {...} // هر دو null و undefined رو میگیره
Array: از Array.isArray استفاده کنید:
if (Array.isArray(value)) {...}
ولی اگه فقط میخواید روش loop بزنید، بهتره iterable بودنش رو چک کنید.
Plain Object: با بررسی [[Prototype]]:
function isPlainObject(object) { return Object.getPrototypeOf(object) === Object.prototype; }
تبدیل تایپ: Casting و Coercion
دو نوع تبدیل تایپ داریم:
Casting — عمدی و صریح:
javascript
Number('123'); // => 123 String(456); // => "456"
Coercion — ضمنی و خودکار توسط جاوااسکریپت:
1 + '123'; // => "1123" (رشته!) [2] * [3]; // => 6 (عدد!) '22' / 2; // => 11 (عدد!)
coercion چون ضمنیه، میتونه منشأ باگهای عجیب بشه. همیشه به تایپ operandهاتون مطمئن باشید.
تبدیل به Boolean
همه مقادیر به Boolean قابل تبدیلن — مگه هفت مقدار falsy.
دو روش صریح:
Boolean(0); // => false Boolean(1); // => true !!0; // => false !!1; // => true
double-bang (!!) یه روش رایجه که ارزش یه مقدار رو به Boolean تبدیل میکنه. مفیده وقتی میخواید مطمئن بشید تابعتون همیشه Boolean برمیگردونه:
function isNamePopulated(name) { return !!name; } isNamePopulated(''); // => false isNamePopulated(null); // => false isNamePopulated('Sandra'); // => true
تبدیل به String
صریحترین روش، String() هست:
String(456); // => "456" String(true); // => "true" String(null); // => "null" String([1, 2, 3]); // => "1,2,3"
دو روش کوتاهتر هم وجود داره، ولی تفاوت ظریفی دارن:
'' + 1234; // => "1234" (از valueOf استفاده میکنه) `${1234}`; // => "1234" (مستقیم toString رو صدا میزنه)
این تفاوت وقتی آبجکت شما هم valueOf و هم toString داره، میتونه نتایج متفاوت بده. پس همیشه از String() استفاده کنید.
تبدیل به Number
صریحترین روش، Number() هست:
Number('10e3'); // => 10000 Number(' 4.6'); // => 4.6 Number('wat'); // => NaN Number(false); // => 0
روش کوتاهتر: unary +:
+'55.66'; // => 55.66 +'foo'; // => NaN
parseInt و parseFloat هم وجود دارن ولی با احتیاط باید ازشون استفاده کرد:
parseInt('100', 8); // => 64 (octal) parseFloat('42.01'); // => 42.01
parseInt radix رو همیشه صریح بدید — وگرنه ممکنه خودش تصمیم بگیره و نتایج غیرمنتظره بگیرید.
ToPrimitive
وقتی جاوااسکریپت میخواد یه آبجکت رو به primitive تبدیل کنه، این ترتیب رو دنبال میکنه:
۱. اگه [Symbol.toPrimitive] داره، ازش استفاده میکنه
۲. اگه نه، valueOf رو امتحان میکنه
۳. اگه نه، toString رو صدا میزنه
const one = { [Symbol.toPrimitive]: () => 1, valueOf: () => 2, toString: () => 3 }; const two = { valueOf: () => 2, toString: () => 3 }; const three = { toString: () => 3 }; 1 == one; // => true 2 == two; // => true 3 == three; // => true
جمعبندی
dynamic typing جاوااسکریپت یه شمشیر دولبهست. اگه بدونید چطور تشخیص بدید و تبدیل کنید، میتونید کد انعطافپذیر و قابل اعتماد بنویسید. اگه ندونید، همون یه خط += میتونه ساعتها debugging بشه.