مفهوم شیء (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
سخن پایانی
بدین ترتیب با مفاهیم و متدهای شیءها در جاوا اسکریپت آشنا شدیم. البته اینها همه موارد مرتبط با شیء نیستند و بدین منظور باید منتظر مطالب دیگر ما باشید. اما این مقاله نقطه شروع مناسبی برای درک مفهوم شیء و شیءگرایی در جاوا اسکریپت محسوب میشود.
اگر این مطلب برایتان مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای طراحی و برنامه نویسی وب
- آموزش جاوا اسکریپت (JavaScript)
- مجموعه آموزشهای ابزارها و راهکارهای مدیریت وبسایتها
- بررسی اشیاء در جاوا اسکریپت
- تابع های جاوا اسکریپت — راهنمای جامع
- ۱۰ کتابخانه و فریمورک جاوا اسکریپت که باید آنها را بشناسید
==