Curiosities / JavaScript Edition Vol. 02 — 2026

JavaScript
The Weird Parts

// type coercion · scope · this · numbers · arrays

JavaScript-ის სამყარო სავსეა ისეთი მოვლენებით, რომლებიც ერთი შეხედვით ყოვლად უაზროდ გამოიყურება. გამოიცანი output, შემდეგ წაიკითხე ახსნა.

25
WTF Moments
5
Categories
Confusion
01 / 05

Type coercion

Q · 01
COERCEWTF
// რა გამოვა?
console.log([] + []);
console.log([] + {});
console.log({} + []);
WTF
Answer
Output "" (ცარიელი string)
"[object Object]"
"[object Object]"

+ ოპერატორი მასივებს კონვერტავს string-ად toString()-ით. ცარიელი მასივი → ცარიელი string. ამიტომ [] + [] = "".

{} object-ი string-ად ხდება "[object Object]". [] + {} = "" + "[object Object]".

საინტერესო: ბრაუზერის console-ში {} + [] შეიძლება დააბრუნოს 0, რადგან წინა {} განიხილება როგორც ცარიელი code block, არა object. ეს დამოკიდებულია კონტექსტზე.

Q · 02
COERCEWTF
// რა გამოვა?
console.log("5" + 3);
console.log("5" - 3);
console.log("5" * "2");
console.log("abc" - 1);
WTF
Answer
Output "53"
2
10
NaN

+ ოპერატორი — თუ ერთ-ერთი ოპერანდი string-ია, მეორეც string-ად კონვერტდება (concatenation).

-, *, / ოპერატორები — ცდილობენ ორივე ოპერანდის რიცხვად კონვერტაციას.

"abc" - 1NaN - 1NaN, რადგან "abc" ვერ იქცა რიცხვად.

წესი: +-ს ენდე სიფრთხილით, დანარჩენი არითმეტიკული ოპერატორები ყოველთვის რიცხვებს შეეცდებიან.

Q · 03
COERCEWTF
// რა გამოვა?
console.log(true + true);
console.log(true + false);
console.log(true + "1");
console.log(true == 1);
console.log(true === 1);
WTF
Answer
Output 2
1
"true1"
true
false

true არის 1, false არის 0 — როცა არითმეტიკულ კონტექსტში გამოიყენება. ამიტომ true + true = 2.

true + "1" — string contextში true ხდება "true", შედეგი "true1".

true == 1true (loose equality, type coercion-ით).

true === 1false (strict equality, ტიპი განსხვავებულია — boolean vs number).

Q · 04
COERCEWTF
// რა გამოვა?
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);
console.log(null > 0);
console.log(null >= 0);
WTF
Answer
Output true
false
false
false
true

null == undefinedtrue (სპეციალური წესი loose equality-ში).

null == 0false — მაგრამ null >= 0true?!

ახსნა: ==-სთვის null ცალკე წესს ემორჩილება — ის უტოლდება მხოლოდ undefined-ს, არა რიცხვებს. მაგრამ >, >, >=, <= ოპერატორები null-ს რიცხვად კონვერტავენ — null0.

ამიტომ null >= 0 = 0 >= 0 = true, მაგრამ null == 0 = false. ეს ერთ-ერთი ცნობილი JS quirk-ია.

Q · 05
COERCEWTF
// რა გამოვა?
console.log([] == false);
console.log([] == ![]);
console.log([0] == false);
console.log("" == false);
WTF
Answer
Output true
true
true
true

ყველაზე ცნობილი JS oddity: [] == ![]true (მასივი თავის უარყოფას უტოლდება).

როგორ: ![]false (object-ი truthy-ია, ! იძლევა false). შემდეგ [] == false: false → 0, [] → "" → 0. 0 == 0true.

[0] == false: false → 0, [0] → "0" → 0. 0 == 0true.

გაკვეთილი: ყოველთვის გამოიყენე === და არასოდეს დაეყრდნო truthy/falsy შედარებებს object-ებთან.

02 / 05

Numbers & math

Q · 06
NUMWTF
// რა გამოვა?
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
WTF
Answer
Output 0.30000000000000004
false

ეს არ არის JS-ის ბაგი — ყველა ენა რომელიც IEEE 754 floating point-ს იყენებს იგივე პრობლემას აწყდება (Python, Java, C++).

0.1 და 0.2 ბინარულ ფორმატში ზუსტად ვერ წარმოდგება (როგორც 1/3 ვერ ჩაიწერება ათობით ფორმატში ზუსტად). ამიტომ მცირე rounding error-ი ჩნდება.

გადაწყვეტა:

// Option 1: დამრგვალება
Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true

// Option 2: გადაყვანა integer-ში
(0.1 * 10 + 0.2 * 10) / 10 === 0.3; // true

ფინანსურ კალკულაციებში გამოიყენე ბიბლიოთეკები (Decimal.js, BigNumber.js) ან მუშაობა მთლიან რიცხვებში (cents-ებში, არა dollar-ებში).

Q · 07
NUMWTF
// რა გამოვა?
console.log(NaN === NaN);
console.log(typeof NaN);
console.log(NaN + 1);
console.log(isNaN("hello"));
console.log(Number.isNaN("hello"));
WTF
Answer
Output false
"number"
NaN
true
false

NaN ერთადერთი მნიშვნელობაა JS-ში, რომელიც საკუთარ თავს არ უტოლდება. ეს IEEE 754-ის სტანდარტია.

typeof NaN"number". დიახ, "Not a Number" არის Number ტიპის. 🤷

ნებისმიერი არითმეტიკა NaN-თან → NaN.

isNaN vs Number.isNaN: ძველი isNaN() ჯერ არგუმენტს რიცხვად კონვერტავს. "hello"NaNtrue.

Number.isNaN() არ აკეთებს კონვერტაციას — აბრუნებს true მხოლოდ ნამდვილი NaN-ისთვის. ყოველთვის გამოიყენე Number.isNaN().

NaN-ის შესამოწმებლად ასევე შეგიძლია: x !== xtrue მხოლოდ NaN-ისთვის.

Q · 08
NUMWTF
// რა გამოვა?
console.log(typeof null);
console.log(null instanceof Object);
console.log(typeof function(){});
console.log(typeof []);
console.log(Array.isArray([]));
WTF
Answer
Output "object"
false
"function"
"object"
true

typeof null === "object" — ეს JavaScript-ის ცნობილი ბაგია, რომელიც პირველი ვერსიიდან მოყვება. ENG-ის მიხედვით უნდა ყოფილიყო "null", მაგრამ ბაგის გასწორებას შეცდომის გაშვება არსებულ კოდებში მოყვებოდა.

მიუხედავად იმისა რომ typeof null = "object", null instanceof Object = false.

ფუნქცია არის object-ის ქვეტიპი, მაგრამ typeof ცალკე აბრუნებს "function".

მასივი object-ია (typeof [] = "object"). მასივის დასადგენად გამოიყენე Array.isArray().

null-ის შესამოწმებლად: value === null.

Q · 09
NUMWTF
// რა გამოვა?
console.log(Math.max());
console.log(Math.min());
console.log(Math.max() > Math.min());
WTF
Answer
Output -Infinity
Infinity
false

Math.max() არგუმენტების გარეშე → -Infinity.

Math.min() არგუმენტების გარეშე → Infinity.

რატომ? ეს ლოგიკურია — როცა მაქსიმუმს ნახავ, ყოველი ახალი რიცხვი უფრო დიდი იქნება ვიდრე -Infinity. ანუ "ცარიელი" საწყისი მნიშვნელობა max-ისთვის -∞-ია, რომ ნებისმიერი რიცხვი მოერგოს. min-ისთვის — პირიქით.

ეს განსაკუთრებით საშიშია მაშინ, როცა Math.max(...arr) გამოიყენება და მასივი ცარიელია — შედეგი იქნება -Infinity, არა 0 ან undefined.

Q · 10
NUMWTF
// რა გამოვა?
console.log(parseInt("10px"));
console.log(parseInt("px10"));
console.log(parseInt("0.0000005"));
console.log(parseInt(0.0000005));
WTF
Answer
Output 10
NaN
0
5

parseInt("10px")10: კითხულობს ციფრებს მანამ, სანამ არა-ციფრულ სიმბოლოს მიაღწევს.

parseInt("px10")NaN: დასაწყისშივე ვერ პოულობს ციფრს.

parseInt("0.0000005")0: კითხულობს მხოლოდ "0", შემდეგ წერტილზე ჩერდება.

WTF momentი: parseInt(0.0000005)5?! რატომ?

რიცხვი ჯერ კონვერტდება string-ად. 0.0000005 string representation-ში ხდება "5e-7" (exponential notation, რადგან რიცხვი ძალიან პატარაა). შემდეგ parseInt("5e-7")5 (კითხულობს მხოლოდ პირველ "5"-ს).

რეკომენდაცია: integer-ისთვის გამოიყენე Number() ან Math.floor().

03 / 05

Scope & hoisting

