در یک پروژه فروشگاهی، سبد خرید روی موبایل کار می‌کرد ولی روی دسکتاپ خطا می‌داد. سه ساعت گشتم تا فهمیدم یک فایل جاوااسکریپت در نسخه دسکتاپ دیرتر از نسخه موبایل بارگذاری می‌شد و در لحظه اجرا، متغیری که انتظار می‌رفت تعریف شده باشد، هنوز تعریف نشده بود. نتیجه: TypeError روی خط اول فایل. آن روز یاد گرفتم که خطای TypeError در جاوااسکریپت، بیشتر از جنس «ترتیب اجرا» و «فرض‌های غلط درباره داده» است تا خطای نحوی. در این راهنما، همان مسیر عیب‌یابی که در ده‌ها پرونده مشابه طی کرده‌ام، به‌طور کامل باز می‌شود.

اگر با مفاهیم پایه جاوااسکریپت آشنا نیستید، ابتدا آموزش جاوااسکریپت از صفر و مفاهیم پایه جاوااسکریپت را بخوانید. برای درک جایگاه این خطا در بین سایر خطاها، پست‌های رفع ReferenceError و رفع undefined در جاوااسکریپت پیش‌نیازهای خوبی هستند.

خطای TypeError در جاوااسکریپت چیست؟

خطای TypeError در جاوااسکریپت زمانی رخ می‌دهد که کد شما عملیاتی را روی یک مقدار انجام دهد که نوع آن مقدار اجازه آن عملیات را نمی‌دهد. مثال: فراخوانی یک متغیر که تابع نیست، خواندن خصوصیت از undefined، یا محاسبه عددی روی یک رشته نامعتبر. TypeError، خطای زمان اجراست، نه خطای نحوی — یعنی کد قبل از رسیدن به این نقطه اجرا شده و خطا فقط در لحظه برخورد با مقدار نامناسب رخ می‌دهد.

این ویژگی، تفاوت بنیادی TypeError با SyntaxError است. در SyntaxError، مرورگر حتی یک خط از کد را اجرا نمی‌کند. در TypeError، کد تا نقطه خطا اجرا می‌شود و سپس متوقف می‌شود. به همین دلیل، TypeError می‌تواند در بعضی شرایط بی‌آزار و در بعضی دیگر فاجعه‌بار باشد.

TypeError، خطای «انتظار غلط از داده» است؛ کد شما فرض کرده داده از یک نوع خاص است، ولی داده چیز دیگری بود.

تفاوت TypeError با ReferenceError و SyntaxError

سه خطای اصلی جاوااسکریپت — TypeError، ReferenceError و SyntaxError — هرکدام یک جنس متفاوت دارند و روش عیب‌یابی متفاوتی می‌خواهند. در تجربه من، تشخیص این سه از هم، اولین گام برای رفع سریع خطاست.

خطامعنامثال
ReferenceErrorمتغیر هرگز تعریف نشدهconsole.log(foo); بدون تعریف foo
TypeErrorعملیات روی نوع اشتباهuser.name() وقتی name رشته است
SyntaxErrorنحو اشتباه در کدconst x = ;

تفاوت عملی: ReferenceError یعنی «چیزی که نمی‌شناسم»، TypeError یعنی «چیزی که می‌شناسم ولی رفتارش غیرمنتظره است»، SyntaxError یعنی «نحو کد اشتباه است». در مصاحبه‌های فنی، این تفاوت جزو پرسش‌های پایه است. مسیر تفصیلی در رفع ReferenceError و رفع SyntaxError.

شش نوع رایج پیام TypeError

خطای TypeError در جاوااسکریپت، شش پیام اصلی دارد. هر پیام، نشانه یک دسته خاص است. شناخت این شش دسته، سرعت عیب‌یابی را چند برابر می‌کند.

نوع اول: Cannot read property of undefined

رایج‌ترین TypeError. رخ می‌دهد وقتی می‌خواهید خصوصیتی از یک مقدار undefined یا null بخوانید:

const user = null;
console.log(user.name);  // TypeError: Cannot read properties of null

// پیام مدرن:
// Cannot read properties of undefined (reading 'name')

در بخش‌های بعدی، الگوهای پیشگیرانه این دسته به‌طور کامل بررسی می‌شود. مسیر تفصیلی در خطای Cannot read property of undefined.

