Skip to content

Latest commit

 

History

History
475 lines (354 loc) · 41.6 KB

File metadata and controls

475 lines (354 loc) · 41.6 KB

Lecture 9 - Functional Programming in JavaScript

Table of contents

Introduction

গত ক্লাসে আমরা ফাংশন নিয়ে আলোচনা করেছিলাম। আজ আমরা জানবো ফাংশনাল প্রোগ্রামিং নিয়ে। আজকের এজেন্ডাগুলো একটু দেখা যাক।

  • Pure Function + Side Effects + Immutability
  • Higher Order Function
  • Function Scope + Closure + Hoisting
  • Callback
  • IIFE (Immediate Invoke Function Expression)

এই ক্লাসে রিকারশন বা কারিং নিয়ে কোনো আলোচনা হবে না। পরবর্তীতে যখন আমাদের এগুলো নিয়ে কাজ করতে হবে তখনই আমরা তা শিখে নিবো।

ফাংশনাল প্রোগ্রামিং নিয়ে আলোচনা করতে গেলে আমাদের আগে জানতে হবে ফাংশনাল প্রোগ্রামিং কি? উইকিপিডিয়ার ভাষায়, 'In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.' মানে ফাংশনাল প্রোগ্রামিং এমন একটা প্যারাডাইম যেখানে প্রোগ্রাম, ফাংশন অ্যপ্লাই ও কম্পোজ করার মাধ্যমে কনস্ট্রাক্ট করা হয়। দুই ধরণের ফাংশনাল প্রোগ্রামিং ল্যাঙ্গুয়েজ আছে। Pure এবং Impure। যে প্রোগ্রামিং ল্যাঙ্গুয়েজে ফাংশনাল প্রোগ্রামিং ছাড়া অন্য কিছু সাপোর্ট করে না সেগুলোকে বলে পিওর ফাংশনাল প্রোগ্রামিং ল্যাঙ্গুয়েজ। আর যেগুলোতে ফাংশনাল প্রোগ্রামিং ছাড়াও OOP সাপোর্ট করে তাদের বলে ইমপিওর প্রোগ্রামিং ল্যাঙ্গুয়েজ। ফাংশনাল প্রোগ্রামিং এর কিছু কনসেপ্ট আছে। সেগুলো বুঝার আগে আমাদের ফাংশন কিভাবে লিখতে হয় তা একটু জানা দরকার। জাভাস্ক্রিপ্টে ফাংশন তিনভাবে লেখা যায়। ফাংশন স্টেটমেন্ট, ফাংশন এক্সপ্রেশন, ফ্যাট এরো ফাংশন (ইএস৬ ভার্সন থেকে)। এগুলোর স্ট্রাকচার একটু দেখা যাক।

// function statement
function func() {}

// Function expression
const myFn = function () {};

// Fat Arrow function
const myFatArrowFn = () => {};

এবার আমরা একটু ফাংশনাল প্রোগ্রামিং এর কনসেপ্ট সম্পর্কে জানার চেষ্টা করি। আপনি যদি Functional Programming Languages: Concepts & Advantages এই লিংকে যান তাহলে দেখবেন এখানে ৫টি কনসেপ্টের কথা বলা হয়েছে। এগুলো হলো -

  • Pure Functions
  • Recursion
  • Referential Transparency
  • Functions are First-Class and Can be Higher-Order
  • Immutability

এর মধ্যে Pure Functions, Immutability নিয়ে আজ আলোচনা হবে। Recursion নিয়ে আজ আলোচনা হবে না তা বলা হয়েছে। ফাংশন যে একটা ফার্স্ট ক্লাস সিটিজেন সেটা আমরা গত ক্লাসে দেখেছি। আজ higher order নিয়ে আলোচনা হবে। Referential Transparency বলতে বুঝাচ্ছে যদি আমরা কোনো ফাংশনকে একটা ভ্যারিয়েবলের মধ্যে স্টোর করে রাখি তাহলে তার ভ্যালু কখনও চেইঞ্জ করা যাবে না। যদিও এটা জাভাস্ক্রিপ্টের জন্য খাটে না। আমরা চাইলে ফাংশনকে ভ্যালু হিসেবে ব্যবহার করে তা চেইঞ্জ করতে পারি। বিস্তারিত জানার জন্য আপনারা Functional Programming Languages: Concepts & Advantages এবং 9 Functional Programming Concepts Everyone Should Know আর্টিকেল দুইটি পড়তে পারেন।

