10 سوال ضروری جاوا اسکریپت

ترجمه و تالیف : عرفان حشمتی
تاریخ انتشار : 30 مهر 99
خواندن در 13 دقیقه
دسته بندی ها : جاوا اسکریپت

جاوا اسکریپت یک زبان برنامه نویسی سمت کلاینت است که در بیش از 90٪ وبسایت‌های جهان استفاده می‌شود. این یکی از پرکاربردترین زبان‌های برنامه نویسی در جهان است. بنابراین امروز در مورد 10 موضوع مهم صحبت خواهیم کرد.

10 سوال مهم پرسیده شده درباره جاوا اسکریپت

1 - چگونه یک عنصر خاص را از یک آرایه حذف کنیم؟

پاسخ اول

ابتدا شاخص عنصر آرایه‌ای را که می‌خواهید با استفاده از indexOf حذف کنید، پیدا کرده و سپس آن را با splice حذف کنید.

متد ()splice با حذف عناصر موجود و یا افزودن عناصر جدید، محتوای آرایه را تغییر می‌دهد.

const array = [2, 5, 9];

console.log(array);

const index = array.indexOf(5);
if (index > -1) {
  array.splice(index, 1);
}

// array = [2, 9]
console.log(array); 

پارامتر دوم splice تعداد عناصر حذف شده است. توجه داشته باشید که splice آرایه را در جای خود اصلاح می‌کند و آرایه جدیدی را برمی‌گرداند که شامل عناصری که حذف شده‌اند، می‌شود.

به دلیل کامل بودن، در اینجا توابع آمده است. تابع اول فقط یک رخداد را حذف می‌کند (یعنی حذف اولین تطبیق 5 از [2،5،9،1،5،8،5] )، در حالی که تابع دوم همه وقایع را حذف می‌کند:

function removeItemOnce(arr, value) { 
    var index = arr.indexOf(value);
    if (index > -1) {
        arr.splice(index, 1);
    }
    return arr;
}

function removeItemAll(arr, value) {
    var i = 0;
    while (i < arr.length) {
        if(arr[i] === value) {
            arr.splice(i, 1);
        } else {
            ++i;
        }
    }
    return arr;
}

پاسخ دوم

برای حذف یک عنصر از یک آرایه در فهرست i:

array.splice(i, 1);

اگر می‌خواهید هر عنصر با مقدار عددی را از آرایه حذف کنید:

for(var i = array.length - 1; i >= 0; i--) {
    if(array[i] === number) {
        array.splice(i, 1);
    }
}

اگر فقط می‌خواهید عنصر موجود در فهرست i را ایجاد کنید که دیگر وجود ندارد، اما نمی‌خواهید شاخص‌های عناصر دیگر تغییر کند:

delete array[i];

2 - چگونه می توان با استفاده از جی‌کوئری یا جاوا اسکریپت کاربر را از یک صفحه به صفحه دیگر هدایت کرد؟

پاسخ اول

کاربر به سادگی با استفاده از جی‌کوئری هدایت نمی‌شود.

جی‌کوئری ضروری نیست و (...)window.location.replace به بهترین وجه یک تغییر مسیر HTTP را شبیه‌سازی می‌کند.

(...)window.location.replace بهتر از windows.location.href است، زیرا replace() صفحه اصلی را در سشن نگه نمی‌دارد، این بدین معنی است که کاربر در یک بازگشت بی پایان گیر نمی‌کند.

اگر می‌خواهید شخصی را شبیه‌سازی کنید که روی لینک کلیک می‌کند، از location.href استفاده کنید.

اگر می‌خواهید تغییر مسیر HTTP را شبیه‌سازی کنید، از location.replace استفاده کنید.

برای مثال:

// similar behavior as an HTTP redirect
window.location.replace("http://stackoverflow.com");

// similar behavior as clicking on a link
window.location.href = "http://stackoverflow.com";

پاسخ دوم

همچنین می‌توانید این کار را همانطور که در زیر نشان داده شده انجام دهید.

$(location).attr('href', 'http://stackoverflow.com')

3 – closure در جاوا اسکریپت چگونه کار می‌کند؟

closure شامل موارد زیر است:

  • یک تابع
  • اشاره به دامنه بیرونی آن تابع (محیط واژگانی)

یک محیط واژگانی بخشی از هر زمینه اجرا (قاب پشته) است و یک نقشه بین شناسه‌ها (یعنی نام متغیرهای محلی) و مقادیر می‌باشد.

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

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

در کد زیر، فرم داخلی یک Closure با محیط واژگانی اجرا شده که هنگام فراخوانی foo ایجاد می‌شود:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

به عبارت دیگر، در جاوا اسکریپت توابع اشاره به "جعبه استیت" خصوصی دارند، که فقط آن‌ها (و سایر توابع اعلام شده در همان محیط واژگانی) به آن دسترسی دارند. این جعبه حالت برای تماس گیرنده عملکرد نامرئی است و مکانیزم بسیار خوبی را برای پنهان کردن داده‌ها و کپسوله‌سازی ارائه می‌دهد.

به یاد داشته باشید که توابع در جاوا اسکریپت می‌توانند مانند متغیرها (توابع کلاس اول) منتقل شوند، به این معنی که این جفت توابع و استیت‌ها می‌توانند در اطراف برنامه شما منتقل شوند. مشابه نحوه انتقال نمونه‌ای از یک کلاس در سی پلاس پلاس.

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

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

در سی و بیشتر زبان‌های رایج دیگر، پس از بازگشت یک تابع، متغیرهای محلی دیگر قابل دسترسی نیستند زیرا قاب پشته از بین رفته است. در جاوا اسکریپت، اگر تابعی را درون یک تابع دیگر فراخوانی کنید، متغیرهای محلی تابع خارجی پس از بازگشت از آن می‌توانند در دسترس باشند. به این ترتیب در کد بالا، پس از بازگشت از foo، از دسترس شی تابع داخلی مخفی باقی می‌ماند.

کاربرد Closureها

Closureها هر زمان که به یک حالت خصوصی مرتبط با یک تابع نیاز داشته باشید؛ مفید هستند. این یک سناریو بسیار متداول است و باید به یاد داشته باشید که جاوا اسکریپت تا سال 2015 یک سینتکس کلاس نداشت و هنوز هم یک سینتکس فیلد خصوصی ندارد. Closureها این نیاز را برآورده می‌کنند.

متغیرهای نمونه خصوصی

در کد زیر، تابع toString روی جزئیات ماشین Closure می‌شود.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

برنامه نویسی فانکشنال (تابع گرا)

در کد زیر، تابع داخلی روی fn و args Closure می‌شود.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

برنامه نویسی رویداد محور

در کد زیر، تابع onClick روی متغیر BACKGROUND_COLOR Closure می‌شود.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

ماژولاسیون

در مثال زیر، تمام جزئیات پیاده‌سازی در داخل یک عبارت تابعی که بلافاصله اجرا می‌شود، پنهان هستند. توابع tick و toString را روی حالت خصوصی و توابع Closure می‌کنند که آن‌ها برای تکمیل کار خود نیاز دارند. Closureها ما را قادر به تعدیل و کپسوله کردن کد خود کرده‌اند.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

مثال‌ها

مثال 1

این مثال نشان می‌دهد که متغیرهای محلی در Closure کپی نمی‌شوند. این Closure به خود متغیرهای اصلی اشاره دارد. مثل اینکه قاب پشته حتی بعد از خارج شدن تابع خارجی در حافظه باقی بماند.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

مثال 2

در کد زیر سه متد ثبت، افزایش و به روزرسانی همه در یک محیط واژگانی Closure می‌شوند.

و هر زمان که createObject فراخوانی شود، یک زمینه اجرای جدید (قاب پشته) ایجاد می‌شود و یک متغیر کاملا جدید x و یک مجموعه جدید از توابع (log و غیره) ایجاد می‌شود که روی این متغیر جدید Closure می‌شوند.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

مثال 3

اگر از متغیرهایی استفاده می‌کنید که با استفاده از var تعریف شده‌اند، مراقب باشید که بدانید کدام متغیر را بسته‌اید. متغیرهای اعلام شده با استفاده از var بلند می‌شوند. این مسئله در جاوا اسکریپت مدرن به دلیل معرفی let و const بسیار کمتر است.

در کد زیر هر بار در اطراف حلقه، یک تابع داخلی جدید ایجاد می‌شود که روی i بسته می‌شود. اما از آنجا که var i خارج از حلقه مرتفع می‌شود، همه این توابع داخلی روی یک متغیر بسته می‌شوند، به این معنی که مقدار نهایی (3)i سه بار چاپ می‌شود.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

نکات پایانی:

  • هر زمان که یک تابع در جاوا اسکریپت تعریف می‌شود، یک Closure ایجاد می‌شود.
  • بازگرداندن یک تابع از داخل یک تابع دیگر مثال کلاسیک Closure است، زیرا حالت داخل تابع خارجی حتی پس از اتمام تابع خارجی نیز به طور ضمنی در دسترس تابع داخلی است.
  • هر زمان که از ()eval در داخل یک تابع استفاده می‌کنید، از Closure استفاده کرده‌اید. متنی که ارزیابی می‌کنید می‌تواند به متغیرهای محلی تابع اشاره کند و در حالت غیر فشرده، حتی می‌توانید با استفاده از ('...=eval ('var foo متغیرهای محلی جدید ایجاد کنید.
  • هنگامی که شما از یک تابع (سازنده تابع) جدید در داخل یک تابع استفاده می‌کنید، در محیط واژگانی بسته نمی‌شود، بلکه در عوض بر بستر جهانی بسته می‌شود. تابع جدید نمی‌تواند به متغیرهای محلی تابع خارجی مراجعه کند.
  • یک Closure در جاوا اسکریپت مانند نگه داشتن مرجع (نه یک کپی) به دامنه در نقطه اعلان تابع است که به نوبه خود یک مرجع به دامنه خارجی آن را تا رسیدن به شی جهانی در بالای زنجیره دامنه حفظ می‌کند.
  • یک Closure وقتی ایجاد می‌شود یک تابع تعریف شده باشد. از این Closure برای پیکربندی متن اجرا هنگام فراخوانی تابع استفاده می‌شود. 
  • هر زمان تابعی فراخوانی شود، یک مجموعه جدیدی از متغیرهای محلی ایجاد می‌شود.

پاسخ جایگزین:

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

بنابراین هر زمان که کلمه کلیدی تابع را مشاهده کردید، کد موجود در آن تابع به متغیرهای اعلام شده در خارج از تابع دسترسی دارد.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

این گزارش 16 وارد سیستم می‌شود زیرا نوار تابع روی پارامتر x و متغیر tmp بسته می‌شود، که هر دو در محیط واژگانی تابع خارجی foo وجود دارد.

نوار تابع همراه با لینک آن با محیط واژگانی تابع foo یک Closure است.

برای ایجاد یک Closure نیازی نیست که یک تابع برگردد. به سادگی به موجب فراخوانی خود، هر تابعی در محیط واژگانی محصور شده Closure می‌شود.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

تابع فوق در گزارش 16 نیز ثبت خواهد شد زیرا کد موجود در نوار هنوز هم می‌تواند به آرگومان x و متغیر tmp اشاره کند، حتی اگر دیگر دامنه مستقیم نداشته باشد.

با این حال، از آنجا که tmp هنوز در داخل Closure نوار آویزان است، قابل افزایش است. با هر بار فراخوانی نوار، این عدد اضافه خواهد شد.

ساده ترین مثال Closure این است:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

وقتی یک تابع جاوا اسکریپت فراخوانی می‌شود، یک زمینه اجرایی جدید ec ایجاد می‌شود. همراه با آرگومان‌های تابع و شی مورد نظر. این زمینه اجرا همچنین لینکی به محیط واژگانی متن اجرای فراخوانی دریافت می‌کند، به این معنی که متغیرهای تعریف شده در محیط واژگانی خارجی (در مثال فوق، هر دو a و b) از ec در دسترس اند.

هر تابع Closureی ایجاد می‌کند زیرا هر تابعی لینکی به محیط واژگانی بیرونی خود دارد.

توجه داشته باشید که خود متغیرها از داخل یک Closure قابل مشاهده هستند، نه کپی آن.

4 - "use strict" در جاوا اسکریپت چه کاری انجام می‌دهد و دلیل آن چیست؟

پاسخ اول

نقل برخی از قسمت‌های جالب:

"Strict Mode یک ویژگی جدید در ECMAScript 5 است که به شما اجازه می‌دهد یک برنامه یا یک تابع را در یک زمینه عملیاتی "استریکت" قرار دهید. این زمینه دقیق مانع از انجام برخی اقدامات می‌شود و موارد استثنایی بیشتری را ایجاد می‌کند."

حالت استریکت از چند طریق به شما کمک می‌کند:

  • این روش برخی از موارد کد زنی معمولی را به خود اختصاص می‌دهد و استثناها را ایجاد می‌کند.
  • وقتی اقدامات نسبتا "ناامنی" انجام می‌شود (مانند دستیابی به شی گلوبال)، از بروز خطا جلوگیری می‌کند.
  • این کار ویژگی‌هایی را از کار می‌اندازد که گیج‌کننده و یا ضعیف هستند.

همچنین توجه داشته باشید که می‌توانید "Strict Mode" را روی کل فایل اعمال کنید یا می‌توانید از آن فقط برای یک تابع خاص استفاده کنید.

// Non-strict code...

(function(){
  "use strict";

  // Define your library strictly...
})();

// Non-strict code...

اگر مجبور باشید کدهای قدیمی و جدید را با هم ترکیب کنید، در این صورت مفید خواهد بود.

بنابراین این کمی شبیه "use strict" است که می‌توانید در Perl استفاده کنید. با شناسایی موارد بیشتری که منجر به شکستگی می‌شود، به شما کمک می‌کند خطاهای کمتری داشته باشید.

حالت استریکت اکنون توسط همه مرورگرهای اصلی پشتیبانی می‌شود.

در داخل ماژول‌های ECMAScript (با دستورات ایمپورت و اکسپورت) و کلاس‌های ES6، حالت استریکت همیشه فعال است و نمی‌توان آن را غیرفعال کرد.

پاسخ دوم

این ویژگی جدید ECMAScript 5 است.

این فقط رشته‌ای است که شما در فایل‌های جاوا اسکریپت خود قرار می‌دهید (یا در بالای فایل یا در داخل یک تابع) که به این شکل است:

"use strict";

اکنون قرار دادن این کد نباید مشکلی در مرورگرهای فعلی ایجاد کند زیرا این فقط یک رشته است. اگر کد شما عملی را نقض کند، ممکن است در آینده کدتان به مشکل بخورد. به عنوان مثال، اگر در حال حاضر بدون تعریف foo ابتدا "foo = "bar داشته باشید، کد شما به مشکل می‌خورد که از نظر ما چیز خوبی است.

5 - چگونه می‌توان بررسی کرد که آیا یک رشته در جاوا اسکریپت دارای زیر رشته است؟

پاسخ

ECMAScript 6، String.prototype.includes را معرفی می‌کند:

const string = "foo";
const substring = "oo";

console.log(string.includes(substring));

اگرچه پشتیبانی از Internet Explorer ندارد. در CMAScript 5 یا محیط‌های قدیمی‌تر، از String.prototype.indexOf استفاده کنید که وقتی یک زیر رشته پیدا نمی‌شود، 1- را برمی گرداند:

var string = "foo";
var substring = "oo";

console.log(string.indexOf(substring) !== -1);

String.prototype.includes در ES6 وجود دارد:

"potato".includes("to");
> true

توجه داشته باشید که این مورد در اینترنت اکسپلورر یا برخی دیگر از مرورگرهای قدیمی با پشتیبانی ES6 بدون نقص کار نمی‌کند. برای استفاده از آن در مرورگرهای قدیمی، ممکن است بخواهید از یک ترنسپایلر مانند Babel، یک کتابخانه shim مانند es6-shim یا این polyfill از MDN استفاده کنید:

if (!String.prototype.includes) {
  String.prototype.includes = function(search, start) {
    'use strict';
    if (typeof start !== 'number') {
      start = 0;
    }

    if (start + search.length > this.length) {
      return false;
    } else {
      return this.indexOf(search, start) !== -1;
    }
  };
}

6 - var functionName = function() {} vs function functionName() {}

پاسخ اول

تفاوت در این است که functionOne یک عبارت تابع است و بنابراین فقط هنگام رسیدن به آن خط تعریف می‌شود، در حالی که functionTwo یک اعلان تابع است و به محض اجرای تابع یا اسکریپت اطراف آن (به دلیل hoisting) تعریف می‌شود.

به عنوان مثال، یک عبارت تابع:

// TypeError: functionOne is not a function
functionOne();

var functionOne = function() {
  console.log("Hello!");
};

و یک تعریف تابع:

// Outputs: "Hello!"
functionTwo();

function functionTwo() {
  console.log("Hello!");
}

قبلا اعلان‌های تابع در داخل بلوک‌ها به طور متناقضی بین مرورگرها اداره می‌شدند. حالت استریکت (که در ES5 معرفی شده است) با استفاده از اعلان‌های تابع محصور آن‌ها، این مسئله را برطرف کرد.

'use strict';    
{ // note this block!
  function functionThree() {
    console.log("Hello!");
  }
}
functionThree(); // ReferenceError

پاسخ دوم

تابع abc() نیز محدوده‌بندی شده است، نام abc در محدوده‌ای که این تعریف با آن مواجه می‌شود تعریف می‌شود. مثال:

function xyz(){
  function abc(){};
  // abc is defined here...
}
// ...but not here

ثانیا ترکیب هر دو سبک نیز امکان پذیر است:

function xyz(){
  function abc(){};
  // abc is defined here...
}
// ...but not here

xyz قرار است تعریف شود. به طور معمول abc در همه مرورگرها تعریف نشده است اما اینترنت اکسپلورر تعریف آن را پشتیبانی نمی‌کند. اما در داخل body آن تعریف خواهد شد:

var xyz = function abc(){
  // xyz is visible here
  // abc is visible here
}
// xyz is visible here
// abc is undefined here

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

function abc(){};
var xyz = abc;

در این حالت، هر دو xyz و abc نام مستعار یک شی هستند:

console.log(xyz === abc); // prints "true"

یک دلیل قانع کننده برای استفاده از سبک ترکیبی، ویژگی "name" اشیا تابع است (توسط اینترنت اکسپلورر پشتیبانی نمی‌شود). اصولا وقتی تابعی مانند زیر تعریف می‌کنید:

console.log(xyz === abc); // prints "true"

نام آن به طور خودکار اختصاص داده می‌شود. اما وقتی آن را مانند زیر تعریف می‌کنید:

var abc = function(){};
console.log(abc.name); // prints ""

نام آن خالی است. یک تابع ناشناس ایجاد کردیم و آن را به برخی از متغیرها اختصاص دادیم.

دلیل خوب دیگر برای استفاده از سبک ترکیبی استفاده از نام داخلی کوتاه برای ارجاع به خود در حالی که نام طولانی و متناقضی را برای کاربران خارجی ارائه می‌دهد:

// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
  // Let it call itself recursively:
  shortcut(n - 1);
  // ...
  // Let it pass itself as a callback:
  someFunction(shortcut);
  // ...
}

در مثال بالا می‌توانیم همین کار را با یک نام خارجی انجام دهیم، اما بسیار نامناسب (و کندتر) خواهد بود.

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

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

function abc(){}

abc در همه جا از دامنه فعلی تعریف شده است:

// We can call it here
abc(); // Works

// Yet, it is defined down there.
function abc(){}

// We can call it again
abc(); // Works

همچنین از طریق یک عبارت بازگشت به بالا:

// We can call it here
abc(); // Works
return;
function abc(){}

این یک عبارت تابع است:

var xyz = function(){};

xyz در اینجا از نقطه انتساب تعریف می‌شود:

// We can't call it here
xyz(); // UNDEFINED!!!

// Now it is defined
xyz = function(){}

// We can call it here
xyz(); // works

اعلان تابع در مقابل بیان تابع دلیل اصلی وجود اختلاف است.

حقیقت خنده دار:

var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"

ما عبارت "بیان تابع" را ترجیح می‌دهیم زیرا از این طریق می‌توانیم میدان دید را کنترل کنیم. وقتی تابع را مانند زیر تعریف می‌کنیم:

var abc = function(){};

ما می‌دانیم که تابع را به صورت محلی تعریف کردیم. وقتی تابع را مانند زیر تعریف می‌کنیم:

var abc = function(){};

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

function abc(){};

7 - چگونه یک ویژگی را از یک شی جاوا اسکریپت حذف کنیم؟

پاسخ اول

می‌توانید مطابق کد زیر عمل کنید:

delete myObject.regex;
// or,
delete myObject['regex'];
// or,
var prop = "regex";
delete myObject[prop];

دمو

var myObject = {
    "ircEvent": "PRIVMSG",
    "method": "newURI",
    "regex": "^http://.*"
};
delete myObject.regex;

console.log(myObject);

پاسخ دوم

می‌توان اشیا موجود در جاوا اسکریپت را به عنوان نقشه‌ای بین کلیدها و مقادیر در نظر گرفت. اپراتور delete برای حذف این کلیدها، که بیشتر به عنوان ویژگی‌های شی شناخته می‌شود، یک بار استفاده می‌شود.

var obj = {
  myProperty: 1    
}
console.log(obj.hasOwnProperty('myProperty')) // true
delete obj.myProperty
console.log(obj.hasOwnProperty('myProperty')) // false

اپراتور حذف مستقیما حافظه را آزاد نمی‌کند و با اختصاص دادن مقدار null یا تعریف نشده به یک ویژگی متفاوت است، با اینکه خاصیت خودش از شی برداشته می‌شود. توجه داشته باشید که اگر مقدار یک ویژگی حذف شده از نوع مرجع (یک شی) باشد و قسمت دیگری از برنامه شما همچنان به آن شی ارجاع داشته باشد، مطمئنا آن شی زباله جمع نخواهد شد تا زمانی که همه منابع به آن دسترسی داشته باشند.

حذف فقط روی خصوصیاتی کار می‌کند که توصیفگر آن‌ها را به عنوان قابل تنظیم علامت گذاری می‌کند.

8 - کدام یک از اپراتورهای برابر (== در مقابل ===) باید در مقایسه‌های جاوا اسکریپت استفاده شود؟

پاسخ اول

عملگر برابری دقیق (===) یکسان با عملگر برابری انتزاعی رفتار می‌کند (==) با این تفاوت که هیچ نوع تبدیلی انجام نمی‌شود و باید یکسان باشند تا برابر باشند.

عملگر == پس از انجام هر تبدیل نوع ضروری، برای برابری مقایسه می‌کند. عملگر === تبدیل را انجام نمی‌دهد، بنابراین اگر دو مقدار از یک نوع نباشند === به راحتی false برمی‌گردد. هر دو به یک اندازه سریع هستند.

جاوا اسکریپت دارای دو مجموعه عملگر برابری است: === و ==! و دوقلوهای آن‌ها == و =!. موارد ذکر شده به خوبی همانطور که انتظار دارید کار می‌کنند. اگر دو عملوند از یک نوع باشند و مقدار یکسانی داشته باشند، === true تولید می‌کند و ==! False تولید می‌کند.

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

اینها برخی از موارد جالب است:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

10 سوال ضروری جاوااسکریپت

توصیه ما این است که هرگز از دوقلوها استفاده نکنید. در عوض، همیشه از === و ==! استفاده کنید.

تمام مقایسه‌های نشان داده شده با عملگر === نادرست هستند.

برای انواع مرجع == و === به طور مداوم مثل هم رفتار کنید (به جز در مورد خاص).

var a = [1,2,3];
var b = [1,2,3];

var c = { x: 1, y: 2 };
var d = { x: 1, y: 2 };

var e = "text";
var f = "te" + "xt";

a == b            // false
a === b           // false

c == d            // false
c === d           // false

e == f            // true
e === f           // true

حالت خاص این است که شما به دلیل روش toString یا valueOf، یک حرف واقعی را با شیئی که ارزش آن را به همان مقدار واقعی مقایسه می‌کنید.

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

"abc" == new String("abc")    // true
"abc" === new String("abc")   // false

در اینجا عملگر == مقادیر دو شی را بررسی می‌کند و true را برمی‌گرداند، اما === می‌بیند که یک نوع نیستند و false برمی‌گردند. کدام یک صحیح است؟ این واقعا به آنچه شما می‌خواهید مقایسه کنید بستگی دارد.

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

پاسخ دوم

استفاده از عملگر == (برابري)

true == 1; //true, because 'true' is converted to 1 and then compared
"2" == 2;  //true, because "2" is converted to 2 and then compared

استفاده از عملگر === (یکسانی)

true === 1; //false
"2" === 2;  //false

دلیل این امر این است که عملگر برابری == نوعی اجبار را اعمال می‌کند، به این معنی که مفسر قبل از مقایسه به طور ضمنی سعی در تبدیل مقادیر دارد.

از طرف دیگر، عملگر یکسانی ===  اجباری را انجام نمی‌دهد، بنابراین در هنگام مقایسه مقادیر را تبدیل نمی‌کند و بنابراین سریع‌تر است (مطابق با معیار بنچمارک JS)، زیرا از یک مرحله عبور می‌کند.

9 - کارآمدترین روش برای کلون سازی عمیق یک شی در جاوا اسکریپت چیست؟

پاسخ

Native deep cloning‍

این مورد "شبیه‌سازی ساختاریافته" نامیده می‌شود، به صورت آزمایشی در نود 11 به بعد کار می‌کند و امیدوارم در مرورگرها قرار بگیرد.

شبیه‌سازی سریع با از دست دادن داده‌ها - JSON.parse / stringify

اگر از Dates، توابع، تعریف نشده‌ها، Infinity ، RegExps ، Maps ، Sets ، Blobs ، FileLists ، ImageDatas، آرایه‌های پراکنده، آرایه‌های تایپ شده یا انواع پیچیده دیگر در شی خود استفاده نمی‌کنید، یک روش بسیار ساده برای کلون سازی عمیق یک شی این است:

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

کلون سازی قابل اطمینان با استفاده از کتابخانه

از آنجا که شبیه‌سازی اشیا بی‌اهمیت نیست (انواع پیچیده، منابع دایره‌ای، عملکرد و ...)، بیشتر کتابخانه‌های اصلی تابعی را برای شبیه‌سازی اشیا ارائه می‌دهند. چرخ را از نو اختراع نکنید! اگر قبلا از کتابخانه استفاده می‌کردید، بررسی کنید آیا عملکرد کلون سازی شی دارد. مثلا:

  • lodash - cloneDeep؛ از طریق ماژول lodash.clonedeep می‌تواند به صورت جداگانه وارد شود و اگر هنوز از کتابخانه‌ای استفاده نمی‌کنید که عملکرد کلون سازی عمیقی را ارائه می‌دهد، بهترین گزینه شماست.
  • AngularJS - angular.copy
  • jQuery – jQuery.extend(true, { }, oldObject); .clone() فقط عناصر DOM را کلون می‌کند.

ES6

برای کامل بودن، توجه داشته باشید که ES6 دو مکانیزم کپی کم عمق را ارائه می‌دهد: ()Object.assign و سینتکس گسترده، که مقادیر تمام خصوصیات قابل شمارش را از یک شی به شی دیگر کپی می‌کند. مثلا:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

پاسخ جایگزین

به این بنچمارک نگاهی بیندازید.

در آزمایشات قبلی که در آن سرعت نگرانی اصلی بود، یافتیم که:

JSON.parse(JSON.stringify(obj))

کندترین راه برای کلون سازی عمیق یک شی (کندتر از جی کوئری است، با دیپ فلگ درست با 10-20 درصد گسترش دهید).

وقتی دیپ فلگ روی false تنظیم شود (کلون کم عمق) jQuery.extend بسیار سریع است. این گزینه خوبی است، زیرا شامل برخی منطق‌های اضافی برای اعتبارسنجی است و از خصوصیات تعریف نشده و غیره کپی نمی‌کند، اما این امر نیز کمی شما را کند می‌کند.

اگر ساختار اشیایی را که می‌خواهید کلون سازی کنید یا می‌توانید از آرایه‌های تو در تو جلوگیری کنید، می‌توانید یک حلقه ساده (var i in obj) برای کلون کردن شی خود هنگام بررسی hasOwnProperty بنویسید و بسیار سریع‌تر از جی‌کوئری خواهد بود.

سرانجام اگر می‌خواهید یک ساختار شی شناخته شده را در یک حلقه کلون سازی کنید، می‌توانید با قرار دادن روش کلون سازی و ساخت دستی آن، عملکرد بسیار بیشتری داشته باشید.

موتورهای ردیابی جاوا اسکریپت در بهینه‌سازی حلقه‌ها، هنگامی که سرعت کاملا ضروری است کلون دستی شود.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

مراقب باشید از روش ((JSON.parse (JSON.stringify (obj در اشیا Date تاریخ استفاده کنید - (()JSON.stringify (new Date نمایش رشته‌ای از تاریخ را با فرمت ISO برمی‌گرداند، که ()JSON.parse دوباره آن را تبدیل نمی‌کند به یک شی Date. برای جزئیات بیشتر به این پاسخ مراجعه کنید.

به علاوه، لطفا توجه داشته باشید که حداقل در Chrome 65، کلون سازی بومی راهی نیست. طبق JSPerf ، انجام کلون سازی بومی با ایجاد یک تابع جدید تقریبا 800 برابر کندتر از استفاده از JSON.stringify است که در تمام نقاط جهان بسیار سریع است.

به روز رسانی برای ES6

اگر از Javascript ES6 استفاده می‌کنید، این روش بومی را برای کلون سازی یا کپی کم عمق امتحان کنید.

Object.assign({}, obj);

10 - چگونه می‌توان یک فایل جاوا اسکریپت را در یک فایل جاوا اسکریپت دیگر قرار داد؟

پاسخ

نسخه‌های قدیمی جاوا اسکریپت هیچ گونه import، include و یا require نداشتند. بنابراین رویکردهای مختلفی برای این مسئله ایجاد شده است.

اما از سال 2015 (ES6)، جاوا اسکریپت دارای استاندارد ماژول‌های ES6 برای وارد کردن ماژول‌ها در نود جی اس است که توسط اکثر مرورگرهای مدرن نیز پشتیبانی می‌شود.

برای سازگاری با مرورگرهای قدیمی، می‌توان از ابزارهای ساخت مانند Webpack و Rollup و یا ابزارهای انتقال مانند Babel استفاده کرد.

ماژول‌های ES6

ماژول‌های (ECMAScript (ES6 از نسخه v8.5 در نود جی اس با پرچم ماژول‌های آزمایشی و حداقل از نسخه v13.8.0 بدون پرچم پشتیبانی می‌شوند. برای فعال کردن "ESM" (در مقابل سیستم ماژول قبلی به سبک CommonJS یا [“CJS”]) یا از "type": "module" در package.json استفاده می‌کنید یا به فایل‌ها پسوند mjs. را می‌دهید. (به طور مشابه، ماژول‌های نوشته شده با ماژول قبلی Node.js CJS را می‌توان cjs. نامگذاری کرد اگر پیش فرض شما ESM باشد.)

استفاده از package.json:

{
    "type": "module"
}

سپس module.js:

export function hello() {
  return "Hello";
}

سپس main.js:

import { hello } from './module.js';
let val = hello();  // val is "Hello";

با استفاده از .mjs شما module.mjs را خواهید داشت:

export function hello() {
  return "Hello";
}

سپس main.mjs:

import { hello } from './module.mjs';
let val = hello();  // val is "Hello";

ماژول ECMAScript در مرورگرها

مرورگرها از Safari 10.1 ، Chrome 61 ، Firefox 60 و Edge 16 برای دانلود مستقیم ماژول‌های ECMAScript پشتیبانی کرده‌اند (پشتیبانی فعلی را در caniuse بررسی کنید. نیازی به استفاده از پسوند Node.js ’.mjs نیست. مرورگرها به طور کامل از پسوندهای فایل در ماژول‌ها / اسکریپت‌ها چشم پوشی می‌کنند.

<script type="module">
  import { hello } from './hello.mjs'; // Or it could be simply `hello.js`
  hello('world');
</script>
// hello.mjs -- or it could be simply `hello.js`
export function hello(text) {
  const div = document.createElement('div');
  div.textContent = `Hello ${text}`;
  document.body.appendChild(div);
}

Import های پویا در مرورگرها

ایمپورت پویا به اسکریپت اجازه می‌دهد اسکریپت‌های دیگر را در صورت لزوم بارگیری کند:

<script type="module">
  import('hello.mjs').then(module => {
      module.hello('world');
    });
</script>

Node.js require

سبک ماژول CJS قدیمی‌تر که همچنان به طور گسترده در نود جی اس استفاده می‌شود، سیستم require/module.exports است.

// mymodule.js
module.exports = {
   hello: function() {
      return "Hello";
   }
}
// server.js
const myModule = require('./mymodule');
let val = myModule.hello(); // val is "Hello" 

روش‌های دیگری نیز برای جاوا اسکریپت وجود دارد که شامل محتوای خارجی جاوا اسکریپت در مرورگرهایی است که نیازی به پیش پردازش ندارند.

AJAX Loading

می‌توانید یک اسکریپت اضافی را با یک تماس AJAX بارگذاری کنید و سپس از eval برای اجرای آن استفاده کنید. این ساده‌ترین راه است، اما به دلیل داشتن مدل امنیتی سندباکس جاوا اسکریپت به دامنه شما محدود می‌شود. همچنین استفاده از eval در را برای اشکالات، هک‌ها و مسائل امنیتی باز می‌کند.

Fetch Loading

مانند ایمپورت‌های پویا می‌توانید یک یا چند اسکریپت را با یک تماس واکشی با استفاده از promise های کنترل ترتیب اجرای وابستگی‌های اسکریپت با استفاده از کتابخانه Fetch Inject بارگیری کنید:

fetchInject([
  'https://cdn.jsdelivr.net/momentjs/2.17.1/moment.min.js'
]).then(() => {
  console.log(`Finish in less than ${moment().endOf('year').fromNow(true)}`)
})

jQuery Loading

کتابخانه جی کوئری قابلیت بارگذاری را در یک خط فراهم می‌کند:

$.getScript("my_lovely_script.js", function() {
   alert("Script loaded but not necessarily executed.");
});

Dynamic Script Loading

می‌توانید یک تگ اسکریپت با URL اسکریپت به HTML اضافه کنید. برای جلوگیری از سربار جی کوئری، این یک راه‌حل ایده‌آل است.

اسکریپت حتی می‌تواند در یک سرور دیگر قرار داشته باشد. علاوه بر این، مرورگر کد را ارزیابی می‌کند. تگ <script> می‌تواند به صفحه وب <head> تزریق شود، یا دقیقا قبل از تگ بسته شدن </bode> درج شود.

function dynamicallyLoadScript(url) {
    var script = document.createElement("script");  // create a script DOM node
    script.src = url;  // set its src to the provided URL

    document.head.appendChild(script);  // add it to the end of the head section of the page (could change 'head' to 'body' to add it to the end of the body section instead)
}

این تابع یک تگ <script> جدید به انتهای قسمت head صفحه اضافه می‌کند، جایی که ویژگی src روی URL تنظیم می‌شود که به عنوان اولین پارامتر به تابع داده می‌شود.

هر دو این راه‌حل‌ها در JavaScript Madness: Dynamic Script Loading مورد بحث و بررسی قرار گرفته است.

تشخیص زمان اجرای اسکریپت

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

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

var js = document.createElement("script");

js.type = "text/javascript";
js.src = jsFilePath;

document.body.appendChild(js);

var s = new MySuperObject();

Error : MySuperObject is undefined

سپس صفحه مورد نظر را با زدن کلید F5 بارگیری کنید و می‌بینید که کار می‌کند!

بنابراین در مورد آن چه باید کرد؟

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

function loadScript(url, callback)
{
    // Adding the script tag to the head as suggested before
    var head = document.head;
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    // Then bind the event to the callback function.
    // There are several events for cross browser compatibility.
    script.onreadystatechange = callback;
    script.onload = callback;

    // Fire the loading
    head.appendChild(script);
}

سپس کدی را که می‌خواهید پس از بارگذاری اسکریپت در تابع lambda بارگیری کنید، می‌نویسید:

var myPrettyCode = function() {
   // Here, do whatever you want
};

سپس همه این‌ها را اجرا می‌کنید:

loadScript("my_lovely_script.js", myPrettyCode);

توجه داشته باشید که ممکن است بسته به مرورگر و اینکه اسکریپت خط را وارد کرده‌اید، اسکریپت پس از بارگیری DOM یا قبل از آن اجرا شود.

سورس کد ادغام / پیش پردازش

همانطور که در بالای این پاسخ ذکر شد، بسیاری از توسعه دهندگان در پروژه‌های خود از ابزار ساخت/انتقال مانند Parcel ، Webpack یا Babel استفاده می‌کنند که به آن‌ها امکان می‌دهد از سینتکس جاوا اسکریپت آینده استفاده کنند. سازگاری معکوس برای مرورگرهای قدیمی‌تر، ترکیب فایل‌ها، کوچک کردن، انجام تقسیم کد و موارد دیگر.

پاسخ جایگزین

اگر به دنبال چیز پیشرفته‌تری هستید، RequireJS را امتحان کنید. مزایای اضافه شده مانند مدیریت وابستگی، همزمانی بهتر و جلوگیری از تکثیر (یعنی بازیابی اسکریپت بیش از یک بار) دریافت خواهید کرد.

می‌توانید فایل‌های جاوا اسکریپت خود را در "ماژول ها" بنویسید و سپس آن‌ها را به عنوان وابستگی در اسکریپت‌های دیگر ارجاع دهید. یا می‌توانید از RequireJS به عنوان یک راه‌حل ساده "go get this script" استفاده کنید.

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

define(['lib/dependency1', 'lib/dependency2'], function (d1, d2) {

     //Your actual script goes here.   
     //The dependent scripts will be fetched if necessary.

     return libraryObject;  //For example, jQuery object
});

implement.js فایل اصلی جاوا اسکریپت شما است که به some-dependency.js بستگی دارد.

require(['some-dependency'], function(dependency) {

    //Your script goes here
    //some-dependency.js is fetched.   
    //Then your script is executed
});

بخشی از GITHub README: RequireJS فایل‌های ساده جاوا اسکریپت و همچنین ماژول‌های تعریف شده بیشتری را بارگیری می‌کند. برای استفاده در مرورگر، از جمله در Web Worker بهینه شده است، اما می‌تواند در سایر محیط‌های جاوا اسکریپتی مانند Rhino و Node نیز استفاده شود. این برنامه Asynchronous Module API را پیاده سازی می‌کند.

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

جمع بندی

این 10 سوال متداول درباره جاوا اسکریپت است. اگر پیشنهاد یا انتقادی دارید، لطفا در بخش زیر با ما در میان بگذارید. در صورت نیاز به کمک، خوشحال خواهیم شد که به شما کمک کنیم.

منبع

گردآوری و تالیف عرفان حشمتی
آفلاین
user-avatar

عرفان حشمتی هستم، مهندس سخت افزار و برنامه نویس و طراح وب سایت، علاقه مند به دنیای آی تی و تکنولوژی، همچنین در حوزه ادیت فیلم و تصویر مطالعه و تمرین می کنم.

دیدگاه‌ها و پرسش‌ها

برای ارسال نظر لازم است ابتدا وارد سایت شوید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید