this در جاوااسکریپت را بهتر بشناسیم
13 اردیبهشت
کلیدواژهی this در جاوااسکریپت یکی از مفهومهایی است که باعث سردرگمی مبتدیان این زبان میشود. شاید یکی از دلایل این موضوع این باشد که کلیدواژهی this در جاوااسکریپت، در مقایسه با زبانهای برنامهنویسی دیگر اندکی متفاوت است. از طرفی یادگیری این کلیدواژه بسیار ضروری است. چرا که برای خواندن و نوشتن کدهای حرفهای و درک مفاهیم جاوااسکریپت همواره به آن نیاز خواهیم داشت. در این مقاله به بررسی این کلیدواژه و کاربردهای مختلفی که در زبان جاوااسکریپت دارد میپردازیم.
this در محیط گلوبال
اگر کلیدواژهی this
در محیط گلوبال (به انگلیسی: Global)، یعنی خارج از توابع به کار برود، به آبجکت گلوبال اشاره خواهد کرد. از آنجایی که در مرورگرها آبجکت گلوبال همان window
است، کلیدواژهی this
معادل window
خواهد بود. به مثال زیر توجه کنید:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
با توجه به کد بالا، this
مساوی window
است و تعریف کردن متغیر بدون کلیدواژه (خط 3) یا به صورت پراپرتی (به انگلیسی: Property) برای آبجکت this
یا به صورت پراپرتی برای آبجکت window
به یک معنی خواهند بود. در اینجا ذکر این نکته میتواند مفید باشد که کلیدواژهی globalThis
در هر جایی فراخوانی شود، اعم از محیط گلوبال یا داخل توابع، مستقیما به آبجکت گلوبال که در مرورگرها آبجکت window
است اشاره میکند.
this در محیط توابع
در محیط توابع، مقدار this
به این بستگی دارد که تابع چطور فراخوانی شود:
فراخوانی معمولی توابع
در فراخوانی سادهی توابع اگر در Strict mode نباشیم، this
آبجکت گلوبال را برمیگرداند که در مرورگرها آبجکت window
است:
function f1() {
return this;
}
// Dar Morur-gar:
f1() === window; // true
// Dar Nodejs
f1() === global; // true
با این حال اگر در حالت Strict mode باشیم، در فراخوانی ساده، که this
مقداردهی نمیشود، this
مقدار undefined
را برمیگرداند:
function f2() {
'use strict'; // strict mode
return this;
}
f2() === undefined; // true
برای مقداردهی به this
هنگام فراخوانی تابع، از متدهای call()
و apply()
استفاده میکنیم:
function add(c, d) {
return this.a + this.b + c + d;
}
var p = {a: 1, b: 3};
add.call(p, 5, 7); // 16
add.apply(p, [10, 20]); // 34
در خط 7 از متد call()
استفاده کردهایم. در این متد اولین پارامتر آبجکتی است که میخواهیم this
نمایندهی آن باشد (در اینجا آبجکت p
). بقیهی پارامترها، به عنوان آرگومانهای تابع، به آن فرستاده میشوند. پس در خط 7، this
را نمایندهی آبجکت p
کردیم و اعداد 5 و 7 را به ترتیب، برای آرگومانهای c
و d
فرستادیم. استفاده از متد apply()
نیز در اینجا مشابه متد call()
است؛ با این تفاوت که آرگومانها به صورت آرایه به تابع فرستاده میشوند.
همچنین در حالت non-strict اگر مقداری که به this
اطلاق میکنیم از نوع آبجکت نباشد، جاوااسکریپت تلاش میکند آن را به آبجکت تبدیل کند. بنابراین اگر مقادیر ابتدایی (به انگلیسی: primitive) مانند عدد 7 یا رشتهی 'foo'
به this
اطلاق شوند، این مقادیر به آبجکت تبدیل میشوند. عدد 7 مانند حالتی که به شکل new Number(7)
تعریف شود و رشتهی 'foo'
مشابه وقتی که به شکل new String('foo')
تعریف شود. برای مثال:
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call('foo'); // [object String]
متد bind
در نسخهی 5 اکمااسکریپت، Function.prototype.bind()
معرفی شد. استفاده از f.bind(sth)
تابعی میسازد که بدنه و حوزهی (به انگلیسی: scope) تابع f
را دارد. اما آبجکت sth
برای همیشه به this
استفاده شده داخل تابع f
میچسبد و از این تابع در هر شرایط و به هر نوعی استفاده کنیم، this
آن تغییری نخواهد داشت.
function f() {
return this.a;
}
var g = f.bind({a: 'azerty'});
console.log(g()); // azerty
var h = g.bind({a: 'yoo'}); // bind tanha 1 baar kaar mikonad!
console.log(h()); // azerty
var o = {a: 37, f: f, g: g, h: h};
console.log(o.a, o.f(), o.g(), o.h()); // 37,37, azerty, azerty
this در توابع فِلِش
هنگام تعریف توابع فلش (به انگلیسی: Arrow Function)، مقدار this
هرچه که باشد، همان مقدار به عنوان this
تابع فلش استفاده میشود. مثلا اگر تابع فلش در محیط گلوبال تعریف شود، this
داخل آن به آبجکت گلوبال اشاره میکند. مثال:
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
حال اگر هنگام صدا زدن تابع فلش از متدهای call()
،apply()
یا bind()
استفاده کنیم، تابع فلش از استفاده آنها صرف نظر میکند. انتقال آرگومان با استفاده از این متدها همچنان برقرار است اما اولین آرگومان که this
را مشخص میکرد بایستی null
ست شود:
var foo = (() => this);
// ُSeda zadan be onvane methode yek object:
var obj = {func: foo};
console.log(obj.func() === globalObject); // true
// talash baraye avaz kardane this ba methode call():
console.log(foo.call(obj) === globalObject); // true
// talash baraye set kardane this ba methode bind():
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
مهم نیست چکار کنیم. this
تابع foo
همان چیزی خواهد بود که هنگام تعریف بوده است. در مثال بالا this
هنگام تعریف foo
آبجکت گلوبال بوده است. همین موضوع برای توابع فلشی که داخل توابع دیگر تعریف میشوند هم صادق است. this
آنها همانی خواهد بود که this
تابع بیرونی آنها به آن اشاره دارد:
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
var fn = obj.bar();
console.log(fn() === obj); // true
var fn2 = obj.bar;
console.log(fn2()() === window); // true
در کد بالا یک متد برای آبجکت obj
تعریف کردهایم که یک تابع فلش را برمیگرداند که خود آن تابع فلش مقدار this
داخل خودش را برمیگرداند (خط 3). چون this
در داخل متد bar
به obj
اشاره میکند، طبق گفتههای بالا انتظار داریم this
تابع فلش هم به طور دائمی به obj
اشاره کند. همین موضوع را در خطهای بعدی آزمایش کردهایم. متد را اجرا کرده و داخل متغیر fn
ریختهایم. حالا متغیر fn
همان تابع فلش ما است. اگر آن را اجرا کنیم میبینیم مقداری که برمیگرداند برابر با obj
است (خط 9). در خط 11 به جای اجرای متد، تابع آن را داخل یک متغیر ریختهایم. (بدون وجود پرانتز). در اینجا this
اشاره شده به این تابع جدید (fn2
)، تغییر پیدا میکند. بنابراین اگر در خط 12 آن تابع و تابع فلش داخلش را اجرا کنیم میبینیم که مقدار this
که برمیگرداند برابر window
است.
this داخل متدهای آبجکت
زمانی که تابعی به عنوان متد یک آبجکت فراخوانی میشود، this
آن به آبجکتی که حاوی آن متد است اشاره میکند. در مثال زیر، زمانی که o.f()
فراخوانی میشود، this
داخل تابع به آبجکت o
اشاره دارد:
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
به این نکته توجه کنید که رفتار this
به این مربوط نمیشود که تابع کجا تعریف شده است. در مثال بالا ما تابع را در داخل آبجکت و حین تعریف آبجکت تعریف کردیم. اما میتوانیم آن را به شکل تابعی مستقل تعریف کرده و بعدا به آبجکتمان اضافه کنیم. در این حالت نیز رفتار مشابهی خواهیم داشت. کد زیر معادل کد بالا عمل میکند:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
این موضوع نشان میدهد تنها چیزی که اهمیت دارد نحوه صدا کردن تابع است که از کجا فراخوانی شود؛ نه مکان تعریف تابع.
نکتهی دیگر در این مورد این است که this
از داخلیترین محیطی که تابع از آن فراخوانی میشود ارث میگیرد. مثال زیر موضوع را روشن میکند:
o.b = {g: independent, prop: 42};
console.log(o.b.g()); // 42
در اینجا تابع independent
به عنوان عضوی از آبجکت o.b
فراخوانی میشود. پس باید به o.b
اشاره کند. این موضوع که o.b
، خود، متدی از آبجکت o
است تاثیری بر موضوع نخواهد داشت و داخلیترین محیط برای تابع independent
همان o.b
است که this
خود را از آن محیط به ارث خواهد برد.
this در زنجیرهی پروتوتایپ آبجکتها
اگر متدی جایی در داخل زنجیرهی پروتوتایپ (به انگلیسی: prototype) آبجکت قرار داشته باشد، با فراخوانی آن متد برای آبجکت، متد برای آن آبجکت فراخوانی میشود. پس طبیعی است که this آن متد به همان آبجکتی که توسطش فراخوانی میشود اشاره کند. مثال:
var o = {f: function() { return this.a + this.b; }};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
در مثال بالا، آبجکت p
به خودی خود دارای متد f
نیست. بلکه این متد را از پروتوتایپ خود به ارث برده است. اینجا وقتی f
را به عنوان متدی از p
صدا میزنیم دقیقا مثل این است که f
متد خود p
است. پس this
به کار رفته داخل آن نیز به p
اشاره خواهد داشت.
this با getter و setterها
این مورد نیز شبیه مورد قبل است. در اینجا نیز this
داخل تابعی که به یک آبجکت get
یا set
شده است به همان آبجکت اشاره میکند. مثال زیر را ببینید:
function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', {
get: sum, enumerable: true, configurable: true});
console.log(o.average, o.sum); // 2, 6
this در سازندهها
در مورد توابعی که به عنوان “سازنده” (به انگلیسی: constructor) استفاده میشوند (با کلیدواژهی new
)، آبجکت this
آنها “ساخته میشود”. البته به شرطی که آبجکتی را برنگردانند. اگر این توابع آبجکتی برگردانند آن آبجکت “ساخته میشود”. اگر جملات پیشین مبهم هستند به مثال زیر توجه کنید:
function C() {
this.a = 37;
this.b = 33;
}
var o = new C();
console.log(o); //{a: 37, b: 33}
function C2() {
this.a = 37;
this.b = 33;
return { a: 38, b: 400 };
}
o = new C2();
console.log(o); // {a: 38, b: 400}
در خط اول تابع C
را تعریف کردهایم. اگر یک آبجکت به اسم o
از روی آن “بسازیم” (خط 6)، آبجکت o
آبجکتی است که با توجه به this
تابع C ساخته میشود. یعنی دارای دو پراپرتی a
و b
با مقادیر 37 و 33 خواهد بود. از طرف دیگر در خط 10، تابع C2
را تعریف کردهایم که با وجود داشتن متدهای a
و b
برای this
، چون خودش یک آبجکت را برمیگرداند (خط 13)، اگر آبجکتی از روی آن “بسازیم” (خط 16)، آن آبجکت همان آبجکتی خواهد بود که تابع برگردانده و مقادیر 37 و 33 با وجود اینکه پردازش میشوند ولی خروجیای ندارند و قابل حذف هستند.
this در کنترلکنندههای وقایع
زمانی که یک تابع به عنوان کنترلکنندهی وقایع (به انگلیسی: Event Handler) استفاده میشود، this
آن به المانی که کنترل کنندهی واقعه برای آن تعریف شده است، اشاره میکند. کد زیر را ببینید:
function bluify(e) {
// Hamishe true
console.log(this === e.currentTarget);
// vaghti "currentTarget = target" bashad, true ast:
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// gereftane liste hameye element-ha:
var elements = document.getElementsByTagName('*');
// ezafe kardane listener baraye amale click,
// vaghti element click shavad pas-zamineh aabi mishavad:
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', bluify, false);
برای کنترلکنندههای وقایع که مستقیما در داخل خود المان نوشته میشوند (inline)، آبجکت this
به همان المان اشاره میکند:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
در کد بالا عبارت button نشان داده میشود. البته توجه کنید که در چنین مواقعی this
فقط در کدهای بیرونی به این شکل رفتار میکند. مثلا در کد زیر this
مربوط به تابع داخلی مقداردهی نشده است. پس مثل حالت محیط گلوبال، به آبجکت گلوبال اشاره خواهد کرد. البته فرض میکنیم در حالت strict نیستیم.
<button onclick="alert((function() { return this; })());">
Show inner this
</button>
برای استفاده از منبع به زبان اصلی اینجا کلیک کنید.
27 تیر جاوااسکریپت Map، Set، و جاوااسکریپت
Map و Set در جاوااسکریپت
اکثر برنامه نویسان با ساختارهای دلده آرایه و آبجکت آشنایی دارند اما در عمل این دو نوع ساختار کافی نبودهاند. برای همین ساختارهای Map و Set در جاوااسکریپت معرفی شدهاند که در ادامه آنها را بررسی میکنیم. بنابراین با ما همراه باشید.
26 خرداد جاوااسکریپت data binding، اتصال داده، و جاوااسکریپت
مفاهیم اولیهی اتصال داده (data binding) در جاوااسکریپت
یکی از مهمترین الگوهای توسعهی اپلیکیشنهای جاوااسکریپتی، جدا کردن دادهها و ظاهر اپلیکیشن از همدیگر و اتصال این دو به یکدیگر به شکل یکسویه یا دوسویه است. به طوری که با تغییر داده، ظاهر مرتبط با آن داده عوض و به شکل متقابل با تغییر در ظاهر برنامه (مانند پر کردن فرم یا تغییر مکان یک شی) داده مرتبط با آن دچار تغییر شود. امروزه اتصال داده در اپلیکیشنهای تحت وب برای برنامهنویس از نان شب واجبتر است! البته فریمورکهای متداول مانند ReactJs، این اتصال داده را به خوبی برای برنامهنویسان پیشبینی کردهاند و در این مقاله قرار نیست فریمورک توسعه بدهیم. اما به دلایلی خوب است اساس و اصول اتصال داده در جاوااسکریپت را درک کنیم. برای فهمیدن این دلایل و کشف راز اتصال داده در جاوااسکریپت با من همراه شوید.
5 اردیبهشت جاوااسکریپت Promise و جاوااسکریپت
Promiseها در جاوااسکریپت - بخش اول
جاوااسکریپت از جمله زبانهایی است که اتفاقات غیر همزمان (asynchronous) در آن معمول و متداول است. مثلا وقتی یک درخواست به سرور میفرستید جاوااسکریپت منتظر نمیماند تا پاسخ آن دریافت شود. بلکه به اجرای خط به خط برنامه ادامه میدهد. اما این همیشه مطلوب نیست. بعضی اوقات لازم است تا از جاوااسکریپت قول بگیریم که بعد از مشخص شدن تکلیف اجرای یک دستور غیر همزمان کاری را انجام دهد. اینجاست که راه Promiseها به قضیه باز میشود. دنیای برنامهنویسی غیر همزمان دنیای مرموز و گاها دشواری است. پس بیایید یکی از بهترین مقالات را در این زمینه مرور کنیم.
17 اردیبهشت جاوااسکریپت Promise و جاوااسکریپت
Promiseها در جاوااسکریپت - بخش چهارم (آخر)
در این بخش از مقاله که بخش پایانی سری مقالات Promiseها است پروژهای که تعریف کرده بودیم را تکمیل میکنیم و نکات ارزشمندی پیرامون استفادهی حرفهای از Promiseها بیان میکنیم. با من همراه باشید.