JavaScript - Type Coercion Explained

অদ্ভুতুড়ে রহস্যের পিছনের রহস্য

·

11 min read

JavaScript - Type Coercion Explained

Type Coercion - নামটা যতটা জটিল, বিষয়টা ততটাও জটিল নয়। সহজ বাংলা ভাষায় বললে, একটা ডেটা টাইপ থেকে অন্য আর একটি ডেটা টাইপে কনভার্ট করার প্রসেসকেই বলে Type Coercion।আর এই কাজটা আমরা প্রথম থেকেই করে আসছি। কখনো নিজের ইচ্ছেই একটা নাম্বারকে স্ট্রিং বা বুলিয়ান এ কনভার্ট করছি, আবার কখনো স্ট্রিং কে নাম্বার এ কনভার্ট করছি।

আমরা নিজের ইচ্ছেই যদি কোনো ডেটাকে অন্য কোনো ডেটাতে কনভার্ট করি, সেইক্ষেত্রে তেমন কোনো সমস্যা নেই। কারণ যা করার আমরা নিজেরাই করছি। আমরা জানি আমাদের ডেটা কোন টাইপে আছে এবং আমরা ঠিক কোন টাইপে কনভার্ট করতে চাচ্ছি। কিন্তু যেহেতু জাভাস্ক্রিপ্ট একটি weakly-typed language সেহেতু সে নিজে থেকেও বিভিন্ন কন্টেক্সটে, বিভিন্ন প্রেক্ষাপটে যে কোনো ডেটাকে একটা টাইপ থেকে অন্য টাইপে কনভার্ট করতে পারে। আর আমরা জাভাস্ক্রিপ্টের যত অদ্ভুত ব্যবহার দেখে থাকি, যত weird syntax দেখে থাকি, তার বেশির ভাগ কারণ এই Implicit Coercion। মানে সহজ কথায় জাভাস্ক্রিপ্ট নিজের মনের মতো যে কোনো ডেটাকে যে কোনো সময় যে কোনো টাইপের ডেটাতে কনভার্ট করে ফেলতে পারে।

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

এই আর্টিকেলে আমরা Coercion সম্পর্কে বিস্তারিত জানার চেষ্টা করব এবং পুরো আর্টিকেলটা ভালো মত বোঝার পরে আমি নিশ্চয়তা দিচ্ছি, যেসব কারণে জাভাস্ক্রিপ্টকে অনেক অদ্ভুত মনে হত তার ৭৫% কনফিউশন দূর হয়ে যাবে এবং আমরা নিচের কোড গুলোর সঠিক উত্তর নিজেরাই বের করতে পারব ব্যাখ্যা সহ।

true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
['x'] == 'x'
[] + null + 1
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
[]+[]+'foo'.split('')

Type Coercion কি?

একটা ডেটাকে অন্য ডেটাতে রূপান্তর করার প্রসেসকে জাভাস্ক্রিপ্টের ভাষায় Type Coercion বলা হয়ে থাকে। এটা দুই ভাবে হতে পারে। আমরা আমাদের প্রয়োজন অনুসারে নিজের ইচ্ছেই একটা ডেটাকে কনভার্ট করতে পারি, এটাকে বলে Explicit Coercion। আবার বিভিন্ন সময় জাভাস্ক্রিপ্ট নিজেই ডেটা গুলোকে এক টাইপ থেকে অন্য টাইপে কনভার্ট করে, এটাকে বলে Implicit Coercion। যেমন - স্ট্রিং এর সাথে (+) ব্যবহার করে কোন নাম্বার যুক্ত করলে সেটি স্ট্রিং এ কনভার্ট হয়ে যায়। এখানে আমরা নিজে থেকে কম্পাইলারকে বলিনি যে নাম্বারকে স্ট্রিং এ কনভার্ট করতে হবে। জাভাস্ক্রিপ্তের কম্পাইলার নিজে থেকেই বুঝে নিয়েছে যে যেহেতু একটা ভ্যালু এখানে স্ট্রিং তাই দুইটা ভ্যালুকে যোগ করতে হলে নাম্বারকেও স্ট্রিং এ কনভার্ট করে নিতে হবে।

Implicit vs Explicit Coercion