نوع دوم: Cannot set property of undefined

مشابه نوع اول، ولی در مرحله نوشتن، نه خواندن:

const obj = {};
obj.user.name = "علی";  // TypeError: Cannot set properties of undefined

اینجا، obj.user تعریف نشده و تلاش برای نوشتن روی name آن، خطا می‌دهد.

نوع سوم: x is not a function

وقتی می‌خواهید چیزی را که تابع نیست، به‌عنوان تابع فراخوانی کنید:

const calculate = 42;
calculate();  // TypeError: calculate is not a function

این خطا در سناریوهای بسیاری رخ می‌دهد: وارد کردن نادرست module، فراموش کردن تعریف تابع، یا تداخل نام‌ها.

نوع چهارم: Cannot convert undefined or null to object

وقتی می‌خواهید عملیاتی مثل Object.keys یا destructuring را روی undefined یا null انجام دهید:

const { name } = null;  // TypeError: Cannot destructure property
Object.keys(undefined);  // TypeError

نوع پنجم: Assignment to constant variable

تلاش برای تغییر مقدار const:

const PI = 3.14;
PI = 3.14159;  // TypeError: Assignment to constant variable

این TypeError، در حقیقت یک خطای منطقی است: انتظار داشتید متغیر قابل‌تغییر باشد ولی const تعریف شده.

نوع ششم: Converting circular structure to JSON

هنگام ارسال داده به API یا ذخیره در localStorage:

const obj = {};
obj.self = obj;
JSON.stringify(obj);  // TypeError: Converting circular structure to JSON

این خطا در پروژه‌های پیچیده با آبجکت‌های درختی رخ می‌دهد. حل آن، نیازمند استفاده از کتابخانه‌هایی مثل flatted یا بازنویسی ساختار داده است.

پیامعلتراه‌حل اصلی
Cannot read property of undefinedخواندن خصوصیت از undefinedOptional chaining (?.)
Cannot set property of undefinedنوشتن در خصوصیت undefinedمقدار پیش‌فرض اولیه
x is not a functionفراخوانی مقدار غیرتابعبررسی typeof
Cannot convert null to objectعملیات روی nullبررسی null قبل از عملیات
Assignment to constantتغییر constاستفاده از let
Circular structure to JSONساختار چرخشیحذف مرجع چرخشی
در خطاهای TypeError، پیام دقیق، ۵۰٪ راه عیب‌یابی است. پیام مدرن جاوااسکریپت، هم عملیات و هم خصوصیت را می‌گوید — آن را دقیق بخوانید.

خواندن دقیق Stack Trace

Stack Trace، ردپای فراخوانی توابع در لحظه خطا است. سه بخش اصلی دارد که هرکدام یک سرنخ حیاتی است. توانایی خواندن دقیق Stack Trace، تفاوت بین عیب‌یابی چند دقیقه‌ای و چند ساعته است.

بخش اول: نوع خطا و پیام

TypeError: Cannot read properties of undefined (reading 'name')

نوع خطا (TypeError) و پیام دقیق را نشان می‌دهد. این پیام، اولین سرنخ است.

بخش دوم: فایل و خط

at getUserName (app.js:42:15)

نام تابع (getUserName)، فایل (app.js)، شماره خط (42) و ستون (15). این اطلاعات، محل دقیق خطا را نشان می‌دهد.

بخش سوم: زنجیره فراخوانی

at getUserName (app.js:42:15)
at displayProfile (app.js:78:5)
at HTMLButtonElement.onclick (index.html:12:3)

زنجیره فراخوانی از پایین به بالا خوانده می‌شود. خطای اصلی در تابع getUserName رخ داده، ولی این تابع از displayProfile و آن هم از کلیک روی یک دکمه فراخوانی شده. ریشه مشکل ممکن است در کلیک کاربر باشد (نداشتن داده هنوز بارگذاری‌شده)، نه در تابع.

عیب‌یابی در Console مرورگر

Console مرورگر، اولین ابزار عیب‌یابی TypeError است. سه روش عملی که در پروژه‌ها به‌کار می‌برم:

روش اول: باز کردن کنسول و پیدا کردن خطای قرمز