Q · 11
SCOPEWTF
// რა გამოვა?
console.log(foo);
console.log(bar);
var foo = 1;
let bar = 2;
WTF
Answer
Output undefined
ReferenceError: Cannot access 'bar' before initialization

var hoisting: declaration აიწევა scope-ის თავში, მაგრამ მნიშვნელობა undefined რჩება assignment-მდე.

let/const hoisting: declaration ასევე hoisted-ია, მაგრამ "Temporal Dead Zone"-ში — მათზე წვდომა გამოცხადებამდე იძლევა ReferenceError-ს.

ეს არის ერთ-ერთი მიზეზი, რატომ უნდა გამოიყენო let/const ნაცვლად var-ისა — bug-ები უფრო ნაკლები ხდება.

Q · 12
SCOPEWTF
// რა გამოვა?
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
WTF
Answer
Output 3
3
3

var i არის function-scoped — ერთი და იგივე i გამოიყენება ყველა იტერაციაში.

როცა setTimeout-ის callback-ები გაშვებას იწყებენ (100ms-ში), loop უკვე დასრულებული აქვს და i = 3.

გადაწყვეტა 1: let-ით — ყოველ იტერაციას აქვს საკუთარი block-scoped i:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2

გადაწყვეტა 2: IIFE-თ closure-ის შექმნა (ძველი ES5 ვერსია):

for (var i = 0; i < 3; i++) {
  ((j) => setTimeout(() => console.log(j), 100))(i);
}
Q · 13
SCOPEWTF
// რა გამოვა?
function test() {
  console.log(typeof a);
  console.log(typeof b);
  var a = 1;
  function b() {}
}
test();
WTF
Answer
Output "undefined"
"function"

Function declarations სრულად hoisted-ია (declaration + body). ანუ b ფუნქცია უკვე ხელმისაწვდომია მისი გამოცხადების ხაზამდე.

Var declarations მხოლოდ declaration-ი hoisted-ია, value undefined-ია assignment-მდე. ამიტომ typeof a = "undefined".

აღსანიშნავია — typeof-ს გამოცხადებული ცვლადებისთვის undefined-ს აბრუნებს, მაგრამ undeclared ცვლადებისთვისაც "undefined"-ს აბრუნებს (არ აგდებს ReferenceError-ს, რაც დებაგისას შეცდომისკენ მიგვიყვანს).

Q · 14
SCOPEWTF
// რა გამოვა?
var x = 1;
function foo() {
  console.log(x);
  var x = 2;
  console.log(x);
}
foo();
WTF
Answer
Output undefined
2

ერთ-ერთი ყველაზე გავრცელებული hoisting-ის ბაგი.

ფუნქციის შიგნით var x hoisted-ია ლოკალური scope-ის თავში. შესაბამისად ფუნქციას აქვს საკუთარი ლოკალური x, რომელიც shadow-ს აკეთებს გლობალურს.

პირველი console.log(x) — ლოკალური x ჯერ undefined-ია (declaration hoisted, assignment არა).

მეორე console.log(x) — assignment-ის შემდეგ, x = 2.

გლობალური x = 1 ფუნქციის შიგნით საერთოდ ვერ მიიღწევა, რადგან ლოკალურმა scope-მა ის "დაფარა".

Q · 15
SCOPEWTF
// რა გამოვა?
let x = 10;
{
  console.log(x);
  let x = 20;
}
WTF
Answer
Output ReferenceError: Cannot access 'x' before initialization

შეიძლება მოგეჩვენოს, რომ აქ უნდა დაბეჭდოს 10 (გარე scope-დან), რადგან შიდა let x = 20 ჯერ არ შესრულებულა.

მაგრამ let hoisted-ია block-ის თავში — Temporal Dead Zone. ანუ block-ის შიგნით x უკვე არსებობს, მაგრამ ჯერ წვდომა არ აქვთ.

გარე x = 10 "დაფარულია" ლოკალური x-ით ბლოკის დასაწყისშივე, მისი მნიშვნელობის მინიჭებამდე.

გაკვეთილი: ცვლადები გამოაცხადე ბლოკის თავში, რომ TDZ-ის ბაგებს თავი აარიდო.

04 / 05

This & functions

Q · 16
THISWTF
// რა გამოვა?
const obj = {
  name: "Anna",
  greet: function() {
    console.log(this.name);
  },
  greetArrow: () => {
    console.log(this.name);
  }
};
obj.greet();
obj.greetArrow();
WTF
Answer
Output "Anna"
undefined

Regular function: this განისაზღვრება გამოძახების კონტექსტიდან. obj.greet()this = objthis.name = "Anna".

Arrow function: this-ს არ აქვს საკუთარი — მემკვიდრეობით იღებს გარე scope-დან. ამ შემთხვევაში გარე scope გლობალურია (browser-ში window, Node-ში module.exports).

გლობალურ scope-ში this.name = undefined (თუ მსგავსი property არ არსებობს).

გაკვეთილი: არასოდეს გამოიყენო arrow function როგორც method object-ში, თუ this-თან მუშაობა გჭირდება.

Q · 17
THISWTF
// რა გამოვა?
const obj = {
  name: "Anna",
  greet: function() {
    return function() {
      console.log(this.name);
    };
  }
};
const fn = obj.greet();
fn();
WTF
Answer
Output undefined (or empty in non-strict)

კლასიკური this-ის "დაკარგვის" პრობლემა.

შიდა ფუნქცია არ არის method-ი — გამოძახდება როგორც regular function (fn()). regular function-ში this არის undefined (strict mode-ში) ან window (non-strict mode-ში).

გადაწყვეტა 1: arrow function (მემკვიდრეობით იღებს this-ს):

greet: function() {
  return () => console.log(this.name);
}

გადაწყვეტა 2: bind():

greet: function() {
  return function() {
    console.log(this.name);
  }.bind(this);
}

გადაწყვეტა 3 (ძველი): const self = this.

Q · 18
THISWTF
// რა გამოვა?
function Person(name) {
  this.name = name;
}
const a = Person("Anna");
const b = new Person("Beka");
console.log(a);
console.log(b.name);
console.log(window.name); // browser only
WTF
Answer
Output undefined
"Beka"
"Anna"

Person("Anna")new-ის გარეშე — regular function call. this = window (non-strict mode), ამიტომ this.name = "Anna" ფაქტობრივად ქმნის window.name-ს. ფუნქცია undefined-ს აბრუნებს (return-ი არ აქვს).

new Person("Beka")new-თან this ახალი object-ია, რომელიც ბრუნდება ავტომატურად.

გადაწყვეტა: ES6 class-ები ან "use strict" — strict mode-ში this = undefined regular function call-ში, ამიტომ this.name = name აგდებს TypeError-ს და გვეუბნება, რომ new დაგვავიწყდა.

Q · 19
THISWTF
// რა გამოვა?
const obj = {
  count: 0,
  increment: function() {
    this.count++;
  }
};
const inc = obj.increment;
inc();
inc();
console.log(obj.count);
WTF
Answer
Output 0

ფუნქცია "გადაიჩხრიკება" object-ისგან, რის გამოც კონტექსტი იკარგება.

const inc = obj.increment — ფუნქციის reference-ის გადაცემა, არა ბმა object-თან.

inc() — გამოძახდება როგორც regular function. this = undefined (strict) ან window (non-strict).

strict mode-ში — TypeError-ს მივიღებთ this.count-ზე. non-strict mode-ში — window.count შეიქმნება, არა obj.count.

ეს ხშირად გვხვდება event listener-ებში: button.addEventListener('click', obj.handler) — handler-ში this = button, არა obj.

გადაწყვეტა: obj.increment.bind(obj) ან arrow function: () => obj.increment().

Q · 20
THISWTF
// რა გამოვა?
function foo() {
  return arguments;
}
const arrowFoo = () => arguments;

console.log(foo(1, 2, 3));
console.log(arrowFoo(1, 2, 3));
WTF
Answer
Output [Arguments] { '0': 1, '1': 2, '2': 3 }
ReferenceError: arguments is not defined

Arrow function-ებს არ აქვთ საკუთარი arguments object — ისინი მემკვიდრეობით იღებენ გარე scope-დან (ან საერთოდ არ აქვთ წვდომა).

Regular function-ებს აქვთ arguments — array-like object ყველა გადაცემული არგუმენტით (მაშინაც კი, როცა parameter-ები განსაზღვრული არ აქვთ).

თანამედროვე გადაწყვეტა — rest parameters:

const arrowFoo = (...args) => args;
arrowFoo(1, 2, 3); // [1, 2, 3]

...args არის ნამდვილი მასივი (array methods-ით), განსხვავებით arguments-ისგან, რომელიც array-like-ია.

05 / 05

Arrays & objects

Q · 21
WTF
// რა გამოვა?
const arr = [10, 1, 2, 11, 100];
console.log(arr.sort());
WTF
Answer
Output [1, 10, 100, 11, 2]

Array.prototype.sort() default-ად ადარებს ელემენტებს როგორც string-ებს, არა რიცხვებს.

