مفهوم شیء (Object) در جاوا اسکریپت — از صفر تا صد

۶۰۱ بازدید
آخرین به‌روزرسانی: ۰۸ شهریور ۱۴۰۲
زمان مطالعه: ۱۱ دقیقه
مفهوم شیء (Object) در جاوا اسکریپت — از صفر تا صد

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

دقت کنید که در ادامه مقدمه نسبتاً طولانی در مورد مفاهیم ابتدایی شی‌ءها ارائه شده است. اگر علاقه‌ای به خواندن این بخش ندارید، می‌توانید با کلیک روی این لینک (+) به بخش متدها مراجعه کنید.

مفاهیم ابتدایی شیءها

شیء مجموعه‌ای از داده‌ها و/یا کارکردهای مرتبط است که معمولاً شامل چند متغیر و تابع هستند. این متغیرها و تابع‌ها وقتی داخل شیء هستند، مشخصات و متدها نامیده می‌شوند.

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

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

نمونه‌ای از شبه سگ/گرگ

// گرگ
// - مشخصات
// -- رنگ مو
// -- رنگ چشم
// -- اندازه
// -- جنسیت
// -- سن
// - اعمال
// -- راه رفتن/دویدن
// -- خوردن
// -- خوابیدن
// سگ بسط یافته از گرگ
// - مشخصات
// -- نام گونه
// - اعمال
// -- ادرار روی شیر آتش‌نشانی—

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

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

ایجاد شیء اولیه

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

// Object Literals

// made using {}
const jeff = {
  age: 25,
  name: 'Jeff',
  greet() {
    console.log('My name is ' + this.name)
  }
}

// access by key name
jeff.age // 25

// access by variable or string keyname
const n = 'name'

jeff[n] // 'Jeff'

// methods are the same
jeff.greet() // My name is Jeff
jeff['greet']() // My name is Jeff

دقت کنید که تابع greet در کد فوق یکی از ویژگی‌های ES6 است. به جای آن می‌توان کلیدی به صورت نرمال تعریف کرد و به آن مقدار یک تابع را داد یا این که ارجاعی به تابع داد یا از یک تابع درون‌خطی (inline) مانند greet: function() {} یا greet: greetFunc استفاده کرد.

روش‌های تعریف شیء

  • خصوصیت/کلید: درون هر شیء به طور معمول متغیرهایی وجود دارند. این متغیرها نام‌هایی دارند که از آن برای دسترسی به مقدار متغیر استفاده می‌کنیم مثلاً name: ‘jeff’
  • متد: متدها تابع‌هایی هستند که درون یک شیء قرار دارند و در اغلب موارد از مشخصات شیء استفاده می‌کنند.

this چیست؟

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

this متغیری است که هنگام ایجاد محتوای اجرایی از سوی جاوا اسکریپت تولید می‌شود و به یک شیء اشاره دارد. این که this به چه شیئی اشاره دارد، بستگی به این دارد که تابع چگونه فراخوانی شده است.

چارچوب اجرایی (Execution Context)  - زمانی که یک تابع فراخوانی می‌شود، یک چارچوب اجرایی ایجاد می‌شود. این چارچوب متغیرهای تابع، this و ارجاع به محیط بیرونی را در خود نگهداری می‌کند.

this به چه چیزی اشاره دارد؟

می‌دانیم که this شیئی است که از سوی جاوا اسکریپت هنگام اجرای تابع به ما داده می‌شود. اما از کجا بدانیم this به چه چیزی اشاره می‌کند؟ این مسئله به شیوه فراخوانی this وابسته است. در ادامه بیشتر توضیح می‌دهیم.

قواعد زیر بر حسب تقدم هستند:

1. اتصال new

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

'new' Keyword Binding

const ObjConstructor = function (x, y, z) {
  this.x = x
  this.y = y

  if (z) this.z = z
}