در مرورگر، با F12 کنسول را باز کنید. خطاهای TypeError با رنگ قرمز نمایش داده می‌شوند. روی شماره خط کلیک کنید تا فایل در تب Sources باز شود. مسیر تفصیلی در پیدا کردن خطاهای JS در Console.

روش دوم: استفاده از console.log برای ردیابی

قبل از خط خطا، مقادیر متغیرهای مشکوک را log کنید:

console.log("user:", user);
console.log("user.name:", user?.name);
console.log("typeof user.name:", typeof user?.name);

در تجربه من، این روش در ۶۰٪ موارد کافی است. تنها محدودیت: در کد حجیم، پرکردن console با logهای اضافی، خودش منبع اشتباه می‌شود. راه‌حل: logهای موقت را پس از عیب‌یابی حذف کنید.

روش سوم: استفاده از console.table برای آبجکت‌ها

console.table(users);

برای آرایه‌ای از آبجکت‌ها، console.table نمایش بصری بهتری می‌دهد و پیدا کردن آبجکت با مقدار نامناسب را سریع‌تر می‌کند.

Breakpoint و Debugger: توقف در لحظه خطا

روش حرفه‌ای عیب‌یابی، Breakpoint است. به‌جای حدس زدن، کد را در نقطه مشخص متوقف می‌کنید و مقدار همه متغیرها را می‌بینید. سه روش عملی:

روش اول: Breakpoint دستی در DevTools

در تب Sources، روی شماره خط کلیک کنید. یک نقطه آبی ظاهر می‌شود. هنگام اجرا، کد در این نقطه متوقف می‌شود. سپس می‌توانید با دکمه‌های Step Over، Step Into و Step Out، خط به خط پیش بروید.

روش دوم: کلمه کلیدی debugger

function getUserName(user) {
    debugger;  // اجرا اینجا متوقف می‌شود
    return user.name;
}

کلمه debugger مثل breakpoint عمل می‌کند، ولی در کد است. در محیط تولید، این کلمه را حذف کنید. مسیر تفصیلی در ابزارهای دیباگ جاوااسکریپت.

روش سوم: Conditional Breakpoint

اگر خطا فقط در شرایط خاص رخ می‌دهد، از شرط استفاده کنید. در DevTools، راست‌کلیک روی شماره خط و Add conditional breakpoint. مثلاً:

user === null

کد فقط وقتی متوقف می‌شود که این شرط برقرار باشد.

Debugger، ابزار جراحی است؛ console.log، ابزار معاینه. برای خطاهای پیچیده، همیشه از debugger استفاده کنید.

پنج ریشه اصلی خطاهای TypeError

در تجربه من، خطاهای TypeError در پروژه‌ها به پنج ریشه اصلی برمی‌گردند. شناخت این پنج، زمان عیب‌یابی را به‌طور چشمگیر کاهش می‌دهد.

ریشه اول: ترتیب اجرا و بارگذاری

کد قبل از بارگذاری DOM یا قبل از آمدن داده از API اجرا می‌شود. مثال:

// اشتباه
const button = document.getElementById("submit");
button.addEventListener("click", handleClick);
// اگر script قبل از DOM اجرا شود، button null است و خطا می‌دهد

راه‌حل: استفاده از defer یا انتظار برای DOMContentLoaded. مسیر تفصیلی در کار با DOM در جاوااسکریپت.

ریشه دوم: پاسخ نامعتبر از API

فیلدی که انتظار دارید همیشه در پاسخ API باشد، در بعضی موارد نیست:

fetch("/api/user")
    .then(response => response.json())
    .then(data => {
        // اگر data.user وجود نداشته باشد، خطا
        console.log(data.user.name);
    });

راه‌حل: optional chaining و مقدار پیش‌فرض.

ریشه سوم: مقدار پیش‌فرض نداشتن آرگومان‌ها

function greet(name) {
    return name.toUpperCase();  // اگر name undefined باشد، خطا
}
greet();  // TypeError

راه‌حل: مقدار پیش‌فرض در پارامتر تابع:

function greet(name = "کاربر") {
    return name.toUpperCase();
}

ریشه چهارم: تداخل متغیر و شیء

متغیری که فکر می‌کنید تابع است، در واقع یک مقدار دیگر گرفته:

let calculate = 10;
if (condition) {
    calculate = () => 20;
}
// در جای دیگری، calculate(); خطا اگر مقدار عددی باشد

