حاضری توی Object ها (جاوا اسکریپت) عمیق تری شی؟

سلام دوستان عزیز :) امروز قصد دارم راجب بحث پرکاربرد Object ها در جاوا اسکریپت (کمی بیشتر از یه ذره) باهاتون صحبت کنم.

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

خب هممون از تعریف کلیشه ای آبجکت که میگن خسته شدیم و هممون میدونیم که اشیاء دارای ویژگی (property) و عملکرد (method) هستند که ... ؛ اما بزارید کمی وارد محیط کد زنی بشیم (که برنامه نویسا با کد حال میکنن :))) )

const obj= {
   a: 5, 
   b: 6,
   add: function() {
    return this.a + this.b;
  }}
// obj -> { a: 5, b: 6, add: [Function: add] }

آره؛ به همین سادگی. اگر با زبان های سطح پایین کار کرده باشید میدونید چقدر برای همین آبجکت ساده باید بجنگید! (فلسفه زبان های اسکریپتی هم همینه که کمتر درگیر مسائل سطح پاین بشیم ؛))

خب حالا بریم سراغ معرفی چنتا ویژگی از همین آبجکت ها؛


استفاده از setters and getters:

const obj= {
	_name: &quotMojtaba&quot,
 	get name() {
		return this._name;
	},
 	set name(value) {
		this._name= value;
	}
}
console.log(obj.name); // &quotMojtaba&quot 
obj.name= &quotAli&quot
console.log(obj.name); // &quotMojtaba&quot 
// obj -> { _name: 'Mojtaba', name: [Getter/Setter] }

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

const obj= {
	_name: &quotMojtaba&quot, 
	getName() {
		return this._name
	},
 	setName(value) {
		this._name= value
	}
}
 console.log(obj.getName()); // &quotMojtaba&quot
 obj.setName('Ali');console.log(obj.getName()); // &quotAli&quot 
/*  obj ->
   {
    _name: 'Mojtaba',
    getName: [Function: getName],
    setName: [Function: setName]
  }
 */

بارز ترین تفاوتی که میتونیم متوجه بشیم اینه که برای گرفتن مقدار name در getter بدون پرانتز این کارو انجام میدیم ولی توی روش قدیمی دقیقا function call انجام میشه؛ همچنین برای ست کردن name در setter سمت راست مساوی ("obj.name= "Ali) که "Ali" هست به عنوان آرگومان به تابع set name پاس داده میشود؛ اما در روش سنتی این کار باز هم توسط function call انجام میشود.

(باید گفت که این ویژگی ها در کلاس ها هم به همین صورت صدق میکنه)

تابع سازنده (constructor) :

تا اینجای کار ما به صورت مستقیم دست به ساخت ابجکت ها میزدیم؛ اما اگه بتونیم یه سازنده داشته باشیم که خودکار این کارو انجام بده ایده ی بدی نیست :)

function Person(name, age) {
        this.name= name;
        this.age= age;
        this.presentation= function() {
                return `Hello, my name is ${this.name} and i'm ${this.age} years old.`
        }
}
const ali= new Person('Ali', 24);
console.log(ali.presentation()); // Hello, my name is Ali and i'm 24 years old.

توابع سازنده رو که بیشتر از کلاس ها میشناسیم؛ قبل از استاندارد ES6 به صورت بالا پیاده سازی میشد؛ اما میتونیم از سازنده ها توی ساختار کلاس ها به صورت زیر کد نویسی کنیم:

class Person {
        constructor(name, age) {
                this.name= name;
                this.age= age;
        }
        get presentation() {
                return `Hello, my name is ${this.name} and i'm ${this.age} years old.`
        }
}
const sara= new Person('Sara', 21);
console.log(sara.presentation);

ساختار بالا ساختار تمیز تری به نظر میاد و کار باهاش منطقی تره و از اونجایی که هدف این مقاله توضیح ساختار کلاس ها نیست ازش عبور میکنیم :)




برسی متد ها و ویژگی های Object:

تابع سازنده ()Object:

با استفاده از این تابع سازنده میتونیم بهش یه آبجکتی پاس بدیم؛ که برامون همون آبجکت رو بسازه (چه مسخره). اما اگه هیچ مقداری پاس ندیم یا null و یا undefined پاس بدیم؛ یه آبجکت خالی برامون میسازه.

const obj1= new Object() // obj is an empty object -> {}
const obj2= new Object(value) // value can be an object

متد ()Object.assign:

این متد برعکس ویژگی قبلی پر کاربرده و برای کپی کردن یک یا چند آبجکت به یک آبجکت مورد استفاده قرار میگیره؛‌ همونطور که در مثال زیر میبینید آبجکت مقدار source داخل آبجکت target کپی شده و مقدار اصلی آبجکت target رو تغیر داده و همونطور که میبینید (key) های مشترک رو فاکتور میگیره و آخرین مقدار رو میگیره (منظور از آخرین مقدار؛‌ مقدارِ آخرین آبجکت برای key مشترک هست)

مقدار بازگشتی این property هم همون target هست :)

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

متد ()Object.create:

این متد مربوط میشه به بحث مفصل prototype ها که (حتما) توی مقاله های بعد اون رو کامل توضیح میدم؛ اما تعریف کلی این ویژگی این هستش که میتونیم ویژگی های یک آبجکت رو به صورت prototype برای آبجکت جدیدمون به ارث بزاریم. (تاکید میکنم که از یه آبجکت و نه از یه سازنده!)

const person = {
   isHuman: false,
   printIntroduction: function() {
      console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
    }
};
const me = Object.create(person);
me.name = 'Matthew'; // &quotname&quot is a property set on &quotme&quot, but not on &quotperson&quot
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: &quotMy name is Matthew. Am I human? true&quot

همونطور که میبینید آبجکت me به صورت prototype هر دو تا ویژگی printIntroduction و isHuman رو به ارث میبره.

متد ()Object.entries:

متد ()Object.entries یک آرایه از آبجکت های (enumerable) به صورت [key, value] بر میگرداند (توجه کنید که هر آبجکت enumerable! که میتونه حتی یه string باشه.)؛‌ به مثال زیر توجه کنید:

const object1 = {
   a: 'somestring',
   b: 42
}; 
console.log(Object.entries(object1))
/*
 [ 
  [ 'a', 'somestring' ],
  [ 'b', 42 ]
 ]
 */

متد ()Object.fromEntrie:

این متد دقیقا برعکس متد قبلی هست؛ یعنی اگه چیزی مثل خروجی بالا رو بهش پاس بدیم یه آبجکت به ما بر میگردونه :)

const entries = new Map([
   ['foo', 'bar'],
   ['baz', 42]
]);
const obj = Object.fromEntries(entries);
console.log(obj);
// expected output: Object { foo: &quotbar&quot, baz: 42 }

متد ()Object.is:

زمانی که میخواهیم دو آبجکت رو از نظر برابری property و محتوا برسی کنیم ازش استفاده میکنیم. خروجی این متد مقدار Boolean میباشد.

Object.is('foo', 'foo');     // true
Object.is(window, window);   // true

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false

Object.is(null, null);       // true

// Special Cases
Object.is(0, -0);            // false
Object.is(-0, -0);           // true
Object.is(NaN, 0/0);         // true

متد ()Object.keys:

این متد یه آرایه از key های یک آبجکت رو به ما بر میگردونه. (میتونیم با استفاده ازش طول یه آبجکت یعنی تعداد property هاش رو به دست بیاریم).

const object1 = {
   a: 'somestring',
   b: 42,
   c: false
};
console.log(Object.keys(object1));
// expected output: Array [&quota&quot, &quotb&quot, &quotc&quot]

متد ()Object.values:

این متد هم بر عکس متد بالا فقط value های یک آبجکت رو در قالب یک آراریه بر میگردونه :)

const object1 = {
   a: 'somestring',
   b: 42,
   c: false
};
console.log(Object.values(object1));
// expected output: Array [&quotsomestring&quot, 42, false]

نتیجه کلی:‌

امیدوارم از این مقاله استفاده لازم رو کرده باشید؛ توی مقاله های بعدی راجب آبجکت ها بیشتر صحبت میکنیم.