Explicit Coercion অনেক সহজ এবং এটা আমরা আগে থেকেই দেখে আসছি। যেমন Number('123') বা Number(true)। দুইটা উদাহরণের ক্ষেত্রেই স্ট্রিং এবং বুলিয়ান ভ্যালু নাম্বারে কনভার্ট হয়ে যাবে। যেহেতু জাভাস্ক্রিপ্ট একটি weakly typed language তাই যে কোনো প্রিমিটিভ ভ্যালু অথবা অবজেক্ট বিভিন্ন সময় অটোমেটিক্যালিও কনভার্ট হতে পারে। এটা সাধারণত ঘটে থাকে যখন আমরা ভিন্ন ভিন্ন অপারেটর ভিন্ন ভিন্ন ভ্যালুর ওপরে ব্যবহার করে থাকি। যেমনঃ 1 == null, 2 / '5', null + new Date() অথবা এটা যে কোনো একটা কন্টেক্সট এর ভিতরেই হতে পারে যেমন - if (value) {...}, এখানে value বুলিয়ান এ কনভার্ট হয়ে যাবে।

শুধুমাত্র একটি অপারেটর কোন রকম implicit coercion ঘটায় না, সেটা হচ্ছে === Strict Equality Operator। অন্য দিকে Loosly Equality Operator, Comparison এবং Type Coercion প্রয়োজনবোধে দুইটাই করে থাকে।

Implicit Type Coercion একটা দোধারি তলোয়ার যা আপনাকে frustrated করবে, অ্যাপ্লিকেশনে ডিফেক্টস তৈরি করবে আবার অন্য দিকে এটা একটা খুবই প্রয়োজনীয় মেকানিসম যা অনেক কম কোডে অনেক বেশি কাজ করতে পারে এবং যা সম্পূর্ণ ভাবে নির্ভর যোগ্য। তবে এর ব্যবহার আপনাকে যথেষ্ট বুঝে শুনেই করতে হবে।

এই আর্টিকেলে আমরা জাভাস্ক্রিপ্ট ইঞ্জিনের সেই সব রুলস গুলো জানার এবং বোঝার চেষ্টা করব যেই রুলস গুলো আমাদেরকে সব থেকে বেশি কনফিউসড করে থাকে। আর যেগুলো জানলে অনেক weird syntax দেখেও আমরা বলতে পারবো এটা একটা সাধারণ কোড।

Three Types of Conversion

প্রথমেই যেই রুলসটা আমাদের মনে রাখতে হবে সেটা হচ্ছে জাভাস্ক্রিপ্টে শুধুমাত্র তিন ধরনের কনভার্শনই সম্ভব,

  • to string
  • to boolen
  • to number

দ্বিতীয়ত, প্রিমিটিভ ডেটা এবং অবজেক্ট এর কনভার্শন লজিক সম্পূর্ণ আলাদা, কিন্তু এরা দুজনেই শুধুমাত্র ওপরের তিনটি টাইপেই কনভার্ট হতে পারে।

প্রথমে আমরা প্রিমিটিভ টাইপ দিয়েই শুরু করি।

String Conversion

যদি আমরা কোনো ভ্যালুকে explicit ভাবে স্ট্রিং এ কনভার্ট করতে চাই সেক্ষেত্রে আমরা String() কন্সট্রাক্টর ফাংশন ব্যবহার করতে পারি। আর যখন স্ট্রিং এর সাথে কোথাও বাইনারি + অপারেটর ব্যবহার করা হয় তখনই এটা Implicit Coercion কে ট্রিগার করে। নিচের উদাহরণ দুইটার দিকে একটু লক্ষ্য করুন,

String(123)                  // explicit 
1234 + ''                     // implicit - 1234 will automatically converted to string

সমস্ত প্রিমিটিভ ভ্যালু নিচের মত করে স্ট্রিং এ কনভার্ট হয়ে যাবে,

String(123)                   // '123'
String(-12.3)                 // '-12.3'
String(null)                  // 'null'
String(undefined)             // 'undefined'
String(true)                  // 'true'
String(false)                 // 'false'

Symbol এর ক্ষেত্রে কনভার্শনটা একটু ভিন্ন হবে কারণ Symbol ইমপ্লিসিট ভাবে কনভার্ট হয় না।

String(Symbol('my symbol'))   // 'Symbol(my symbol)'
'' + Symbol('my symbol')      // TypeError is thrown

Boolean Conversion

Explicit ভাবে একটি ভ্যালুকে বুলিয়ান এ কনভার্ট করতে আমরা Boolean() কন্সট্রাক্টর ফাংশনকে কল করতে পারি। যে কোনো লজিক্যাল কন্টেক্সট এ অটোমেটিক্যালি Implicit কনভার্শন হবে অথবা লজিক্যাল অপারেটর (|| && !) এর মাধ্যমে Implicit Coercion ট্রিগার হবে।

Boolean(2)          // explicit
if (2) { ... }      // implicit due to logical context
!!2                 // implicit due to logical operator
2 || 'hello'        // implicit due to logical operator