const one = new ObjConstructor(3, 2, 10) // { x: 3, y: 2, z: 10 }
const two = new ObjConstructor(7, 5) // { x: 7, y: 5 }

2. اتصال صریح

اتصال صریح: در این حالت this با استفاده از call، apply یا bind انتساب می‌یابد.

//explicit Binding

Function c () {

    Return this

}

//.call,.apply,.bind allows us to set this ourselves

c.call({ test: ‘binding’}) // {test: ‘binding’}

3. اتصال ضمنی

اگر تابع با یک شیء داخلش فراخوانی شود در این صورت this به آن شیء اشاره دارد، مانند person.greet

// Implicit Binding

this automatically set inside an object
const person = {
  name: 'Bob',

  greet: function () {
    return 'Hello, I am ' + this.name
  }
}

person.greet() // 'Hello, I am Bob'

دقت کنید که این تابع لازم نیست حتماً به صورت صریح داخل شیء باشد. بلکه می‌تواند ارجاعی به تابع باشد مانند greet: greetFunc. آنچه که مهم است این است که فراخوانی تابع به صوت ارجاع به شیء صورت گرفته باشد.

4. this پیش‌فرض

به طور پیش‌فرض this به شیء گلوبال اشاره دارد. اگر strict mod فعال شده باشد، این this مقدار تعریف نشده (undefined) خواهد داشت.

//Default This

function thisDefault() {
  return this
}

thisDefault() // window/global

function thisStrict() {
  "use strict"
  return this
}

thisStrict() // undefined

تابع‌های Arrow

قواعدی که در بخش فوق اشاره کردیم در مورد تابع‌های Arrow صدق نمی‌کنند. تابع‌های Arrow از this حاصل از تابع تعریف شده در آن، یا مقدار پیش‌فرض گلوبال استفاده می‌کنند.

// Arrow Function Binding

const arrow = () => {
  return this
}

arrow() // global/window

arrow.call({ test: 'binding' }) // global/window

function c () {
  return this
}

c.call({ test: 'binding' }) // { test: 'binding' }


const fromObj = {
  name: 'this in an object',

  deepThis: function() {
    const setname = (n) => {
      this.name = n

      return this
    }

    return setname('Deep this')
  }
}

fromObj.deepThis() // { name: 'Deep this', deepThis: [Function: deepThis] }

این‌ها قواعدی بودند که باید در مورد کاربرد this در جاوا اسکریپت در ذهن خود داشته باشید.

نمونه‌های اولیه (Prototypes)

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

  • وراثت – زمانی که یک شیء متدها و مشخصات خود را با شیء دیگری به اشتراک می‌گذارد، این حالت وراثت نامیده می‌شود.
  • وراثت کلاسیک – این نوع از وراثت را در زبان‌های برنامه‌نویسی مانند c، جاوا و غیره می‌توان مشاهده کرد.
  • وراثت پروتوتایپی – وقتی یک مشخصه را نتوان در یک شیء یافت، آن شیء در ساختار زنجیره مانند به نام پروتوتایپ جستجو می‌کند. اگر آن را در شیء پروتوتایپ بعدی نیابد به ترتیب پروتوتایپ‌های بعدی را می‌گردد.

برای مثال، وقتی یک آرایه دارید و از متدی مانند sort. استفاده می‌کنید که روی آرایه وجود ندارد. این متد روی پروتوتایپ قرار دارد.

در ادامه نمونه کد ساده‌ای از این وضعیت را ارائه کرده‌ایم:

// Prototype Chain Example

// simple dog object
const dog = {
  sound: 'Woof!',

  bark() {
    console.log(this.sound)
  }
}

dog.bark() // 'Woof!'

const max = {
  sound: 'RAAARGH!'
}

// dont set proto like this
// just using __proto__ for demo purposes
max.__proto__ = dog

max.bark() // RAAARGH!

// it doesnt see bark method on max
// so it goes up the prototype chain to dog
// it still uses max as 'this'

