জাভাস্ক্রিপ্ট অনেক জনপ্রিয় হলেও এর কিছু weird ব্যবহারের জন্য অনেকের কাছেই জাভাস্ক্রিপ্ট বেশ confusing programming language হিসাবে পরিচিত। আমরা যদি জাভাস্ক্রিপ্ট এর confusing parts গুলো ব্যবহারে সচেতন থাকি তাহলে আমরা জাভাস্ক্রিপ্টকে ভালভাবে ব্যবহার করতে পারব।
এই ব্লগ সিরিজের প্রথম পার্ট আগে পড়তে চাইলে এইখানে পাওয়া যাবে ।
"This"
জাভাস্ক্রিপ্টে this keyword
রেফার করে আমরা জাভাস্ক্রিপ্ট কোডের যে অংশকে রান করার ট্রাই করছি তার execuation state কোনটি। যেটা নানা ভাবে অনেক সময় খুবই কনফিউজিং। নিচের উদারনটি খেয়াল করি ।
function helloworld() {
console.log(this);
}
helloworld(); // Here console.log will log this as a window object
এমন কি আমরা যদি ফাংশনকে কোন ভ্যারিয়েবল এ আস্যাইন করে কল করি তাহলে দেখা যাবে যে এটা window object কেই রেফার করে আছে।
let helloworld = function() {
console.log(this);
}
helloworld(); // console.log will log this as a window object
কিন্তু আমরা যদি কোন অবজেক্ট এর প্রপার্টি হিসাবে ফাংশন নেই এবং তার ভিতরে আমরা লগ করি তাহলে দেখব this
এই ক্ষেত্রে এই অবজেক্টেই রেফার করে ।
let obj = {
name: "I am function",
func: function() {
console.log(this); // console.log: {name: "I am function", func: ƒ}
this.name = "I am function and I am updating"
console.log(this); // console.log: {name: "I am function and I am updating", func: ƒ}
}
}
obj.func();
এটাও ঠিক আছে। যে কোন ফাংশন global namespace এ থাকলে তার execution context হল global namespace যেখানে this রেফার হচ্ছে window variable কে । কিন্তু যদি ফাংশন কোন অবজেক্ট এর ভিতর থাকে তার জন্য আলাদা execution context তৈরি হয় এবং তা অবশ্যই ফাংশনের execution context। এই অবস্থায় this অবজেক্টকে রেফার করছে। কিন্তু যদি আমরা নিচের উদাহরণ দেখিঃ
let obj = {
name: "I am object",
func: function() {
console.log(this); // log: {name: "I am object", func: ƒ}
this.name = "I am object and I am updated"
let newFunc = function() {
console.log(this); // log: window object
this.name = "hello world" // this will also update window name variable
}
newFunc(); // {name: "I am object and I am updated", func: ƒ}
console.log(this);
}
}
obj.func();
এই রকম কেন হল তা ব্যাখ্যা করার আগে আমরা নিচের আরও একটা উদাহরণ দেখে নেই,
let obj = {
name: "I am object",
func: function() {
console.log(this); // log: {name: "I am object", func: ƒ}
this.name = "I am object and I am updated"
let newFunc = function() {
console.log(this); // log: window object
this.name = "hello world" // this will also update window name variable
let anotherObj = {
name: "I am anotherObj",
func: function() {
console.log(this); // log: {name: "I am anotherObj", func: f}
}
}
anotherObj.func();
}
newFunc(); // {name: "I am object and I am updated", func: ƒ}
console.log(this);
}
}
obj.func();
this
সব সময় যেখানে ব্যবহার করা হয় তার execution state কোনটি তাকে রেফার করে । সহজ ভাষায় this
তার owner কে রেফার করে। যদি আমরা ফাংশনের ভিতর this
ব্যবহার করি by default যদি ঐ ফাংশনের owner কোন অবজেক্ট হয়, তাহলে this ঐ অবজেক্টকে রেফার করে, যদি কোন ফাংশন হয় তাহলে তা global namespace এর window কে রেফার করে। এই জন্য প্রথম newFunc
এর owner func
যা নিজে একটি ফাংশন তাই newFunc
এর ভিতরের this
window কে রেফার করে কিন্তু newFunc
এর ভিতরের একটি অবজেক্ট anotherObj এর ভিতর যখন নতুন func ফাংশনকে ডিক্লেয়ার করা হয় এবং তার owner একজন অবজেক্ট তাই func এর ভিতরের this
তার owner অবজেক্ট anotherObj
কে রেফার করে । এই ব্যাপারগুলো অনেক কনফিউজিং। এর ধরনের ক্ষেত্রে Javascript
এর কিছু best practices
আছে। আমরা পরবর্তী সিরিজগুলোতে তা দেখব।
Event Loop
জাভাস্ক্রিপ্ট সিঙ্গেল থ্রেডেড প্রোগ্রামিং লাঙ্গুয়েঞ্জ, এর মানে হইল এর একটা মাত্র Call Stack
এবং একটি মাত্র Memory Heap
আছে । জাভাস্ক্রিপ্ট তার কোডগুলাকে অডারে একের পর এক চালায় এবং আউটপুট বের করে । কোন একটা কিছুর আউটপুট বের করার আগে পরের অডারের কাজগুলা হল্ট অবস্থায় থাকে মানে কোন কাজ হয় না যতক্ষণ না আগের কাজটি শেষ হচ্ছে । সাধারণ, বেশিরভাগ ব্রাউজারগুলিতে প্রতিটি ব্রাউজারের ট্যাবটির জন্য একটি ইভেন্ট লুপ থাকে যাতে প্রতিটি প্রক্রিয়া আলাদা হয়ে যায় এবং যাতে কখনও অসীম লুপ বা সময় সাপেক্ষ প্রসেসিং এর জন্য সম্পূর্ণ ব্রাউজারটি ব্লক হয়ে যাওয়া এড়ানো যায়। Call Stack
আসলে কিভাবে কাজ করে জিনিসটা একটা উদাহরণের মাধ্যমে দেখা যাকঃ
const boo = () => console.log('boo')
const thoo = () => console.log('thoo')
const foo = () => {
console.log('foo')
boo();
thoo();
}
foo();
এই কোডের আউটপুট আসবে
foo
boo
thoo
Call Stack
এ কোড চালানোর সময় ডাটাস্ট্রাকচারের Stack
এর LIFO
( Last In First Out ) যথাযথ অনুসরণ করা হয়। এইখানে প্রথমে Call Stack
Empty ছিল, প্রথমে ফাংশন foo
insert হইছে এরপর console.log('foo')
Stack এ insert হইছে, তারপর console.log('foo')
এর জন্য foo
আউটপুট এ আসছে, তারপর যথাক্রমে boo
এবং console.log('boo')
insert হয়েছে এবং পর্যায়ক্রমে console.log('boo')
এবং boo
ফাংশন কল Pop হয়েছে । সর্বশেষ thoo
ফাংশন কল এবং console.log('thoo')
একই জিনিস অনুসরণ করে সর্বশেষ Call Stack
থেকে ফাংশন foo
বের হয়েছে । সবকিছু যা হবার তাই হইছে, কোন প্রবলেম নাই । যদি আমরা উপরের উদাহরণকে একটু পরিবর্তন করি এভাবে
const boo = () => console.log('boo')
const thoo = () => console.log('thoo')
const foo = () => {
console.log('foo')
setTimeout(boo, 0)
thoo();
}
তাহলে এই কোডের আউটপুট আসবে
foo
thoo
undefined
boo
কেন এমন হয়? এর উত্তর হল Javascript Message Queue
। Call Stack
যখন sequentially
একটার পর একটা কাজ করতে থাকে এবং javascript Engine
যদি দেখে এখানে WebApi
কল প্রয়োজন আছে তখন তা ঐ কাজটাকে একটি Message Queue
এর মধ্যে দিয়ে দেয়, যখন Call Stack
এর সব গুলো কাজ Pop করা শেষ হয় তখন Message Queue
থেকে একটার পর একটা event নিয়ে চেক করা হয় এর আউটপুট আসছে কিনা বা অন্য কিছু করতে হবে কিনা । এইখানে setTimeout
পাওয়া মাত্র জাভাস্ক্রিপ্ট ইঞ্জিন তাকে মেসেজ কিউতে পুশ করে দেয় এবং যখন boo
ফাংশনের console.log('boo')
কে প্রিন্ট করে মেসেস কিউ চেক করতে যায় প্রথম টার্মে setTimeout
তখনও কাজ শেষ না করার জন্য প্রথমে undefiend
রিটান করছে এবং পরের টার্মে console.log('boo')
। প্রথমের undefiend
ব্রাউজার ভেদে নাও আসতে পারে ।
Null, Undefined and NaN
Null, Undefiend and NaN জাভাস্ক্রিপ্টের বহুল ব্যবহৃত এবং নানা কারণে অনেক বেশি কনফিউজিং জিনিস। আমরা যদি তাদের টাইপ দেখি
typeof null; // object
typeof undefiend; // undefiend
typeof NaN; // number
অর্থাৎ তারা সবাই আলাদা, কিন্তু নিজেদের মধ্যে কম্পারিজন যদি দেখি
null == NaN ; // false
null == undefined; // true
undefined == NaN; // false
null === undefined ; // false
!null === !undefined; // true
আবার তাদের যদি ভ্যালু এর সাথে কম্পারিজন করে দেখি
!null; // true
null == false; // false
null === false; // false
null == true; // false
null === true; // false
!undefined; // true;
undefined == false; // false
undefined === false; // false
undefined == true; // false
undefined === true; // false
!NaN; // true;
NaN == false; // false
NaN === false; // false
NaN == true; // false
NaN === true; // false
একটি null ভ্যালু রিপ্রেসেন্টস একটি রেফারেন্স যা সাধারণত ইচ্ছাকৃতভাবে একটি অস্তিত্বহীন বা অবৈধ অবজেক্ট বা ঠিকানার দিকে নির্দেশ করে। আর undefined গ্লোবাল প্রিমিটিভ টাইপ undefined কে নির্দেশ করে এর মানে হচ্ছে এমন কিছু যা এখনও নির্দিষ্ট করা হয়নি। NaN এর ফুল ফর্ম হচ্ছে Not a Number (যদিও তার টাইপ আবার number)। যদিও NaN এর ব্যাপারটা জাভাস্ক্রিপ্ট ইঞ্জিনের কাছে থেকে লজিকাল রেসপন্স পাওয়া যায়।
যখন নাম্বার না এমন কোন টাইপকে নাম্বার টাইপের কিছুর সাথে arithmetic কোন অপারেশন করার চেষ্টা করা হয় তখন জাভাস্ক্রিপ্ট ইঞ্জিন NaN রিটার্ন করে ।
"hello" / 12;
// NaN
20 * "hi"
// NaN