mozilla
আপনার অনুসন্ধান ফলাফল

    নতুন করে শিখি জাভাস্ক্রিপ্ট (জাভাস্ক্রিপ্ট টিউটোরিয়াল)

    ভূমিকা

    জাভাস্ক্রিপ্ট নিয়ে কেন নতুন করে ভূমিকা দেওয়ার দরকার পরল? কারণ দুনিয়ায় সবচেয়ে ভুল বোঝা প্রোগ্রামিং ভাষা হিসেবে জাভাস্ক্রিপ্টের ব্যাপক খ্যাতি (!) আছে। আমাদের অনেকেই সি/জাভা/পিএইচপি বা অন্য কোন ভাষায় কোডে মোটামোটি ভালভাবে শিখে ফেলার পরে জাভাস্ক্রিপ্ট শিখতে গিয়ে দেখি বাহ, এটা তো সি/জাভা'র মতই! তারপর ভালমত না শিখেই কাজ করতে যাই জাভাস্ক্রিপ্টে... প্রত্যাশা মত ফলাফল আসে না আর এরপর গলা খুলে জাভাস্ক্রিপ্টের গালমন্দ করি। অথচ জাভাস্ক্রিপ্ট অনেক শক্তিশালী - যেটা অনেক সময় ভাষাটির সহজ-সরল চেহারা দেখে বুঝে ওঠা হয় না। ২০০৫ এ আমরা দেখেছি অনেক নামী-দামী জাভাস্ক্রিপ্ট এপ্লিকেশন বাজারে এসেছে - কাজেই জাভাস্ক্রিপ্টে গভীর জ্ঞান রাখা যে যেকোন ডেভেলপারের জন্য আবশ্যকীয় সেটা না বললেও চলবে!

    ভাষাটির ইতিহাস দিয়ে শুরু করা যাক। ১৯৯৫ সালে নেটস্কেপের প্রকৌশলী Brendan Eich জাভাস্ক্রিপ্ট তৈরি করেন, যেটা মুক্তি পায় ১৯৯৬ সালের শুরুর দিকে নেটস্কেপ ২ (ব্রাউজার) এর সাথে। এর নাম দেয়া হয়েছিল LiveScript, কিন্তু মার্কেটিং কৌশলের গ্যাড়াকলে পড়ে দুর্ভাগ্যজনত এর নাম জাভাস্ক্রিপ্ট হয়ে যায়, সান মাইক্রোসিস্টেম এর জাভা ল্যাংগুয়েজের জনপ্রিয়তাকে পুঁজি করার জন্য। জাভা আর জাভাস্ক্রিপ্টের মাঝে তেমন কোন মিল না থাকা সত্ত্বেও সেই থেকে তাই জাভাস্ক্রিপ্ট নামটা নিয়ে বিভ্রান্তি থেকে গেছে।

    মাইক্রোসফট এই প্রোগ্রামিং ভাষার সাথে প্রায় মিলে যায় এরকম একটি ল্যাংগুয়েজ JScript নাম দিয়ে প্রায় ৩ মাস পর ইন্টারনেট এক্সপ্লোরারের সাথে বাজারে নিয়ে আসে। এদিকে নেটস্কেপ Ecma International (স্ট্যান্ডার্ড নির্ধারণ করে এরকম একটি ইউরোপীয়ান সংস্থা) এর কাছে ল্যাংগুয়েজটি উপস্থাপন করে - যার ফলাফল ১৯৯৭ সালে ECMAScript এর প্রথম সংস্করণ হিসেবে বাজারে আসে। ১৯৯৯ সালে এই স্ট্যান্ডার্ডটি আরো উন্নত হয় ECMAScript সংস্করণ ৩ হিসেবে - আর সেই থেকে ভাষাটির তেমন কোন বড় পরিবর্তন হয়নি। চতুর্থ সংস্করণটি ভেস্তে যায়, ভাষাটির জটিলতা নিয়ে মতবিরোধের ফলাফল হিসেবে। তবে এই চতুর্থ সংস্করণের অনেক অংশবিশেষ কে ভিত্তি হিসেবে ধরে ২০০৯ সালে নতুন ECMAScript এর পঞ্চম সংস্করণ প্রকাশ করা হয়।

    Specification মোটামোটি অপরিবর্তিত থাকাটা আসলে ডেভেলপারদের জন্য খুশির খবর, কারণ অনেকেই এর মাঝে ভাষাটির সাথে খাপ খাইয়ে নিতে পেরেছেন। আমি বিশেষভাবে, ৩য় সংস্করণের বিশেষ বিশেষ অংশ নিয়ে আলোচনা করব এখন, আর সবার পরিচিত নাম জাভাস্ক্রিপ্ট ব্যাবহার করব।

    অন্য সব প্রোগ্রামিং ভাষার সাথে জাভাস্ক্রিপ্টের বড় অমিল হল - এতে কোন ইনপুট/আউটপুটের বালাই নেই। একটা হোস্ট এনভায়রনমেন্টে চলবে ধরে নিয়েই জাভাস্ক্রিপ্ট ডিজাইন করা হয়েছে - সবচেয়ে পরিচিত হোস্ট এনভায়রনমেন্ট হচ্ছে ব্রাউজার। হোস্ট এনভায়রনমেন্টের দায়িত্ব হচ্ছে জাভাস্ক্রিপ্ট কীভাবে বাইরের জগতের সাথে ডেটা আদান প্রদান বা যোগাযোগ করবে সেটার আয়োজন করা। ব্রাউজার ছাড়াও Adobe Acrobat, Photoshop, Yahoo!'র Widget engine এমনকি সার্ভারে-চলে এরকম পরিবেশেও জাভাস্ক্রিপ্ট ইন্টারপ্রেটারের ব্যবহার দেখা যায়।

    হালকা পাতলা ধারণা

    জাভাস্ক্রিপ্ট একটি ওবজেক্ট-ওরিয়েন্টেড, ডায়নামিক প্রোগ্রামিং ভাষা। এতে আছে ডেটা টাইপ, অপারেটর, গুরুত্বপূর্ণ কিছু অবজেক্ট (যেগুলো সব সময় আপনি ব্যবহার করতে পারবেন) আর ফাংশন বা মেথড। জাভা আর সি প্রোগ্রামিং ভাষা থেকে বেশ কিছু সিন্ট্যাক্স ধার করে নেওয়ায় যারা এসব ভাষায় পারদর্শী তাদের জন্য সুখবর আরকি! তবে অন্য অনেক ভাষার সাথে জাভাস্ক্রিপ্ট এর একটা বড় পার্থক্য হল এতে কোন ক্লাস (class) নেই, বরং "প্রটোটাইপ" নামের নতুন এক ধারণা কে কাজে লাগিয়ে ক্লাস এর কাজ কর্ম করা হয়ে থাকে। আরো একটা বড় পার্থক্য হল জাভাস্ক্রিপ্ট এ যেকোন "ফাংশন" আসলে একেককটি অবজেক্ট! ফাংশনে আপনি কোড রাখতে পারবেন, আরো পারবেন অবজেক্টের মত কোডের এক জায়গা থেকে আরেক জায়গায় পাস (pass) করাতে।

    চলুন শুরু করি যেকোন ভাষার মৌলিক উপাদান নিয়ে: type মানে কী কী রকমের ডেটা থাকতে পারে। জাভাস্ক্রিপ্টে যেসব টাইপ আছে সেগুলো হলঃ

    ... আরও আছে বিশেষ দু'টি টাইপ - "Undefined" আর "Null". আর আছে Arrays, যেটি আসলে অবজেক্টের-ই একটি বিশেষ ধরণ। আছে তারিখ নিয়ে কাজকারবারের জন্যে Dates, আছে Regular Expressions - এগুলোও অবজেক্ট। আর আগেই তো বলা হয়েছে জাভাস্ক্রিপ্টে ফাংশন-ও আসলে অবজেক্ট। টাইপ হচ্ছে তাই:

    • সংখ্যা (Number)
    • স্ট্রিং (String)
    • বুলিয়ান
    • অবজেক্ট
      • ফাংশন
      • Array
      • Date
      • RegExp
    • Null (নাল)
    • Undefined (অসংজ্ঞায়িত)

    ওহ আর বলতে প্রায় ভুলেই গিয়েছিলাম, ত্রুটি ধরার জন্যে কিছু Error টাইপও আছে।

    সংখ্যা বা Numbers

    সংখ্যা টাইপ জাভাস্ক্রিপ্ট এ "double-precision 64-bit format IEEE 754 values", নির্দেশনা অনুসরণ করে। ফলস্বরূপ কিছু বিশেষ ঘটনা ঘটতে পারে। জাভাস্ক্রিপ্টে কোন "Integer" ধরণের টাইপ নাই, তাই অংক কষার সময় মাঝে মাঝে একটু সচেতন থাকবেন C অথবা জাভা ডেভেলপাররা। যেমন ধরুনঃ

    0.1 + 0.2 == 0.30000000000000004
    

    বাস্তবে integer গুলোকে ৩২-বিট ইন্টেজার ধরে নিয়ে কাজ করে জাভাস্ক্রিপ্ট (আর সংরকষণ ও একই ভাবে কিছু ব্রাউজারে করা হয়ে থাকে)। Bit-wise অপারেশন করার সময় এটা মাথায় রাখতে হবে। বিস্তারিত দেখতে পারেন জাভাস্ক্রিপ্টের পূর্ণাঙ্গ Number রেফারেন্স এ।

    অংক কষার জন্য স্ট্যান্ডার্ড সব অপারেটর জাভাস্ক্রিপ্টে আছে, যোগ বিয়োগ, ভাগশেষ (modulus) ইত্যাদি নিয়ে কাজ করা যায়। বলতে ভুলে গেছি Math নামে একটি গ্লোবাল অবজেক্ট আছে গণিত নিয়ে বিশদভাবে কাজকারবার করার জন্যেঃ

    Math.sin(3.5);
    var d = Math.PI * r * r;
    

    বিল্ট-ইন parseInt() ফাংশন ব্যবহার করে কোন string কে integer এ রূপান্তর করতে পারবেন। আর ফাংশনের দ্বিতীয় প্যারামিটার হিসেবে রূপান্তরের ভিত্তি (base) দিতে পারবেন, যদিও এই দ্বিতীয় প্যারামিটার টা ঐচ্ছিক এটা অবশ্যই দেওয়া উচিতঃ

    > parseInt("123", 10)
    123
    > parseInt("010", 10)
    10
    

    ২য় প্যারামিটারে base না পাঠালে অপ্রত্যাশিত ফলাফল আসতে পারেঃ

    > parseInt("010")
    8
    

    এমন ফল এসেছে কারণ শুরুতে 0 দেখে parseInt ফাংশনটা ধরে নিয়েছে ১ম প্যারামিটারে পাঠানো সংখ্যাটা অকটাল ভিত্তিতে আছে।

    বাইনারী সংখ্যাকে দশমিক ভিত্তিতে নিয়ে আসা একদম সোজাঃ

    > parseInt("11", 2)
    3
    

    একইভাবে parseFloat() নামেও বিল্ট-ইন ফাংশন আছে যেটা দিয়ে ভগ্নাংশ (floating point number) এ রূপান্তর (parse) করা যায়। তবে parseInt() এর সাথে এর একট অমিল - এটা সবসময় দশমিক ভিত্তিতে আছে ধরে নিয়ে কাজকর্ম করে।

    "+" অপারেটর ব্যবহার করেও কোন মান কে সংখ্যায় রূপান্তর করা যায়। এখানে "+" ইউনারী অপারেটর হিসেবে কাজ করেঃ

    > + "42"
    42 
    

    তবে আপনি যদি সংখ্যা নয়, এমন কোন String কে পার্স করতে যান তাহলে বিশেষ এক ধরণের মান ফাংশনের return ভ্যালু হিসেবে পাওয়া যায় -  NaN ("Not a Number" এর সংক্ষিপ্ত রূপ):

    > parseInt("hello", 10)
    NaN
    

    গাণিতিক কোন অপারেটর এর কোন এক NaN পাশে থাকলেই ফলাফাল হিসেবে NaN পাওয়া যাবেঃ

    > NaN + 5
    NaN
    

    বিল্ট-ইন isNaN() ফাংশন ব্যবহার করে বের করতে পারবেন কোন কিছু NaN কিনাঃ

    > isNaN(NaN)
    true
    

    জাভাস্ক্রিপ্টে অসীম মান নিয়ে কাজ করার জন্য আছে বিশেষ মান Infinity এবং -Infinity:

    > 1 / 0
    Infinity
    > -1 / 0
    -Infinity
    

    কোন মান Infinity, -Infinity অথবা NaN কিনা টেস্ট করতে পারবনে বিল্ট-ইন isFinite() ফাংশন দিয়েঃ

    > isFinite(1/0)
    false
    > isFinite(-Infinity)
    false
    > isFinite(NaN)
    false
    
    খেয়াল করুনঃ parseInt() আর parseFloat() ফাংশন দুইটি আপনার দেয়া প্যারামিটারকে ততক্ষণ পার্স করতে থাকবে যতক্ষণ না এটি প্যারামিটারে এমন কোন অক্ষর (character) পাচ্ছে যেটা কিনা অবৈধ (মানে যে ভিত্তিতে বা base থেকে রূপান্তর করা হচ্ছে সেই ভিত্তিতে এই অক্ষরটি থাকতে পারে না)। ফাংশনদুটি এই প্রথম অবৈধ অক্ষর এর আগ পর্যন্ত পার্স করে রিটার্ন করবে। তবে, "+" অপারেটর টি অন্যরকম, প্যারামিটারে কোন অবৈধ ক্যারেকটার থাকলে এটা সোজাসাপটা NaN রিটার্ন করে। ব্যাপারটা ভাল মত বুঝার জন্যে "10.2abc" এটাকে দুইটা ফাংশন দিয়ে পার্স করার কোড লিখে দেখুন!

    স্ট্রিং

    জাভাস্ক্রিপ্টে স্ট্রিং হল অন্য সব ভাষার মত পরপর অনেকগুলো ক্যারেকটার (character) - আরো ভালো মত বলতে গেলে ইউনিকোড ক্যারেক্টার। প্রতিটা ক্যারেকটারকে ১৬-বিট নাম্বার দিয়ে প্রকাশ করা যায়। তাই যাদের এপ্লিকেশনে বিভিন্ন ভাষাভাষী দেশের জন্যে সাপোর্ট দেয়া লাগবে তারা তালিয়া বাজাতে পারেন!

    আপনার যদি কখনো একটা মাত্র ক্যারেকটার ব্যবহার করার প্রয়োজন পরে, তাহলে ১ লেংথ (length) এর স্ট্রিং ব্যবহার করুন যেহেতু ক্যারেকটার বলে কোন টাইপ জাভাস্ক্রিপ্টে নাই।

    আগেই বলেছি জাভাস্ক্রিপ্টে সব স্ট্রিং ই একেক্টা অব্জেক্ট। কয়টা ক্যারেকটার আছে স্ট্রিং এ জানতে হলে length প্রোপার্টি (অবজেক্ট এর ফাংশন) ব্যবহার করুন।

    > "hello".length
    5
    

    এই প্রথম আমরা কোন অব্জেক্ট ব্যবহারের কোড দেখলাম! স্ট্রিং অবজেক্ট এর আরো অনেক ফাংশন ও কিন্তু আছেঃ

    > "hello".charAt(0)
    h
    > "hello, world".replace("hello", "goodbye")
    goodbye, world
    > "hello".toUpperCase()
    HELLO
    

    অন্যান্য টাইপ

    জাভাস্ক্রিপ্টের একটা অনন্য বৈশিষ্ট্য হল এটা null আর undefined এ দুটি জিনিস এক না। Null হচ্ছে 'অবজেক্ট' টাইপের এক ধরণের অবজেক্ট যেটা কোডার স্বেচ্ছায় কোন ভ্যারিয়েবলে এসাইন (asign) করেছেন। আর undefined হল 'undefined' টাইপের একটা অবজেক্ট - কোন ভ্যারিয়েবল যদি কোন ভ্যালু দিয়ে ইনিশিয়ালাইজ (initialize) করা না হয় (সোজা বাংলায় ভ্যারিয়েবল ডিক্লেয়ার করার সময় বা পরে যদি তাতে "=" চিহ্ন দিয়ে কোন মান এ্যাসাইন না করা হয়) তাহলে সেটা undefined অবস্থায় থাকে। ভ্যারিয়েবল নিয়ে আমরা পরে আলোচনা করব। জাভাস্ক্রিপ্টে ভ্যারিয়েবলে কোন ভ্যালু না দিয়েই ভ্যারিয়েবলটি তৈরি করা যায় - তখন তার ভ্যালু হিসেবে থাকে এই 'undefined'।

    জাভাস্ক্রিপ্টে বুলিয়ান বলে একটা টাইপ আছে, যার মান হতে পারে শুধুমাত্র true অথবা false (এই দুইটাই কি-ওয়ার্ড)। নিচের নিয়ম অনুযায়ী যেকোন ভ্যালু কে বুলিয়ানে কনভার্ট করা যায়ঃ

    1. false, 0, শূন্য স্ট্রিং (""), NaN, null, এবং undefined এগুলাকে বুলিয়ানে কনভার্ট করলে false পাওয়া যাবে।
    2. অন্য যেকোন টাইপের ভ্যলু বুলিয়ানে কনভার্ট করলে true পাওয়া যায়।

    Boolean() ফাংশন ব্যবহার করে আপনি এই কনভার্সন করেই ছাড়তে পারেনঃ

    > Boolean("")
    false
    > Boolean(234)
    true
    

    কিন্তু আমাদের এভাবে explicitly কনভার্ট করতে হবে না, কারণ জাভাস্ক্রিপ্ট যখন কোথাও বুলিয়ান প্রত্যাশা করে কিন্তু অন্য কোন টাইপ পায়, তখন সে চুপিচুপি এই কনভার্ট টি করে নেয়।

    অন্য ভাষার মত বুলিয়ান অপারেটর যেমন && (লজ্যিকাল AND), , || (লজ্যিকাল or), আর ! (লজ্যিকাল not) আছে।

    ভ্যারিয়েবল (চলক?!)

    var কী-ওয়ার্ড ব্যবহার করে নতুন কোন ভ্যারিয়েবল তৈরি করুনঃ

    var a;
    var name = "simon";
    

    আগেই বলেছি, ভ্যারিয়েবলে কোন ভ্যালু এসাইন না করলে সেটা undefined টাইপ হয়ে বসে থাকে।

    গুরুত্বপূর্ণঃ অন্যান্য প্রোগ্রামিং ভাষার সাথে বড় একটা পার্থক্য হল জাভাস্ক্রিপ্ট কোডে ব্লক লেভেলে ভ্যারিয়েবলের স্কোপ বলে কিছু নেই। ভ্যারিয়েবল টা যেই ফাংশনে আছে, পুরা ফাংশনে এই ভ্যারিয়েবলের একটাই স্কোপ থাকে। তাই যদি কোন if বা লুপের মধ্যে কোন ভ্যারিয়েবল তৈরি করেন তাহলে পুরা ফাংশনেই সেটার স্কোপ থাকবে।

    অপারেটর

    জাভাস্ক্রিপ্টের নিউমেরিক (numeric) অপারেটরগুলো হল +, -, *, / আর % - মানে কিনা ভাগশেষ বের করার অপারেটর।  = ব্যবহার করে ভ্যারিয়েবলে ভ্যালু দেয়া হয়। জটিল এসাইনমেন্ট অপারেটরও আছে যেমন += আর -=। এই জটিল অপারেটরগুলোকে ভেঙ্গে এইভাবে কল্পনা করতে পারেনঃ x = x অপারেটর y.

    x += 5
    x = x + 5
    

    ভ্যারিয়েবলের ভ্যালু এক বাড়াতে বা কমাতে যথাক্রমে ++ আর -- অপারেটর ব্যবহার করতে পারেন। সি/জাভার মত এগুলোর প্রিফিক্স/পোস্টফিক্স দুটো ফর্ম-ই আছে।

    আর আমরা তো আগেই জেনেছি + অপারেটর দিয়ে স্ট্রিং জোড়া লাগানো যায় (concatenation)

    > "hello" + " world"
    hello world
    

    আপনি যদি স্ট্রিং কে অন্য কোন সংখ্যা (বা অন্য কোন মান) এর সাথে যোগ করতে যান তাহলে সবকিছু প্রথমে স্ট্রিং এ কনভার্ট করে নেওয়া হয়ঃ

    > "3" + 4 + 5
    345
    > 3 + 4 + "5"
    75
    

    কোন মানকে স্ট্রিং এ কনভার্ট করার তাই আরেকটা চোরাই বুদ্ধি হচ্ছে ঐ মানের সাথে শূন্য স্ট্রিং ("")যোগ করা।

    তুলনা (Compare) করার জন্যে জাভাস্ক্রিপ্টের কাছে আছে <, >, <= আর >= অপারেটর। এগুলো সংখ্যা আর স্ট্রিং দুইটার বেলাতেই কাজ করে। তবে == অপারেটর ব্যবহার করে দুইটা মান সমান কিনা সেটা চেক করাটা একটু অপ্রত্যাশিত হতে পারে, কারণ == অপারেটরের দুই পাশে দুই ধরণের টাইপের ভ্যালু দিলে জাভাস্ক্রিপ্ট প্রয়োজন মত কনভার্ট করে নেয়ঃ

    > "dog" == "dog"
    true
    > 1 == true
    true
    

    জাভাস্ক্রিপ্টের এই মাতব্বরী বন্ধ করতে চাইলে === অপারেটর ব্যবহার করুনঃ

    > 1 === true
    false
    > true === true
    true
    

    আপনি হয়ত ভাবছেন != আর !== অপারেটেরের কথা - হ্যা এগুলো তো আছেই।

    জাভাস্ক্রিপ্ট দিয়ে বিট-লেভেলে (bitwise) অপারেশন ও করতে পারবেন।

    কন্ট্রোল স্ট্রাকচার

    C বা জাভাতে ব্যবহার করে আসা if অথবা else জাভাস্ক্রিপ্টেও একইভাবে ব্যবহার করা যাবেঃ

    var name = "kittens";
    if (name == "puppies") {
      name += "!";
    } else if (name == "kittens") {
      name += "!!";
    } else {
      name = "!" + name;
    }
    name == "kittens!!"
    

    জাভাস্ক্রিপ্টে while আর do-while লুপ আছে, হুবুহু সি-জাভার মত। যদি চান যে লুপটি অন্ততঃ একবার চালাতেই হবে তাহলে do-while ব্যবহার করুন।

    while (true) {
      // an infinite loop!
    }
    
    var input;
    do {
      input = get_input();
    } while (inputIsNotValid(input))
    

    সি-জাভার মত for লুপ ব্যবহার করে এক লাইনেই লুপ কন্ট্রোল করতে পারেনঃ

    for (var i = 0; i < 5; i++) {
      // Will execute 5 times
    }
    

    শর্ট-কাটে লজিক প্রয়োগ করতে পারেন && আর || অপারেটরের একটি অন্যরকম ব্যবহার করে। অপারেটর গুলোর ২য় অপারেন্ড কার্যকর (execute) হবে কিনা নির্ভর করে ১ম অপারেন্ডের ওপরঃ

    var name = o && o.getName();
    

    অথবা ডিফল্ট মান বসানোর জন্যঃ

    var name = otherName || "default";
    

    কন্ডিশনাল এক্সপ্রেশনের জন্য সি-জাভার মত টার্নারী অপারেটর (যেগুলোর অপারেন্ড ৩টি) ব্যবহার করতে পারেনঃ

    var allowed = (age > 18) ? "yes" : "no";
    

    একাধিক ব্রাঞ্চের কন্ট্রোল লজিকের জন্যে switch ব্যবহার করতে পারেন। জাভাস্ক্রিপ্টের switch-case স্ট্রিং এর জন্যে কাজ করেঃ

    switch(action) {
        case 'draw':
            drawit();
            break;
        case 'eat':
            eatit();
            break;
        default:
            donothing();
    }
    

    break না ব্যবহার করলে পরের case এও আপনার কোডের কন্ট্রোল চলে যাবে - এরকম কোডিং সাধারণতঃ করা হয়না। তাই আপনি যদি break ব্যবহার না করেন তাহলে কমেন্টে লিখে রাখুন যাতে কনফিউশন তৈরি না হয়।

    switch(a) {
        case 1: // fallthrough
        case 2:
            eatit();
            break;
        default:
            donothing();
    }
    

    default ক্লজটি ঐচ্ছিক। আরেকটি মজার দিক হল switch আর case দু'জায়গাতেই আপনি এক্সপ্রেশন লিখতে পারেন। তুলনা করা হয় === অপারেটর ব্যবহার করেঃ

    switch(1 + 3) {
        case 2 + 2:
            yay();
            break;
        default:
            neverhappens();
    }
    

    অবজেক্ট

    জাভাস্ক্রিপ্টে অবজেক্ট হচ্ছে এক কথায় name-value pair। নিচে যেগুলো পয়েন্ট করা হয়ছে সেগুলোর সাথে কোন পার্থক্য নেইঃ

    • Python এর dictionary
    • Perl আর ruby'র hash
    • C/C++ এর hashtable
    • জাভা'র HashMap
    • PHP'র associative array

    এই ডেটা-স্ট্রাকচারটা এত বেশি ব্যবহৃত হয় যে এটা সম্পর্কে নতুন করে বলার সুযোগ কম। যেহেতু জাভাস্ক্রিপ্টে সবকিছুই অবজেক্ট এটা আশা করা স্বাভাবিক যে জাভাস্ক্রিপ্ট প্রোগ্রামে অনেক বেশি হ্যাশটেবিল লুক-আপ হবে... আমরা বেঁচে গেছি কারণ অনেক দ্রুত এই লুক-আপ করা যায়!

    জাভাস্ক্রিপ্ট অবজেক্টের "name" হিসেবে স্ট্রিং ব্যবহার করতে হবে, আর "value" অংশে যা মন চায় ব্যবহার করতে পারবেন। তার মানে ভ্যালু হিসেবে অন্য অবজেক্ট ও রাখতে পারেন। এভাবে যত জটিল চান সেরকম অবজেক্ট-ই তৈরি করতে পারবেন।

    একটা শূণ্য অবজেক্ট ২ভাবে তৈরি করতে পারেনঃ

    var obj = new Object();
    

    আরঃ

    var obj = {};
    

    ওপরের দুইটা উপায় সমার্থক। নিচের পদ্ধটি-তিকে বলা হয় অবজেক্ট লিটারেল, আর এটি JSON সিন্ট্যাক্সের ও একটি অংশ। তাই নিচের পদ্ধটি-তি যত বেশি ব্যবহার করা যায় ভাল!

    অবজেক্ট তৈরি হয়ে যাওয়ার পর এর প্রোপার্টি (সম্পদ?!) দুইভাবে এক্সেস করতে পারবেনঃ

    obj.name = "Simon";
    var name = obj.name;
    

    এবং...

    obj["name"] = "Simon";
    var name = obj["name"];
    

    দুইটা পদ্ধতি-ই সমার্থক, তবে ২য় পদ্ধটিতে দেখুন, অবজেক্টের name স্ট্রিং হিসেবে দেওয়া হচ্ছে, মানে এই নামটি আমরা ভ্যারিয়েবলে রেখে রানটাইমে দিতে পারি! অবশ্য এই পদ্ধটিতি ব্যবহার করলে কিছু জাভাস্ক্রিপ্ট ইঞ্জিন আর মিনিফায়ার (কোড সংক্ষিপ্ত করে যেসব টুলস) কোড অপ্টিমাইজ করতে পারে না। আবার এই পদ্ধতি ব্যবহার করে সংরক্ষিত name সমূহ গেট-সেট করতে পারেনঃ

    obj.for = "Simon"; // Syntax error, because 'for' is a reserved word
    obj["for"] = "Simon"; // works fine
    

    আগেই দেখানো অবজেক্ট লিটেরাল সিন্ট্যাক্স ব্যবহার করে পুরো অবজেক্ট গোড়াতেই ইনিশিয়ালাইজ করে নিতে পারেনঃ

    var obj = {
        name: "Carrot",
        "for": "Max",
        details: {
            color: "orange",
            size: 12
        }
    }
    

    অবজেক্টের এট্রিবিউট এক্সেস একের-পর-এক (চেইন) করতে পারেনঃ

    > obj.details.color
    orange
    > obj["details"]["size"]
    12
    

    Array

    জাভাস্ক্রিপ্টের Array আসলে বিশেষ ধরণের অবজেক্ট - রেগুলার অবজেক্টের মতই array কাজ করে বেশিরভাগ সময়ে ( numeric প্রোপার্টি, মানে ০,১,২,... ইন্ডেক্সে থাকা প্রোপার্টি শুধুমাত্র [] সিন্ট্যাক্স দিয়েই এক্সেস করা যাবে)। সব array'র 'length" ম্যাজিক প্রোপার্টি আছে, যার মান হচ্ছেঃ (ওই array'র সর্বোচ্চ ইন্ডেক্স + ১)

    প্রাগৈতিহাসিক(!) নিয়মে array ব্যবহার-পদ্ধতিঃ

    > var a = new Array();
    > a[0] = "dog";
    > a[1] = "cat";
    > a[2] = "hen";
    > a.length
    3
    

    তবে স্মার্ট এপ্রোচ হচ্ছে array লিটেরাল ব্যবহার করাঃ

    > var a = ["dog", "cat", "hen"];
    > a.length
    3
    

    সাবধানঃ লিটের‍্যাল ব্যবহার করে সবশেষের প্রোপার্টির পর কমা রেখে দিলে ব্রাউজার-ভেদে বিভিন্ন অবস্থা তৈরি হতে পারে - তাই সর্বশেষ array element এর পর কমা রেখে দেওয়া চলবে না।

    খেয়াল করুনঃ array.length মানেই যে array তে কয়টা প্রোপার্টি আছে তা কিন্তু সবসময় ঠিক না। নিচের কোড দেখুনঃ

    > var a = ["dog", "cat", "hen"];
    > a[100] = "fox";
    > a.length
    101
    

    আবারো বলছিঃ length প্রোপার্টি হচ্ছে: (সবথেকে বড় ইন্ডেক্স + ১)

    আপনি যদি এমন কোন প্রোপার্টি নিয়ে কাজ করতে চান যেটির ইন্ডেক্স ভুল, তাহলে 'undefined' পাবেন মান হিসেবে।

    > typeof a[90]
    undefined
    

    ওপরে যা বলা হল তা মাথায় রেখে নিচের কোড দিয়ে array এর সব এলিমেন্ট নিয়ে কাজ করতে পারবেন (iteration):

    for (var i = 0; i < a.length; i++) {
        // Do something with a[i]
    }
    

    তবে ওপরের কোডিং টা অত ভাল হল না, কারণ বারবার array.length প্রোপার্টি'র মান খুঁজতে হচ্ছে। নিচের কোড টা বেশি ভাল (কার্যকরী):

    for (var i = 0, len = a.length; i < len; i++) {
        // Do something with a[i]
    }
    

    আর নিচেরটা হচ্ছে বস-লেভেলের কোডিং ;-)

    for (var i = 0, item; item = a[i++];) {
        // Do something with item
    }
    

    খেয়াল করুনঃ লুপের দুই সেমিকোলনের মাঝের অংশ ভ্যারিয়েবল এসাইনমেন্ট আর false কিনা টেস্টিং দুই-ই করা হচ্ছে। এই লুপ ব্রেক করবে প্রথম "falsy" এলিমেন্ট (যেমন 'undefined') পাওয়া মাত্রই।

    কাজেই, যদি array'র কোন এলিমেন্ট "falsy" হওয়ার সম্ভাবনা থাকে, তাহলে ওপরের তথাকতিত "বস-লেভেল" কোডিং ব্যবহার করা যাবে না। যেমন, কোন array এলিমেন্ট এর মান false হিসেবে গণ্য হলেই (যেমন শূণ্য স্ট্রিং) পরের ভ্যালিড এলিমেন্ট ও কিন্তু আর এক্সেস করা যাবে না। আপনি যদি নিশিচ থাকেন যে array তে কোন "falsy" এলিমেন্ট নেই (যেমন অবজেক্টের array অথবা DOM নোড ইত্যাদি) তাহলেই ওপরের পদ্ধতি ব্যবহার করুন।

    for...in লুপ ব্যবহার করেও array এলিমেন্টগুলোতে iterate করতে পারবেন। কেউ যদি Array.prototype (পরে আলোচনা করেছি) ব্যবহার করে নতুন নতুন প্রোপার্টি যোগ করেন এরে তে, তাহলে সেগুলোও এই লুপ দিয়ে iterate করা যাবে:

    for (var i in a) {
      // Do something with a[i]
    }
    

    Array তে নতুন এলিমেন্ট যোগ করার সবচাইতে নিরাপদ পদ্ধতিঃ

    a[a.length] = item;                 // same as a.push(item);
    

    যেহেতু a.length সবসময় এরে এর সর্বোচ্চ ইন্ডেক্স + ১ কাজেই আপনি নিশ্চিত থাকতে পারেন যে আপনি ফাকা স্থানেই নতুন এলিমেন্ট বসাচ্ছেন।

    Array'র সাথে অনেক ফাংশন প্যাকেট করে দেওয়া হয়েছেঃ

    Method name Description
    a.toString()  
    a.toLocaleString()  
    a.concat(item[, itemN]) প্রদত্ত আইটেম (এলিমেন্ট) গুলো সহ নতুন একটি Array রিটার্ন করে।
    a.join(sep)  
    a.pop() শেষ এলিমেন্ট-টিকে এরে থেকে মুছে দিয়ে রিটার্ন করে।
    a.push(item[, itemN]) এরে'র শেষে এক/একাধিক আইটেম যোগ করে
    a.reverse()  
    a.shift()  
    a.slice(start, end) একটি সাব-এরে রিটার্ন করে
    a.sort([cmpfn]) তুলনা করার জন্য একটি ফাংশন প্যারামিটারে দিতে পারেন (ঐচ্ছিক)
    a.splice(start, delcount[, itemN]) এরে'র আইটেমগুলো মুছে দিয়ে/রিপ্লেস করতে পারবেন।
    a.unshift([item]) এরে'র শুরুতে আইটেম কে যোগ করে।

    ফাংশন

    জাভাস্ক্রিপ্ট ভালোমত শিখতে হলে অবজেক্টের পাশাপাশি ফাংশন সম্পর্কেও ভাল ধারণা থাকা দরকার। নিচে খুবই নিরীহ-দর্শন একটা ফাংশন দেখা যাচ্ছেঃ

    function add(x, y) {
        var total = x + y;
        return total;
    }
    

    ফাংশনের বেসিক জানার জন্যে এইটুকুই যথেষ্ট। ফাংশনে এক/একাধিক প্যারামিটার/আর্গুমেন্ট পাঠাতে পারেন। ফাংশনের বডিতে যত খুশি স্টেটমেন্ট লিখুন, নিজের ইচ্ছামত "লোকাল" ভ্যারিয়েবল তৈরি করুন। ফাংশনের যেকোন জায়গায় return স্টেটমেন্ট ব্যবহার করতে পারেন - এই স্টেটমেন্টের পরের কোন স্টেটমেন্ট আর কার্যকর হবে না মানে ফাংশন থেকে তখন-ই প্রোগ্রাম বের হয়ে যাবে। return এর পর যেই ভ্যালু দিবেন ফাংশনটি তার caller কে সেই ভ্যালুটি রিটার্ন করবে। return এর পর কোন ভ্যালু না দিলে (অথবা আদৌ কোন রিটার্ন স্টেটমেন্ট ব্যবহার না করলে) জাভাস্ক্রিপ্ট undefined কে রিটার্ন ভ্যালু হিসেবে পাঠিয়ে থাকে।

    আপনি যদি ফাংশন কল করার সময় কোন প্যারামিটারের ভ্যালু না পাঠান, তাহলে 'undefined' ঐ প্যারামিটারের ভ্যালু হিসেবে চলে যাবে ফাংশনের কাছেঃ

    > add()
    NaN // You can't perform addition on undefined
    

    অন্য প্রোগ্রামিং ভাষার সাথে একটুখানি অমিলঃ আপনি চাইলে ফাংশন যতগুলো প্যারামিটার নিয়ে কাজ করে, তার থেকে বেশি প্যারামিটারও পাঠাতে পারেন!

    > add(2, 3, 4)
    5 // added the first two; 4 was ignored
    

    একটু হাস্যকর মনে হলেও, ফাংশনের নিজস্ব arguments নামের একটা array এর মত ভ্যারিয়েবল আছে - এর এলিমেন্ট হিসেবে ফাংশনে যতগুলো ভ্যালু প্যারামিটার হিসেবে পাঠানো হয়েছে সবগুলোই থাকে। আসুন add ফাংশনটিকে এমনভাবে লিখি যাতে যত খুশি প্যারামিটার পাঠানো হোক না কেন সবগুলোই যোগ করা যায়ঃ

    কিছুটা "আজাইরা" ধাঁচের মনে হলেও জাভাস্ক্রিপ্টে ফাংশগুলোর মধ্যে arguments নামের একটি ভ্যারিয়েবল আছে, যাতে ফাংশনটিতে পাস করা সকল ভ্যালু থাকে! আসুন ফাংশনটিকে এমনভাবে পরিবর্তন করি যেন তা ইচ্ছেমত আর্গুমেন্ট নিতে পারে!

    function add() {
        var sum = 0;
        for (var i = 0, j = arguments.length; i < j; i++) {
            sum += arguments[i];
        }
        return sum;
    }
    
    > add(2, 3, 4, 5)
    14
    

    এবার গড় বের করার একটি ফাংশন (ফাংশন নাম্বার ১) লিখে ফেলিঃ

    function avg() {
        var sum = 0;
        for (var i = 0, j = arguments.length; i < j; i++) {
            sum += arguments[i];
        }
        return sum / arguments.length;
    }
    > avg(2, 3, 4, 5)
    3.5
    

    এটা কাজের ফাংশন সন্দেহ নাই, তবে নতুন একটা ঝামেলা তৈরি করলঃ avg() ফাংশন কমা-দিয়ে-আলাদা-করা এক গাদা আর্গুমেন্ট নেয় - কিন্তু আপনি যদি আর্গুমেন্ট হিসেবে শুধু একটি array পাঠাতে চান? তাহলে avg() ফাংশনটা এভাবে (ফাংশন নাম্বার ২) লেখা যেতে পারেঃ

    function avgArray(arr) {
        var sum = 0;
        for (var i = 0, j = arr.length; i < j; i++) {
            sum += arr[i];
        }
        return sum / arr.length;
    }
    > avgArray([2, 3, 4, 5])
    3.5
    

    কিন্তু নতুন করে না লিখে আগের ১-নাম্বার ফাংশনটা-ই ব্যবহার করতে পারলে ভাল হত! এক কাজের জন্য কয়টা ফাংশন লিখব, আর কি কাজ কর্ম নাই নাকি? সৌভাগ্যবশতঃ, জাভাস্ক্রিপ্ট দিয়ে যেকোন ফাংশনের apply() মেথড কল করে (যেহেতু ফাংশন-ও এক ধরণের অবজেক্ট, তাই ফাংশনের-ও মেথড/প্রোপার্টি ইত্যাদি থাকতে পারে!) আর্গুমেন্ট হিসেবে যেকোন array পাঠাতে পারেনঃ

    > avg.apply(null, [2, 3, 4, 5])
    3.5
    

    এই apply() মেথডের ২য় প্যারামিটারের ভ্যালুটাই ফাংশনের arguments হিসেবে ব্যবহৃত হবে। প্রথম আর্গুমেন্টের কাজ কি সেটা পরে বলব। আর হ্যা, এখন তো বুঝতে পারলেন যে ফাংশন-ও জাভাস্ক্রিপ্টে আসলে অবজেক্ট! (প্রমাণিত)

    জাভাস্ক্রিপ্টে বেওয়ারিশ (anonymous!) ফাংশন তৈরি করতে পারবেনঃ

    var avg = function() {
        var sum = 0;
        for (var i = 0, j = arguments.length; i < j; i++) {
            sum += arguments[i];
        }
        return sum / arguments.length;
    }
    

    ওপরের "বেওয়ারিশ" ফাংশন আর function avg() এর মাঝে কোন পার্থক্য নাই, অন্ততঃ সিমান্ট্যাকালি। কিন্তু এই anonymous ফাংশন অনেক পাওয়ারফুল - কারণ এভাবে আপনি কোডের যেকোন জায়গায় ফাংশন তৈরি করে নিতে পারেন  - যেখানে হয়ত আগে সাদামাটা এক্সপ্রেশন দেওয়া লাগত। যেমন, একটা ট্রিক দেখুনঃ আমরা জানি জাভাস্ক্রিপ্টে "ব্লক" লেভেলে ভ্যারিয়েবলের নতুন কোন স্কোপ নাই, কিন্তু ফাংশন লেভেলে ভ্যারিয়েবলের স্কোপ আছে। এখন যদি আমরা কোন ফাংশনের ভেতর C ল্যাংগুয়েজের মত ব্লক লেভেলে কোন ভ্যারিয়েবলের স্কোপ চাই, তাহলে চট করে একটা বেওয়ারিশ ফাংশন লিখে ফেলতে পারিঃ

    var a = 1;
    var b = 2;
    (function() {
      var b = 3; // b ভ্যারিয়েবলের নতুন স্কোপ তৈরি করলাম!
      a += b;
    })();
    a; // 4
    b; // 2 - b এর মান আগেরটাই আছে।
    

    জাভাস্ক্রিপ্টে ফাংশন রিকারসিভলি (কোন ফাংশনের ভেতর নিজেকেই আবার কল করা) কল করতে পারবেন। Tree - সদৃশ ডেটা স্ট্রাকচার নিয়ে কাজ করতে এটা উপকারী। যেমন, DOM নিয়ে কাজ করার সময় কাজে লাগে।

    function countChars(elm) {
        if (elm.nodeType == 3) { // TEXT_NODE
            return elm.nodeValue.length;
        }
        var count = 0;
        for (var i = 0, child; child = elm.childNodes[i]; i++) {
            count += countChars(child);
        }
        return count;
    }
    

    অবশ্য এভাবে কাজ করতে গিয়ে anonymous ফাংশন কল করতে গেলে ঝামেলা হবেঃ এনোনিমাস ফাংশনকে রিকার্সিভ কল কিভাবে করবেন? ওদের তো নাম-ই নাই! এই ঝামেলা দূর করতে এসে গেল "named anonymous ফাংশন":

    var charsInBody = (function counter(elm) {
        if (elm.nodeType == 3) { // TEXT_NODE
            return elm.nodeValue.length;
        }
        var count = 0;
        for (var i = 0, child; child = elm.childNodes[i]; i++) {
            count += counter(child);
        }
        return count;
    })(document.body);
    

    এভাবে এনোনিমাস ফাংশনকে দেওয়া নাম শুধুমাত্র ঐ ফাংশনের ভেতরেই কাজ করবে। এভাবে যেমন জাভাস্ক্রিপ্ট ইঞ্জিন অপ্টিমাইজ ভাবে কাজ করতে পারে, সেরকম আপনার কোডও সহজবোধ্য হয়।

    মনের মত অবজেক্ট

    খেয়াল করুন: জাভাস্ক্রিপ্ট দিয়ে অবজেক্ট-ওরিয়েন্ট প্রোগ্রামিং করতে চাইলে আরো বিস্তারিত আলোচনা দেখুন এখানেঃ জাভাস্ক্রিপ্টে অবজেক্ট-ওরিয়েন্টেডের হাতেখড়ি

    এই প্যারার টাইটেল নিয়ে একটু মজা করলাম... হেহে। ইংরেজিতে টাইটেল ছিল "Custom Objects" :-P

    ক্ল্যাসিক অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং মানেই class নামের কী-ওয়ার্ড... জাভাস্ক্রিপ্ট একটু অন্যরকম। আমরা সাধারণত অবজেক্ট কে ডেটা আর সেইসব ডেটার ওপর কাজ করার জন্য মেথড এর একটা কালেকশনকেই বুঝি। জাভাস্ক্রিপ্টে কোন class স্টেটমেন্ট নাই, জাভাস্ক্রিপ্ট prototype-ভিত্তিক প্রোগ্রামিং ভাষা, তাই সবার এই ব্যাপারটা একটু শিখে নেওয়ার দরকার আছে। জাভাস্ক্রিপ্টে আসলে ফাংশন-ই ক্লাস হিসেবে কাজ করে। আসুন একটা person অবজেক্ট চিন্তা করি... যাতে firstname আর lastname দু'টি প্রোপার্টি আছে। এখন, কোন ব্যাক্তির নাম প্রিন্ট করার ২টা পদ্ধতি চিন্তা করিঃ একটা হল আগে firstname প্রিন্ট করব, পরে lastname। আর অন্য পদ্ধতিটা উল্টা, আগে lastname প্রিন্ট করবঃ

    function makePerson(first, last) {
        return {
            first: first,
            last: last
        }
    }
    function personFullName(person) {
        return person.first + ' ' + person.last;
    }
    function personFullNameReversed(person) {
        return person.last + ', ' + person.first
    }
    > s = makePerson("Simon", "Willison");
    > personFullName(s)
    Simon Willison
    > personFullNameReversed(s)
    Willison, Simon
    

    কিন্তু... ওপরের আজব কোড দেখে যেকারর-ই মেজাজ খারাপ হওয়ার  কথা। এভাবে কোড করতে থাকলে শেষপর্যন্ত আপনার স্ক্রিপ্টের global স্কোপেই হাজার হাজার ফাংশন পয়দা হয়ে বসে থাকবে। আমরা চাই অবজেক্টের সদস্য মেথডগুলো শুধু ওই অবজেক্টের স্কোপেই থাকুক। যেহেতু ফাংশন == অবজেক্ট, আমরা নিমেষেই সুন্দর কোডিং করে ফেলতে পারিঃ

    function makePerson(first, last) {
        return {
            first: first,
            last: last,
            fullName: function() {
                return this.first + ' ' + this.last;
            },
            fullNameReversed: function() {
                return this.last + ', ' + this.first;
            }
        }
    }
    > s = makePerson("Simon", "Willison")
    > s.fullName()
    Simon Willison
    > s.fullNameReversed()
    Willison, Simon
    

    ওপরের কোডে দেখুন আমরা প্রথমবারের মত 'this' কীওয়ার্ড ব্যবহার করলাম। কোন ফাংশনের ভেতর যখন this ব্যবহার করা হয় তখন সেটি ঐ ফাংশনটি'র অবজেক্ট কে রেফার (refer)  করে থাকে, যদি কিনা আপনি ডট (.) অথবা ব্রাকেট ব্যবহার করে মেথডটিকে কল করে থাকেন। কিন্তু অন্য কোন ভাবে কল করলে this আসলে global অবজেক্ট কে রেফার করে। এই ব্যাপারটা ঠিকমত না বুঝতে পারলে ভুল করার সম্ভাবনা থাকে। উদাহরণ দেখুনঃ

    > s = makePerson("Simon", "Willison")
    > var fullName = s.fullName;
    > fullName()
    undefined undefined
    

    যখন আমরা fullName() কল করলাম (s.fullname() না) , 'this' আসলে global অবজেক্ট কে রেফার করে। এই গ্লোবাল অবজেক্ট এর যেহেতু first অথবা last নামের কোন প্রোপার্টি নাই, তাই undefined পাওয়া যাচ্ছে।

    নতুনদের জন্য এই বিষয়টা একটু ভ্রান্তিকর মনে হতে পারে... যাই হোক! এই this কী-ওয়ার্ড ব্যবহার করে আমরা makePerson কে আরেকটু ভাল চেহারা দিতে পারিঃ

    function Person(first, last) {
        this.first = first;
        this.last = last;
        this.fullName = function() {
            return this.first + ' ' + this.last;
        }
        this.fullNameReversed = function() {
            return this.last + ', ' + this.first;
        }
    }
    var s = new Person("Simon", "Willison");
    

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

    দেখতে সুন্দর লাগলেও আসলে এখনো একটা ঝামেলা রয়ে গেছে, যত বার আমরা new ব্যবহার করে makePerson এর অবজেক্ট তৈরি করব, প্রতিটা অবজেক্টের জন্য fullName() আর fullNameRevesed() ফাংশনদুটি'র নতুন কপি তৈরি হবে! কিন্তু আসলে তো উচিত শুধু ক্লাসের সদস্য ভ্যারিয়েবল গুলোর অবজেক্ট-প্রতি কপি তৈরি হওয়া, ফাংশন এর কপি তৈরি করাটা অপ্রয়োজনীয়। প্রতিটা অবজেক্টের জন্য এভাবে মেম্বার ফাংশন তৈরি না করে আসলে প্রচলিত অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং এর ধারণা অনুযায়ী কোন ক্লাসের সকল অবজেক্ট বা ইন্সট্যান্সের উচিত ক্লাসের সদস্য ফাংশন শেয়ার করা।

    function personFullName() {
        return this.first + ' ' + this.last;
    }
    function personFullNameReversed() {
        return this.last + ', ' + this.first;
    }
    function Person(first, last) {
        this.first = first;
        this.last = last;
        this.fullName = personFullName;
        this.fullNameReversed = personFullNameReversed;
    }
    

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

    function Person(first, last) {
        this.first = first;
        this.last = last;
    }
    Person.prototype.fullName = function() {
        return this.first + ' ' + this.last;
    }
    Person.prototype.fullNameReversed = function() {
        return this.last + ', ' + this.first;
    }
    

    Person.prototype এমন একটি অবজেক্ট যা Person ফাংশনের সব ইন্সট্যান্স বা অবজেক্ট শেয়ার করে। এভাবে "prototype chain" নামের বিশেষ ধরণের লুক-আপ চেইন তৈরি হয়ঃ যখন আপনি Person এর এমন কোন প্রোপার্টি এক্সেস করতে চান যেটা এখনো সেট করা হয়নি, জাভাস্ক্রিপ্ট Person.prototypeখুজে দেখবে (লুক-আপ) সেখানে এই প্রোপার্টি টি সেট করা আছে কিনা। সহজ বাংলায়, Person.prototype এ আমরা যে সকল প্রোপার্টি (ফাংশন বা ডেটা) সেট করব সেটা Person এর সকল ইন্সট্যান্স/অবজেক্ট এক্সেস করতে পারবে।

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

    এই প্রটোটাইপ আসলে অনেক পাওয়ারফুল টুল। জাভাস্ক্রিপ্ট আপনাকে যেকোন কিছুর প্রটোটাইপ যেকোন সময় পরিবর্তন করার সুযোগ দেয়। যার মানে, রানটাইমেই আপনি অবজেক্টে নতুন নতুন মেথড যোগ করতে পারেন! এটাও সি/জাভার সাথে অন্যতম পার্থক্য প্রটোটাইপ-ভিত্তিক ল্যাংগুয়েজ যেমন জাভাস্ক্রিপ্ট এর।

    > s = new Person("Simon", "Willison");
    > s.firstNameCaps();
    TypeError on line 1: s.firstNameCaps is not a function
    > Person.prototype.firstNameCaps = function() {
        return this.first.toUpperCase()
    }
    > s.firstNameCaps()
    SIMON
    

    মজা দেখুনঃ বিল্ট-ইন অবজেক্টের প্রটোটাইপ ও এভাবে পরিবর্তন করা সম্ভব! আসুন আমরা বিল্ট ইন String এ নতুন একটা মেথড যোগ করি স্ট্রিং উল্টে দেওয়ার জন্যঃ

    > var s = "Simon";
    > s.reversed()
    TypeError on line 1: s.reversed is not a function
    > String.prototype.reversed = function() {
        var r = "";
        for (var i = this.length - 1; i >= 0; i--) {
            r += this[i];
        }
        return r;
    }
    > s.reversed()
    nomiS
    

    এই নতুন মেথড স্ট্রিং লিট্যারেল এও কাজ করবেঃ

    > "This can now be reversed".reversed()
    desrever eb won nac sihT
    

    আগে যেমনটা বলেছিলাম, prototype হচ্ছে একটা বিশেষ চেইনের অংশ। এই চেইনের গোড়ায় আছে Object.prototype, যার একটা মেথড হল toString() - এই মেথড টা কল হয় যখন আপনি কোন একটা অবজেক্ট কে স্ট্রিং হিসেবে পেতে চাচ্ছেন (জাভা'র toString() এর মতন)। ডিবাগিং করার জন্য এটা উপকারী হবেঃ

    > var s = new Person("Simon", "Willison");
    > s
    [object Object]
    > Person.prototype.toString = function() {
        return '<Person: ' + this.fullName() + '>';
    }
    > s
    <Person: Simon Willison>
    

    মনে আছে আমরা avg.apply() এর প্রথম প্যারামিটারে null পাঠিয়েছিলাম? এই apply() এর প্রথম প্যারামিটারে যেই অবজেক্ট পাঠাবেন, this সেটাকে রেফার করবে। আমরা new এর একটা সহজ-সরল ইমপ্লেমেন্টেশন করতে পারিঃ

    function trivialNew(constructor) {
        var o = {}; // Create an object
        constructor.apply(o, arguments);
        return o;
    }
    

    অবশ্য এটা পুরোপুরি new হিসেবে ব্যবহার করা যাবে না কারণ প্রটোটাইপ চেইন তৈরি করা হয়নি। এরকম কোড সচরাচর দেখা যায়না, তবে চাইলে যে করতে পারবেন সেটা দেখানো হল আরকি।

    call নামের apply() এর মতই একটা ফাংশন আছে। পার্থক্য হল, array এর বদলে এটি বিশদ-ভাবে (কমা সেপারেটেড) আর্গুমেন্ট নিয়ে থাকেঃ

    function lastNameCaps() {
        return this.last.toUpperCase();
    }
    var s = new Person("Simon", "Willison");
    lastNameCaps.call(s);
    // Is the same as:
    s.lastNameCaps = lastNameCaps;
    s.lastNameCaps();
    

    ফাংশনের ভেতরে ফাংশন (Inner functions)

    জাভাস্ক্রিপ্টে ফাংশনের ভেতরে ফাংশন লেখা যায় এটা আমরা আগে makePerson() উদাহরণে দেখেছি। একটা গুরুত্বপূর্ণ ব্যাপার খেয়াল রাখতে হবে যে জাভাস্ক্রিপ্টে এই Inner ফাংশন তার প্যারেন্ট ফাংশনের স্কোপ এক্সেস করতে পারেঃ

    function betterExampleNeeded() {
        var a = 1;
        function oneMoreThanA() {
            return a + 1;
        }
        return oneMoreThanA();
    }
    

    তাই কোড বুঝার আওতায় রাখা আর মেইন্টেইন করা সহজ জাভাস্ক্রিপ্টেঃ যদি কোন ফাংশন (ফাংশন নাম্বার-১) এমন কয়েকটা ফাংশনের ওপর নির্ভর করে যেই ফাংশনগুলা অন্য কোথাও কাজে লাগে না, তাহলে আমরা সেই ফাংশনগুলোকে এই ১-নাম্বার ফাংশনের ইনার (inner) ফাংশন হিসেবে রেখে দিতে পারি। এতে করে, global স্কোপের আওতায় থাকা ফাংশনের সংখ্যা কমবে - এটা সবসময় ভাল অভ্যাস হিসেবে বিবেচনা করা হয়।

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

     ক্লোজার (Closures)

    ক্লোজার জাভাস্ক্রিপ্টের অফার করা সেরা এবস্ট্রাকশন (abstractions) গুলোর একটি - অবশ্য ঠিকভাবে না বুঝলে কনফিউজিং হতে পারে। এটা আসলে কী?

    function makeAdder(a) {
        return function(b) {
            return a + b;
        }
    }
    x = makeAdder(5);
    y = makeAdder(20);
    x(6)
    ?
    y(7)
    ?
    

    makeAdder নাম দেখেই বুঝা যাচ্ছে এটা নতুন নতুন adder ফাংশন তৈরি করে! এই নতুন ফাংশনগুলো কে যখন কিনা আবার কল করা হবে কোন আর্গুমেন্ট দিয়ে, makeAdder ফাংশনে দেয়া আর্গুমেন্টটির সাথে এই নতুন আর্গুমেন্ট কে যোগ করবে।

    এখানে যা হচ্ছে অনেকটাই Inner ফাংশনের সাথে মিলে যায়ঃ একটা ফাংশনকে অন্য কোন ফাংশনের মধ্যে তৈরি করা হয়ছে - ভেতরের ফাংশনটি বাইরের (প্যারেন্ট) ফাংশনের সব ভ্যারিয়েবল এক্সেস করতে পারে। একমাত্র পার্থক্য হচ্ছে বাইরের (outer) ফাংশনটি কিন্তু রিটার্ন করে গিয়েছে, তাই কমন-সেন্স থেকে মনেহতে পারে যে এর লোকাল ভ্যারিয়েবল গুলো আর নেই। কিন্তু আসলে, এগুলো এখনো বহাল তবিয়তেই আছে - না থাকলে adder ফাংশনগুলো তো কাজ করত না! আরো লক্ষ্যণীয় যে makeAdder এর লোকাল ভ্যারিয়েবলের দু'টি আলাদা, ভিন্ন "কপি" দেখা যাচ্ছে - যার একটিতে a ভ্যারিয়েবলের মানে 5 আর অন্যটিতে এই মান 20। তাই, ওপরের ফাংশন কল  দু'টির ফলাফল হবে নিম্নরূপঃ

    x(6) // returns 11
    y(7) // returns 27
    

    পর্দার আড়ালেঃ যখন জাভাস্ক্রিপ্ট কোন ফাংশন execute করে, ঐ ফাংশনের লোকাল ভ্যারিয়েবল গুলো রাখার জন্যে একটি 'স্কোপ' অবজেক্ট তৈরি করা হয়। এই স্কোপ ভ্যারিয়েবল ইনিশিয়ালাইজ করা হয় ফাংশনে প্যারামিটার হিসেবে যেসব ভ্যারিয়েবল পাঠানো হয়েছিল সেগুলো দ্বারা। এটা গ্লোবাল অবজেক্টের মতই যেখানে সব গ্লোবাল ভ্যারিয়েবল আর ফাংশনগুলো থাকে, কিন্তু দু'টি পার্থক্য সহ। প্রথমতঃ প্রতিবার একটি ফাংশন execute হওয়া শুরু করে একটি নতুন স্কোপ অবজেক্ট তৈরি হয়ে যায়। গ্লোবাল স্কোপ অবজেক্টের সাথে দ্বিতীয় পার্থক্যটি হল, গ্লোবাল স্কোপের প্রোপার্টি সরাসরি এক্সেস করা যায় (যেমন ব্রাউজারের ক্ষেত্রে এই গ্লোবাল অবজেক্ট টি হল window), কিন্তু ফাংশনের স্কোপের ক্ষেত্রে এটি সম্ভব নয়। উদাহরণস্বরূপ বলা যায়, ফাংশনের স্কোপ অবজেক্টের প্রোপার্টিকে iterate করে এক্সেস করা সম্ভব নয়।

    তাই যখন makeAdder কল করা হয়েছিল, একটা স্কোপ অবজেট তৈরি হয়ে গেছে। এই স্কোপ অবজেক্টের একটাই প্রোপার্টিঃ a ভ্যারিয়েবল যেটি কিনা এই ফাংশনের একমাত্র প্যারামিটার। makeAdder তারপর নতুন একটা ফাংশন তৈরি করে রিটার্ন করে। এই মুহূর্তে জাভাস্ক্রিপ্টের গারবেজ কালেকটর (কোন ভ্যারিয়েবল গুলোর আর প্রয়োজন নাই সেগুলো খুজে বের করে এরা যে মেমরি দখল করে রেখেছিল তা অন্য নতুন ভ্যারিয়েবলের জন্য ফ্রি করে দেওয়া'র পদ্ধতি) এর makeAdder এর স্কোপ অবজেট গায়েব করে ফেলার কথা, কিন্তু makeAdder যে ফাংশনটি তৈরি করে রিটার্ন করল তার মাঝে makeAdder এর স্কোপ অবজেক্টের একটা রেফারেন্স থেকে যায়। যে কারণে, যতক্ষণ makeAdder এর রিটার্ন করা ফাংশনের কোন না কোন রেফারেন্স কোডে অবশিষ্ট থাকছে (সহজভাবে যতক্ষণ এটি ব্যবহৃত হচ্ছে), ততক্ষণ makeAdder এর স্কোপ অবজেক্ট-টিও অক্ষত থাকবে।

    স্কোপ অবজেক্টেরা প্রোটোটাইপ চেইনে মত স্কোপ চেইন মেইনটেইন করে।

    ক্লোজার হচ্ছে একট ফাংশন আর এই ফাংশনকে যেই ফাংশন তৈরি করেছিল তার স্কোপ অবজেক্টের একটা সমন্বয় (combination)।

    ক্লোজার আপনাকে স্টেট মনে রাখার একটা সুবিধা দেয় - যে কারণে অবজেক্টের বদলে ক্লোজার ব্যবহার করা যেতে পারে।

    মেমরি অপচয় (Memory leaks)

    ক্লোজার ব্যবহারের একটি দুর্ভাগ্যজনক পার্শ্বপ্রতিক্রিয়া হল এটি ইন্টার্নেট এক্সপ্লোরারে মেমরি অপচয় করে। জাভাস্ক্রিপ্টে গার্বেজ কালেকশন পদ্ধতি ব্যবহার করা হয় অবজেক্টের দখল করা মেমরি ফ্রি করার জন্যে। অবজেক্ট যখন তৈরি হয় তখন তাদের মেমরি দেওয়া (allocate) করা হয় - যখন ঐ অবজেক্টের আর কোন রেফারেন্স কোডে অবশিষ্ট থাকে না তখন সেই মেমরি ফ্রি করে দেওয়া হয়। হোস্ট যেসব অবজেক্ট সরবরাহ করে সেগুলো হ্যান্ডেল সেই হোস্ট নিজেই করে।

    ব্রাউজার হোস্টগুলোর বিশাল পরিমাণে অবজেক্ট ম্যানেজ করা লাগে - যেই HTML পাতাটি দেখানো হচ্ছে - তার জন্যে DOM এর অবজেক্ট গুলো। এই অবজেক্টগুলো ম্যানেজ আর রিকভারি করার দায়িত্ব ব্রাউজারের।

    এই কাজের জন্যে ইন্টারনেট এক্সপ্লোরার (IE) তার নিজের গার্বেজ কালেকশন পদ্ধতি ব্যবহার করে, জাভাস্ক্রিপ্টের পদ্ধতি থেকে যেটি আলাদা। এই দুই এর মাঝে ইন্টারেকশনের জন্যে মেমরি অপচয় হতে পারে।

    IE তে মেমরি অপচয় হবে যখনি কোন জাভাস্ক্রিপ্ট অবজেক্ট আর নেটিভ অবজেক্টের মাঝে সার্কুলার (circular) রেফারেন্স তৈরি হবে। নিচের কোড দেখুনঃ

    function leakMemory() {
        var el = document.getElementById('el');
        var o = { 'el': el };
        el.o = o;
    }
    

    ওপরের কোডে যে সার্কুলার রেফারেন্স তৈরি হল তার জন্যে IE, el আর o অবজেক্টের দখল করা মেমরি ফ্রি করবে না যতক্ষণ না ব্রাউজার পুরোপুরি রিস্টার্ট করা হচ্ছে। ফলাফল মেমরি অপচয়...

    ওপরের কেইসটি প্রোগ্রামারের চোখ এড়িয়ে যেতে পারে, কারণ এই মেমরি অপচয় অনেক্ষণ-ধরে-চলছে এরকম বড়সর এপ্লিকেশনের ক্ষেত্রেই প্রভাব ফেলে। কোন এপ্লিকেশন জটিল ডেটা স্ট্রাকচার ব্যবহার করে বা লুপের মধ্যে মমরি অপচয় করলেও সেটা চোখে পড়বে।

    সার্কুলার রেফারেন্স ওপরের কোডের মত এত সহজে চোখে নাও পড়তে পারে। অনেক সময় মেমরি নষ্ট করা ডেটা স্ট্রাকচারে অনেক লেভেলের রেফারেন্সের পরে সার্কুলার রেফারেন্স তৈরি হতে পারে - তখন আর চট করে সার্কুলার রেফারেন্স ধরা যাবে না।

    ক্লোজার ব্যবহার করলে অনিচ্ছাকৃত মেমরি অপচয় হতে পারে। নিচের কোড টা দেখুনঃ

    function addHandler() {
        var el = document.getElementById('el');
        el.onclick = function() {
            this.style.backgroundColor = 'red';
        }
    }
    

    ওপরের কোডে HTML এলিমেন্ট টিকে লাল  রং করা হবে যখন কেউ এর উপরে ক্লিক করবে। একই সাথে মেমরি লীক ও হতে থাকবে। কেন? কারণ এনোনিমাস inner ফাংশনটি'র ক্লোজারে el এর একটা রেফারেন্স অনিচ্ছাকৃতভাবে থেকে যাবে। এর ফলে একটা জাভাস্ক্রিপ্ট অবজেক্ট (ফাংশনটি) আর একটা নেটিভ অবজেক্ট (el) এর মাঝে সার্কুলার রেফারেন্স তৈরি হল।

    এই সমস্যা সমাধানের কয়েকটা উপায় আছে। সবথেকে সহজটি হল আদৌ কোন el ভ্যারিয়েবল ব্যবহার না করাঃ

    function addHandler(){
        document.getElementById('el').onclick = function(){
            this.style.backgroundColor = 'red';
        }
    }
    

    আরেকটা মজার উপায় হল এক ক্লোজার এর সার্কুলার রেফারেন্স অন্য আরেকটি ক্লোজার দিয়ে নষ্ট করাঃ

    function addHandler() {
        var clickHandler = function() {
            this.style.backgroundColor = 'red';
        };
        (function() {
            var el = document.getElementById('el');
            el.onclick = clickHandler;
        })();
    }
    

    Inner ফাংশনটি সরাসরি execute হয়, এর ভেতরের সবকিছু clickHandler এর তৈরি করা ক্লোজার থেকে আড়াল করে।

    ক্লোজার থেকে দূরে থাকার আরেকটি ভাল বুদ্ধি হল window.onunload ইভেন্টে সার্কুলার রেফারেন্স দূর করা। অনেক ইভেন্ট লাইব্রেরী এই পদ্ধতি ব্যবহার করে। কিন্তু এরকম করলে আবার ফারায়ফক্স ১.5 এর bfcache কাজ করবে না, তাই ফায়ারফক্সে unload লিসেনার রেজিস্টার করা উচিত হবে না (যদি এমন হয় যে ব্যবহার না করে উপায় নাই তাহলে অবশ্য ভিন্ন কথা)।

    মূল ডকুমেন্টের খবরাখবর...

    • লেখকঃ Simon Willison
    • শেষ সম্পাদনাঃ মার্চ ৭, ২০০৬
    • কপিরাইটঃ © 2006 Simon Willison, contributed under the Creative Commons: Attribute-Sharealike 2.0 license.
    • আরো তথ্যঃ এই টিউটোরিয়াল নিয়ে আরো তথ্যের জন্য (আর মূল আলোচনার স্লাইডের লিংক পেতে) দেখুন Simon's Etech weblog post.

     

    অনুবাদ সংক্রান্ত তথ্যঃ টেকনিকাল তথ্য অপরিবর্তিত রেখে ভাবানুবাদের পাশাপাশি সামান্য পরিমার্জনা করা হয়েছে।

    ডকুমেন্ট ট্যাগ এবং অবদানকারী

    Contributors to this page: shafiul, tuxboy, fscholz, teoli
    সর্বশেষ হালনাগাদ করেছেন: fscholz,
    সাইডবার লুকানো