Skip to content
On this page

بسم الله الرّحمن الرّحیم

شیءگرایی در جاوااسکریپت

شیء

با استفاده از اشیاء می‌توان یک سری مقادیر مرتبط با هم را یکجا در کنار همدیگر ذخیره کرد. مثلاً به‌جای ذخیره‌سازی اسم، فامیل، ایمیل و سنّ یک کاربر در چهار متغیّر مختلف، می‌توان فقط یک متغیّر ساخت و تمام مشخّصات کاربر را در قالب یک شیء در آن متغیّر ذخیره کرد.

js
let user = {
    'name': 'مجتبی',
    'family': 'قاسم زاده',
    'email': 'mojtaba@si2.ir',
    'phone-number': '+989373495980',
    'age': 22
}

به هر کدام از مواردی که سمت چپ علامت «:» نوشته می‌شود خصیصه (property) می‌گوییم. در مثال فوق name، family، email و age خصیصه‌های این شیء هستند. در صورتی که مقدار خصیصه یک function باشد به آن متُد (Method) می‌گوییم. برای دسترسی به مقادیر خصیصه‌ها می‌توان از عملگر براکت استفاده کرد.

js
alert(user['name']) // عبارت «مجتبی» نمایش داده می‌شود
user['family'] = 'قاسم زاده تهرانی' //‎ مقدار خصیصه‌ تغییر می‌کند
user['is-male'] = true // یک خصیصه‌ی جدید به شیء اضافه می‌شود
delete user['email'] // یک خصیصه از شیء حذف می‌شود

اسم خصیصه می‌تواند هر متنی باشد، ولی اگر در آن کاراکترهای غیر مجاز به کار نرفته باشد، مثل «فاصله» یا «-» یا هر کاراکتر دیگری که در نامگذاری متغیّرها مجاز نیست، می‌توان کمی ساده‌تر هم نوشت. کد زیر دقیقاً معادل همین مجموعه کدهای مثال‌های قبلی است.

js
let user = {
    name: 'مجتبی',
    family: 'قاسم زاده',
    email: 'mojtaba@si2.ir',
    'phone-number': '+989373495980', //در نام‌خصیصه کاراکتر غیرمجاز داریم
    age: 22
}

alert(user.name)
user.family = 'قاسم زاده تهرانی' 
user['is-male'] = true // در نام خصیصه کاراکتر غیر مجاز وجود دارد
delete user.email

مقدار خصیصه خودش می‌تواند یک شیء دیگر باشد:

js
let film = {
    title: 'به وقت شام',
    director: {
        name: 'ابراهیم',
        family: 'حاتمی کیا'
    }
}

alert(film.director.family) // حاتمی کیا

نکته: خود شیء در متغیّر ذخیره نمی‌شود، بلکه آدرس آن ذخیره می‌شود.

js
// یک شیء در حافظه ساخته میشود و آدرس آن در متغیری نگهداری می‌شود
let mojtaba = {
    name: 'مجتبی',
    family: 'قاسم زاده'
}

// همان آدرس در متغیّر دیگری ریخته می‌شود، یعنی شیء مزبور کپی نمی‌شود
let mohammad = mojtaba
mohammad.name = 'محمد'
alert(mojtaba.name) // محمد

آرایه

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

js
let studentNames = ['علی', 'اکبر', 'احمد']

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

js
alert(studentNames[0]) // Ali
alert(studentNames[2]) // Ahmad
studentNames[1] = 'Ebrahim' // تغییر مقدار یکی از آیتم‌ها
studentNames[3] = 'Moosa' // اضافه کردن آیتم جدید به لیست

آرایه‌ها در اصل اشیائی هستند که نام خصیصه‌هایشان عدد است. آرایه‌ها یک خصیصه‌ی خاص با نام length دارند که تعداد آیتم‌های لیست را نگهداری می‌کند.

js
let studentMarks = [20, 19, 18.5, 19.5, 19.5, 20]
alert(studentMarks.length) // 6

حلقه‌ی for in

گاهی لازم می‌شود به ازای تک تک خصیصه‌های شیء دستوراتی را تکرار کنیم. یعنی مثلاً اگر شیء مورد نظر ما ۵ خصیصه داشت، می‌خواهیم مجموعه دستورات ما ۵ مرتبه تکرار شود و در هر بار انجام آن دستورات به یکی از خصیصه‌ها دسترسی پیدا کنیم. در چنین مواقعی از حلقه‌ی for in استفاده می‌کنیم. مثال:

js
let phones = {
    'ali': '09343434340',
    'hassan': '09518198212',
    'farshad': '09344674385',
    'sadegh': '09356878964',
}

for (let user in phones) {
    console.log('Name is ' + user + ' And phone is ' + phones[user])
}

/**
 * خروجی به این شکل خواهد بود
 * Name is ali And phone is 09343434340
 * Name is hassan And phone is 09518198212
 * Name is farshad And phone is 09344674385
 * Name is sadegh And phone is 09356878964
 */

البته حلقه‌ی for in تنها برای اشیاء معمولی توصیه می‌شود و بهتر است برای آرایه‌ها از حلقه‌ی for معمولی استفاده کنید، به جای for in.

js
let users = ['ali', 'hassan', 'farshad', 'sadegh']

 for (let i = 0; i < users.length; i++) {
    console.log(i + ' - ' + users[i])
 }
 
 /* خروجی به این شکل خواهد بود
 * 0 - ali
 * 1 - hassan
 * 2 - farshad
 * 3 - sadegh
 */

تابع سازنده

یک روش دیگر برای ساخت اشیاء استفاده از تابع سازنده است. تابع سازنده یک تابع خاص است که برای ساخت اشیائی با نوع یکسان به کار می‌رود. مثلاً از آن‌جا که تمام اشیائی که مشخّصات کاربران را در خود نگهداری می‌کنند از نظر متدها و نام خصیصه‌ها یکسان هستند، می‌توانیم یک تابع خاص بنویسیم که باعث جلوگیری از انجام کارهای تکراری شود. تابع سازنده مثل تابع معمولی ساخته می‌شود و فقط نحوه‌ی فراخوانی آن کمی متفاوت است.

js
function User() {}
let ali = new User()

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

نکته: به تابع سازنده، «کلاس» نیز گفته می‌شود. وقتی تابعی با کلمه‌ی new فراخوانی شود به صورت خودکار یک شیء return می‌کند، حتّی اگر داخل خود تابع اصلاً کلمه‌ی return را ننوشته باشید یا اینکه حتّی چیزی را return کرده باشید که شیء نباشد!

اگر بخواهید در داخل خود تابع سازنده به این شیء جدیدی که به صورت خودکار return می‌شود دسترسی داشته باشید و خصیصه‌ها یا متدهایی به آن اضافه کنید از کلمه‌ی کلیدی this استفاده کنید.

js
function User(name, family) {
    this.name = name
    this.family = family
}

let hossein = new User('Hossein', 'Shamohammadi')

alert(hossein.name)   // Hossein

آشنایی با this

ابتدا با یک مثال شروع می‌کنیم

js
function logThis() {
    console.log(this)
}

let testObject = { 'logThis': logThis }
let testObject2 = {}

logThis()   // undefined
new logThis()   // object
new testObject.logThis()   // object
testObject.logThis()   // testObject
logThis.call(testObject2)   // testObject2
testObject.logThis.call(testObject2)   // testObject2

در جاوااسکریپت متغیّری وجود دارد به نام this. مقدار این متغیّر به صورت خودکار تعیین می‌شود و در شرایط مختلف مقادیر متفاوتی می‌گیرد. در حالت عادی اگر کد ما در حالت strict اجرا شود مقدار آن undefined است (و در حالت غیر strict معادل شیء window است).

کاربرد this در داخل بدنه‌ی تابع است و اینکه چه مقداری بگیرد به چگونگی فراخوانی تابع بر می‌گردد.

  • اگر یک تابع کاملاً عادی باشد و عادی هم فراخوانی شود، this معادل undefined می‌شود.
  • اگر تابع با کلمه‌ی new فراخوانی شود (تابع سازنده باشد)، this معادل همان شیء جدیدی است که تابع سازنده آن را تولید می‌کند و به صورت خودکار return می‌شود.
  • اگر تابع به عنوان متد یک شیء فراخوانی شود، this معادل همان شیء است. در نهایت اگر به هر دلیلی خواستید خودتان به صورت دستی در هنگام فراخوانی تابع، this را به آن بدهید می‌توانید از متد call استفاده کنید. پارامتر اوّل متد call همان this است، و پارامترهای بعدی آن به ترتیب همان پارامترهای ورودی تابع اصلی در هنگام فراخوانی هستند.