راه‌حل: نام‌گذاری معنادار و عدم استفاده مکرر از یک نام برای انواع مختلف.

ریشه پنجم: رفتار this در callback

const obj = {
    name: "علی",
    greet: function() {
        setTimeout(function() {
            console.log(this.name);  // TypeError: this undefined
        }, 1000);
    }
};

راه‌حل: arrow function:

setTimeout(() => {
    console.log(this.name);
}, 1000);

الگوهای پیشگیرانه در کد

پیشگیری از TypeError، بیش از رفع آن اهمیت دارد. شش الگوی پیشگیرانه که در پروژه‌ها به‌کار می‌برم:

الگوی اول: Optional Chaining

// اشتباه
const city = user.address.city;

// درست
const city = user?.address?.city ?? "نامشخص";

الگوی دوم: Nullish Coalescing

const count = data?.count ?? 0;
const name = user?.name || "ناشناس";

تفاوت ?? و ||: اولی فقط برای null و undefined، دومی برای همه مقادیر falsy (شامل صفر و رشته خالی). مسیر تفصیلی در مفاهیم پایه جاوااسکریپت.

الگوی سوم: بررسی نوع قبل از فراخوانی

if (typeof callback === "function") {
    callback();
}

الگوی چهارم: Destructuring با مقدار پیش‌فرض

const { name = "ناشناس", email = "" } = user || {};

الگوی پنجم: آرایه‌های امن

// اشتباه
items.forEach(item => console.log(item.name));

// درست
(items || []).forEach(item => console.log(item?.name));

الگوی ششم: Bookmark برای مقادیر پیش‌فرض

const initial = {
    name: "",
    items: [],
    user: {},
};

نقش TypeScript در پیشگیری

TypeScript، پیشرفته‌ترین ابزار پیشگیری از TypeError است. با تعریف نوع صریح، بخش بزرگی از خطاها قبل از اجرا کشف می‌شوند. مثال:

interface User {
    name: string;
    email: string;
    address?: {
        city: string;
    };
}

function getUserName(user: User): string {
    return user.name;  // اگر user null باشد، در زمان کامپایل خطا
}

TypeScript، خطاهایی مثل خواندن خصوصیت از undefined، فراخوانی متغیر غیرتابعی و ناسازگاری نوع را قبل از اجرا نشان می‌دهد. مسیر تفصیلی در TypeScript برای توسعه‌دهندگان JavaScript.

TypeScript، TypeError را از زمان اجرا به زمان کامپایل منتقل می‌کند؛ همین یک تغییر، تفاوت بین عیب‌یابی ساعت‌ها و پیشگیری در چند ثانیه است.

TypeError در کدهای async و Promise

خطاهای TypeError در کدهای async، شکل متفاوتی دارند. سه الگوی رایج:

الگوی اول: Unhandled Promise Rejection

fetch("/api/data")
    .then(response => response.json())
    .then(data => data.user.name);  // اگر user نباشد، خطا در Promise

خطا در Promise، اگر catch نشود، به‌صورت Unhandled Rejection گزارش می‌شود. مسیر تفصیلی در Unhandled Promise Rejection.

الگوی دوم: await روی مقدار غیر Promise

const data = await getData();  // اگر getData تابع نباشد، TypeError

الگوی سوم: try/catch با async/await

async function fetchUser() {
    try {
        const response = await fetch("/api/user");
        const data = await response.json();
        return data.user.name;
    } catch (error) {
        console.error("خطا در دریافت اطلاعات کاربر:", error);
        return "ناشناس";
    }
}

مسیر تفصیلی در async/await در جاوااسکریپت.

TypeError در وردپرس و پروژه‌های فرانت

در پروژه‌های وردپرسی، خطاهای TypeError اغلب از سه منبع می‌آیند:

منبع اول: جی‌کوئری در نسخه‌های قدیمی

قالب‌ها یا افزونه‌های قدیمی که بر اساس jQuery نوشته شده‌اند، اگر jQuery دیرتر از اسکریپتشان بارگذاری شود، خطای TypeError می‌دهند. راه‌حل: مدیریت صحیح dependency در wp_enqueue_script. مسیر تفصیلی در هوک‌های وردپرس.

منبع دوم: افزونه‌های صفحه‌ساز