const catDog = {
  sound: 'Meowoof'
}

// 2 layers of prototypes!
catDog.__proto__ = max

catDog.bark() // Meowoof

// 1. catDog - does not have bark method
// 2. Go up prototype - max object does not have bark
// 3. Go up again - bark is on dog

در کد زیر نیز سازنده معمولی و پروتوتایپ‌ها مقایسه شده‌اند:

// Prototype Example

// lets first take a look at a constructor we saw earlier
// but we'll add a method called 'getCoords'

const ObjConstructor = function (x, y) {
  this.x = x
  this.y = y

  this.getCoords = () => {
    return [ this.x, this.y ]
  }
}

const one = new ObjConstructor(3, 2) // { x: 3, y: 2 }
const two = new ObjConstructor(7, 5) // { x: 7, y: 5 }

// testing that it works
one.getCoords() // [ 3, 2 ]
two.getCoords() // [ 7, 5 ]

// they each have their own function in memory!
one.getCoords === two.getCoords // false
Object.is(one.getCoords, two.getCoords) // false


// Now let's try with a prototype
// and see what happens

const withProto = function (x, y) {
  this.x = x
  this.y = y
}

withProto.prototype.getCoords = function() {
  return [ this.x, this.y ]
}

const pOne = new withProto(3, 2) // { x: 3, y: 2 }
const pTwo = new withProto(7, 5) // { x: 7, y: 5 }

// testing that it works
pOne.getCoords() // [ 3, 2 ]
pTwo.getCoords() // [ 7, 5 ]

// only one function exists in memory!
pOne.getCoords === pTwo.getCoords // true
Object.is(pOne.getCoords, pTwo.getCoords) // true

pOne.prototype // undefined - has no own prototype

// links to parent's prototype
pOne.__proto__ // withProto { getCoords: [Function] }

ساختار کلاس

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

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

  • تعریف کلاس (Class Definition ) – تعریف کلاس نشان دهنده روش معرفی کلاس است، یعنی کلاس از نوع سطح بالا است یا از کلاس دیگری بسط یافته است.
  • سازنده (Constructor ) – متدهای مقداردهی اولیه در هر وهله از کلاس است.
  • سوپر (Super) – از سوپر برای ارسال آرگومان‌ها به کلاس والد و استفاده از متدهای والد استفاده می‌شود.
  • متدهای استاتیک (Static Methods ) – متدهایی هستند که روی سازنده کلاس کار می‌کنند؛ اما روی وهله‌ها کاربردی ندارند.
  • متدهای وهله‌ای (Instance Methods) – متدهای دارای this که به وهله‌های خاصی از یک کلاس تعلق دارند.

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

// Classes

// we'll start with an Animal,
// very basic and make few assumptions

// top level class
class Animal {
  // the values we pass in go here
  constructor(name) {
    this.name = name
  }

  // static method - only exists on Animal constructor
  static extinct(species) {
    console.log(species + ' have gone extinct!')
  }
}

// use it by calling new ConstructorName(props)
const firstAnimal = new Animal('Frank')
// Animal { name: 'Frank' }

// we'll say a mammal comes next in the chain
// a mammal is an animal and has all the same properties
class Mammal extends Animal {
  constructor(name, hasFur) {
    // call the Animal constructor passing the name along
    super(name)

    this.hasFur = hasFur
    this.warmBlooded = true
    this.level = 0
  }

  // instance method that applys
  // to a specific mammal
  eat(food) {
    console.log(this.name + ' eats a ' + food)
    this.level++
  }
}


class Wolf extends Mammal {
  constructor(name) {
    // all wolves have fur, so pass true
    super(name, true)

    this.carnivore = true
  }
}

// static properties have to be
// defined outside of the class body
Wolf.speciesName = 'wolves'

const bob = new Wolf('Bob')
const fido = new Wolf('Fido')
Wolf {
  name: 'Fido',
  hasFur: true,
  warmBlooded: true,
  carnivore: true,
  level: 0
}

