ما به دیتابیسی از بیش از هزار پروژه مراجعه کرده، و 10 خطای برتر در JavaScript را برای شما توسعه دهنگان پیدا کردیم. حال به شما نشان خواهیم داد که عامل آنها چیست و چگونه میتوانید جلوی بروز آنها را بگیرید.
ما بر روی خطاهایی که احتمال بروزشان برای شما بیشتر است، بر حسب تعداد پروژههایی که آنها را تجربه کرده بودند، تمرکز کردیم.
در عکس زیر، ۱۰ خطای برتر JavaScript را میبینید:
خطاها برای خوانایی راحتتر، کوتاه شدهاند. حال بیایید به هر کدام نگاهی داشته باشیم و ببینیم که چگونه میتوانید از آنها جلوگیری کنید.
۱. Uncaught TypeError: Cannot read property
اگر یک توسعهدهنده JavaScript هستید، احتمالا تعداد دفعات زیادی این خطا را دیدهاید. این مورد، وقتی که یک ویژگی را میخوانید یا متدی را بر روی یک آبجکت تعریف نشده فراخوانی میکنید، در Chrome بروز میدهد. میتوانید آن را به راحتی در کنسول Chrome Developer آزمایش کنید.
این خطا میتواند به دلایل زیادی بروز دهد، اما یک دلیل رایج آن، راهاندازی نادرست یک state در هنگام رندر کردن کامپوننتهای رابط کاربری است. بیایید به نحوه بروز آن در مثالی از یک برنامه واقع،ی نگاهی داشته باشیم. ما React را مثال میزنیم، اما همین اصول راهاندازی نامناسب به Angular، Vue یا هر فریموورک دیگری هم صدق میکنند.
class Quiz extends Component {
componentWillMount() {
axios.get('/thedata').then(res => {
this.setState({items: res.data});
});
}
render() {
return (
{this.state.items.map(item =>
{item.name}
)}
); } }
در اینجا، دو نکته مهم وجود دارد:
- State یک کامپوننت به شکل تعریف نشده (undefined) شروع میشود.
- وقتی که به طور ناهمگام دادهها را دریافت میکنید، کامپوننت بدون توجه به این که componentWillMount یا componentDidMount در constructor دریافت شده است یا نه، قبل از این که دادهها بارگذاری شوند، حداقل یک بار رندر میشود. این به این معنی است که ItemList آیتمها را به صورت تعریف نشده میگیرد، و شما خطای Uncaught TypeError: Cannot read property ‘map’ of undefined را در کنسول میبینید.
برطرف کردن این مشکل آسان است. آسان ترین راه: state را با مقادیر اولیه منطقی در constructor راهاندازی کنید.
class Quiz extends Component { // Added this: constructor(props) { super(props); // Assign state itself, and a default value for items this.state = { items: [] }; } componentWillMount() { axios.get('/thedata').then(res => { this.setState({items: res.data}); }); } render() { return (
{this.state.items.map(item =>
{item.name}
)}
); } }
کد دقیق در برنامه شما ممکن است تفاوت داشته باشد، اما امیدواریم که اطلاعات کافی برای جلوگیری یا برطرف کردن این خطا داده باشیم. در غیر این صورت، ادامه متن را بخونید؛ زیرا مثالهای بیشتری درباره خطاهای مربوطه خواهیم زد.
۲. TypeError: ‘undefined’ is not an object (evaluating)
این خطا که زمانی که یک ویژگی را میخوانید یا متدی را بر روی یک آبجکت تعریف نشده فراخوانی میکنید، در Safari بروز میدهد. میتوانید این را به راحتی در کنسول Safari Developer آزمایش کنید. این مورد دقیقا مشابه خطای قبلی در Chrome است، اما Safari از پیام متفاوتی برای نمایش دادن آن استفاده میکند.
۳. TypeError: null is not an object (evaluating)
این خطا که زمانی که یک ویژگی را میخوانید یا متدی را بر روی یک آبجکت خالی (null) فراخوانی میکنید، در Safari بروز میدهد. میتوانید این را به راحتی در کنسول Safari Developer آزمایش کنید.
نکته جالب این است که در JavaScript، خالی (null) و تعریف نشده (undefined) یکی نیستند، و به همین دلیل است که دو پیام متفاوت را مشاهده میکنیم. «تعریف نشده» معمولا متغیری است که تعریف نشده است، اما «خالی» به این معنی است که مقدار آن متغیر، خالی است. برای این که مطمئن شوید آنها با هم برابر نیستند، استفاده از عملگر Strict Equality را آزمایش کنید.
یکی از مواردی که ممکن است این خطا بروز دهد، زمانی است که سعی میکنید از یک عنصر DOM، قبل از این که آن عنصر بارگذاری شود در JavaScript استفاده کنید. علت آن این است که ایپیآی DOM، برای اشاره به آبجکتهایی که خالی هستند، مقدار خالی را بر میگرداند
هر کد JavaScript که با عناصر DOM کار میکند و آنها را اجرا میکند، باید بعد از این که عناصر DOM بارگذاری شدند، اجرا شود. کد JavaScript به مانند HTML، از بالا به پایین تفسیر میشود. پس اگر تگی قبل از عنصر DOM وجود داشته باشد، کد JavaScript داخل تگ اسکریپت، زمانی که مرورگر کد HTML را parse میکند، اجرا میشود. اگر عناصر DOM قبل از بارگذاری Script ساخته نشده باشند، این خطا را دریافت میکنید.
در این مثال، میتوانیم این مشکل را با اضافه کردن یک Event Listener که زمانی که صفحه آماده است به ما اطلاع میدهد، حل کنیم. زمانی که addEventListener اجرا میشود، متد init() میتواند از عناصر DOM استفاده کند.
۴. (unknown): Script error
این خطای اسکریپت زمانی رخ میدهد که یک خطای JavaScript دریافت نشده، از Domain Boundaryها در نقض Policyهای میان ریشهای، رد میشود. برای مثال، اگر کد JavaScript خود را بر روی یک CDN میزبانی کنید، هر خطای دریافت نشدهای (خطاهایی که به جای قرار گرفتن در Try-catch در صفحه window.onerror handler نمایش داده میشوند) به جای این که اطلاعات کاملی از خود داشته باشد، به شکل ساده «Script error» گزارش داده میشود. این یک اقدام امنیتی است که در جهت جلوگیری از انتقال دادهها میان دامنهها طراحی شده است، و در غیر این صورت اجازه مخابره را نخواهد داشت.
برای دریافت پیامهای خطای اصلی، این مراحل را دنبال کنید:
۱- Access-Control-Allow-Origin را ارسال کنید
تنظیم Access-Control-Allow-Origin به «*» نشان میدهد که منبع مورد نظر از هر دامنهای به درستی قابل دسترسی است. اگر میخواهید، میتوانید «*» را با دامنه خود جایگزین کنید: برای مثال، Access-Control-Allow-Origin: www.example.com. گرچه، رسیدگی به چند دامنه، کمی پیچیده است و اگر از یک CDN استفاده میکنید، با توجه به مشکلاتی که ممکن است به وجود بیاورد، ارزش زحمات مورد نیاز را ندارد.
در زیر، مثالهایی از نحوه تنظیم این Header در محیطهای مختلف را میبینید:
Apache
در پوشهای که فایلهای JavaScript شما قرار دارند، فایلی به نام .htaccess بسازید و این محتویات را در آن قرار دهید:
Header add Access-Control-Allow-Origin "*"
Nginx
دستور العمل add-header را به بلوکی که فایلهای JavaScript شما را در خود دارد، اضافه کنید:
location ~ ^/assets/ { add_header Access-Control-Allow-Origin *; }
HAProxy
کد زیر را به backend کمکی خود که فایلهای JavaScript در آن قرار دارند، اضافه کنید:
rspadd Access-Control-Allow-Origin:\ *
۲- بر روی تگ اسکریپت، crossorigin را برابر با “anonymous” قرار دهید
در کد HTML خود، برای هر اسکریپتی که Access-Control-Allow-Origin را تنظیم کردهاید، بر روی تگ SCRIPT مقدار crossorigin=”anonymous” را قرار دهید. قبل از اضافه کردن ویژگی crossorigin، مطمئن شوید که Header مورد نظر به اسکریپت ارسال میشود. در فایرفاکس، اگر صفت crossorigin حاضر است اما Access-Control-Allow-Origin نیست، اسکریپت مورد نظر اجرا نخواهد شد.
5. TypeError: Object doesn’t support property
این خطا، خطایی است که وقتی یک متد تعریف نشده را فراخوانی میکنید، در اینترنت اکسپلورر بروز میدهد. میتوانید آن را در کنسول IE Developer آزمایش کنید.
این مورد معادل خطای «TypeError: ‘undefined’ is not a fuction» در Chrome است. بله؛ مرورگرهای مختلف میتوانند پیامهای مختلفی برای خطای مشابه داشته باشند.
این یک مشکل مشترک برای اینترنت اکسپلورر در وباپلیکیشنهایی که از JavaScript namespacing استفاده میکنند، است. وقتی که مسئله این خطا است، در ۹۹.۹ درصد موارد، این عدم توانایی اینترنت اکسپلورر در اتصال متدها در namespace فعلی به کلمه کلیدی this است که باعث بروز خطا میشود. برای مثال، زمانی که به JavaScript دستور میدهید تا Rollbar را با متد isAwesome نیماسپیس کند. به طور معمول، اگر در نیماسپیس Rollbar باشید، میتوانید متد isAwesome را با این سینتکس فراخوانی کنید:
this.isAwesome();
گوگل کروم، فایرفاکس و اپرا با خوشحالی این سینتکس را خواهند پذیرفت؛ اما اینترنت اکسپلورر در سمت دیگر، نخواهد پذیرفت. بدین ترتیب، بهترین روش در هنگام استفاده از JavaScript namespacing این است که namespaceها را از پیش تصحیح کنید.
Rollbar.isAwesome();
6. TypeError: ‘undefined’ is not a function
این خطا، زمانی که یک تابع تعریف نشده را فراخوانی میکنید، در Chrome بروز میدهد. میتوانید آن را در کنسول Chrome Developer و همچنین کنسول Mozilla Firefox Developer آزمایش کنید.
همینطور که تکنیکهای کدنویسی و الگوهای طراحی JavaScript در طی سالهای اخیر پیچیدهتر شده اند، افزایش متناظری در تکثیر scopeهای خود ارجاع در توابع پیشتیبان و Closureها وجود داشته است، که خود منبع بسیاری از گیجیهای توسعه دهندگان است.
این قطعه کد را در نظر داشته باشید:
function testFunction() { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); // what is "this"? }, 0); };
اجرای کد بالا، با این خطا مواجه میشود: “Uncaught TypeError: undefined is not a function.”. علت دریافت خطای بالا، این است که وقتی setTimeout() را فراخوانی میکنید، در واقع در حال فراخوانی window.setTimeout() هستید. در نتیجه، تابع ناشناسی که به setTimeout() انتقال داده میشود، در پنجره آبجکت تعریف میشود، که هیچ clearBoadr()ای ندارد.
یک راه حل سنتی و سازگار با مرورگرهای قدیمی، این است که در داخل یک متغیر که بتواند توسط یک closure میزبانی شود، به this ارجاع کنید. برای مثال:
function testFunction () { this.clearLocalStorage(); var self = this; // save reference to 'this', while it's still this! this.timer = setTimeout(function(){ self.clearBoard(); }, 0); };
روش جایگزین برای مرورگرهای جدید، این است که از متد bind() برای انتقال ارجاع مناسب استفاده کنید:
function testFunction () { this.clearLocalStorage(); this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this' }; function testFunction(){ this.clearBoard(); //back in the context of the right 'this'! };
7. Uncaught RangeError: Maximum call stack
این خطا، تحت دو شرایط میتواند در Chrome بروز دهد. یکی از آنها وقتی که است که شما یک تابع بازگشتی را فراخوانی میکنید، اما خاتمه نمیابد. میتوانید آن را در کنسول Chrome Developer آزمایش کنید.
حالت دیگر، وقتی که است که یک عدد خارج از محدوده را به یک تابع انتقال دهید. بسیاری از توابع، فقط محدودهای خاص از اعداد را برای مقدار ورودی خود میپذیرند. برای مثال، Number.toExponential(digits) و Number.toFixed(digits) اعداد 0 تا 20 را میپذیرند، و Number.toPrecision(digits) اعداد ۱ تا ۲۱ را میپذیرد.
var a = new Array(4294967295); //OK var b = new Array(-1); //range error var num = 2.555555; document.writeln(num.toExponential(4)); //OK document.writeln(num.toExponential(-2)); //range error! num = 2.9999; document.writeln(num.toFixed(2)); //OK document.writeln(num.toFixed(25)); //range error! num = 2.3456; document.writeln(num.toPrecision(1)); //OK document.writeln(num.toPrecision(22)); //range error!
8. TypeError: Cannot read property ‘length’
این خطا به علت خواندن طول ویژگی یک متغیر تعریف نشده در Chrome بروز میدهد. میتوانید آن را در کنسول Chrome Developer آزمایش کنید.
معمولا طولی که بر روی یک آرایه تعریف شده است را مییابید، اما ممکن است که اگر آرایهای راهاندازی نشده است، یا نام یک متغیر در یک متن دیگر مخفی شده است نیز به این خطا بر بخورید. بیایید این خطا را به کمک مثال زیر درک کنیم:
var testArray= ["Test"]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction();
وقتی که یک تابع را به همراه پارامترهایی تعریف میکنید، این پارامترها تبدیل به پارامترهای local میشوند. این به این معنی است که حتی اگر متغیرهایی با نام testArray دارید، پارامترهایی با همین نام که داخل یک تابع هستند، همچنان local در نظر گرفته میشوند.
برای حل این مشکل، دو راه دارید:
۱. پارامترهای داخل بیانیه تابع را حذف کنید. (اگر میخواهید به متغیرهای خارج از یک تابع دسترسی داشته باشید، به هیچ پارامتری برای تابع خود نیاز ندارید):
var testArray = ["Test"]; /* Precondition: defined testArray outside of a function */ function testFunction(/* No params */) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction();
۲. در هنگام انتقال آرایهای که تعریف کردیم، تابع را فراخوانی کنید:
var testArray = ["Test"]; function testFunction(testArray) { for (var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction(testArray);
9. Uncaught TypeError: Cannot set property
وقتی که سعی میکنیم به یک متغیر تعریف نشده دسترسی داشته باشید، همیشه مقدار undefined را بر میگرداند و نمیتوانیم هیچ یک از ویژگیهای undefined را دریافت کرده یا تنظیم کنیم. در این صورت، برنامه خطای «Uncaught TypeError cannot set property of undefined.» را نشان خواهد داد.
برای مثال، در مرورگر Chrome:
اگر آبجکت test وجود ندارد، خطای «Uncaught TypeError cannot set property of undefined.» بروز خواهد داد.
10. ReferenceError: event is not defined
این خطا وقتی بروز میدهد که سعی میکنید به یک متغیر که تعریف نشده است، یا خارج از scope فعلی قرار دارد، دسترسی داشته باشید. میتوانید آن را به راحتی در مرورگر Chrome آزمایش کنید:
اگر در هنگام استفاده از سیستم مدیریت رویداد با این خطا مواجه میشوید، مطمئن شوید که از آبجکت رویداد که به عنوان یک پارامتر منتقل میشود استفاده کنید. مرورگرهای قدیمیتر مانند اینترنت اکسپلورر، یک رویداد متغیر global را دارند، اما بر روی تمام مرورگرها پشتیبانی نمیشود. کتابخانههایی مثل jQuery سعی میکنند که این رفتار را معمولیتر کنند. با این اوصاف، بهترین روش این است که از آبجکت انتقال داده شده به تابع مدیریت رویداد خود استفاده کنید.
function myFunction(event) { event = event.which || event.keyCode; if(event.keyCode===13){ alert(event.keyCode); } }
نتیجه گیری
امیدواریم که چیز جدیدی یاد گرفته باشید و بتوانید در آینده از بروز خطاها جلوگیری کنید. اما همچنان، حتی در صورت استفاده از بهترین روشها نیز، برخی خطاهای غیر منتظره بروز میدهند.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید