سلام
همه سوالها و جواباشون رو یه جا میتونی اینجا داشته باشی :)
جاوااسکریپت، سه تا روش داره تا مقادیر رو که وجود ندارند و یا نباید که به طور کلی وجود داشته باشند، رو نشون بده:
ا.تو یه سری شرایط هست که مقدار undefined برمیگرده (قبلش باید گفته بشه که undefined خودش یه دیتا تایپ مستقله):
1) زمانیکه متغیر بدون اینکه مقداری بهش واگذار بشه (assign)، تعریف میشه (declare)، درنتیجه تا زمانی که مقداردهی نشه، js بهش مقدار undefined میده:
let name; console.log(name); //undefined
2) زمانی که ابجکت یا ارایه مقدار دهی نشده باشن:
let numArray = [1,2,,4]; console.log(numArray); //[1, 2, empty, 4] typeof(numArray[2]) //"undefined"
3) زمانی که متغیری رو میخوای با تابعی مقدار دهی کنی که اون تابع اصلا مقدار بازگشتی نداره:
let add = (a,b) => { let c = a+b; //return c; } let sum = add(2,3); console.log(sum); //Output: undefined
یه کلمه رزرو شده در js محسوب میشه و میشه خیلی راحت با استفاده از خود کلمه، متغیر رو با مقدار null، مقداردهی کرد. درواقع null داره به مفسر جیاس میگه این متغیر مقداری نداره! null خودش یه دیتا تایپ یا نوع داده مستقله و زیر مجموعه هیچ کدوم از دیتا تایپهای دیگ نیست.
let life = null; console.log(life); //null
درواقع زمانی ما با ارور not defined مواجه میشیم که بخوایم از متغیری قبل تعریف شدنش استفاده کنیم:
//(1) console.log(a); //output: ReferenceError: a is not defined //------------------------------------------------------------
اگر از var استفاده کنیم، خروجی این میشه:
console.log(b); var b = 5; //Output:- undefined
چرا a اینجا خروجیش not defined زده ولی b ارورundefined نشون داده؟ به خاطر یه مفهمومی به نام hoisting. که بعدا احتمالا بهش میرسیم چون اینم یکی از پرتکراراست:).
تفاوت بین null , undefined:
نوع null و undefined با هم فرق میکنن(این که نوع داده null، ابجکت نشون داده میشه یه اشتباه/ سوتی شاید محسوب میشه)
console.log(typeof(undefined)); //"undefined" console.log(typeof(null)); //"object" , must be null
یکی دیگر از تفاوتها اینه که اگر عملیات حسابی (arithmetic) روی هر مقدار عددی و null انجام بشه، خروجی عدد خواهد بود ولی اگر با undefined انجام بشه، خروجی NaN (Not a Number) هستش.
console.log(+null) //0 let a = 7 + null; console.log(a); // 7 let b = 7 * null; console.log(b); // 0 console.log(+undefined) //NaN let c = 8 + undefined; console.log(c); //NaN let d = 8 * undefined; console.log(d); //NaN
پ.ن: خودم null رو به سوال اضافه کردم.
پ.ن2: جواب سوال ترجمه بخشی از اینجاست.
if( x <= 100 ) {...}
if( !(x > 100) ) {...}
قبلش یه توضیح درباره NaN:
جاوااسکریپت نوع داده عددی (Number) داره که برای مقادیر صحیح (integer) و غیرصحیح(float) استفاده میشه. در کنار این اعداد معمولی، یکسری مقادیر عددی خاص وجود داره که متعلق به این نوعِ دادهاست، که یکیش همین دوست عزیزمون NaN (Not a Number)هستش.
درواقع NaN، نشون دهنده ارور محاسباتی هست. به بیان دیگر، نتیجه درستنبودن یا تعریف نشدن عملیات ریاضی هست، به طور مثال (فقط حواست باشه که تو مثال زیر اگر به جای تقسیم عمل جمع بذاری نتیجه NaN نخواهد بود):
alert( "not a number" / 2 ); // NaN, such division is erroneous
یکی از کاربرداش میتونه مواقعی باشه که میخوای عدد بودن یا نبودن مقدار ورودی رو امتحان کنی.
خوب حال بریم سراغ جواب سوال:
یکی از چیزهایی که باید بهش توجه کرد، اینه که NaN نمیتونه مساوی یا بزرگتر یا کوچکتر از یه مقدار عددی باشه. NaN نه از 100 بزرگتره و نه مساویه 100 هست و نه از 100 کوچکتر. پس تو شرط اولی نتیجه false هست اما تو دومی به علت وجود ! درنهایت نتیجه شرط true خواهد شد و شرط برقراره.
این موضوع برای هر مقداری که به نوعِداده Number تبدیل میشه و خروجیش NaN میشه هم درسته، مثل: undefined, [1,2,3], {a:22}
در جاوااسکریپت وقتی ابجکت تعریف میکنیم، میتونیم از دور روش بهش فانکشن اضافهکنیم:
1- داخل خود کانستراکتور ابجکت:
this.func=function(){...}
2- با استفاده از prototype:
obj.prototype.func=function(){...}
در هر دورش، func() توسط تمام نمونههای (instances) ساختهشده از ابجکت، قابل دسترسه. اما استفاده از کدومش بهتره یا کاراتره؟
در یک کلمه: از prototype استفاده کن. چرا؟ در بیشتر شرایط کاراتره. اگر هدف، به اشتراک گذاشتن یه متد بین تمام نمونهها باشه، از پروتوتایپ استفاده بشه، کاراتره.
1 - تغیر متدی که با پروتوتایپ تعریف شده، اسانتره.
حتی بعد ازینکه نمونهها ساخته شدن، تغییر متدی که با پروتوتایپ تعریف شده، اسانتره. نمونههای قبل و بعد از تغییر متد، همه از کد اپدیتشده استفاده میکنن. درحالی که اگر به روش اول، داخل خود کانستراکتور ، متد رو تعریف کنی، این اتفاق نمیافته:
مثال:
function Parent(gender){ this.gender = gender; // attach the common function to each object instance. this.yellAtChild = function(){ console.log('Somebody gonna get a hurt real bad!'); } } // Let's create dad and mom and start yelling at kids. var dad = new Parent('male'); var mom = new Parent('female'); dad.yellAtChild(); // Somebody gonna get a hurt real bad! mom.yellAtChild(); // Somebody gonna get a hurt real bad! // but, Russell has decide to sue you if you use his catch phrase. // Let's try to tell our already created dad & mon to use different phrase. // ERROR: Not possible to do this way. Parent.yellAtChild = function() { .... } // You need to override the `yellAtChild` method for each object instance. dad.yellAtChild = function(){ console.log('Shut up!'); }; mom.yellAtChild = function(){ console.log('Go to bed!'); } dad.yellAtChild(); // Shut up! mom.yellAtChild(); // Go to bed
همانطور که میبینید اگر میخواید متد تغییر کنه، متد دونهدونه نمونهها رو تغییر بدین که خوب زمانبره!!
اما اگر با پروتوتایپ تعریف و بعدش بخوایم تغییر بدیم:
function Parent(gender){ this.gender = gender; } // Attach the common function to prototype. Parent.prototype.yellAtChild = function(){ console.log('Somebody gonna get a hurt real bad!'); }; // Let's create dad and mom and start yelling at kids. var dad = new Parent('male'); var mom = new Parent('female'); dad.yellAtChild(); // Somebody gonna get a hurt real bad! mom.yellAtChild(); // Somebody gonna get a hurt real bad! // but, Russell has decide to sue you if you use his catch phrase. // Simple: Just modify the function at Parent.prototype. Parent.prototype.yellAtChild = function(){ console.log('You are grounded.'); }; dad.yellAtChild(); // You are grounded mom.yellAtChild(); // You are grounded
الان که متد با پروتوتایپ تعریف شده، بعد از تغییر متد همه نمونهها هم باهاش اپیدت میشن. فقط این دوستمون که مثال رو زده خیلی عصبانی بوده:)
2- پورتوتایپ سریعتره و حافظه کمتری میگیره.
زمانی که شما متدی رو در کانستراکتور ابجکت تعریف میکنی، هر کدوم از نمونهها یک کپی از اون متد رو برای خودشون دارن که باعث میشه هم از نظر زمان و هم حافظه کارآمد نباشه.
اما برعکس، متد به پروتوتایپ ابجکت، پیوست بخوره، برای همه نمونهها، یک نسخه از متد وجود داره که باعث میشه بهینهتر از نظر زمانی و حافظه عمل کنه.
پ.ن1: جواب این سوال ترجمه این صفحه است.
کلا مفهوم کلوژر، یکی از مفاهیم به قول خارجیها under the hood محسوب میشه.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
به بیان خیلی سادهتر، کلوژر فانکشنیه که میتونه به متغیرهایی که بیرون خودش هم هستن، دسترسی داشته باشه. به بیان دیگر، کلوژر فانکشنیه که داخل یک فانکشن دیگ (تابع پدر)تعریف شده و میتونه به متغیرهایی که بیرون از خودش یعنی تابع پدر(ها)، تعریف شدهان، نیز دسترسی داشته باشه.
کلوژر به متغیرها در سه scop زیر، دسترسی داره:
برای اینکه این مفهوم رو عمیقتر متوجه بشیم، کلا باید درباره مفاهیم مربوط به nested functionها، کد بلاک و scope خوند و بعدش عمیقتر بشیم و بریم سراغ مفهمومی به نام lexical environment.
حقیقتا صحبت درباره این مفاهیم اینجا جاش نیست و حتی اگر بود، من نمیتونستم.
یه مثال ساده از کلوژر:
function makeFunc() { let name = 'Mozilla'; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc(); //mozilla
اینجا ما یک فانکشن اصطلاحا بیرونی (outer) و یا تابع پدر داریم که nested function هم هست. که درون خودش یک متغیر محلی (local)به نام name و یک تابع به نام displayName ( که inner function محسوب میشه)، تعریف کرده. مقدار بازگشتی تابع بیرونیمون هم، تابع displayName هست و وظیفه تابع درونیمون، هم نشوندادن مقدار ذخیرهشده در متغیر name هست.
وقتی myFunc رو برابر makeFunc() قرار میدیم، دv واقع داریم myFunc رو برابر مقدار بازگشتیِ تابعِ makeFunc قرار میدیم که درواقع همون تابع displayName هست.
وقتی myFunc() صدا زده میشه، خروجی "mozilla" رو نشون میده! اینکه چه اتفاقی میافته که این تابع میتونه به مقدار name که یک متغیر محلی محسوب میشه، دسترسی داشته باشه، نشون دهنده مفهموم کلوژر هست که برای فهم بیشتر باید همون کلمات کلیدی که بالا گفته شد رو خوند تا متوجه شد.
من برای یادگیرفتن این موضوع از منابع زیر کمک گرفتم:
1- https://amnsingh.medium.com/lexical-environment-the-hidden-part-to-understand-closures-71d60efac0e0
2- https://javascript.info/closure
کلا منبع خیلی خوبیه برای یادگیری جاوااسکریپت.
3- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
4- https://dev.to/abdulazizcode/scope-lexical-environment-4pdd
console.log(mul(2)(3)(4)); // output : 24 console.log(mul(4)(3)(4)); // output : 48
جواب:
function mul (x) { return function (y) { // anonymous function return function (z) { // anonymous function return x * y * z; }; }; }
تو این مثال، درواقع ما یک تابعی به نام mul تعریف کردیم که یک ارگیومنت x میگیره و خروجیش یه تابع دیگاست که اونم یه ارگیومنت دیگ به نام y میگیره و اونم خروجیش یک تابع دیگ هست که ارگیومنت دیگ به نام z میگیره و درنهایت خروجیش ضرب هر سه ارگیومنتهاست.
let mul2=mul(2); let mul3=mul(3); console.log(mul2(3)(4)); console.log(mul3(4)(5));
میتونیم اینجا بگیم که تابع mul درواقع کارخونه تولید تابعاست طوری که توابعی رو تولید میکنه که یک مقدار خاص رو در ارگیومنتهاش ضرب میکنه. تو مثال بالا کارخونه mul، دو تا تابع جدید تولید کرده که یکیش، 2 رو در ارگیومنتهاش ضرب میکنه و اون یکی، هم 3 رو در ارگیومنتهاش ضرب میکنه.
درواقع، mul2 و mul3 هر دو کلوژر هستند. هر دو یک بدنه تابع مشابه دارن، اما lexical environmentشان متفاوت هست. در lexical environment اولی، x برابراست با 2 و lexical environment دومی، x برابراست با 3.
فرض کنید یه ارایه داریم:
let arr=[1,2,3]
چهطوری میتوینم این ارایه رو خالی کنیم؟
جواب:
به چند روش میشه ارایه رو خالی کرد که باید براساس شرایط تصمیم گرفت از کدومش استفاده کنیم:
روش اول: ارایه رو به مقدار [] مقداردهی (assign) کنیم.
arr = [];
روش سریعیه ولی باید بدانیم که ما با assignکردن ارایه arr یعنی داریم مقدار جدیدی بهش میدیدم و اگر به arr اصلی یه ارایه دیگ بهش اشاره میکرد، اون ارایه دیگ، مقدارش خالی نمیشه:
let myArr=arr arr=[] console.log(myArr) //[1,2,3]
همانطور که میبینید ارایه myArr درستنخورده میمونه!
روش دوم: طول ارایه رو برابر صفر قرار بدیم.
arr.length=0
مقدار طول یا lengthِ هم قابلیت خوندن داره و هم میتونیم تغییرش بدیم. زمانی که طول ارایه رو برابر صفر قرار میدیم، تمام المنتهای درون ارایه رو حذف میکنیم. که این روش، برعکس روش قبلی، ریفرنسهایی که به ارایه اصلی میشد رو هم اپدیت میکنه و اونها رو هم خالی میکنه.
let myArr=arr arr.length=0 console.log(myArr) //[]
روش سوم: استفاده از متد ()splice.
arr.splice(0,arr.length)
متد splice، کلا وظیفهاش تغییر دادن محتوای اصلی ارایهاست و این کار رو با جایگزینی یا حذف محتوای ارایه و یا اضافهکردن ایتم جدید انجام میده. پیادهکردن این متد رو ارایه، ارایهمون تغییر میکنه.
اینجا داریم به متد splice میگیم که از ایندکس 0 شروع کن و به اندازه طول ارایه که در اینجا 3 هست، برو جلو، دونهدونه المنتها رو حذف کن.
تو این روش هم، ریفرنسهایی که به ارایه اصلی میشه، اپدیت میشن:
let myArr=arr; arr.splice(0,arr.length) console.log(myArr); //[]
روش سوم: استفاد از متد ()pop
متد pop کارش حذف المنت از اخر ارایه هست به این صورت که با هر بار پیادهکردنش روی یک ارایه، اخرین المنت از ارایه حذف میشه.
while(arr.length > 0){ arr.pop(); }
تو این روش هم، ریفرنسهایی که به ارایه اصلی میشه، اپدیت میشن ولی نسبت به روشهای قبل، کارایی کمتری داره.
برای این کار میتونیم از متد Array.isArray() استفاده کنیم. خروجی این متد true یا false هستش:
//Creating some variables var v1 = {name: "John", age: 18}; var v2 = ["red", "green", "blue", "yellow"]; var v3 = [1, 2, 3, 4, 5]; var v4 = null; //Testing the variables data type typeof(v1); // Returns: "object" typeof(v2); // Returns: "object" typeof(v3); // Returns: "object" typeof(v3); // Returns: "object" //Testing if the variable is an array Array.isArray(v1); // Returns: false Array.isArray(v2); // Returns: true Array.isArray(v3); // Returns: true Array.isArray(v4); // Returns: false
این متد تو بیشتر browserهای معروف و رایج ساپورت میشه.
یه روش دیگ وجود داره برای اینکار: toString(). این متد رو همه شیها از طریق پروتوتایپشون ارث میبرن و بهش دسترسی دارند. Array، Date, Function همه جزء اشیاء توکار(built-in) محسوب میشن. همه اینها درنهایت از Object.prototype ارث می برن. بهخاطر همینم یه عدهای میگن "همهچی از اشیاء ارث میبرن".
این یه عکسیه از این پیج برداشته شده:
حالا اینها چه ربطی به سوال داره؟ اینه که همه اینها به تابع toString دسترسی دارند. این تابع چیکار میکنه؟ این تابع به صورت خودکار هروقت که شی باید به صورت رشته نمایش داده بشه و یا زمانی که شیای در شرایطی بهش ارجاع بشه که یک رشته به عنوان خروجی ازش انتظار میره، صدا زده میشه!
Every object has a toString() method that is automatically called when the object is to be represented as a text value or when an object is referred to in a manner in which a string is expected
اگر این متد، دستکاری نشه، خروجیش به صورت "[object type]" خواهد بود.
let obj = {}; console.dir(obj.toString()); //[object object]
حالا ما میخوایم بدونیم ایا این شی از نوع ارایه هست یا نه؟
const toString = Object.prototype.toString; toString.call(new Date); // [object Date] toString.call(new Array); //[object Array] toString.call(new String); // [object String] toString.call(Math); // [object Math] // Since JavaScript 1.8.5 toString.call(undefined); // [object Undefined] toString.call(null); // [object Null]
میتونیم با استفاده از متد Function.prototype.call و یا Function.prototype.apply نوع شی رو پیدا کرد.
از سوال 8 تا سوال 13، درباره اپریتور delete هستش. اول سوالها رو به ترتیب مینویسم و بعد به ترتیب جوابش رو میذارم.
var output = (function(x) { delete x; return xک })(0); console.log(output);
var x = 1; var output = (function() { delete x; return x; })(); console.log(output);
var x = { foo : 1}; var output = (function() { delete x.foo; return x.foo; })(); console.log(output);
var Employee = { company: 'xyz' } var emp1 = Object.create(Employee); delete emp1.company console.log(emp1.company);
var trees = ["redwood", "bay", "cedar", "oak", "maple"]; delete trees[3];
var trees = ["xyz", "xxxx", "test", "ryan", "apple"]; delete trees[3]; console.log(trees.length);
The JavaScript delete
operator removes a property from an object; if no more references to the same property are held, it is eventually released automatically.
با استفاده از این اپریتور میشه پراپرتی یک ابجکت رو حذف کرد و بعد از حذفش، اون پراپرتی از دسترس خارج میشه و undefined برمیگردونه.
مقدار باززگشتی این اپریتور زمانی که بتونه پراپرتی رو حذف کنه، true هست، اگر اون پراپرتی رو پیدا نکنه مقدار بازگشتیش true هست ولی روی ابجکت تاثیری نداره!
این اپریتور روی متغیر، توابع هیچ تاثیری نداره و مقدار بازگشتیش false هستش.
نباید این اپریتور رو روی اشیاء پیشفرض مثل window, Math, Date استفاده بشه چون ممکنه باعث اختلال کل برنامه بشه!
به طور مثال:
let person = { name: "John", age:52 } console.log(delete person.age); //true console.log(person); //{name: 'John'}
حالا اگر این اپریتور رو روی تابع و یا متغیر استفاده کنیم:
let num=5; let sum=(a,b)=>{ return a+b; } console.log(delete num) //false console.log(delete sum) //false console.log(num) //num is still there, -> 5 console.log(sum(2,2)) //4
خروجی false به این علته که اپریتور delete روی متغیر چی محلی و چه گلوبالی و همینطور روی تابع تاثیر نداره و دستنخورده باقی میمانند.
اما، کد زیر خروجیش چیه؟
num = 5 console.log(delete num); //true console.log(num); // Uncaught ReferenceError: num is not defined
چرا؟ وقتی یه متغیر رو بدون کیورد var تعریف میکنیم، اون متغیر به عوان پراپرتی ابجکت window ذخیره میشه. پس وقتی روش delete میزنیم، درواقع داریم یکی از پراپرتیهای window رو حذف میکنیم.
جواب سوال 8 و 9: خروجی سوال 8، 0 و خروجی سوال 9، 1 هست.
همانطور که گفته شد این اپریتور فقط پراپرتیهای ابجکت رو حذف میکنه و روی توابع و متغیرها تاثیری نداره! در سوال 8 و 9، x شی نیست. در سوال 8، x یک متغیر محلی و در 9 یک متغیر سراسری محسوب میشه که با کیورد var تعریف شده. در هر دو، x دستنخورده باقی میمونه.
جواب سوال 10: undefined
اینجا، x یک شی و foo هم پراپرتیش هست. کاملا واضحه که عمل حذف پراپرتی با موفقیت انجام میشه و چون حذف شده، از دسترس خارج میشه و درنتیجه زمانی که یخوایم بهش دسترسی داشته باشیم، خروجی undefined هست.
جواب سوال 11: "xyz"
اینجا emp1 با استفاده از متد create از روی ابجکت Employee ایجاد شده:
The Object.create()
method creates a new object, using an existing object as the prototype of the newly created object.
درواقع با متد create میایم ابجکت جدیدی تولید میکنیم طوری که ابجکت موجود، به عنوان پرتوتایپ به ابجکت جدید اضافه میشه.
var Employee = { company: "xyz", }; var emp1 = Object.create(Employee); console.log(emp1); /* output: {} [[Prototype]]: Object <-------------- company: "xyz" [[Prototype]]: Object */
همانطور که میبینید، ابجکت emp1 پراپرتیِ company را تو پرتوتایپش داره و اپریتور delete روی پراپرتی موجود در پروتوتایپ تاثیری نداره. اما اگر به صورت زیر بنویسیم، عمل حذف انجام خواهد شد:
console.log(delete emp1.__proto__.company); //true
جواب سوال 12 و 13:
ایا میشه اپریتور delete رو روی المنتهای ارایه پیاده کرد؟ ازونجایی که ارایه خودش نوعی از ابجکت محسوب میشه، المنتهای ارایه رو میشه با این اپریتور حذف کنیم. اما باید حواسمون باشه که:
اپریتور، المنت رو حذف میکنه ولی طول ارایه و نیز ایندکسهای ارایه اپدیت نمیشن و اینکه در پوزیشن المنت حذفشده، empty/undefined نشون دادهمیشه.
var trees = ["redwood", "bay", "cedar", "oak", "maple"]; delete trees[3]; console.log(trees) /* output: (5) ['redwood', 'bay', 'cedar', empty, 'maple'] 0: "redwood" 1: "bay" 2: "cedar" 4: "maple" length: 5 [[Prototype]]: Array(0) */ console.log(trees[3]===undefined) //true
به جای ایندکس 3 که حذف شده، empty نشون داده میشه و طول ارایه همچنان 5 باقی مونده.
جواب سوال 13: گفتیم که این اپریتور طول ارایه رو بعد از عمل حذف اپدیت نمیکنه پس خروجی 5 خواهد بود.
منابع استفاده شده:
https://levelup.gitconnected.com/5-facts-about-delete-operator-in-javascript-c16fd2588cd
https://www.geeksforgeeks.org/javascript-delete-operator/