صفحه‌سازها در فرآیند بارگذاری، گاهی به عناصر DOM که هنوز آماده نشده‌اند دسترسی دارند. خطا در صفحه‌ساز، اغلب به‌شکل TypeError در کنسول ظاهر می‌شود. مسیر تفصیلی در بررسی المنتور.

منبع سوم: اسکریپت‌های چایلد تم

اسکریپت‌های سفارشی که در چایلد تم نوشته می‌شوند، اگر بدون بررسی ترتیب اجرا باشند، TypeError می‌دهند. مسیر تفصیلی در قالب چایلد وردپرس.

اشتباهات رایج در رفع خطا

  1. سرکوب خطا با try/catch بدون بررسی ریشه: خطا را پنهان می‌کند ولی مشکل منطقی سر جای خود است.
  2. استفاده از "|| {}" در همه جا: این روش خطا را رفع می‌کند ولی باگ منطقی (خالی بودن داده) را پنهان می‌کند.
  3. نادیده گرفتن Stack Trace: بدون خواندن مسیر خطا، هر جستجویی کورکورانه است.
  4. عدم بررسی data قبل از دسترسی: همیشه بررسی کنید که داده برگشتی از API، ساختار مورد انتظار دارد.
  5. نادیده گرفتن رفتار this: در callbackها، this متفاوتی وجود دارد. استفاده از arrow function راه‌حل است.
  6. عدم استفاده از TypeScript: در پروژه‌های بزرگ، عدم استفاده از TypeScript، خطاهای TypeError را چند برابر می‌کند.
  7. کپی-پیست کد بدون تطبیق: کدی از یک پروژه در پروژه دیگر، ممکن است در پروژه مقصد TypeError بدهد چون ساختار داده متفاوت است.
  8. فراموش کردن تست در مرورگرهای قدیمی: بعضی APIهای مدرن در مرورگرهای قدیمی وجود ندارند و خطای TypeError می‌دهند.

پرسش‌های پرتکرار درباره TypeError

خطای TypeError در جاوااسکریپت چیست و چه تفاوتی با ReferenceError دارد؟

خطای TypeError زمانی رخ می‌دهد که عملیاتی روی یک مقدار نامناسب انجام دهید — مثلاً خواندن خصوصیت از undefined. ReferenceError زمانی رخ می‌دهد که متغیر هرگز تعریف نشده باشد. تفاوت عملی: TypeError یعنی «متغیر را می‌شناسم ولی نوعش اشتباه است»، ReferenceError یعنی «متغیر را نمی‌شناسم». مسیر تفصیلی در رفع ReferenceError.

چطور خطای Cannot read property of undefined را رفع کنم؟

سه راه‌حل: اول، استفاده از optional chaining: user?.name. دوم، استفاده از مقدار پیش‌فرض: (user || {}).name. سوم، بررسی if (user) { ... } قبل از دسترسی. در تجربه من، optional chaining بهترین ترکیب خوانایی و امنیت را دارد. مسیر تفصیلی در خطای Cannot read property.

چرا خطای TypeError در Console مرورگر ظاهر می‌شود؟

خطاهای جاوااسکریپت در زمان اجرا، به‌طور پیش‌فرض در Console مرورگر نمایش داده می‌شوند. برای دیدن آن‌ها، F12 را بزنید و به تب Console بروید. خطاها با رنگ قرمز و مسیر فایل و شماره خط نمایش داده می‌شوند. مسیر تفصیلی در پیدا کردن خطاهای JS در Console.

آیا خطای TypeError سایت را از کار می‌اندازد؟

نه، خطای TypeError کل سایت را از کار نمی‌اندازد. این خطا فقط اجرای فایل جاوااسکریپت را در آن نقطه متوقف می‌کند. بقیه بخش‌های سایت که به آن فایل وابسته نیستند، به کار خود ادامه می‌دهند. در بعضی شرایط، خطا در یک فایل می‌تواند بخش‌هایی از رابط کاربری را از کار بیندازد (مثلاً سبد خرید اگر روی همان فایل وابسته باشد).

چطور از خطای TypeError پیشگیری کنیم؟

پنج الگوی پیشگیرانه: optional chaining برای دسترسی امن، nullish coalescing برای مقدار پیش‌فرض، بررسی typeof برای تابع، destructuring با مقدار پیش‌فرض، و استفاده از TypeScript. در پروژه‌های بزرگ، TypeScript بهترین پیشگیری است — خطاها را قبل از اجرا نشان می‌دهد.

آیا خطای x is not a function همان TypeError است؟

بله، x is not a function یکی از پیام‌های TypeError است. این پیام وقتی ظاهر می‌شود که می‌خواهید چیزی را به‌عنوان تابع فراخوانی کنید، ولی آن مقدار تابع نیست. دلایل رایج: وارد کردن نادرست module، فراموش کردن تعریف تابع، یا تداخل نام‌ها.

چطور خطای TypeError در کدهای async را رفع کنم؟

سه گام عملی: اول، استفاده از try/catch داخل async function. دوم، بررسی داده بازگشتی از API قبل از دسترسی. سوم، استفاده از optional chaining برای دسترسی‌های امن. مسیر تفصیلی در async/await در JavaScript.

آیا TypeError در همه مرورگرها یکسان است؟

پیام TypeError در همه مرورگرها مدرن (Chrome، Firefox، Safari، Edge) یکسان نیست، ولی مفهوم کلی یکسان است. Chrome و Firefox پیام‌های دقیق‌تری با جزئیات خصوصیت و عملیات می‌دهند. Safari و Edge پیام‌های کوتاه‌تر ولی کافی می‌دهند. تفاوت اصلی در جزئیات پیام است، نه در مفهوم خطا.

آیا می‌توانم خطای TypeError را در سطح فایل مدیریت کنم؟

بله، با استفاده از event listener سراسری:

window.addEventListener("error", function(event) {
    if (event.error instanceof TypeError) {
        console.error("TypeError captured:", event.error);
        // مدیریت خطا
    }
});

این روش، برای سناریوهای پیشرفته مدیریت خطا مناسب است. برای پروژه‌های معمولی، try/catch محلی بهتر است.

آیا TypeScript همه TypeErrorها را پیشگیری می‌کند؟

TypeScript بخش بزرگی از TypeErrorها را پیشگیری می‌کند، ولی نه همه. خطاهای مربوط به داده‌های ورودی از API خارجی، در زمان کامپایل قابل‌تشخیص نیستند و باید در زمان اجرا با runtime validation (مثل Zod یا io-ts) بررسی شوند. ترکیب TypeScript با runtime validation، پوشش کاملی ارائه می‌دهد.

جمع‌بندی مسیر

خطای TypeError در جاوااسکریپت، رایج‌ترین خطای فرانت‌اند است که از جنس «فرض غلط درباره داده» می‌آید، نه خطای نحوی. سه گام عملی که از امروز می‌توانید بردارید: اول، در کنسول مرورگر، پیام دقیق TypeError را با دقت بخوانید — این پیام، نام خصوصیت و عملیات را می‌گوید. دوم، Stack Trace را کامل بخوانید تا ریشه واقعی خطا (نه فقط محل خطا) را پیدا کنید. سوم، برای پیشگیری، optional chaining و مقدار پیش‌فرض را در همه دسترسی‌ها عادت کنید.

تجربه من از پروژه‌های مختلف: خطاهای TypeError در فرانت‌اند، بیش از آنکه ناشی از کد بد باشند، ناشی از فرض‌های غلط درباره داده‌های API، ترتیب بارگذاری DOM و رفتار this در callbackها هستند. تفاوت اصلی بین توسعه‌دهنده حرفه‌ای و تازه‌کار در این است که حرفه‌ای‌ها از همان روز اول، با optional chaining و TypeScript کد می‌نویسند؛ تازه‌کارها بعد از دیدن خطا، سراغ این ابزارها می‌روند. اگر این عادت را از همان ماه اول بگنجانید، خطاهای TypeError از زندگی شما حذف می‌شوند — یا حداقل، به‌سرعت قابل‌رفع می‌شوند.

اگر در پروژه خود با TypeError روبرو شده‌اید که ریشه‌اش در جای غیرمنتظره‌ای بود — مثلاً در یک کتابخانه خارجی، یک polyfill قدیمی، یا یک رفتار خاص مرورگر — در دیدگاه‌ها بنویسید. تجربه‌های واقعی هر پروژه، این راهنما را برای خواننده بعدی که در همان نقطه ایستاده، دقیق‌تر می‌کند. 🛠️