Pure Function and side effects

পিওর ফাংশন আর পিওর ফাংশনাল ল্যাঙ্গুয়েজ সম্পূর্ণ দুইটা আলাদা টার্ম। দুইটার মধ্যে গুলিয়ে ফেলবেন না। পিওর ফাংশন বলতে পিওর ওয়াটার ধরি আমরা। যেটা সম্পূর্ণ পিওর, যা পান করলে আমার কোনো সাইড ইফেক্ট থাকবে না। পিওর ফাংশনও তাই। যে ফাংশনের কোনো সাইড ইফেক্ট থাকবে না। যেটা আজ যে আউটপুট দিবে সেটা ১০০ বছর পরেও সেইম আউটপুট দিবে। কিন্তু আমরা তো জানি ফাংশন আমরা ব্যবহারই করি ভিন্ন ভিন্ন আউটপুট পাওয়ার জন্য। তাহলে একই আউটপুট কিভাবে সম্ভব? চলুন আমরা একটা উদাহরণ দেখি।

function sum(a, b) {
	return a + b;
}

sum(10, 20); // 30

এই ফাংশনটা একটা পিওর ফাংশন। কিভাবে? এই ফাংশন দ্বারা কোনো কিছু চেইঞ্জ বা আপডেট করা হচ্ছে না। এখানে শুধুমাত্র এটার মধ্যে যে ভ্যারিয়েবল দেয়া হবে সেটা নিয়েই কাজ করবে। এর দ্বারা বাইরের কোনো ভ্যারিয়েবলের ভ্যালু চেইঞ্জ করা যাবে না। মানে কোনো সাইড ইফেক্ট নাই। এটা একটা দিক। আরেকটা দিক হলো যদি আর্গুমেন্ট সেইম থাকে যেমন sum এর আর্গুমেন্ট যদি ১০ এবং ২০ হয় তাহলে আজকে যেমন এর আউটপুট ৩০ আসবে, ১০০ বছর পর দিলেও এর আউটপুট সেই ৩০ আসবে। এখন একটা প্রশ্ন আসতে পারে এই সাইড ইফেক্ট জিনিসটা কি? এটা বুঝার জন্য আমরা নিচের উদাহরণটা একটু দেখি।

let limit = 100;
function changeLimit(limit) {
	limit = 500;
}

changeLimit(limit);
console.log(limit); // 100

এটা একটা পিওর ফাংশন। কারণ এই ফাংশন বাইরের limit ভ্যারিয়েবলকে পরিবর্তন করছে না। তার মানে এর কোনো সাইড ইফেক্ট নাই। সাইড ইফেক্ট না থাকলে তাকে আমরা পিওর ফাংশন বলি। এবার ফাংশনটা অন্যভাবে লিখি একটু।

let limit = 100;
function changeLimit() {
	limit = 500;
}

changeLimit(limit);
console.log(limit); // 500

এবার কিন্তু ফাংশনটা ভ্যারিয়েবলের ভ্যালু চেইঞ্জ করে ফেলেছে। তার মানে এটার সাইড ইফেক্ট আছে। তাই এটা একটা ইমপিওর ফাংশন। আরেকটা উদাহরণ দেখি।

const arr = [1, 2, 3];
function add(arr, data) {
	arr = [...arr, data];
	return arr;
}

এটা একটা পিওর ফাংশন। কারণ এটা মিউটেবল না, এটা নতুন অ্যারে রিটার্ন করছে। য়ামি যে অ্যারে দিয়েছি তার কোনো চেইঞ্জ সে আনছে না। তার মানে কোনো সাইড এফেক্টও নাই। এজন্য এটা একটা পিওর ফাংশন। এবার যদি আমরা এই ফাংশনকে অন্যভাবে লিখি তাহলে কেমন হবে?

const arr = [1, 2, 3];
function add(data) {
	arr.push(data);
}

এটা পুরোপুরি একটা ইমপিওর ফাংশন। কারণ তা সরাসরি arr ভ্যারিয়েবলের ডাটা আপডেট করছে। তার মানে সাইড ইফেক্ট হচ্ছে।

এবার আপনাদের কাছে প্রশ্ন নিচের ফাংশনটা কি পিওর নাকি ইমপিওর?

function log(msg) {
	console.log(msg);
}

এটা দেখতে আপাতদৃষ্টিতে পিওর ফাংশন মনে হলেও এটা একটা ইমপিওর ফাংশন। কারণ এটা কনসোলে লগ হচ্ছে। তাই যে ফাংশনে console.log() দেয়া থাকবে সেটা ইমপিওর হওয়ার পসিবিলিটি বেশি।

Higher order function

Higher order function এই টার্ম যেখানে আসবে সেখানে এটার সাথে আরেকটা টার্ম যুক্ত হবে সেটা হলো ফার্স্ট ক্লাস সিটিজেন। ফার্স্ট ক্লাস সিটিজেন বলতে আমরা ফাংশনকে ভ্যালু হিসেবে ট্রিট করতে পারবো এটা বুঝায়। এটা সম্পর্কে আমরা আগে লেকচারে বুঝেছিলাম। সেটা থেকে এটা অন্তত প্রমাণিত যে জাভাস্ক্রিপ্টে ফাংশন একটা ভ্যালু।

একটা ফাংশন হাইয়ার অর্ডার হওয়ার জন্য দুইটা শর্ত আছে।

  • Function can be passed as an argument.
  • Function can be returned from another function.

এই দুইটা শর্তের কারণে জাভাস্ক্রিপ্ট ফাংশন যে কতটা পাওয়ারফুল তা যারা এক্সপার্ট হয়ে গেছেন তারা বুঝতে পারবেন।

ফাংশনাল প্রোগ্রামিং নিয়ে কাজ করতে হলে অবশ্যই ফাংশন পিওর হতে হবে। সবার প্রথমে আমরা প্রথমে খুব সাধারণ একটা হাইয়ার অর্ডার ফাংশন তৈরি করে ফেলি। আমরা একটা যোগ করার ফাংশন তৈরি করবো।

function sum(a, b) {
	const r = a + b;
	return r;
}

এটা যদিও পিওর ফাংশন, কিন্তু এটা মোটেও হাইয়ার অর্ডার ফাংশন না। কারণ এটা শর্তমতে কোনো ফাংশন রিটার্নও করছে না, আবার কোনো ফাংশনকে আর্গুমেন্ট আকারে পাসও করছে না। এখন এই ফাংশনকে হাইয়ার অর্ডার ফাংশন বানালে আমাদের কি উপকার হবে সেটা আমাদের বুঝতে হবে। আমরা কেন জোর করে একটা ফাংশনকে হাইয়ার অর্ডার ফাংশন বানাবো? ধরুন আমরা দুইটা র‍্যান্ডম নাম্বার জেনারেট করে সেই দুইটা নাম্বার দিয়ে কিছু ম্যাথমেটিকাল অপারেশন করার ফাংশন বানাবো। কিন্তু আর্গুমেন্ট আকারে আমি পাশ করবো একটা ভ্যালু।

function randomSum(max) {
	const random1 = Math.floor(Math.random() * max);
	const random2 = Math.floor(Math.random() * max);
	return random1 + random2;
}

function randomSub(max) {
	const random1 = Math.floor(Math.random() * max);
	const random2 = Math.floor(Math.random() * max);
	return random1 - random2;
}

function randomSqrSum(max) {
	const random1 = Math.floor(Math.random() * max);
	const random2 = Math.floor(Math.random() * max);
	return random1 * random1 + random2 * random2;
}

কিন্তু এখানে দেখা যাচ্ছে র‍্যান্ডম নাম্বার জেনারেট করার অপারেশন সব ফাংশনেই একই। আমরা DRY (Don't Repeat Yourself) নীতি ফলো করি। কিন্তু এখানে তো অনেকবার রিপিট হলো। তাই আমরা চিন্তা করলাম আমরা ঐ দুই লাইনের জন্য একটা ফাংশন বানিয়ে রাখবো।

function generateTwoNumbers(max) {
	const random1 = Math.floor(Math.random() * max);
	const random2 = Math.floor(Math.random() * max);

	return {
		random1,
		random2,
	};
}

এবার আমরা আমাদের পূর্বের ফাংশনগুলিতে এই ফাংশন ব্যবহার করে আমাদের সমস্যার সমাধান করে ফেলবো।

function randomSum(max) {
	const { random1, random2 } = generateTwoNumbers(max);
	return random1 + random2;
}

function randomSub(max) {
	const { random1, random2 } = generateTwoNumbers(max);
	return random1 - random2;
}

function randomSqrSum(max) {
	const { random1, random2 } = generateTwoNumbers(max);
	return random1 * random1 + random2 * random2;
}

আমরা আমাদের প্রব্লেম সলভ করে ফেললাম। কিন্তু দুইদিন পর আমাদের ক্লায়েন্ট বললো, যে তার আরো ফাংশন দরকার, গুণ করার, ভাগ করার সহ আরো বিভিন্ন। এবার তো আবার আমাদের ফাংশন লিখতে হবে। এখন প্রথম লাইনটাও সবার জন্য সেইম। শুধু ম্যাথমেটিকাল অপারেশনটা ভিন্ন। আমরা যদি সেই ম্যাথমেটিকাল অপারেশন লেখার দায়িত্ব ইউজারের উপর ছেড়ে দিয়ে একটা ফাংশন বানিয়ে ফেলি তাহলে পরবর্তীতে যতো রিকোয়ারমেন্টই আআসুক আমাদের আর কোনো কোড রিপিট না করে সেটা বানিয়ে ফেলতে পারবো। আমরা আমাদের অপারেশন পার্টকে একটা ফাংশন হিসেবে আর্গুমেন্ট আকারে পাস করার দায়িত্ব দিয়ে রাখবো ইউজারকে। সে যা চায় সেই অপারেশনই সে করতে পারবে। সেটাকে তার হাতে দিয়ে বাকিগুলোকে নিয়ে আমরা একটা ফাংশন বানিয়ে রাখি। যার প্যারামিটার হিসেবে থাকবে max কতো আমরা দিতে চাচ্ছি, এবং একটা কলব্যাক ফাংশন যার মধ্যে সেই অপারেশনটা লেখা থাকবে। কলব্যাক ফাংশন বলতে বুঝায় আমরা একটা ফাংশনের আর্গুমেন্ট আকারে আরেকটা ফাংশন পাশ করবো। যে ফাংশনকে আর্গুমেন্ট আকারে পাস করা হচ্ছে সেটাই কলব্যাক ফাংশন। চলুন কাজটা করে দেখি।

function generateTwoRandNumber(max, cb) {
	const random1 = Math.floor(Math.random() * max);
	const random2 = Math.floor(Math.random() * max);
	const result = cb(random1, random2);
	return result;
}

এখানে আমরা সরাসরি অপারেশনটা না লিখে ইউজারকে আমাদের জেনারেট করা দুইটা নাম্বার দিয়ে দিলাম, আর বললাম তুমি তোমার মতো ফাংশন ক্রিয়েট করো, কিন্তু ফাংশনের আর্গুমেন্ট আকারে জাস্ট আমার এই নাম্বার দুইটা দিয়ে দিবে। এবার সেই ফাংশনের রেজাল্ট আমরা result ভ্যারিয়েবলের মধ্যে স্টোর করে সেটাকে রিটার্ন করে দিলাম। এবার ক্লায়েন্ট যে অপারেশনই চাইবে আমরা তা ফটাফট করে দিতে পারবো। ধরেন, আমার প্রথম রিকোয়ারমেন্ট শুধু দুইটা নাম্বার প্রিন্ট করা।

generateTwoRandNumber(100, (rand1, rand2) => console.log(rand1, rand2));

এরপর রিকোয়ারমেন্ট আসলো ১০০০ এর মধ্যে দুইটা যেকোনো নাম্বার যোগ করার।

generateTwoRandNumber(1000, (rand1, rand2) => rand1 + rand2);

এভাবে আমরা যা খুশি সে অপারেশন করতে পারি, খুব সহজে।

generateTwoRandNumber(10, (rand1, rand2) => rand1 * rand2);
generateTwoRandNumber(10, (rand1, rand2) => rand1 * rand1 + rand2 * rand2);

যেহেতু আমাদের হাইয়ার অর্ডার ফাংশনের প্রথম শর্তমতে একটা ফাংশনকে আর্গুমেন্ট হিসেবে পাস করতে পারছি তাই এটা একটা হাইয়ার অর্ডার ফাংশন।

এবার আসি আমাদের দ্বিতীয় শর্তে। এখন একটা ফাংশন থেকে আরেকটা ফাংশন রিটার্ন করার কি প্রয়োজন? আমরা একটু নিচের উদাহরণগুলো দেখি। ধরা যাক আমাদের একটা স্কয়ার করার ফাংশন বানাতে বলা হলো।

function sqr(a) {
	return a * a;
}

এবার যদি কিউব করতে বলার হয় তাহলে আমরা কিউব করার একতা ফাংশন বানাবো।

function cube(a) {
	return a * a * a;
}

কিন্তু আসল কাজ হচ্ছে এখানে পাওয়ারের কাজ। তার মানে কোনো নাম্বারের কততম পাওয়ার আমরা বের করতে চাচ্ছি মূল কাজ হচ্ছে সেটা। সুতরাং আমরা এত ফাংশন না লিখে পাওয়ারের জন্যই একটা ফাংশন বানিয়ে ফেললে হয়ে যায়।

function power(p) {
	return function (n) {
		let result = 1;
		for (let i = 1; i <= p; i++) {
			result *= n;
		}
		return result;
	};
}

এখানে আমরা p হিসেবে নিবো কততম পাওয়ার সেটা। আর যে ফাংশন আমরা রিটার্ন করেছি তার আর্গুমেন্ট হিসেবে নিবো কত নাম্বারের পাওয়ার বের করতে চাইছি সেটা যেটাকে n দ্বারা প্রকাশ করেছি। এবার একটু অপারেশন দেখি।

const sqr = power(2);
const cube = power(3);
const power8 = power(8);

console.log('SQR', sqr); // SQR [Function (anonymous)]
console.log('cube', cube); // cube [Function (anonymous)]
console.log('power8', power8); // power8 [Function (anonymous)]

এখানে দেখা যাচ্ছে একটা ফাংশন রিটার্ন করছে যার আর্গুমেন্ট হিসেবে নাম্বার দিতে হবে। তাহলে আমরা নিচের কাজটা করতে পারি। যে ভ্যারিয়েবলগুলো নিয়েছি তাদের আর্গুমেন্ট হিসেবে নাম্বার দিয়ে দিলেই আমরা স্কয়ার, কিউব এবং ৮ম পাওয়ার পেয়ে যাবো।

console.log('SQR', sqr(2)); // SQR 4
console.log('cube', cube(2)); // cube 8
console.log('power8', power8(2)); // power8 256

মূলত ডায়নামিক্যালি কোনো ফাংশন জেনারেট করার জন্য এবং পুরো সিস্টেমের একটা abstract layer প্রোভাইড করার জন্য আমরা একটা ফাংশন থেকে আরেকটা ফাংশন রিটার্ন করে থাকি। অর্থাৎ হাইয়ার অর্ডার ফাংশন ব্যবহার করে থাকি।

Hidden Concepts

জাভাস্ক্রিপ্টের কিছু হিডেন কনসেপ্ট আছে। যেগুলো আপনি সরাসরি চোখে দেখবেন না বা কোড করার জন্য সরাসরি কোনোদিন কাজে লাগবে না। এজন্য এগুলোকে বলা হয় হিডেন কনসেপ্ট। এগুলো হলো -

  • Scope
  • Closure
  • Execution Context
  • Hositing

Scope

আমরা যদি উপরের power ফাংশনের ভিতরের ফাংশনকে বাইরে বের করে এনে দেখি তাহলে কেমন দেখা যায় দেখি।

const f = function (n) {
	let result = 1;
	for (let i = 1; i <= p; i++) {
		result *= n;
	}
	return result;
};
function power(p) {
	return f;
}

এখানে একটা error throw করবে, Reference error: p is not defined। কারণ জাভাস্ক্রিপ্ট লেক্সিক্যাল স্কোপিং সাপোর্ট করে। মানে কোন ভ্যারিয়েবল কোথায় accessible, এটাকেই মূলত স্কোপ বলা হয়। এখন কোন ভ্যারিয়েবল বা কোন ফাংশন কোথায় এক্সেসিবল তা সেট করা হয় যখন lexing হয়। এখন lexing আবার কি? আমরা যে কোডগুলো লিখি কম্পিউটার তার কিছুই বুঝে না। কম্পিউটার বুঝে মেশিন কোড মানে বাইনারি। আমাদের লিখিত কোডকে ভেঙেচুরে মেশিন কোডে রূপান্তর করে কম্পিউটারের কাছে দিলেই কম্পিউটার তা বুঝতে পারবে। এই যে মেশিন কোডে রূপান্তরের প্রসেস, তার প্রথম ধাপই হলো parsing and lexing। অর্থাৎ আমরা যে ফাইলটা দিচ্ছি, সেটা জাভাস্ক্রিপ্ট ইঞ্জিন, অন্যান্য ল্যাঙ্গুয়েজের ক্ষেত্রে কম্পাইলার, lexing বা parsing করবে, এর মধ্যে কি কি আছে তা পড়ে ফেলবে। পড়ে সেই কোডগুলোকে টুকরো টুকরো করে ফেলবে। টুকরো টুকরো করে একটা Abstract Binary Tree (ABS) বানাবে। এই Tree বানানোর প্রসেসটাকেই বলা হচ্ছে lexing করা। আর এই lexing করার কাজটা হয় কম্পাইল করারও আগে। যেহেতু কম্পাইল করার আগে lexing হচ্ছে, তাই কাটাছেঁড়ার সময় যখন সে f ফাংশনে p পাচ্ছে না তখন সে error throw করবে। পাবে কিভাবে, সেতো কোড রান করছে না। জাস্ট পড়ছে। পড়ার সময় যদি এরর পায় সে সেটা আমাদের দিয়ে দিচ্ছে। এজন্য lexical scoping এর কিছু প্রব্লেম আছে। যদি কম্পাইল টাইমে scope সেট হতো তাহলে কম্পাইল করার সময় অনেক কিছু বুঝতে পারতো। যদি রানটাইমে scope সেট হতো তাহলে রান করার সময় আমরা ডায়নামিক্যালিভাবে scoping তৈরি করতে পারতাম। কিন্তু জাভাস্ক্রিপ্ট যেহেতু lexical scoping সাপোর্ট করে তাই এই কাজটা আমরা জাভাস্ক্রিপ্টে পারবো না। যদি f ফাংশনকে আমরা power এর মধ্যে রাখি তাহলে সে p যদি f এর কাছে না পায় তাহলে বাইরে দিয়ে দেখলে দেখবে প্যারেন্ট ফাংশনের কাছে p আছে। তাহলে আর কোনো সমস্যা নাই।

function power(p) {
	const f = function (n) {
		let result = 1;
		for (let i = 1; i <= p; i++) {
			result *= n;
		}
		return result;
	};
	return f;
}

যদি আমরা console.log(sqr.toString()) করি, সে একটা ফাংশন রিটার্ন করবে। এখন এই ফাংশনকে কপি করে যদি অন্য নামে আমরা স্টোর করে কল করি সে আমাদের error throw করবে। কিন্তু কেন? এই জিনিসটা লিখে বুঝানো একটু কঠিন। এটা বুঝার জন্য আপনারা ভিডিওর 1:18:21 থেকে 1:34:06 পর্যন্ত দেখুন। তাহলে ভাল করে বুঝবেন।

এখন এই lexical scoping কিছু রুলস মেনে চলে। ভ্যারিয়েবল মূলত আমরা দুইভাবে লিখতে পারি। একটা হলো গ্লোবালি, আরেকটা লোকালি। এর উপর ভিত্তি করে স্কোপ প্রধানত দুই ধরণের।

  • Global
  • Local

গ্লোবাল বলতে মূলত বুঝায় আমরা কোনো ফাংশনে ভ্যারিয়েবল নিবো না, জাস্ট একটা ফাইল ক্রিয়েট করবো, করে সেখানে ভ্যারিয়েবল ডিক্লেয়ার করবো। আর লোকাল ভ্যারিয়েবল একটা ফাংশনের মধ্যে ডিক্লেয়ার করা হয়।

const a = 10;
function mostOuter() {
	function outer() {
		console.log(a);
	}
}

এখানে lexing করার সময় যখন a পাবে, তখন সে খুঁজবে a তার ব্লকের মধ্যে আছে কিনা। না থাকলে সে বাইরের ব্লকে যাবে। সেখানেও না পেলে গ্লোবালি খুঁজবে। এভাবে lexical scoping কনসেপ্টটা কাজ করবে। এই উদাহরণে a, global ভ্যারিয়েবল হিসেবে আছে। এই স্কোপকে গ্লোবাল স্কোপ বলে।

function mostOuter() {
	function outer() {
		const a = 10;
		console.log(a);
	}
}

এখানে a আছে লোকাল ভ্যারিয়েবল হিসেবে কারণ তা একটা ফাংশনের মধ্যে আছে। এটাকে গ্লোবালি এক্সেস করা যাবে না। এই ধরণের স্কোপকে বলা হয় লোকাল স্কোপ।

এই দুইটা ছাড়াও আরেকটা স্কোপ আছে। সেটা হলো Block scope। যেখানেই ব্লক আছে সেখানেই একটা স্কোপ তৈরি করা। ব্লক বলতে {} এর মধ্যে যা লেখা হয়। এর মধ্যে যা লেখা হবে তা বাইরে থেকে এক্সেস নেয়া পসিবল হবে না। এটাই ব্লক স্কোপ।

{
	const notScoped = 'not scoped';
}
console.log(notScoped); // Error

আবার এই ব্লক স্কোপের মধ্যে lexing কথাটা প্রযোজ্য। যেমনঃ

{
	const notScoped = 'scoped';
	{
		{
			{
				console.log(notScoped); // scoped
			}
		}
	}
}

তার মানে সে তার ব্লকে notScoped না পেয়ে তার আউটার ব্লকে গেছে। এভাবে যেতে যেতে শেষ ব্লকে গিয়ে পেয়েছে। এটা lexing এর কনসেপ্ট।

স্কোপ বুঝার জন্য আপনারা গল্পে গল্পে জাভাস্ক্রিপ্ট স্কোপ ভিডিওটা দেখতে পারেন।

Closure

Closure হলো একটি মেমোরি যা আমরা একটা ফাংশন নিঃশেষ হওয়ার পরে ব্যবহার করতে পারি। যেমন sqr ফাংশন কল করার পরে আমরা p এর ভ্যালু হিসেবে 2 পাই। এটা তখনই পাবো যখন ঐ ফাংশনটা পুরোপুরি শেষ হয়ে যাবে। এটাই ক্লোজার। আরো ভালভাবে বুঝতে আপনারা গল্পে গল্পে ক্লোজার এই ভিডিওটি দেখতে পারেন।

Execution Context

আমরা একটু আগে কিছু ফাংশন বানাই।

function A(a) {
	console.log('I am A');
}

function B() {
	A();
}

function C() {
	B();
	B();
}
function D() {
	C();
	A();
}

D();
/* 
I am A
I am A
I am A
*/

কোন ফাংশনের পর কোন ফাংশন কল হবে ্তা নির্ভর করে কল স্ট্যাকের উপর। স্ট্যাক একটা ডাটা স্ট্রাকচার। এর নীতি হলো Last In First Out (LIFO)। মানে সবার শেষে যে আসবে সে সবার আগে বের হবে। আমরা যখন প্লেট ধুয়ে একটার উপর একটা রাখি তখন প্রথম প্লেট রাখি সবার নিচে আর শেষ প্লেট রাখি সবার উপরে। যখন আমরা প্লেট নিই তখন উপর থেকেই নিই, অর্থাৎ সবার শেষে যে প্লেটটা রেখেছিলাম সেটা নিই আগে। আর প্রথমে যেটা রেখেছিলাম সবার নিচে, সেটা নিই সবার শেষে। কল স্ট্যাক এভাবে কাজ করে। এই কোডে যখন D কল করা হলো তখন সে D এর ভিতর গিয়ে কল করবে C কে। এরপর D pause হয়ে যাবে। D এর উপর C চলে যাবে। এরপর C তে যাওয়ার পর সে কল করবে B কে। এবার B চলে যাবে C এর উপর। এবার B তে গিয়ে কল হবে A। তাহলে A চলে যাবে B এর উপর। A তে গিয়ে A কল করার পর কল স্ট্যাকের উপর থেকে A চলে যাবে। এরপর যেহেতু A কল হয়ে গেছে, সেহেতু B চলে যাবে স্ট্যাক থেকে। এরপর ফিরে আসবে C তে। C তে আসার পর দেখা গেলো B ফাংশনের কাজ আছে আরেকটা। সেটাও আগের মতো শেষ হয়ে আবার ফিরে আসবে C তে। এবার C এর কাজ শেষ। C স্ট্যাক থেকে চলে যাবে। এরপর ফিরে আসবে D তে। D তে আসার পর C এর কাজ শেষ। এবার যাবে A তে। A চলে যাবে স্ট্যাকে D এর উপর। A কল হয়ে আবার D তে ফিরে আসবে। এবার D এর কাজ শেষ। এরপর ফাইনালি স্ট্যাক থেকে D চলে যাবে।

লেখাটা পড়ে হয়তো মাথা চক্কর দিতে পারে। ভালভাবে বোঝার জন্য ভিডিওর 2:00:33 থেকে 2:24:39 পর্যন্ত দেখুন।

Hoisting

function randomSum(max) {
	const random1 = Math.floor(Math.random() * max);
	const random2 = Math.floor(Math.random() * max);
	t();
	function t() {
		console.log(test);
	}
	var test = 'something';
	t();
	return random1 + random2;
}

const r = randomSum(15);

যখন কোনো একটা ফাংশন জাভাস্ক্রিপ্ট দেখে তখন সে প্রথমে কিছু এক্সিকিউট না করে সব পড়ে নেয়। এরপর যেখানে ফাংশন পাবে সেখানে একটা রেফারেন্স তৈরি করে নেয়, আর যেখানে var পায় সেখানে undefined বসিয়ে দেয়। উপরের ফাংশনে যখন t কে একবার test ডিক্লেয়ার করার পূর্বে কল করা হয়েছে আরেকবার পরে কল করা হয়েছে। প্রথমবার আউটপুট আসবে undefined, দ্বিতীয়বার আসবে 'something'। কারণ জাভাস্ক্রিপ্ট ফাংশন প্রথমেই test এর ভ্যালু undefined বসিয়ে দিয়েছে ক্রিয়েশনাল ফেইজে। এরপর যখন এক্সিকিউশন করতে গেছে তখন প্রথমবার সেই undefined দিয়েছে এবং পরেরবার যেহেতু এক্সিকিউট হয়ে গেছে তাই 'something' দিয়েছে। এই কনসেপ্টকে বলে hoisting। একটা ভ্যারিয়েবল আমরা ডিফাইন করা পূর্বে তার ভ্যালুর এক্সেস পাচ্ছি এটাই hoisting। আমরা যদি t ফাংশনকে এভাবে না লিখে এক্সপ্রেশন আকারে লিখি

var t = function () {
	console.log(test);
};

তাহলে আমাদেরকে একটা error দিবে TypeError: t is not a function. কারণ তা ক্রিয়েশনাল ফেইজে var দেখার কারণে t এর ভ্যালু undefined বসিয়ে দিয়েছে। আর undefined কে তো কল করা যায় না। Point to be noted, hoisting is only applicable for var (before ES6 version), it will not work for let and const. ES6 ভার্সনে hoisting বলে কোনো টার্ম নেই। তাই আমরা ডিফাইন করার আগে যদি কল করি সেক্ষেত্রে আমাদেরকে error দিবে।

Callback

কলব্যাক ফাংশন বলতে বুঝায় আমরা একটা ফাংশনের আর্গুমেন্ট আকারে আরেকটা ফাংশন পাশ করবো। যে ফাংশনকে আর্গুমেন্ট আকারে পাস করা হচ্ছে সেটাই কলব্যাক ফাংশন। উপরে generateTwoNumbers() এর উদাহরণে কলব্যাক ফাংশন দেখানো হয়েছে।

IIFE (Immediate Invoke Function Expression)

আমরা যদি কোনো ফাংশন লিখে মাত্র কল করি সেটাকে বলছি Immediate Invoke Function Expression (IIFE)। যেমনঃ

(function (name) {
	console.log(name);
})('Nayem');

(() => {
	console.log('Test');
})();

এখন এটা আমরা এভাবে না লিখে গ্লোবালিও তো লিখতে পারতাম জাস্ট console.log('Nayem') এবং console.log('test') লিখে। এভাবে লেখার দরকার কি? আমরা যখন কোনোকিছু গ্লোবালি ডিক্লেয়ার করি তখন সেটা পাবলিকলি এক্সপোজ থাকে। যে কেউ তার এক্সেস পায়। আমি চাইছি আমার ডাটা সিকিউর রাখার জন্য। সেটা যদি একটা ফাংশনের মধ্যে রাখি সেক্ষেত্রে সেটা সিকিউর থাকে। মূলত এই কারণে আমরা IIFE ব্যবহার করে থাকি।

Resource for this lecture

এই লেকচারের সমস্ত রিসোর্স লেকচার ৯ এ পাবেন।

Source Code

এই লেকচারের সমস্ত সোর্স কোড এই লিংকে এ পাবেন।

AUTHOR

Aditya Chakraborty