Note: লজিক্যাল অপারেটর গুলো ইন্টার্নাল ভাবে বুলিয়ান কনভার্শন ঘটিয়ে থাকে, কিন্তু সেটা অরিজিনাল ভ্যালুই রিটার্ন করে থাকে।

// the following expression will returns number 123, instead of returning true
// 'hello' and 123 are still coerced to boolean internally to calculate the expression
let x = 'hello' && 123;   // x === 123

যেহেতু বুলিয়ান ভ্যালু এর ক্ষেত্রে শুধুমাত্র দুইটাই রেসাল্ট আসতে পারে, তাই falsy ভ্যালু গুলোকে মনে রাখা অনেক সহজ হবে।

Boolean('')           // false
Boolean(0)            // false     
Boolean(-0)           // false
Boolean(NaN)          // false
Boolean(null)         // false
Boolean(undefined)    // false
Boolean(false)        // false

যে কোনো ভ্যালু যা ওপরের লিস্টে নেই সেটি আটোমেটিক্যালি true রিটার্ন করবে, এমনকি অবজেক্ট, অ্যারে এবং ফাংশন এর ক্ষেত্রেও সেটি true রিটার্ন করবে। Symbol, Empty Array, Empty Object ও true রিটার্ন করবে।

Boolean({})             // true
Boolean([])             // true
Boolean(Symbol())       // true
!!Symbol()              // true
Boolean(function() {})  // true
Numeric Conversion

Explicit কনভার্শন এর জন্য শুধুমাত্র Number() কন্সট্রাক্টর ফাংশন কল করলেই সেটি নাম্বার এ রূপান্তরিত হয়ে যাবে। Number এর ক্ষেত্রে Implicit কনভার্শনটি একটু ট্রিকি, কারণ এটি অসংখ্য ভাবে ট্রিগারড হতে পারে।

  • comparison operators (>, <, <=,>=)
  • bitwise operators ( | & ^ ~)
  • arithmetic operators (- + * / % ). Note, that binary+ does not trigger numeric conversion when any operand is a string.
  • unary + operator
  • loose equality operator == (incl. !=) Note that == does not trigger numeric conversion when both operands are strings
Number('123')   // explicit 
+'123'          // implicit 
123 != '456'    // implicit 
4 > '5'         // implicit 
5/null          // implicit 
true | 0        // implicit

প্রিমিটিভ ভ্যালু গুলো যেভাবে নাম্বার এ কনভার্ট হয়ে থাকে,

Number(null)                   // 0
Number(undefined)              // NaN
Number(true)                   // 1
Number(false)                  // 0
Number(" 12 ")                 // 12
Number("-12.34")               // -12.34
Number("\\n")                   // 0
Number(" 12s ")                // NaN
Number(123)                    // 123

যখন একটা স্ট্রিং কে নাম্বার এ কনভার্ট করা হয়, তখন জাভাস্ক্রিপ্ট ইঞ্জিন প্রথমে লিডিং এবং ট্রেইলিং স্পেস গুলোকে, \n, \t ক্যারেক্টার গুলোকে ট্রিম করে ফেলে। যদি ট্রিমড স্ট্রিংটি ভ্যালিড কোন নাম্বার কে রিপ্রেসেন্ট না করে সেক্ষেত্রে এটি NaN রিটার্ন করবে, আর স্ট্রিংটি যদি এম্পটি হয় সেক্ষেত্রে 0 রিটার্ন করবে।

null এবং undefined একটু ভিন্ন ভাবে কাজ করবে। null হয়ে যাবে 0 এবং undefined হয়ে যাবে NaN

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

১। যখন null অথবা undefined এর সাথে == অপারেটর ব্যবহার করা হবে তখন কোন রকম নিউমেরিক কনভার্শন ঘটবে না। null শুধুমাত্র null অথবা undefined এর সাথেই কম্পেয়ার করা যায়, অন্য কোন কিছুর সাথেই এর কম্পারিসন সম্ভব নয়।

null == 0               // false, null is not converted to 0
null == null            // true
undefined == undefined  // true
null == undefined       // true

২। NaN কখনই কারোর সমান হতে পারে না, এমনকি নিজের ও না।

NaN == NaN       // false

Type Coercion for Objects

এতক্ষণ পর্যন্ত আমরা প্রিমিটিভ ভ্যালুর কনভার্শন দেখলাম যা খুব একটা মজাদার ছিল না। এবার আমরা জাভাস্ক্রিপ্টের weird behaviour গুলোর সাথে পরিচিত হতে যাচ্ছি।

যখন জাভাস্ক্রিপ্ট ইঞ্জিন [1] + [2, 3] এই রকম কোন এক্সপ্রেশন এর সম্মুখীন হয় তখন এটি প্রথমেই অবজেক্টটিকে প্রিমিটিভ ভ্যালুতে রূপান্তর করে ফেলে। তারপর এই রূপান্তরিত ভ্যালুটি আবার কনভার্ট হয়ে ফাইনাল রেসাল্ট দিয়ে থাকে। যেকোনো অবজেক্ট কনভার্ট হয়ে শুধুমাত্র number, string বা boolean ই হতে পারে।

সব থেকে সহজ হচ্ছে বুলিয়ান এ কনভার্ট করা। যে কোনো ভ্যালু যা কিনা প্রিমিটিভ না সব সময়ের জন্যই সেটা true রিটার্ন করবে। এমনকি অবজেক্টটি যদি ফাঁকা থাকে, অথবা অ্যারেতে কোন এলিমেন্ট নাও থাকে, তবুও এটি true রিটার্ন করবে।

অবজেক্ট গুলো [[ToPrimitive]] ইন্টার্নাল মেথডের মাধ্যমে প্রিমিটিভ ভ্যালুতে কনভার্ট হয় এবং এই মেথডটি ব্যবহার করেই স্ট্রিং এবং নিউমেরিক কনভার্শন ঘটে থাকে।

[[ToPrimitive]] নিচের মত করে কাজ করে থাকে,

একটা ইনপুট এবং যেই টাইপে কনভার্ট করতে চাচ্ছি সেই টাইপ সহ [[ToPrimitive]] ফাংশনটি পাস করা হবে। Preffered type is Optional.

নিউমেরিক এবং স্ট্রিং দুইটা কনভার্শনই ঘটে থাকে দুইটা মেথডের সাহায্যেঃ valueOf এবং toString। দুইটা মেথডই আগে থেকে ডিক্লেয়ার করা আছে Object.prototype এর সাথে এবং সমস্ত অবজেক্ট থেকে এটা ব্যবহার করা যায়।

সাধারণ ভাবে অ্যালগোরিদমটি নিচের মত কাজ করে থাকে,

  • যদি ইনপুটটি আগে থেকেই প্রিমিটিভ হয়ে থাকে, কিছুই করতে হবে না, শুধুমাত্র এটাকেই রিটার্ন করবে
  • call input.toString(), যদি রেসাল্ট প্রিমিটিভ হয়, রিটার্ন করবে
  • call input.valueOf(), যদি রেসাল্ট প্রিমিটিভ হয়, রিটার্ন করবে
  • যদি input.toString() অথবা input.valueOf() কোনটাই প্রিমিটিভ রিটার্ন করতে না পারে তাহলে error থ্রো করবে

নিউমেরিক কনভার্শনের ক্ষেত্রে এটি প্রথমে valueOf() ফাংশনকে কল করবে এবং ফলব্যাক হিসেবে toString() ফাংশনকে কল করবে। স্ট্রিং কনভার্শনের ক্ষেত্রে ঠিক উল্টা ব্যাপারটা ঘটবে, প্রথমে toString() কল হবে, আর ফলব্যাক হিসেবে valueOf() কল হবে।

বেশিরভাগ বিউল্টইন টাইপের valueOf ফাংশনটি নেই, আর থাকলেও এটা নিজ অবজেক্টকেই রিটার্ন করে থাকে। এবং যেহেতু এটা প্রিমিটিভ না তাই কম্পাইলার এটাকে ইগ্নোর করে থাকে। সেই হিসেবে নিউমেরিক কনভার্শন এবং স্ট্রিং কনভার্শন মূলত একই ভাবে কাজ করে থাকে।

Examples

Example - 1: Binary + অপারেটর নিউমেরিক কনভার্শন ট্রিগার করছে -

true + false
==> 1 + 0
==> 1

Example - 2: অ্যারিথমেটিক / অপারেটর নিউমেরিক কনভার্শন ট্রিগার করছে -

12 / '6'
==> 12 / 6
==> 2

Example - 3: + অপারেটরটি সাধারণত লেফট থেকে রাইটের দিকে কাজ করে থাকে। আর স্ট্রিং এর সাথে + অপারেটর স্ট্রিং কনভার্শন ট্রিগার করছে।

'number' + 15 + 3
==> 'number15' + 3
==> 'number153'

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

15 + 3 + 'number'
==> 18 + 'number'
==> '18number'

Example - 5: কম্প্যারিসন অপারেটর নিউমেরিক কনভার্শন কে ট্রিগার করছে।

[1] > null
==> '1' > 0
==> 1 > 0
==> true

Example - 6: জাভাস্ক্রিপ্টে ইউনারি + অপারেটর এর ক্ষমতা বাইনারি + অপারেটরের থেকে বেশি. তাই + 'bar' এক্সপ্রেশনটা সবার আগে ইভালুয়েট হবে। ইউনারি + অপারেটর নিউমেরিক কনভার্শন ট্রিগার করে থাকে আর 'bar' কোনো ভ্যালিড নাম্বার না। তাই এটি NaN রিটার্ন করবে। সেকেন্ড স্টেপে স্ট্রিং এবং বাইনারি + অপারেটর স্টিং কনভার্শন ট্রিগার করবে।

'foo' + + 'bar'
==> 'foo' + (+'bar')
==> 'foo' + NaN
==> 'fooNaN'

Example - 7: কম্প্যারিসন অপারেটর == নিউমেরিক কনভার্শন ট্রিগার করে থাকে। এখানে স্ট্রিং 'true' কনভার্ট হয়ে NaN এ পরিণত হবে যেহেতু এটি কোন ভ্যালিড নাম্বার না, আর true কনভার্ট হয়ে 1 হয়ে যাবে।

'true' == true
==> NaN == 1
==> false
'false' == false
==> NaN == 0
==> false

Example - 8: কম্প্যারিসন অপারেটর সাধারণ নিউমেরিক কনভার্শন ট্রিগার করে। কিন্তু null এর ক্ষেত্রে এটি সত্য নয়। null শুধুমাত্র null এবং undefined এর সাথে কম্পেয়ার করা যায়। বাকি সব ক্ষেত্রে এটি false রিটার্ন করবে।

null == ''
==> false

Example - 9: !! অপারেটর যে কোনো নন এম্পটি স্ট্রিং কে বুলিয়ান true তে কনভার্ট করে ফেলে।

!!'false' == !!'true'
==> true == true
==> true

Example - 10: == অপারেটর অ্যারে এর জন্য নিউমেরিক কনভার্শন ট্রিগার করবে, কিন্তু অ্যারের valueOf মেথড অ্যারেকেই রিটার্ন করবে এবং এটা যেহেতু প্রিমিটিভ ভ্যালু না, তাই এটা ইগনোরড হবে। এর পরে এর toString মেথড কল হবে এবং যা ['x'] থেকে 'x' রিটার্ন করবে।

['x'] == 'x'
==> 'x' == 'x'
==> true

Example - 11: + অপারেটর অ্যারে এর জন্য নিউমেরিক কনভার্শন ট্রিগার করবে। অ্যারে এর valueOf মেথড ইগনোরড হবে এবং toString মেথড একটা এম্পটি স্ট্রিং রিটার্ন করবে। প্রথম ভ্যালুটা স্ট্রিং হয়ে যাওয়ার কারণে বাকি ভ্যালু গুলোও স্ট্রিং এ কনভার্ট হবে।

[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'

Example - 12: Logical || এবং && অপারেটর ইন্টার্নাল ভাবে বুলিয়ান নিয়ে কাজ করলেও রিটার্ন করে থাকে অরিজিনাল ভ্যালু। এখানে 0 falsy ভ্যালু, কিন্তু '0' truthy ভ্যালু। কারণ এটি নন এম্পটি স্ট্রিং।

0 || '0' && {}
==> (0 || '0') && {}
==> (false || true) && true  // internally 
==> '0' && {}
==> true && true  // internally
==> {}

Example - 13: এখানে প্রথম কার্লি ব্রাকেটটি ম্যাথমেটিক্যাল এক্সপ্রেশন হিসেবে ব্যবহৃত হচ্ছে এবং ইগ্নোরড হচ্ছে। +[] এটা প্রথমে এম্পটি স্ট্রিং এ কনভার্ট হবে এবং পরে 0 তে কনভার্ট হবে যেহেতু এর পূর্বে একটি ইউনারি + অপারেটর রয়েছে।

{} + [] + {} + [1]
==> +[] + {} + [1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + [1]
==> '0[object Object]' + [1]
==> '0[object Object]' + '1'
==> '0[object Object]1'

Example - 14:

!+[]+[]+![]
==> (!+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'

Example - 15:

[]+[]+'foo'.split('')
==> []+[]+['f', 'o', 'o']
==> '' + '' + 'f,o,o'
==> 'f,o,o'

পুরো আর্টিক্যালটির ব্যাখ্যাসহ ভিডিও

এরকম আরও ইনফরমেটিভ টিউটোরিয়াল পেতে Stack Learner এর ইউটিউব চ্যানেলে সাবস্ক্রাইব করে রাখুন।

রেফারেন্সঃ Javascript Type Coercion Explained