نکته: مباحثی که در مورد this تا به حال گفته شد مربوط به تابع پیکانی (Arrow function) نمی‌شود؛ آن‌ها به صورت کلّی تأثیری روی this نمی‌گذارند.

پروتوتایپ (Prototype)

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

هر تابعِ سازنده دارای خصیصه‌ای با نام prototype است. مقدار این خصیصه همیشه یک شیء است. نکته‌ی جالب اینجاست که هر خصیصه‌ای که به prototype داده شود، به صورت خودکار به شیئی که از آن کلاس ساخته می‌شود نیز تعلّق می‌گیرد.

js
function Circle(radius) {
    this.r = radius
}

Circle.prototype.getArea = function () {
    return 2 * Math.PI * this.r
}
Circle.prototype.getPerimeter = function () {
    return Math.PI * this.r * this.r
}

let c = new Circle(4)
alert( c.getArea() )
alert( c.getPerimeter() )

به دلیل کاربرد زیاد این مورد، از نسخه‌ی ۲۰۱۵ جاوااسکریپت روشی برای ساده‌تر برای نوشتن آن اضافه شد. کد زیر دقیقاً معادل کد بالاست:

js
class Circle {
    constructor(radius) {
        this.r = radius
    }
    getArea() {
        return 2 * Math.PI * this.r
    }
    getPerimeter() {
        return Math.PI * this.r * this.r
    }
}

let c = new Circle(4)
alert( c.getArea() )
alert( c.getPerimeter() )

به صورت کلّی هرگاه می‌خواهید به خصیصه‌ای از یک شیء دسترسی پیدا کنید (فرقی نمی‌کند از طریق براکت یا نقطه) ابتدا بررسی می‌شود که آیا خود همان شیء دارای آن خصیصه هست؟ اگر بود از همان استفاده می‌شود، وگرنه بررسی می‌کند آیا آن خصیصه در پروتوتایپ هست؟ و اگر بود از آن استفاده می‌شود.

ارث‌بری

فرض کنید در اپلیکیشن خود کلاسی به نام User دارید. ولی کاربران شما چند دسته می‌شوند که هر دسته علاوه بر متدهایی که هر شیء باید از کلاس User داشته باشد نیاز به یک سری متدهای دیگر هم دارند. یک عده اپراتورهای سیستم هستند، یک عده اعضای عادی سیستم هستند و یک یا چند نفر هم مدیران هستند. حالا ما باید به ازای تک تک این موارد کلاس‌های مجزّایی بسازیم (مثلا سه کلاس NormalUser و Operator و Admin). از طرفی همه‌ی این کلاس‌های مجزّا قرار است تمام متدهای کلاس User را نیز داشته باشند، چون به هر حال همه‌ی آن‌ها کاربران اپلیکیشن ما هستند و متدهای یکسانی از این جهت دارند. در چنین مواقعی از ارث‌بری استفاده می‌کنیم.

js
class User {
    constructor(name) {
        this.name = name
    }
    setPassword(newPassword) {
        this.password = newPassword
    }
}

class NormalUser extends User {
    constructor(name, email) {
        super(name) // تابع سازنده‌ی کلاس پدر را فراخوانی می‌کند
        this.email = email
    }
}

class Operator extends User {
    banUser(username) { /*...*/ }
}

class Admin extends Operator {
    deleteApplication() { /*...*/ }
}

let operator1 = new Operator('علی علوی')
operator1.setPassword('123')
operator1.banUser('فلانی')

همان طور که می‌بینید هنگام ساخت کلاس می‌توان با کلمه‌ی کلیدی extends از ارث‌بری استفاده کرد. وقتی می‌نویسیم class Operator extends User یعنی کلاس Operator تمام متدهایی را که کلاس User دارد باید داشته باشد و علاوه بر آن‌ها متدهای خاص خودش را نیز دارد. به کلاسی که از آن ارث‌بری می‌کنیم کلاس پدر، و به کلاسی که از پدرش ارث می‌برد کلاس فرزند می‌گوییم. در مثال قبلی Operator فرزند و User پدر اوست. اگر در کلاس فرزند متدی هم‌نام با یکی از متدهای کلاس پدر وجود داشته باشد، اولویّت با کلاس فرزند است.

اگر در داخل کلاس فرزند بخواهیم به یکی از متدهای کلاس پدر دسترسی پیدا کنیم می‌توانیم از کلمه‌ی کلیدی super استفاده کنیم.