Understand Type Coercion in Javascript
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
সমস্ত প্রিমিটিভ ভ্যালু নিচের মত করে স্ট্রিং এ কনভার্ট হয়ে যাবে -
String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
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: লজিক্যাল অপারেটর গুলো ইন্টার্নাল ভাবে বুলিয়ান কনভার্শন ঘটিয়ে থাকে, কিন্তু সেটা অরিজিনাল ভ্যালুই রিটার্ন করে থাকে।
// 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
কখনই কারোর সমান হতে পারে না, এমনকি নিজের ও না।
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'
রেফারেন্সঃ