fido.eat('rabbit') // Fido eats a rabbit
// fido is now level 1, no other wolves affected

fido.extinct('Wolves') // undefined
Animal.extinct('Wolves') // Wolves have gone extinct!

نکات بنیادی مهم

موارد زیر نکات مهمی هستند که باید همواره به خاطر داشته باشید:

  • در جاوا اسکریپت هر چیزی که از نوع مقدماتی (PRIMITIVE) نباشد، یک شیء است.
  • روش‌های زیادی برای ایجاد شیء وجود دارد.
  • شیءها روشی برای توصیف اشیا یا داده‌های دارای مشخصات و متد هستند.
  • this در زمان اجرا تعیین می‌شود.
  • کلاس‌ها تنها یک نوع متفاوت از ساختار هستند که موجب سهولت برنامه‌نویسی می‌شوند (Syntactic sugar)

متدهای رایج

در ادامه قطعه کدهایی ارائه شده است که کاربرد متدها را در برخی موقعیت‌ها به همراه توضیحاتی نشان می‌دهند. در اغلب موارد شما متدهای خاص خود را روی شیءها تعریف می‌کنید؛ اما چند متد استاتیک هستند که همواره مفید محسوب می‌شوند. این متدها و تکنیک‌هایی برای کارهای خاصی مانند تعریف حلقه روی یک شیء در ادامه آمده است.

Getter ها و Setter ها

ما می‌توانیم از Getter و Setter برای کنترل دسترسی به شیء استفاده کنیم.

// Getters and Setters

// Getter
// allows us to return a custom value
// when property is accessed

const postsController = {
  postIds: [ 5, 3, 11, 22 ],

  get latest() {
    return this.postIds[this.postIds.length - 1];
  }
}

postsController.latest // 22

// Setter
// similar to Getter except
// it handles setting values

const browser = {
  active: 'google',
  history: [],

  set currentPage(val) {
    this.history.push(this.active)
    this.active = val
  }
}

browser.currentPage = 'bing'
browser.currentPage = 'medium'

console.log(browser)
{
  active: 'medium',
  history: [ 'google', 'bing' ],
  currentPage: [Setter]
}

استفاده از آرایه کلید-جفت در شیء

// Key-Pair Array to Object

// Convert this to an Object
const arr = [
  [ 'key', 'value' ],
  [ 'x', 100 ],
  [ 'y', 200 ]
]

// Use Object.fromEntries(iterable)

// results in
const obj = Object.fromEntries(arr)
{
  key: 'value',
  x: 100,
  y: 200
}

// convert back
Object.entries(obj)
[
  [ 'key', 'value' ],
  [ 'x', 100 ],
  [ 'y', 200 ]
]

غیر قابل تغییر (Immutable) ساختن یک شیء

می‌توان از ()Object.freeze برای غیر قابل تغییر ساختن یک شیء استفاده کرد.

کیفیت یک شیء فریز شده به صورت زیر است:

  • مشخصات جدیدی نمی‌توان اضافه کرد.
  • مشخصات موجود را نمی‌توان حذف کرد.
  • مقادیر مشخصات را نمی‌توان تغییر داد.
  • پروتوتایپ را نمی‌توان تغییر داد.
// Make an Object Immutable

// Use Object.freeze(obj)

const obj = {
  prop: 500
}

Object.freeze(obj)

obj.prop = 1

obj // { prop: 500 }

const arr = [ 1, 2, 3 ]

Object.freeze(arr)

arr[0] = 5
arr.push(2) // error

arr // [ 1, 2, 3 ]

کپی سطحی (Shallow) از یک شیء

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

// Shallow Copy

// Use Object.assign(target, ...sources)
// target being the new object,
// sources are objects you copy from

const a = {
  prop: 'value'
}

const b = {
  pos: {
    x: 500,
    y: 200
  }
}

const c = Object.assign({}, a)

// { prop: 'value' }

// values are copied, different objects
c === a // false
Object.is(c, a) // false


// references are still the same
const d = Object.assign({}, b)

// { pos: { x: 500, y: 200 } }
d.pos === b.pos // true
delete d.pos.x

console.log(d, b)
// both objects were affected
// { pos: { y: 200 } } { pos: { y: 200 } }


// Spread operator also shallow copies

const e = {...b}

e.pos === b.pos // true

کپی عمیق (Deep) یک شیء

کپی عمیق باعث می‌شود که کل یک شیء بدون هیچ ارجاعی به شیء قبلی در محل جدیدی کپی شود.

// Deep Copy

const basic = {
  pos: {
    x: 250,
    y: 500
  }
}

// Use JSON trick
// This will only work when there are no methods

// convert to a string then parse it back into JS
const clone = JSON.parse(JSON.stringify(basic))

clone.pos.x = 5

console.log(clone, basic)
// { pos: { x: 5, y: 500 } } { pos: { x: 250, y: 500 } }

تبدیل شیء به JSON

کد زیر روش تبدیل یک شیء به قالب JSON را نشان می‌دهد.

// Object to JSON

const obj = {
  pos: {
    x: 250,
    y: 500
  },

  privateKey: 123
}

// use JSON.stringify(val, replacer, space)
// Val usually being an object
// replacer for replacing values
// space for formatting

JSON.stringify(obj)

// ugly JSON string
'{"pos":{"x":250,"y":500},"privateKey":123}'

// make it pretty
JSON.stringify(obj, null, 2)
/*
{
  "pos": {
    "x": 250,
    "y": 500
  },
  "privateKey": 123
}
*/

// Replacer
JSON.stringify(obj, (key, val) => {
  if (key === 'privateKey') {
    return undefined
  }

  return val
})
'{"pos":{"x":250,"y":500}}'

// Array Replacer Filter

// only allow these keys
const allowed = ['pos', 'x', 'y']

JSON.stringify(obj, allowed)

'{"pos":{"x":250,"y":500}}'

تعریف حلقه روی شیء

کد زیر روش تعریف حلقه روی یک شیء را نشان می‌دهد:

// Loop an Object

const room = {
  x: 200,
  y: 200,
  children: {
    badGuy: {
      health: 500
    }
  }
}

// Loop Keys
Object.keys(room).forEach(key => {
  const val = room[key]

  console.log(key, val)
})
/*
  x 200
  y 200
  children { badGuy: { health: 500 } }
*/

// Loop Values
Object.values(room).forEach(val => {
  console.log(val)
})
/*
  200
  200
  { badGuy: { health: 500 } }
*/

// for loop, loops keys
for (let a in room) {
  console.log(a)
}
/*
  x
  y
  children
*/

// Entries
for (let [key, val] of Object.entries(room)) {
  console.log(key, val)
}
/*
  x 200
  y 200
  children { badGuy: { health: 500 } }
*/

بررسی وجود یک کلید در شیء

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

// Check if a Key exists in an Object

const obj = {
  a: 1
}

// Use 'in'
// be aware it will also look at prototype

'a' in obj // true
'b' in obj // false
'toString' in obj // true


// use obj.hasOwnProperty(prop)
// will not check prototype

obj.hasOwnProperty('a') // true
obj.hasOwnProperty('toString') // false

سخن پایانی

بدین ترتیب با مفاهیم و متدهای شیءها در جاوا اسکریپت آشنا شدیم. البته این‌ها همه موارد مرتبط با شیء نیستند و بدین منظور باید منتظر مطالب دیگر ما باشید. اما این مقاله نقطه شروع مناسبی برای درک مفهوم شیء و شیءگرایی در جاوا اسکریپت محسوب می‌شود.

اگر این مطلب برایتان مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
codeburst
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *