نکاتی بر جاوا اسکریپت<!-- --> - Husen's Blog

نکاتی بر جاوا اسکریپت

نکاتی بر JS که همیشه از ذهن من و اطرافیانم پاک می شوند و نیاز به یادآوری دارند. بعضی موارد در js هستند که در دیگر زبان ها وجود ندارند یا بصورت استاندارد تری وجود دارند و درک آنها در دنیای js سخت است چنین مواردی را هم اینجا اضافه میکنم.

destructuring آبجکت های تودرتو

const obj = {
    title: 'foo',
    child: {
        title2: 'bar'
    }
}

let {
    title,
    child,
    child: { title2 }
} = obj

console.log(title) // foo
console.log(child) // [object Object]
console.log(title2) // bar

تفاوت let و const و var

var اجازه می دهد تا یک متغیر با همان نام را در یک محدوده بدون ایجاد خطا مجدداً تعریف کنیم. ولی const و let دراین مورد خطا می دهد.

var name = 'John'
var name = 'Jim'
console.log(name) // -> 'Jim'

const name = 'John'
const name = 'Jim'
// -> ERROR

var دارای محدوده تابع است، در حالی که const/let دارای محدوده بلوکی هستند. بلوک کد ساختار {...} است که از دستورات تابع، if، for، while و غیره پیروی می کند

var اعلان ها را به بالای دامنه منتقل می کند، که متغیرها را در کل محدوده در دسترس قرار می دهد. به این کار hoisting گفته می شود و می تواند باعث ایجاد Error شود. اگر به متغیر const/let قبل از اعلان ارجاع دهید، خطا می دهد.

تفاوت اساسی const/let این است که درواقع هر متغییر یک بچسبی است برای یک ویژگی زمانی که به یک ویژگی برچسبی را با const تعریف کنیم دیگر این برچسب قابل تغییر نیست. ولی این را تضمین نمیکند که ویژگی ثابت بماند فقط تضمین میکند که برچسب ثابت خواهد ماند.

function f1() {
    // code block starts
    var name // declaration is invisibly hoisted here when the code is processed

    console.log(name) // -> undefined

    var name = 'John'
} // code block ends

function f2() {
    // no hoisting with let

    console.log(name) // -> ERROR

    const name = 'John'
}

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

const name = 'John'
name = 'Jim'
// -> ERROR

توابع بازگشتی (Recursion)

توابعی که خود را فراخوانی می کند. و باید داخل چنین توابعی از شرط ها استفاده کرد در غیر اینصورت تابع بازگشتی بصورت نامحدود به فراخوانی خود ادامه میدهد. قظعه کدی برای محاسبه فاکتوریل با توابع بازگشتی:

// program to find the factorial of a number
function factorial(x) {
    // if number is 0
    if (x === 0) {
        return 1
    }

    // if number is positive
    else {
        return x * factorial(x - 1)
    }
}
console.log(factorial(3)) // 6

اپراتور == و ===

اپراتور مساوی برابر بودن دو عملوند را چک کرده و بصورت Boolean جوابی برمیگرداند.

  • "==" فقط برابری ویژگی ها را چک می کند
  • "===" علاوه بر چک ویژگی ها نوع آنها را هم چک می کند
let num1 = 1
let num1s = "1"

let res1 = num1 == num1s
let res2 = num1 === num1s

console.log(`== oprator: ${res1}, === operator: ${res2}`) 
// Output: == oprator: true, === operator: false

value و reference types

ما در جاوا اسکریپت دو دسته تایپ داریم یکی value types هست و در بخش بعدی Refrence types هستند زمانی که refrence type باشد در حقیقت به رفرنس اون در مموری اشاره میکند ولی اگر value type باشد به خود آن متغیر اشاره میکند. بصورت خلاصه Primitive ها با ویژگی هایشان کپی می شوند و Object ها با ارجاع

let number = 10;
function inc(number) {
  number++;
}
inc(number);
console.log(number)

درصورت اجرای این کد خروجی 10 خواهد بود چرا که در بالا اشاره شد Primitive ها با ویژگی هایشان کپی می شوند.

حال اگر از Object استفاده کنیم:

let obj = { value: 10 }

function inc(obj) {
    obj.value++
}
inc(obj)
console.log(obj)

درصورت اجرا خروجی { value: 11 } خواهد بود چرا که Object ها با ارجاع کپی می شوند.

متد reduce()

این متد چهار آرگیومنت دارد که معمولا فقط دوتای اول را مورد استفاده قرار می دهند.

  • accumulator مقدار برگشتی تکرار قبلی
  • currentValue آیتم فعلی در آرایه
  • index ایندکس آیتم فعلی
  • array آرایه اصلی که reduce()بر روی آن فراخوانی شده است
  • initialValue این آرگیومنت اختیاری است. در صورت ارائه، به عنوان مقدار اولیه accumulator درنظر گرفته می شود.
const numbers = [1, 2, 3, 4, 5, 6]

let summation = numbers.reduce(
  (accumulator, currentValue) => accumulator + currentValue
)

console.log(summation) // 21

Hoisting

زمانی که از let و const برای تعریف متغیر استفاده میکنیم قبل تعریف آن نمیتوانیم مقداردهی کنیم و با ارور ReferenceError مواجه می شویم.(اگر از var برای تعریف متغیر استفاده کنید با ارور undfined روبرو می شوید)

var x = 5
var y

elem = document.getElementById("demo")
elem.innerHTML = x + " " + y

y = 7

// Output: x is 5 and y is undefined

قضیه درمورد توابع متفاوت است. درتوابع declared می شود بالاتر از تعریف تابع آنرا فراخوانی کرد.

hoisted() // Output: "This function has been hoisted."

function hoisted() {
    console.log('This function has been hoisted.')
}

ولی اگر تابع بصورت expression باشد باید بعد تعریف تابع عمل فراخوانی انجام بگیرد.

expression() //Output: "TypeError: expression is not a function

var expression = function () {
   console.log('Will this work?')
}

اگر هردو نوع تابع را باهم ترکیب کنیم باز هم باید بعد تعریف تابع عمل فراخوانی انجام بگیرد.

expression() // Ouput: TypeError: expression is not a function

var expression = function hoisting() {
   console.log('Will this work?')
}

درمورد کلاس ها هم فرقی ندارد به چه صورتی تعریف کنید در نهایت باید از قانون hoisting پیروی کنید.

var Polygon = class Polygon {
   constructor(height, width) {
       this.height = height
       this.width = width
   }
}

var Square = new Polygon()
Square.height = 10
Square.width = 10
console.log(Square)

Property Descriptors

پراپرتی های آبجک شامل سه ویژگی خاص است.که آنها را flag می نامند.

  • writable در صورتی که true باشد، مقدار را می توان تغییر داد. در غیر این صورت، فقط خواندنی در نظر گرفته می شود.
  • enumerable وقتی true است، در داخل حلقه قابل فهرست شدن است. در غیر این صورت قابل فهرست شدن نیست.
  • configurable اگر true باشد، ویژگی قابل حذف کردن است در غیر این صورت قابل حذف نیست.
let person = { name: 'Husen' }

Object.defineProperty(person, 'name', {
   writable: false,
   enumerable: false,
   configurable: false
})

person.name = 'John'
console.log(person) // Object { name: "Husen" }
console.log(Object.keys(person)) // Array []

delete person.name
console.log(person) // Object { name: "Husen" }

توابع و آبجکت ها

در جاوا اسکریپت همه فانکشن ها آبجکت هستند. و برای ساخت آجکت هم میشه استفاده کرد.

function make_person(firstname, lastname, age) {
   person = {}
   person.firstname = firstname
   person.lastname = lastname
   person.age = age
   return person
}
make_person('Joe', 'Smith', 23)
// {firstname: "Joe", lastname: "Smith", age: 23}

یک تابع میتواند به this ارجاع دهد و اگر با عملگر new فراخوانی شود، یک آبجکت با ویژگی هایی که بر روی this تعریف شده اند را برمیگرداند. this در چنین مواردی به شی جدیدی که ما ایجاد می کنیم اشاره می کند.

function make_person_object(firstname, lastname, age) {
   this.firstname = firstname
   this.lastname = lastname
   this.age = age
   // Note, we did not include a return statement
}

تفاوت کلیدی بین make_person و make_person_object این است که اگر ما make_person را با کلمه کلیدی this تعریف کنیم هیچ تفاوتی ایجاد نخواهد کرد. با این حال، فراخوانی make_person_object() بدون عملگر new، این ویژگی های ما را در این شیء فعلی مشخص می کند (به طور کلی window اگر در مرورگر کار بکنیم.)

var Joe = make_person_object('Joe', 'Smith', 23)
console.log(Joe) // undefined
console.log(window.firstname) // "Joe" (oops)

var John = new make_person_object('John', 'Smith', 45)
console.log(John) // {firstname: "John", lastname: "Smith", age: 45}

توابع regular و constructor

متد .bind()

توابع genrator

genrator ها نوع خاصی از تابع هستند که الگوریتم iteration را تعریف می کند و اجرای آن پیوسته نیست اما می تواند متوقف شود و بعداً از سر گرفته شود. context آن بین ورودی های متوالی به خاطر سپرده می شود.

توابع genrator با کلمه کلیدی function* تعریف می شوند.

function* daysGenerator() {
   const daysList = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
   for (let i = 0; i < daysList.length; i++) {
       yield daysList[i]
   }
}

const daysGen = daysGenerator()
console.log(daysGen.next())

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

هنگامی که متد next() روی genrator فراخوانی می شود، تابع genrator تا زمانی که با اولین کلمه کلیدی yield مواجه شود اجرا می شود. کلمه کلیدی yield اجرای تابع را متوقف می کند و مقدار عبارتی را که بعد از کلمه کلیدی yield است برمی گرداند. زمانی که متد next() صدا زده می شود، genrator اجرا را با دستور پس از yield از سر می گیرد. کد را تا زمانی که به yield بعدی یا انتهای genrator برسد اجرا می شود.

function* simpleGenerator() {
    yield 1
    yield 3
    yield 5
}

const simpleGen = simpleGenerator()

console.log(simpleGen.next()) // { value: 1, done: false }
console.log(simpleGen.next()) // { value: 3, done: false }
console.log(simpleGen.next()) // { value: 5, done: false }
console.log(simpleGen.next()) // { value: undefined, done: true }

متد .every() و .some()

متد .every() چک میکند که آیا همه آیتم ها شرط لازم دا دارند یا نه. و متد .some() چک میکند که حداقل یکی از آیتم ها شرط لازم را داشته باشد.

const ages = [32, 33, 16, 40]
console.log(ages.some(checkAge)) // true
console.log(ages.every(checkAge)) // false

function checkAge(age) {
    return age > 18
}