String შედარება სიმბოლო-სიმბოლოდ ხდება: "10" < "2", რადგან "1" < "2" ASCII-ში.

სწორი ვერსია — comparator function-ით:

arr.sort((a, b) => a - b);
// [1, 2, 10, 11, 100]

ეს ერთ-ერთი ყველაზე გავრცელებული ბაგია JS-ში — განსაკუთრებით საშიშია, რადგან კოდი არ აგდებს error-ს, უბრალოდ არასწორ შედეგს იძლევა.

Q · 22
WTF
// რა გამოვა?
const arr = [1, 2, 3];
arr.length = 0;
console.log(arr);

const arr2 = [1, 2, 3];
arr2.length = 10;
console.log(arr2);
console.log(arr2[5]);
WTF
Answer
Output []
[1, 2, 3, <7 empty items>]
undefined

JS-ში მასივის length არის changeable property, არა ჩვეულებრივი getter.

arr.length = 0 — ცარიელ მასივად აქცევს! ეს ერთ-ერთი ყველაზე სწრაფი გზაა მასივის გასუფთავებისთვის.

arr2.length = 10 — ქმნის "sparse array"-ს, სადაც index-ები 3-9 არის "empty slots" (არ იქნება undefined, არამედ ფიზიკურად არ არსებობს).

arr2[5]undefined, მაგრამ 5 in arr2false. ამიტომაა განსხვავებული map/forEach-ის ქცევა sparse array-ებზე — ისინი skip-ავენ empty slot-ებს.

Q · 23
WTF
// რა გამოვა?
const obj = { a: 1 };
const arr = [1, 2, 3];

console.log(Object.keys(obj));
console.log(Object.keys(arr));
console.log(arr.hasOwnProperty('1'));
console.log(arr.hasOwnProperty(1));
WTF
Answer
Output ["a"]
["0", "1", "2"]
true
true

მასივები სინამდვილეში სპეციალური object-ებია, სადაც index-ები არის string keys.

Object.keys([1, 2, 3])["0", "1", "2"] — index-ები string-ებია, არა რიცხვები!

როცა ვწერთ arr[1], JS ახდენს implicit conversion-ს arr["1"]-ად. ამიტომ arr.hasOwnProperty('1') და arr.hasOwnProperty(1) — ორივე true.

სხვა საინტერესო:

arr["0"] === arr[0]; // true
arr.foo = "bar";  // ეს მუშაობს! object-ია
arr.length;        // 3 (არ ცვლის length-ს)
Object.keys(arr); // ["0", "1", "2", "foo"]
Q · 24
WTF
// რა გამოვა?
const a = { val: 1 };
const b = { val: 1 };

console.log(a == b);
console.log(a === b);
console.log(JSON.stringify(a) === JSON.stringify(b));

const c = a;
console.log(a === c);
WTF
Answer
Output false
false
true
true

Object-ები (და მასივები) reference-ით ედარებიან, არა value-ით.

a და b ერთიდაიგივე content-ი აქვთ, მაგრამ ისინი მეხსიერებაში სხვადასხვა ადგილზე ცხოვრობენ — შესაბამისად a === b = false.

const c = a — c-ში გადადის a-ს reference (იგივე მისამართი). ამიტომ a === c = true.

Deep equality-ისთვის:

// მარტივი ვარიანტი (ყველა შემთხვევაში არ მუშაობს)
JSON.stringify(a) === JSON.stringify(b);

// უკეთესი — lodash, Ramda, ან საკუთარი deep compare

JSON.stringify ვერ ამუშავებს: ფუნქციებს, undefined-ს, circular references-ს, Date-ს (string ხდება), Map/Set-ს და ა.შ.

Q · 25
WTF
// რა გამოვა?
const obj = {};
obj[true] = "boolean";
obj[1] = "number";
obj["1"] = "string";
console.log(obj);
console.log(Object.keys(obj).length);
WTF
Answer
Output { '1': "string", true: "boolean" }
2

Plain object-ის key-ები ყოველთვის string-ებად ხდება (Symbol-ის გარდა).

obj[1] = "number" → key გახდა "1" (string).

obj["1"] = "string" → იგივე key-ია, ანუ ცვლის წინა მნიშვნელობას.

obj[true] = "boolean" → key გახდა "true", რაც განსხვავდება "1"-ისგან.

ფინალური object-ში მხოლოდ ორი key-ია: "1" და "true".

თუ რეალურად სხვადასხვა ტიპის key-ები გჭირდება — გამოიყენე Map:

const m = new Map();
m.set(1, "number");
m.set("1", "string");
m.set(true, "boolean");
console.log(m.size); // 3