[{"data":1,"prerenderedAt":2401},["ShallowReactive",2],{"blog-uk-how-to-count-words-javascript":3},{"id":4,"title":5,"alt":6,"author":7,"body":8,"category":2370,"description":2371,"extension":2372,"faq":2373,"image":2388,"meta":2389,"navigation":1670,"path":2390,"publishedAt":2391,"seo":2392,"stem":2393,"tags":2394,"__hash__":2400},"blog\u002Fuk\u002Fhow-to-count-words-javascript.md","Як рахувати слова в JavaScript: 5 методів порівняно","Таблиця порівняння підрахунку слів у JavaScript: split проти regex проти Intl.Segmenter для Unicode та CJK-мов","Vibe Apps Pro Team",{"type":9,"value":10,"toc":2337},"minimark",[11,19,26,29,34,37,102,105,107,111,119,196,199,264,270,272,280,352,367,444,462,467,469,477,549,554,591,652,657,659,667,729,749,843,846,851,853,860,1026,1042,1056,1158,1163,1199,1201,1205,1337,1343,1350,1352,1356,1359,1362,1378,1388,1555,1564,1566,1570,1631,1633,1637,1640,1834,1837,1839,1843,1854,1868,1882,1884,1888,1891,2173,2182,2184,2188,2192,2206,2213,2232,2239,2251,2255,2260,2264,2270,2274,2294,2298,2306,2310,2323,2325,2333],[12,13,14,18],"p",{},[15,16,17],"code",{},"text.split(' ').length",". Кожен джуніор-JS-розробник це відправляв у прод. І воно неправильне щонайменше у три різні способи.",[12,20,21,22,25],{},"Це повний розбір п'яти підходів до того, як рахувати слова в JavaScript — що кожен реально робить під капотом, де він ламається і який варто взяти. Спойлер: це ",[15,23,24],{},"Intl.Segmenter",".",[27,28],"hr",{},[30,31,33],"h2",{"id":32},"чому-рахувати-слова-важче-ніж-здається","Чому рахувати слова важче, ніж здається",[12,35,36],{},"Текст відчувається простим. Слова ж розділені пробілами, так? Окрім випадків:",[38,39,40,52,62,72,78,88],"ul",{},[41,42,43,47,48,51],"li",{},[44,45,46],"strong",{},"Подвійні пробіли"," між реченнями (",[15,49,50],{},"hello  world"," → split дає 3 токени, а не 2)",[41,53,54,57,58,61],{},[44,55,56],{},"Нерозривні пробіли"," (",[15,59,60],{}," ",") — невидимі в браузері, постійно вставляються з Word, PDF і Google Docs",[41,63,64,67,68,71],{},[44,65,66],{},"Табуляції й переноси рядка"," — валідні пробільні символи, які ",[15,69,70],{},"split(' ')"," ігнорує",[41,73,74,77],{},[44,75,76],{},"CJK-текст"," — у китайській, японській, корейській узагалі немає пробілів між словами",[41,79,80,83,84,87],{},[44,81,82],{},"Емодзі"," — сімейне емодзі (",[15,85,86],{},"👨‍👩‍👧‍👦",") це 1 видимий символ, але 11 кодових одиниць UTF-16, 6 кодових точок Unicode і 1 кластер графем",[41,89,90,93,94,97,98,101],{},[44,91,92],{},"Скорочення й дефіси"," — ",[15,95,96],{},"don't",", ",[15,99,100],{},"state-of-the-art"," — це 1 слово чи 2?",[12,103,104],{},"Більшість багів підрахунку невидимі, поки на твій застосунок не натрапить неангломовний користувач.",[27,106],{},[30,108,110],{"id":109},"пять-методів","П'ять методів",[112,113,115,116,118],"h3",{"id":114},"метод-1-textsplit-length-наївний-split","Метод 1: ",[15,117,17],{}," — наївний split",[120,121,126],"pre",{"className":122,"code":123,"language":124,"meta":125,"style":125},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","function countWords(text) {\n  return text.split(' ').length;\n}\n","js","",[15,127,128,155,190],{"__ignoreMap":125},[129,130,133,137,141,145,149,152],"span",{"class":131,"line":132},"line",1,[129,134,136],{"class":135},"spNyl","function",[129,138,140],{"class":139},"s2Zo4"," countWords",[129,142,144],{"class":143},"sMK4o","(",[129,146,148],{"class":147},"sHdIc","text",[129,150,151],{"class":143},")",[129,153,154],{"class":143}," {\n",[129,156,158,162,166,168,171,174,177,180,182,184,187],{"class":131,"line":157},2,[129,159,161],{"class":160},"s7zQu","  return",[129,163,165],{"class":164},"sTEyZ"," text",[129,167,25],{"class":143},[129,169,170],{"class":139},"split",[129,172,144],{"class":173},"swJcz",[129,175,176],{"class":143},"'",[129,178,179],{"class":143}," '",[129,181,151],{"class":173},[129,183,25],{"class":143},[129,185,186],{"class":164},"length",[129,188,189],{"class":143},";\n",[129,191,193],{"class":131,"line":192},3,[129,194,195],{"class":143},"}\n",[12,197,198],{},"Це перше, що люди пишуть. І воно неправильне одразу.",[120,200,202],{"className":122,"code":201,"language":124,"meta":125,"style":125},"countWords('hello  world')  \u002F\u002F → 3 (extra empty string token)\ncountWords('hello\\tworld')  \u002F\u002F → 1 (tab not counted as separator)\ncountWords('')              \u002F\u002F → 1 (empty string gives [''], not [])\n",[15,203,204,225,249],{"__ignoreMap":125},[129,205,206,209,211,213,216,218,221],{"class":131,"line":132},[129,207,208],{"class":139},"countWords",[129,210,144],{"class":164},[129,212,176],{"class":143},[129,214,50],{"class":215},"sfazB",[129,217,176],{"class":143},[129,219,220],{"class":164},")  ",[129,222,224],{"class":223},"sHwdD","\u002F\u002F → 3 (extra empty string token)\n",[129,226,227,229,231,233,236,239,242,244,246],{"class":131,"line":157},[129,228,208],{"class":139},[129,230,144],{"class":164},[129,232,176],{"class":143},[129,234,235],{"class":215},"hello",[129,237,238],{"class":164},"\\t",[129,240,241],{"class":215},"world",[129,243,176],{"class":143},[129,245,220],{"class":164},[129,247,248],{"class":223},"\u002F\u002F → 1 (tab not counted as separator)\n",[129,250,251,253,255,258,261],{"class":131,"line":192},[129,252,208],{"class":139},[129,254,144],{"class":164},[129,256,257],{"class":143},"''",[129,259,260],{"class":164},")              ",[129,262,263],{"class":223},"\u002F\u002F → 1 (empty string gives [''], not [])\n",[12,265,266,269],{},[44,267,268],{},"Вердикт:"," не відправляй це в прод. Ніколи.",[27,271],{},[112,273,275,276,279],{"id":274},"метод-2-texttrimsplitsfilterbooleanlength-залатаний-split","Метод 2: ",[15,277,278],{},"text.trim().split(\u002F\\s+\u002F).filter(Boolean).length"," — залатаний split",[120,281,283],{"className":122,"code":282,"language":124,"meta":125,"style":125},"function countWords(text) {\n  return text.trim().split(\u002F\\s+\u002F).filter(Boolean).length;\n}\n",[15,284,285,299,348],{"__ignoreMap":125},[129,286,287,289,291,293,295,297],{"class":131,"line":132},[129,288,136],{"class":135},[129,290,140],{"class":139},[129,292,144],{"class":143},[129,294,148],{"class":147},[129,296,151],{"class":143},[129,298,154],{"class":143},[129,300,301,303,305,307,310,313,315,317,319,322,325,328,330,332,335,337,340,342,344,346],{"class":131,"line":157},[129,302,161],{"class":160},[129,304,165],{"class":164},[129,306,25],{"class":143},[129,308,309],{"class":139},"trim",[129,311,312],{"class":173},"()",[129,314,25],{"class":143},[129,316,170],{"class":139},[129,318,144],{"class":173},[129,320,321],{"class":143},"\u002F",[129,323,324],{"class":215},"\\s",[129,326,327],{"class":143},"+\u002F",[129,329,151],{"class":173},[129,331,25],{"class":143},[129,333,334],{"class":139},"filter",[129,336,144],{"class":173},[129,338,339],{"class":164},"Boolean",[129,341,151],{"class":173},[129,343,25],{"class":143},[129,345,186],{"class":164},[129,347,189],{"class":143},[129,349,350],{"class":131,"line":192},[129,351,195],{"class":143},[12,353,354,355,358,359,362,363,366],{},"Значно краще. ",[15,356,357],{},"\u002F\\s+\u002F"," матчить будь-яку послідовність пробільних символів — пробіли, табуляції, переноси рядка, повернення каретки. ",[15,360,361],{},"trim()"," опрацьовує пробіли на початку й у кінці. ",[15,364,365],{},"filter(Boolean)"," відкидає порожні рядки.",[120,368,370],{"className":122,"code":369,"language":124,"meta":125,"style":125},"countWords('hello  world')     \u002F\u002F → 2 ✓\ncountWords('hello\\tworld')     \u002F\u002F → 2 ✓\ncountWords('')                 \u002F\u002F → 0 ✓\ncountWords('héllo wörld')      \u002F\u002F → 2 ✓ (accent characters preserved)\n",[15,371,372,390,410,424],{"__ignoreMap":125},[129,373,374,376,378,380,382,384,387],{"class":131,"line":132},[129,375,208],{"class":139},[129,377,144],{"class":164},[129,379,176],{"class":143},[129,381,50],{"class":215},[129,383,176],{"class":143},[129,385,386],{"class":164},")     ",[129,388,389],{"class":223},"\u002F\u002F → 2 ✓\n",[129,391,392,394,396,398,400,402,404,406,408],{"class":131,"line":157},[129,393,208],{"class":139},[129,395,144],{"class":164},[129,397,176],{"class":143},[129,399,235],{"class":215},[129,401,238],{"class":164},[129,403,241],{"class":215},[129,405,176],{"class":143},[129,407,386],{"class":164},[129,409,389],{"class":223},[129,411,412,414,416,418,421],{"class":131,"line":192},[129,413,208],{"class":139},[129,415,144],{"class":164},[129,417,257],{"class":143},[129,419,420],{"class":164},")                 ",[129,422,423],{"class":223},"\u002F\u002F → 0 ✓\n",[129,425,427,429,431,433,436,438,441],{"class":131,"line":426},4,[129,428,208],{"class":139},[129,430,144],{"class":164},[129,432,176],{"class":143},[129,434,435],{"class":215},"héllo wörld",[129,437,176],{"class":143},[129,439,440],{"class":164},")      ",[129,442,443],{"class":223},"\u002F\u002F → 2 ✓ (accent characters preserved)\n",[12,445,446,449,450,453,454,457,458,461],{},[44,447,448],{},"Де ламається:"," CJK-текст. ",[15,451,452],{},"'你好世界'.trim().split(\u002F\\s+\u002F)"," повертає ",[15,455,456],{},"['你好世界']"," — один токен, а не чотири слова. Також рахує токени лише з пунктуації: якщо на вході ",[15,459,460],{},"-- --",", отримаєш 2 фантомні «слова».",[12,463,464,466],{},[44,465,268],{}," годиться для суто англійських інструментів. Зламано для глобальної аудиторії.",[27,468],{},[112,470,472,473,476],{"id":471},"метод-3-textmatchbwbg-length-класична-регулярка","Метод 3: ",[15,474,475],{},"(text.match(\u002F\\b\\w+\\b\u002Fg) || []).length"," — класична регулярка",[120,478,480],{"className":122,"code":479,"language":124,"meta":125,"style":125},"function countWords(text) {\n  return (text.match(\u002F\\b\\w+\\b\u002Fg) || []).length;\n}\n",[15,481,482,496,545],{"__ignoreMap":125},[129,483,484,486,488,490,492,494],{"class":131,"line":132},[129,485,136],{"class":135},[129,487,140],{"class":139},[129,489,144],{"class":143},[129,491,148],{"class":147},[129,493,151],{"class":143},[129,495,154],{"class":143},[129,497,498,500,502,504,506,509,511,513,516,519,522,524,526,530,533,536,539,541,543],{"class":131,"line":157},[129,499,161],{"class":160},[129,501,57],{"class":173},[129,503,148],{"class":164},[129,505,25],{"class":143},[129,507,508],{"class":139},"match",[129,510,144],{"class":173},[129,512,321],{"class":143},[129,514,515],{"class":160},"\\b",[129,517,518],{"class":215},"\\w",[129,520,521],{"class":143},"+",[129,523,515],{"class":160},[129,525,321],{"class":143},[129,527,529],{"class":528},"sbssI","g",[129,531,532],{"class":173},") ",[129,534,535],{"class":143},"||",[129,537,538],{"class":173}," [])",[129,540,25],{"class":143},[129,542,186],{"class":164},[129,544,189],{"class":143},[129,546,547],{"class":131,"line":192},[129,548,195],{"class":143},[12,550,551,552,25],{},"Ти бачитимеш це всюди на Stack Overflow. Проблема в ",[15,553,518],{},[12,555,556,557,559,560,563,564,567,568,571,572,575,576,579,580,583,584,453,587,590],{},"У JavaScript ",[15,558,518],{}," — це ",[15,561,562],{},"[A-Za-z0-9_]",". Оце весь набір. Кожен символ поза ASCII — кирилиця (",[15,565,566],{},"привет","), арабська (",[15,569,570],{},"مرحبا","), грецька (",[15,573,574],{},"γεια","), корейська (",[15,577,578],{},"안녕",") — невидимий для цієї регулярки. Запасний ",[15,581,582],{},"|| []"," тебе видає: без нього ",[15,585,586],{},".match()",[15,588,589],{},"null"," за відсутності збігів, а це був би весь рядок для нелатинського тексту.",[120,592,594],{"className":122,"code":593,"language":124,"meta":125,"style":125},"countWords('hello world')   \u002F\u002F → 2 ✓\ncountWords('привет мир')    \u002F\u002F → 0 ✗ (Cyrillic not matched)\ncountWords('héllo')         \u002F\u002F → 1, but counts 'h llo' internally → actually still 1 but accented chars may be excluded\n",[15,595,596,614,633],{"__ignoreMap":125},[129,597,598,600,602,604,607,609,612],{"class":131,"line":132},[129,599,208],{"class":139},[129,601,144],{"class":164},[129,603,176],{"class":143},[129,605,606],{"class":215},"hello world",[129,608,176],{"class":143},[129,610,611],{"class":164},")   ",[129,613,389],{"class":223},[129,615,616,618,620,622,625,627,630],{"class":131,"line":157},[129,617,208],{"class":139},[129,619,144],{"class":164},[129,621,176],{"class":143},[129,623,624],{"class":215},"привет мир",[129,626,176],{"class":143},[129,628,629],{"class":164},")    ",[129,631,632],{"class":223},"\u002F\u002F → 0 ✗ (Cyrillic not matched)\n",[129,634,635,637,639,641,644,646,649],{"class":131,"line":192},[129,636,208],{"class":139},[129,638,144],{"class":164},[129,640,176],{"class":143},[129,642,643],{"class":215},"héllo",[129,645,176],{"class":143},[129,647,648],{"class":164},")         ",[129,650,651],{"class":223},"\u002F\u002F → 1, but counts 'h llo' internally → actually still 1 but accented chars may be excluded\n",[12,653,654,656],{},[44,655,268],{}," прийнятно для дев-інструментів, що обробляють лише ASCII. Тихий провал для всього іншого.",[27,658],{},[112,660,662,663,666],{"id":661},"метод-4-textmatchplgu-length-unicode-property-escapes","Метод 4: ",[15,664,665],{},"(text.match(\u002F\\p{L}+\u002Fgu) || []).length"," — Unicode Property Escapes",[120,668,670],{"className":122,"code":669,"language":124,"meta":125,"style":125},"function countWords(text) {\n  return (text.match(\u002F\\p{L}+\u002Fgu) || []).length;\n}\n",[15,671,672,686,725],{"__ignoreMap":125},[129,673,674,676,678,680,682,684],{"class":131,"line":132},[129,675,136],{"class":135},[129,677,140],{"class":139},[129,679,144],{"class":143},[129,681,148],{"class":147},[129,683,151],{"class":143},[129,685,154],{"class":143},[129,687,688,690,692,694,696,698,700,702,705,708,710,713,715,717,719,721,723],{"class":131,"line":157},[129,689,161],{"class":160},[129,691,57],{"class":173},[129,693,148],{"class":164},[129,695,25],{"class":143},[129,697,508],{"class":139},[129,699,144],{"class":173},[129,701,321],{"class":143},[129,703,704],{"class":164},"\\p",[129,706,707],{"class":215},"{L}",[129,709,327],{"class":143},[129,711,712],{"class":528},"gu",[129,714,532],{"class":173},[129,716,535],{"class":143},[129,718,538],{"class":173},[129,720,25],{"class":143},[129,722,186],{"class":164},[129,724,189],{"class":143},[129,726,727],{"class":131,"line":192},[129,728,195],{"class":143},[12,730,731,734,735,738,739,742,743,745,746,748],{},[15,732,733],{},"\\p{L}"," — це Unicode Property Escape, що означає «будь-яка літера Unicode». Прапор ",[15,736,737],{},"u"," обов'язковий — без нього V8 кидає ",[15,740,741],{},"SyntaxError",", бо ",[15,744,704],{}," невалідний у не-Unicode режимі. Прапор ",[15,747,529],{}," знаходить усі збіги глобально.",[120,750,752],{"className":122,"code":751,"language":124,"meta":125,"style":125},"countWords('hello world')      \u002F\u002F → 2 ✓\ncountWords('привет мир')       \u002F\u002F → 2 ✓ (Cyrillic works)\ncountWords('héllo wörld')      \u002F\u002F → 2 ✓ (accented chars work)\ncountWords('你好 世界')         \u002F\u002F → 2 ✓ (space-separated CJK)\ncountWords('你好世界')          \u002F\u002F → 1 ✗ (no spaces, counts as one match)\n",[15,753,754,770,788,805,823],{"__ignoreMap":125},[129,755,756,758,760,762,764,766,768],{"class":131,"line":132},[129,757,208],{"class":139},[129,759,144],{"class":164},[129,761,176],{"class":143},[129,763,606],{"class":215},[129,765,176],{"class":143},[129,767,440],{"class":164},[129,769,389],{"class":223},[129,771,772,774,776,778,780,782,785],{"class":131,"line":157},[129,773,208],{"class":139},[129,775,144],{"class":164},[129,777,176],{"class":143},[129,779,624],{"class":215},[129,781,176],{"class":143},[129,783,784],{"class":164},")       ",[129,786,787],{"class":223},"\u002F\u002F → 2 ✓ (Cyrillic works)\n",[129,789,790,792,794,796,798,800,802],{"class":131,"line":192},[129,791,208],{"class":139},[129,793,144],{"class":164},[129,795,176],{"class":143},[129,797,435],{"class":215},[129,799,176],{"class":143},[129,801,440],{"class":164},[129,803,804],{"class":223},"\u002F\u002F → 2 ✓ (accented chars work)\n",[129,806,807,809,811,813,816,818,820],{"class":131,"line":426},[129,808,208],{"class":139},[129,810,144],{"class":164},[129,812,176],{"class":143},[129,814,815],{"class":215},"你好 世界",[129,817,176],{"class":143},[129,819,648],{"class":164},[129,821,822],{"class":223},"\u002F\u002F → 2 ✓ (space-separated CJK)\n",[129,824,826,828,830,832,835,837,840],{"class":131,"line":825},5,[129,827,208],{"class":139},[129,829,144],{"class":164},[129,831,176],{"class":143},[129,833,834],{"class":215},"你好世界",[129,836,176],{"class":143},[129,838,839],{"class":164},")          ",[129,841,842],{"class":223},"\u002F\u002F → 1 ✗ (no spaces, counts as one match)\n",[12,844,845],{},"Числа й окрема пунктуація виключаються автоматично, що зазвичай і потрібно.",[12,847,848,850],{},[44,849,268],{}," чудово для латиниці, кирилиці, арабської, грецької, єврейської та CJK із пробілами. Усе ще не вміє сегментувати CJK без пробілів.",[27,852],{},[112,854,856,857,859],{"id":855},"метод-5-intlsegmenter-правильна-відповідь","Метод 5: ",[15,858,24],{}," — правильна відповідь",[120,861,863],{"className":122,"code":862,"language":124,"meta":125,"style":125},"function countWords(text) {\n  const segmenter = new Intl.Segmenter('und', { granularity: 'word' });\n  let count = 0;\n  for (const { isWordLike } of segmenter.segment(text)) {\n    if (isWordLike) count++;\n  }\n  return count;\n}\n",[15,864,865,879,936,951,988,1006,1012,1021],{"__ignoreMap":125},[129,866,867,869,871,873,875,877],{"class":131,"line":132},[129,868,136],{"class":135},[129,870,140],{"class":139},[129,872,144],{"class":143},[129,874,148],{"class":147},[129,876,151],{"class":143},[129,878,154],{"class":143},[129,880,881,884,887,890,893,896,898,901,903,905,908,910,913,916,919,922,924,927,929,932,934],{"class":131,"line":157},[129,882,883],{"class":135},"  const",[129,885,886],{"class":164}," segmenter",[129,888,889],{"class":143}," =",[129,891,892],{"class":143}," new",[129,894,895],{"class":164}," Intl",[129,897,25],{"class":143},[129,899,900],{"class":139},"Segmenter",[129,902,144],{"class":173},[129,904,176],{"class":143},[129,906,907],{"class":215},"und",[129,909,176],{"class":143},[129,911,912],{"class":143},",",[129,914,915],{"class":143}," {",[129,917,918],{"class":173}," granularity",[129,920,921],{"class":143},":",[129,923,179],{"class":143},[129,925,926],{"class":215},"word",[129,928,176],{"class":143},[129,930,931],{"class":143}," }",[129,933,151],{"class":173},[129,935,189],{"class":143},[129,937,938,941,944,946,949],{"class":131,"line":192},[129,939,940],{"class":135},"  let",[129,942,943],{"class":164}," count",[129,945,889],{"class":143},[129,947,948],{"class":528}," 0",[129,950,189],{"class":143},[129,952,953,956,958,961,963,966,968,971,973,975,978,980,982,985],{"class":131,"line":426},[129,954,955],{"class":160},"  for",[129,957,57],{"class":173},[129,959,960],{"class":135},"const",[129,962,915],{"class":143},[129,964,965],{"class":164}," isWordLike",[129,967,931],{"class":143},[129,969,970],{"class":143}," of",[129,972,886],{"class":164},[129,974,25],{"class":143},[129,976,977],{"class":139},"segment",[129,979,144],{"class":173},[129,981,148],{"class":164},[129,983,984],{"class":173},")) ",[129,986,987],{"class":143},"{\n",[129,989,990,993,995,998,1000,1003],{"class":131,"line":825},[129,991,992],{"class":160},"    if",[129,994,57],{"class":173},[129,996,997],{"class":164},"isWordLike",[129,999,532],{"class":173},[129,1001,1002],{"class":164},"count",[129,1004,1005],{"class":143},"++;\n",[129,1007,1009],{"class":131,"line":1008},6,[129,1010,1011],{"class":143},"  }\n",[129,1013,1015,1017,1019],{"class":131,"line":1014},7,[129,1016,161],{"class":160},[129,1018,943],{"class":164},[129,1020,189],{"class":143},[129,1022,1024],{"class":131,"line":1023},8,[129,1025,195],{"class":143},[12,1027,1028,1030,1031,1034,1035,97,1038,1041],{},[15,1029,24],{}," — це API інтернаціоналізації W3C, доступний у всіх сучасних JavaScript-рантаймах (Baseline 2023). Передавай ",[15,1032,1033],{},"'und'"," як локаль для незалежної від локалі сегментації або свою конкретну локаль (",[15,1036,1037],{},"'zh'",[15,1039,1040],{},"'ja'",") для правил із урахуванням мови.",[12,1043,1044,1045,1047,1048,1051,1052,1055],{},"Прапорець ",[15,1046,997],{}," — ось ключ: він ",[15,1049,1050],{},"true"," для справжніх слів і ",[15,1053,1054],{},"false"," для пробілів, пунктуації й роздільників. Жодної фільтрації не треба.",[120,1057,1059],{"className":122,"code":1058,"language":124,"meta":125,"style":125},"countWords('hello world')      \u002F\u002F → 2 ✓\ncountWords('привет мир')       \u002F\u002F → 2 ✓\ncountWords('你好世界')          \u002F\u002F → 2 ✓ (你好 = hello, 世界 = world — dictionary segmentation)\ncountWords(\"don't stop\")       \u002F\u002F → 2 ✓ (contraction = 1 word)\ncountWords('state-of-the-art') \u002F\u002F → 4 ✓ (hyphenated = 4 words, matches editorial convention)\ncountWords('')                 \u002F\u002F → 0 ✓\n",[15,1060,1061,1077,1093,1110,1129,1146],{"__ignoreMap":125},[129,1062,1063,1065,1067,1069,1071,1073,1075],{"class":131,"line":132},[129,1064,208],{"class":139},[129,1066,144],{"class":164},[129,1068,176],{"class":143},[129,1070,606],{"class":215},[129,1072,176],{"class":143},[129,1074,440],{"class":164},[129,1076,389],{"class":223},[129,1078,1079,1081,1083,1085,1087,1089,1091],{"class":131,"line":157},[129,1080,208],{"class":139},[129,1082,144],{"class":164},[129,1084,176],{"class":143},[129,1086,624],{"class":215},[129,1088,176],{"class":143},[129,1090,784],{"class":164},[129,1092,389],{"class":223},[129,1094,1095,1097,1099,1101,1103,1105,1107],{"class":131,"line":192},[129,1096,208],{"class":139},[129,1098,144],{"class":164},[129,1100,176],{"class":143},[129,1102,834],{"class":215},[129,1104,176],{"class":143},[129,1106,839],{"class":164},[129,1108,1109],{"class":223},"\u002F\u002F → 2 ✓ (你好 = hello, 世界 = world — dictionary segmentation)\n",[129,1111,1112,1114,1116,1119,1122,1124,1126],{"class":131,"line":426},[129,1113,208],{"class":139},[129,1115,144],{"class":164},[129,1117,1118],{"class":143},"\"",[129,1120,1121],{"class":215},"don't stop",[129,1123,1118],{"class":143},[129,1125,784],{"class":164},[129,1127,1128],{"class":223},"\u002F\u002F → 2 ✓ (contraction = 1 word)\n",[129,1130,1131,1133,1135,1137,1139,1141,1143],{"class":131,"line":825},[129,1132,208],{"class":139},[129,1134,144],{"class":164},[129,1136,176],{"class":143},[129,1138,100],{"class":215},[129,1140,176],{"class":143},[129,1142,532],{"class":164},[129,1144,1145],{"class":223},"\u002F\u002F → 4 ✓ (hyphenated = 4 words, matches editorial convention)\n",[129,1147,1148,1150,1152,1154,1156],{"class":131,"line":1008},[129,1149,208],{"class":139},[129,1151,144],{"class":164},[129,1153,257],{"class":143},[129,1155,420],{"class":164},[129,1157,423],{"class":223},[12,1159,1160,1162],{},[44,1161,268],{}," використовуй це. Саме це браузери застосовують усередині для перевірки орфографії й виділення тексту.",[1164,1165,1166],"blockquote",{},[12,1167,1168,1171,1172,60,1175,1177,1178,1181,1182,1185,1186,1194,1195,1198],{},[44,1169,1170],{},"Примітка щодо Node.js:"," на Node.js 16+ зі стандартним складанням ",[15,1173,1174],{},"full-icu",[15,1176,24],{}," працює з коробки. Якщо ти на старішій версії чи на складанні ",[15,1179,1180],{},"small-icu"," (поширене в деяких Docker-образах), можеш отримати ",[15,1183,1184],{},"TypeError: Intl.Segmenter is not a constructor",". Полагодь це, встановивши пакет ",[1187,1188,1192],"a",{"href":1189,"rel":1190},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ffull-icu",[1191],"nofollow",[15,1193,1174],{}," і передавши ",[15,1196,1197],{},"--icu-data-dir"," під час старту — або просто оновись до Node 18+, де повні дані ICU вшито за замовчуванням.",[27,1200],{},[30,1202,1204],{"id":1203},"таблиця-порівняння-точності","Таблиця порівняння точності",[1206,1207,1208,1236],"table",{},[1209,1210,1211],"thead",{},[1212,1213,1214,1218,1221,1224,1227,1230,1233],"tr",{},[1215,1216,1217],"th",{},"Метод",[1215,1219,1220],{},"Англійська",[1215,1222,1223],{},"Діакритика",[1215,1225,1226],{},"Кирилиця\u002FАрабська",[1215,1228,1229],{},"CJK (без пробілів)",[1215,1231,1232],{},"Порожній рядок",[1215,1234,1235],{},"Скорочення",[1237,1238,1239,1261,1281,1300,1319],"tbody",{},[1212,1240,1241,1246,1249,1252,1254,1256,1259],{},[1242,1243,1244],"td",{},[15,1245,70],{},[1242,1247,1248],{},"✗ (подвійні пробіли)",[1242,1250,1251],{},"✓",[1242,1253,1251],{},[1242,1255,1251],{},[1242,1257,1258],{},"✗ (повертає 1)",[1242,1260,1251],{},[1212,1262,1263,1268,1270,1272,1274,1277,1279],{},[1242,1264,1265],{},[15,1266,1267],{},"trim().split(\u002F\\s+\u002F)",[1242,1269,1251],{},[1242,1271,1251],{},[1242,1273,1251],{},[1242,1275,1276],{},"✗",[1242,1278,1251],{},[1242,1280,1251],{},[1212,1282,1283,1288,1290,1292,1294,1296,1298],{},[1242,1284,1285],{},[15,1286,1287],{},"\u002F\\b\\w+\\b\u002Fg",[1242,1289,1251],{},[1242,1291,1276],{},[1242,1293,1276],{},[1242,1295,1276],{},[1242,1297,1251],{},[1242,1299,1251],{},[1212,1301,1302,1307,1309,1311,1313,1315,1317],{},[1242,1303,1304],{},[15,1305,1306],{},"\u002F\\p{L}+\u002Fgu",[1242,1308,1251],{},[1242,1310,1251],{},[1242,1312,1251],{},[1242,1314,1276],{},[1242,1316,1251],{},[1242,1318,1251],{},[1212,1320,1321,1325,1327,1329,1331,1333,1335],{},[1242,1322,1323],{},[15,1324,24],{},[1242,1326,1251],{},[1242,1328,1251],{},[1242,1330,1251],{},[1242,1332,1251],{},[1242,1334,1251],{},[1242,1336,1251],{},[12,1338,1339,1340,1342],{},"Рядок ",[15,1341,24],{}," — єдиний, де всі галочки.",[12,1344,1345],{},[1346,1347],"img",{"alt":1348,"src":1349},"Те саме речення, відрендерене латиницею, кирилицею, арабською, CJK та емодзі — зелена галочка над Intl.Segmenter, червоний хрестик над \\w-регуляркою","\u002Farticles\u002Fhow-to-count-words-javascript\u002Fsection-unicode.webp",[27,1351],{},[30,1353,1355],{"id":1354},"міркування-щодо-продуктивності","Міркування щодо продуктивності",[12,1357,1358],{},"Для документа на 1000 слів усі п'ять методів мізерні — менш ніж 1 мс на будь-якій сучасній машині. Різниця проявляється на масштабі.",[12,1360,1361],{},"На 100 000 слів (повний рукопис роману):",[38,1363,1364,1373],{},[41,1365,1366,1367,1369,1370],{},"Регулярні методи (",[15,1368,1306],{},") виконуються за ~20–40 мс — досить швидко для підрахунку в реальному часі на подіях ",[15,1371,1372],{},"input",[41,1374,1375,1377],{},[15,1376,24],{}," виконується за ~80–120 мс — усе ще менше за 100 мс, але вже близько до порога для плавного UI на 60fps",[12,1379,1380,1383,1384,1387],{},[44,1381,1382],{},"Емпіричне правило:"," для вхідних даних понад 50 000 слів запускай лічильник у Web Worker. Передай текст через ",[15,1385,1386],{},"postMessage",", запусти сегментатор у контексті воркера й відправ результат назад. Головний потік лишається незаблокованим.",[120,1389,1391],{"className":122,"code":1390,"language":124,"meta":125,"style":125},"\u002F\u002F word-count.worker.js\nself.onmessage = ({ data: text }) => {\n  const segmenter = new Intl.Segmenter('und', { granularity: 'word' });\n  let count = 0;\n  for (const { isWordLike } of segmenter.segment(text)) {\n    if (isWordLike) count++;\n  }\n  self.postMessage(count);\n};\n",[15,1392,1393,1398,1428,1472,1484,1514,1528,1532,1549],{"__ignoreMap":125},[129,1394,1395],{"class":131,"line":132},[129,1396,1397],{"class":223},"\u002F\u002F word-count.worker.js\n",[129,1399,1400,1403,1405,1408,1410,1413,1416,1418,1420,1423,1426],{"class":131,"line":157},[129,1401,1402],{"class":164},"self",[129,1404,25],{"class":143},[129,1406,1407],{"class":139},"onmessage",[129,1409,889],{"class":143},[129,1411,1412],{"class":143}," ({",[129,1414,1415],{"class":173}," data",[129,1417,921],{"class":143},[129,1419,165],{"class":147},[129,1421,1422],{"class":143}," })",[129,1424,1425],{"class":135}," =>",[129,1427,154],{"class":143},[129,1429,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470],{"class":131,"line":192},[129,1431,883],{"class":135},[129,1433,886],{"class":164},[129,1435,889],{"class":143},[129,1437,892],{"class":143},[129,1439,895],{"class":164},[129,1441,25],{"class":143},[129,1443,900],{"class":139},[129,1445,144],{"class":173},[129,1447,176],{"class":143},[129,1449,907],{"class":215},[129,1451,176],{"class":143},[129,1453,912],{"class":143},[129,1455,915],{"class":143},[129,1457,918],{"class":173},[129,1459,921],{"class":143},[129,1461,179],{"class":143},[129,1463,926],{"class":215},[129,1465,176],{"class":143},[129,1467,931],{"class":143},[129,1469,151],{"class":173},[129,1471,189],{"class":143},[129,1473,1474,1476,1478,1480,1482],{"class":131,"line":426},[129,1475,940],{"class":135},[129,1477,943],{"class":164},[129,1479,889],{"class":143},[129,1481,948],{"class":528},[129,1483,189],{"class":143},[129,1485,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512],{"class":131,"line":825},[129,1487,955],{"class":160},[129,1489,57],{"class":173},[129,1491,960],{"class":135},[129,1493,915],{"class":143},[129,1495,965],{"class":164},[129,1497,931],{"class":143},[129,1499,970],{"class":143},[129,1501,886],{"class":164},[129,1503,25],{"class":143},[129,1505,977],{"class":139},[129,1507,144],{"class":173},[129,1509,148],{"class":164},[129,1511,984],{"class":173},[129,1513,987],{"class":143},[129,1515,1516,1518,1520,1522,1524,1526],{"class":131,"line":1008},[129,1517,992],{"class":160},[129,1519,57],{"class":173},[129,1521,997],{"class":164},[129,1523,532],{"class":173},[129,1525,1002],{"class":164},[129,1527,1005],{"class":143},[129,1529,1530],{"class":131,"line":1014},[129,1531,1011],{"class":143},[129,1533,1534,1537,1539,1541,1543,1545,1547],{"class":131,"line":1023},[129,1535,1536],{"class":164},"  self",[129,1538,25],{"class":143},[129,1540,1386],{"class":139},[129,1542,144],{"class":173},[129,1544,1002],{"class":164},[129,1546,151],{"class":173},[129,1548,189],{"class":143},[129,1550,1552],{"class":131,"line":1551},9,[129,1553,1554],{"class":143},"};\n",[12,1556,1557,1558,1563],{},"Якщо хочеш звірити свою реалізацію з еталонною, встав текст у наш ",[44,1559,1560],{},[1187,1561,1562],{"href":321},"Лічильник слів"," — працює на 100% у твоєму браузері, нуль даних на жоден сервер — і порівняй лічбу зі своєї функції з тим, що показує він.",[27,1565],{},[30,1567,1569],{"id":1568},"коли-який-метод-використовувати","Коли який метод використовувати",[1206,1571,1572,1582],{},[1209,1573,1574],{},[1212,1575,1576,1579],{},[1215,1577,1578],{},"Сценарій",[1215,1580,1581],{},"Рекомендований метод",[1237,1583,1584,1593,1602,1611,1621],{},[1212,1585,1586,1589],{},[1242,1587,1588],{},"Швидкий скрипт лише для англійської",[1242,1590,1591],{},[15,1592,1267],{},[1212,1594,1595,1598],{},[1242,1596,1597],{},"Продакшен-застосунок, багатомовний",[1242,1599,1600],{},[15,1601,1306],{},[1212,1603,1604,1607],{},[1242,1605,1606],{},"Продакшен-застосунок + підтримка CJK",[1242,1608,1609],{},[15,1610,24],{},[1212,1612,1613,1616],{},[1242,1614,1615],{},"Node.js CLI, будь-яка мова",[1242,1617,1618,1620],{},[15,1619,24],{}," (Node ≥16)",[1212,1622,1623,1626],{},[1242,1624,1625],{},"Легасі-браузери (IE, старий Safari)",[1242,1627,1628,1630],{},[15,1629,1267],{}," + примітка про поліфіл",[27,1632],{},[30,1634,1636],{"id":1635},"крайові-випадки-з-реального-світу-для-тестів","Крайові випадки з реального світу для тестів",[12,1638,1639],{},"Перш ніж відправляти лічильник слів у прод, прожени його на цих входах. Якщо бодай якийсь дає неочікуваний результат — у твоєму методі є баг:",[120,1641,1643],{"className":122,"code":1642,"language":124,"meta":125,"style":125},"\u002F\u002F 1. Multiple whitespace types\n\"hello\\t\\nworld\"         \u002F\u002F expect: 2\n\n\u002F\u002F 2. Non-breaking space (pasted from Word)\n\"hello world\"       \u002F\u002F expect: 2\n\n\u002F\u002F 3. Zero-width space (pasted from web)\n\"hello​world\"       \u002F\u002F expect: 1 or 2 (debatable, document your choice)\n\n\u002F\u002F 4. Pure punctuation\n\"... --- ???\"            \u002F\u002F expect: 0\n\n\u002F\u002F 5. Numbers only\n\"123 456\"                \u002F\u002F expect: 0 (if counting \"words\" = letters only)\n                         \u002F\u002F expect: 2 (if counting tokens)\n\n\u002F\u002F 6. Mixed script\n\"hello мир\"              \u002F\u002F expect: 2\n\n\u002F\u002F 7. Emoji in text\n\"Great job 🎉\"           \u002F\u002F expect: 2 (emoji is not a word)\n\n\u002F\u002F 8. Hyphenated compound\n\"state-of-the-art design\" \u002F\u002F Intl.Segmenter → 5; \u002F\\p{L}+\u002Fgu → 5; split → 2\n",[15,1644,1645,1650,1666,1672,1677,1688,1692,1697,1709,1713,1719,1732,1737,1743,1756,1762,1767,1773,1786,1791,1797,1810,1815,1821],{"__ignoreMap":125},[129,1646,1647],{"class":131,"line":132},[129,1648,1649],{"class":223},"\u002F\u002F 1. Multiple whitespace types\n",[129,1651,1652,1654,1656,1659,1661,1663],{"class":131,"line":157},[129,1653,1118],{"class":143},[129,1655,235],{"class":215},[129,1657,1658],{"class":164},"\\t\\n",[129,1660,241],{"class":215},[129,1662,1118],{"class":143},[129,1664,1665],{"class":223},"         \u002F\u002F expect: 2\n",[129,1667,1668],{"class":131,"line":192},[129,1669,1671],{"emptyLinePlaceholder":1670},true,"\n",[129,1673,1674],{"class":131,"line":426},[129,1675,1676],{"class":223},"\u002F\u002F 2. Non-breaking space (pasted from Word)\n",[129,1678,1679,1681,1683,1685],{"class":131,"line":825},[129,1680,1118],{"class":143},[129,1682,606],{"class":215},[129,1684,1118],{"class":143},[129,1686,1687],{"class":223},"       \u002F\u002F expect: 2\n",[129,1689,1690],{"class":131,"line":1008},[129,1691,1671],{"emptyLinePlaceholder":1670},[129,1693,1694],{"class":131,"line":1014},[129,1695,1696],{"class":223},"\u002F\u002F 3. Zero-width space (pasted from web)\n",[129,1698,1699,1701,1704,1706],{"class":131,"line":1023},[129,1700,1118],{"class":143},[129,1702,1703],{"class":215},"hello​world",[129,1705,1118],{"class":143},[129,1707,1708],{"class":223},"       \u002F\u002F expect: 1 or 2 (debatable, document your choice)\n",[129,1710,1711],{"class":131,"line":1551},[129,1712,1671],{"emptyLinePlaceholder":1670},[129,1714,1716],{"class":131,"line":1715},10,[129,1717,1718],{"class":223},"\u002F\u002F 4. Pure punctuation\n",[129,1720,1722,1724,1727,1729],{"class":131,"line":1721},11,[129,1723,1118],{"class":143},[129,1725,1726],{"class":215},"... --- ???",[129,1728,1118],{"class":143},[129,1730,1731],{"class":223},"            \u002F\u002F expect: 0\n",[129,1733,1735],{"class":131,"line":1734},12,[129,1736,1671],{"emptyLinePlaceholder":1670},[129,1738,1740],{"class":131,"line":1739},13,[129,1741,1742],{"class":223},"\u002F\u002F 5. Numbers only\n",[129,1744,1746,1748,1751,1753],{"class":131,"line":1745},14,[129,1747,1118],{"class":143},[129,1749,1750],{"class":215},"123 456",[129,1752,1118],{"class":143},[129,1754,1755],{"class":223},"                \u002F\u002F expect: 0 (if counting \"words\" = letters only)\n",[129,1757,1759],{"class":131,"line":1758},15,[129,1760,1761],{"class":223},"                         \u002F\u002F expect: 2 (if counting tokens)\n",[129,1763,1765],{"class":131,"line":1764},16,[129,1766,1671],{"emptyLinePlaceholder":1670},[129,1768,1770],{"class":131,"line":1769},17,[129,1771,1772],{"class":223},"\u002F\u002F 6. Mixed script\n",[129,1774,1776,1778,1781,1783],{"class":131,"line":1775},18,[129,1777,1118],{"class":143},[129,1779,1780],{"class":215},"hello мир",[129,1782,1118],{"class":143},[129,1784,1785],{"class":223},"              \u002F\u002F expect: 2\n",[129,1787,1789],{"class":131,"line":1788},19,[129,1790,1671],{"emptyLinePlaceholder":1670},[129,1792,1794],{"class":131,"line":1793},20,[129,1795,1796],{"class":223},"\u002F\u002F 7. Emoji in text\n",[129,1798,1800,1802,1805,1807],{"class":131,"line":1799},21,[129,1801,1118],{"class":143},[129,1803,1804],{"class":215},"Great job 🎉",[129,1806,1118],{"class":143},[129,1808,1809],{"class":223},"           \u002F\u002F expect: 2 (emoji is not a word)\n",[129,1811,1813],{"class":131,"line":1812},22,[129,1814,1671],{"emptyLinePlaceholder":1670},[129,1816,1818],{"class":131,"line":1817},23,[129,1819,1820],{"class":223},"\u002F\u002F 8. Hyphenated compound\n",[129,1822,1824,1826,1829,1831],{"class":131,"line":1823},24,[129,1825,1118],{"class":143},[129,1827,1828],{"class":215},"state-of-the-art design",[129,1830,1118],{"class":143},[129,1832,1833],{"class":223}," \u002F\u002F Intl.Segmenter → 5; \u002F\\p{L}+\u002Fgu → 5; split → 2\n",[12,1835,1836],{},"Випадок із дефісом — той, що збиває людей найбільше. Універсальної «правильної» відповіді немає — англійські стайлгайди не згодні між собою. Обери поведінку, задокументуй її й будь послідовним.",[27,1838],{},[30,1840,1842],{"id":1841},"що-використовує-лічильник-слів-на-цьому-сайті","Що використовує Лічильник слів на цьому сайті",[12,1844,1845,1847,1848,1850,1851,1853],{},[1187,1846,1562],{"href":321}," на editlyapp.com використовує ",[15,1849,24],{}," із ",[15,1852,1306],{}," як запасний варіант для середовищ, де API ще недоступний. Сегментатор працює на головному потоці для документів до 50 000 слів і перемикається на Web Worker для більших входів — утримуючи відгук UI під 16 мс незалежно від довжини рукопису.",[12,1855,1856,1857,1861,1862,1850,1864,1867],{},"Це той самий підхід, що описаний у статті ",[1187,1858,1860],{"href":1859},"\u002Fblog\u002Freadability-score-explained","Оцінка читабельності простими словами",", де сегментація речень використовує ",[15,1863,24],{},[15,1865,1866],{},"granularity: 'sentence'",", щоб точно живити формулу Flesch-Kincaid.",[12,1869,1870,1871,1875,1876,1878,1879,1881],{},"Якщо ти будуєш текстовий інструмент на регулярках і треба протестувати патерни на реальному вмісті, інструмент ",[1187,1872,1874],{"href":1873},"\u002Ffind-replace","Пошук і заміна"," на цьому сайті підтримує повні регулярки з прапором ",[15,1877,737],{}," — зручно для валідації твоїх патернів ",[15,1880,1306],{},", перш ніж заводити їх у застосунок.",[27,1883],{},[30,1885,1887],{"id":1886},"остаточна-реалізація","Остаточна реалізація",[12,1889,1890],{},"Ось готова до продакшену версія, що опрацьовує кожен випадок вище:",[120,1892,1894],{"className":122,"code":1893,"language":124,"meta":125,"style":125},"\u002F**\n * Count words in any language using Intl.Segmenter.\n * Falls back to Unicode regex for environments without Segmenter support.\n *\u002F\nfunction countWords(text) {\n  if (!text || !text.trim()) return 0;\n\n  if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n    const segmenter = new Intl.Segmenter('und', { granularity: 'word' });\n    let count = 0;\n    for (const { isWordLike } of segmenter.segment(text)) {\n      if (isWordLike) count++;\n    }\n    return count;\n  }\n\n  \u002F\u002F Fallback: Unicode Property Escapes (all modern browsers, no IE)\n  return (text.match(\u002F\\p{L}+\u002Fgu) || []).length;\n}\n",[15,1895,1896,1901,1906,1911,1916,1930,1964,1968,2002,2047,2060,2091,2106,2111,2120,2124,2128,2133,2169],{"__ignoreMap":125},[129,1897,1898],{"class":131,"line":132},[129,1899,1900],{"class":223},"\u002F**\n",[129,1902,1903],{"class":131,"line":157},[129,1904,1905],{"class":223}," * Count words in any language using Intl.Segmenter.\n",[129,1907,1908],{"class":131,"line":192},[129,1909,1910],{"class":223}," * Falls back to Unicode regex for environments without Segmenter support.\n",[129,1912,1913],{"class":131,"line":426},[129,1914,1915],{"class":223}," *\u002F\n",[129,1917,1918,1920,1922,1924,1926,1928],{"class":131,"line":825},[129,1919,136],{"class":135},[129,1921,140],{"class":139},[129,1923,144],{"class":143},[129,1925,148],{"class":147},[129,1927,151],{"class":143},[129,1929,154],{"class":143},[129,1931,1932,1935,1937,1940,1942,1945,1948,1950,1952,1954,1957,1960,1962],{"class":131,"line":1008},[129,1933,1934],{"class":160},"  if",[129,1936,57],{"class":173},[129,1938,1939],{"class":143},"!",[129,1941,148],{"class":164},[129,1943,1944],{"class":143}," ||",[129,1946,1947],{"class":143}," !",[129,1949,148],{"class":164},[129,1951,25],{"class":143},[129,1953,309],{"class":139},[129,1955,1956],{"class":173},"()) ",[129,1958,1959],{"class":160},"return",[129,1961,948],{"class":528},[129,1963,189],{"class":143},[129,1965,1966],{"class":131,"line":1014},[129,1967,1671],{"emptyLinePlaceholder":1670},[129,1969,1970,1972,1974,1977,1979,1982,1984,1987,1989,1992,1994,1996,1998,2000],{"class":131,"line":1023},[129,1971,1934],{"class":160},[129,1973,57],{"class":173},[129,1975,1976],{"class":143},"typeof",[129,1978,895],{"class":164},[129,1980,1981],{"class":143}," !==",[129,1983,179],{"class":143},[129,1985,1986],{"class":215},"undefined",[129,1988,176],{"class":143},[129,1990,1991],{"class":143}," &&",[129,1993,895],{"class":164},[129,1995,25],{"class":143},[129,1997,900],{"class":164},[129,1999,532],{"class":173},[129,2001,987],{"class":143},[129,2003,2004,2007,2009,2011,2013,2015,2017,2019,2021,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045],{"class":131,"line":1551},[129,2005,2006],{"class":135},"    const",[129,2008,886],{"class":164},[129,2010,889],{"class":143},[129,2012,892],{"class":143},[129,2014,895],{"class":164},[129,2016,25],{"class":143},[129,2018,900],{"class":139},[129,2020,144],{"class":173},[129,2022,176],{"class":143},[129,2024,907],{"class":215},[129,2026,176],{"class":143},[129,2028,912],{"class":143},[129,2030,915],{"class":143},[129,2032,918],{"class":173},[129,2034,921],{"class":143},[129,2036,179],{"class":143},[129,2038,926],{"class":215},[129,2040,176],{"class":143},[129,2042,931],{"class":143},[129,2044,151],{"class":173},[129,2046,189],{"class":143},[129,2048,2049,2052,2054,2056,2058],{"class":131,"line":1715},[129,2050,2051],{"class":135},"    let",[129,2053,943],{"class":164},[129,2055,889],{"class":143},[129,2057,948],{"class":528},[129,2059,189],{"class":143},[129,2061,2062,2065,2067,2069,2071,2073,2075,2077,2079,2081,2083,2085,2087,2089],{"class":131,"line":1721},[129,2063,2064],{"class":160},"    for",[129,2066,57],{"class":173},[129,2068,960],{"class":135},[129,2070,915],{"class":143},[129,2072,965],{"class":164},[129,2074,931],{"class":143},[129,2076,970],{"class":143},[129,2078,886],{"class":164},[129,2080,25],{"class":143},[129,2082,977],{"class":139},[129,2084,144],{"class":173},[129,2086,148],{"class":164},[129,2088,984],{"class":173},[129,2090,987],{"class":143},[129,2092,2093,2096,2098,2100,2102,2104],{"class":131,"line":1734},[129,2094,2095],{"class":160},"      if",[129,2097,57],{"class":173},[129,2099,997],{"class":164},[129,2101,532],{"class":173},[129,2103,1002],{"class":164},[129,2105,1005],{"class":143},[129,2107,2108],{"class":131,"line":1739},[129,2109,2110],{"class":143},"    }\n",[129,2112,2113,2116,2118],{"class":131,"line":1745},[129,2114,2115],{"class":160},"    return",[129,2117,943],{"class":164},[129,2119,189],{"class":143},[129,2121,2122],{"class":131,"line":1758},[129,2123,1011],{"class":143},[129,2125,2126],{"class":131,"line":1764},[129,2127,1671],{"emptyLinePlaceholder":1670},[129,2129,2130],{"class":131,"line":1769},[129,2131,2132],{"class":223},"  \u002F\u002F Fallback: Unicode Property Escapes (all modern browsers, no IE)\n",[129,2134,2135,2137,2139,2141,2143,2145,2147,2149,2151,2153,2155,2157,2159,2161,2163,2165,2167],{"class":131,"line":1775},[129,2136,161],{"class":160},[129,2138,57],{"class":173},[129,2140,148],{"class":164},[129,2142,25],{"class":143},[129,2144,508],{"class":139},[129,2146,144],{"class":173},[129,2148,321],{"class":143},[129,2150,704],{"class":164},[129,2152,707],{"class":215},[129,2154,327],{"class":143},[129,2156,712],{"class":528},[129,2158,532],{"class":173},[129,2160,535],{"class":143},[129,2162,538],{"class":173},[129,2164,25],{"class":143},[129,2166,186],{"class":164},[129,2168,189],{"class":143},[129,2170,2171],{"class":131,"line":1788},[129,2172,195],{"class":143},[12,2174,2175,2176,2178,2179,2181],{},"Зверни увагу на дві речі. По-перше, ранній return на порожньому вході чи лише з пробілів — ",[15,2177,24],{}," на порожньому рядку повертає нуль сегментів, але гард тут явний. По-друге, перевірка наявності фічі ",[15,2180,24],{}," замість try\u002Fcatch — чистіше й дешевше.",[27,2183],{},[30,2185,2187],{"id":2186},"faq","FAQ",[112,2189,2191],{"id":2190},"який-найточніший-спосіб-рахувати-слова-в-javascript","Який найточніший спосіб рахувати слова в JavaScript?",[12,2193,2194,1850,2196,2199,2200,2202,2203,2205],{},[15,2195,24],{},[15,2197,2198],{},"granularity: 'word'"," — найточніший. Це стандартний API W3C, вбудований у V8, який коректно опрацьовує CJK, арабську, тайську (без меж за пробілами), кластери емодзі, слова через дефіс і скорочення. Для більшості суто англійських випадків ",[15,2201,1306],{}," із прапором ",[15,2204,737],{}," — надійна, простіша альтернатива.",[112,2207,2209,2210,2212],{"id":2208},"чому-textsplit-length-повертає-неправильну-лічбу","Чому ",[15,2211,17],{}," повертає неправильну лічбу?",[12,2214,2215,2216,453,2219,2222,2223,2225,2226,2229,2230,25],{},"Три причини. По-перше, він рахує порожні рядки, коли в тексті є кілька пробілів поспіль — ",[15,2217,2218],{},"'hello  world'.split(' ')",[15,2220,2221],{},"['hello', '', 'world']",", довжина 3, а не 2. По-друге, він не бачить табуляцій (",[15,2224,238],{},"), переносів рядка (",[15,2227,2228],{},"\\n",") і нерозривних пробілів (U+00A0). По-третє, він рахує пробіли на початку\u002Fв кінці як фантомні слова, поки ти не зробиш ",[15,2231,361],{},[112,2233,2235,2236,2238],{"id":2234},"чи-працює-bwbg-для-неанглійського-тексту","Чи працює ",[15,2237,1287],{}," для неанглійського тексту?",[12,2240,2241,2242,559,2244,2246,2247,2202,2249,25],{},"Ні. ",[15,2243,518],{},[15,2245,562],{},". Кожен кириличний, арабський, грецький, єврейський, корейський, китайський і японський символ дає нуль збігів. Якщо ти будуєш бодай щось для неамериканської аудиторії, ця регулярка мовчки видає неправильну лічбу. Натомість використовуй ",[15,2248,1306],{},[15,2250,737],{},[112,2252,2254],{"id":2253},"що-таке-intlsegmenter-і-чи-безпечно-його-використовувати-в-проді","Що таке Intl.Segmenter і чи безпечно його використовувати в проді?",[12,2256,2257,2259],{},[15,2258,24],{}," — це API інтернаціоналізації W3C, вбудований у V8 (Chrome\u002FNode.js), SpiderMonkey (Firefox) і JavaScriptCore (Safari). Він досяг статусу Baseline 2023 — усі три головні браузерні рушії його підтримують. Для Node.js доступний починаючи з v16.0.0. Можеш використовувати без поліфілу в будь-якому сучасному середовищі.",[112,2261,2263],{"id":2262},"як-рахувати-слова-в-nuxt-чи-react-застосунку-з-великими-текстами","Як рахувати слова в Nuxt чи React застосунку з великими текстами?",[12,2265,2266,2267,2269],{},"Для текстів до ~50 000 слів будь-який метод працює досить швидко на головному потоці. Для більших входів — рукописів, вставлених книжок, масової обробки — винеси у Web Worker, щоб не фризити UI. Передавай сирий рядок через ",[15,2268,1386],{}," і запускай сегментатор у контексті воркера.",[112,2271,2273],{"id":2272},"скорочення-рахуються-як-одне-слово-чи-два","Скорочення рахуються як одне слово чи два?",[12,2275,2276,2277,97,2279,97,2282,2285,2286,1850,2288,2290,2291,2293],{},"В англійській прозі скорочення (",[15,2278,96],{},[15,2280,2281],{},"it's",[15,2283,2284],{},"you're",") мають рахуватися як одне слово — це збігається з тим, як рахують редактори, вчителі й видавці. ",[15,2287,24],{},[15,2289,2198],{}," коректно повертає ",[15,2292,96],{}," як один сегмент-слово.",[112,2295,2297],{"id":2296},"чому-мій-лічильник-не-збігається-з-google-docs-чи-microsoft-word","Чому мій лічильник не збігається з Google Docs чи Microsoft Word?",[12,2299,2300,2301,1850,2303,2305],{},"Google Docs і Microsoft Word використовують пропрієтарні алгоритми токенізації, що публічно не задокументовані. Google Docs зазвичай виключає виноски й рахує складні слова через дефіс як одне слово. Word за замовчуванням враховує виноски й може ділити слова через дефіс інакше залежно від встановленого мовного пакета. ",[15,2302,24],{},[15,2304,997],{}," дає найближче наближення до галузевого редакторського підрахунку — і, на відміну від обох платформ, його поведінка повністю передбачувана, бо специфікація W3C публічна.",[112,2307,2309],{"id":2308},"як-рахувати-слова-не-рахуючи-числа-чи-токени-лише-з-пунктуації","Як рахувати слова, не рахуючи числа чи токени лише з пунктуації?",[12,2311,2312,2313,2315,2316,2319,2320,2322],{},"З ",[15,2314,24],{}," перевіряй ",[15,2317,2318],{},"segment.isWordLike === true"," — API автоматично позначає пунктуацію й пробіли як несловесні сегменти. З ",[15,2321,1306],{}," за визначенням матчаться лише послідовності літер, тож числа й окрема пунктуація виключаються.",[27,2324],{},[12,2326,2327,2328,2332],{},"Глибший погляд на те, як патерни регулярок поводяться на реальному тексті, — у ",[1187,2329,2331],{"href":2330},"\u002Fblog\u002Fregex-find-replace-guide","Гайді з регулярних виразів: пошук і заміна",", що покриває групи захоплення, квантифікатори й lookahead — усе, що треба, щоб будувати надійні патерни обробки тексту поза підрахунком слів.",[2334,2335,2336],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":125,"searchDepth":157,"depth":157,"links":2338},[2339,2340,2352,2353,2354,2355,2356,2357,2358],{"id":32,"depth":157,"text":33},{"id":109,"depth":157,"text":110,"children":2341},[2342,2344,2346,2348,2350],{"id":114,"depth":192,"text":2343},"Метод 1: text.split(' ').length — наївний split",{"id":274,"depth":192,"text":2345},"Метод 2: text.trim().split(\u002F\\s+\u002F).filter(Boolean).length — залатаний split",{"id":471,"depth":192,"text":2347},"Метод 3: (text.match(\u002F\\b\\w+\\b\u002Fg) || []).length — класична регулярка",{"id":661,"depth":192,"text":2349},"Метод 4: (text.match(\u002F\\p{L}+\u002Fgu) || []).length — Unicode Property Escapes",{"id":855,"depth":192,"text":2351},"Метод 5: Intl.Segmenter — правильна відповідь",{"id":1203,"depth":157,"text":1204},{"id":1354,"depth":157,"text":1355},{"id":1568,"depth":157,"text":1569},{"id":1635,"depth":157,"text":1636},{"id":1841,"depth":157,"text":1842},{"id":1886,"depth":157,"text":1887},{"id":2186,"depth":157,"text":2187,"children":2359},[2360,2361,2363,2365,2366,2367,2368,2369],{"id":2190,"depth":192,"text":2191},{"id":2208,"depth":192,"text":2362},"Чому text.split(' ').length повертає неправильну лічбу?",{"id":2234,"depth":192,"text":2364},"Чи працює \u002F\\b\\w+\\b\u002Fg для неанглійського тексту?",{"id":2253,"depth":192,"text":2254},{"id":2262,"depth":192,"text":2263},{"id":2272,"depth":192,"text":2273},{"id":2296,"depth":192,"text":2297},{"id":2308,"depth":192,"text":2309},"Dev Tools","Від наївного split() до Intl.Segmenter — 5 методів підрахунку слів у JavaScript за точністю, підтримкою Unicode та крайовими випадками. Який везти в прод.","md",[2374,2376,2378,2380,2382,2384,2386],{"question":2191,"answer":2375},"Intl.Segmenter з granularity: 'word' — найточніший метод. Це стандартний API W3C, вбудований у V8, який коректно опрацьовує CJK, арабську, тайську (де немає меж за пробілами), а також кластери емодзі, слова через дефіс і скорочення. Для більшості суто англійських сценаріїв \u002F\\p{L}+\u002Fgu з прапором u — надійна й простіша альтернатива.",{"question":2362,"answer":2377},"Три причини. По-перше, він рахує порожні рядки, коли в тексті є кілька пробілів поспіль — 'hello  world'.split(' ') повертає ['hello', '', 'world'], довжина 3, а не 2. По-друге, він не бачить табуляції (\\t), переносів рядка (\\n) і нерозривних пробілів (U+00A0) як роздільників слів. По-третє, він залишає пунктуацію, причеплену до слів, частиною токена-слова, що спотворює метрики унікальних слів.",{"question":2364,"answer":2379},"Ні. У JavaScript \\w — це скорочення для [A-Za-z0-9_]; воно матчить лише ASCII-літери, цифри й підкреслення. Кожен кириличний, арабський, грецький, єврейський, корейський, китайський і японський символ дає нуль збігів. Якщо ти будуєш бодай щось для неамериканської аудиторії, ця регулярка мовчки видає неправильну лічбу. Натомість використовуй \u002F\\p{L}+\u002Fgu з прапором u.",{"question":2254,"answer":2381},"Intl.Segmenter — це API інтернаціоналізації W3C, вбудований у V8 (Chrome\u002FNode.js), SpiderMonkey (Firefox) і JavaScriptCore (Safari). Він досяг статусу Baseline 2023 — тобто його підтримують усі три головні браузерні рушії. Для Node.js він доступний починаючи з v16.0.0. Можеш використовувати його без поліфілу в будь-якому сучасному середовищі.",{"question":2263,"answer":2383},"Для текстів до ~50 000 слів будь-який із методів працює досить швидко на головному потоці. Для більших вхідних даних — рукописів, вставлених книжок, масової обробки — винеси це у Web Worker, щоб не фризити UI. API Intl.Segmenter не можна передати у воркер напряму, тож передавай сирий рядок через postMessage і запускай сегментатор у контексті воркера.",{"question":2273,"answer":2385},"В англійській прозі скорочення (don't, it's, you're) мають рахуватися як одне слово — це збігається з тим, як рахують редактори, вчителі й видавці. Методи 1–3 (на основі split і \\w-регулярки) роблять це правильно випадково, бо апостроф розділяє їх на два токени лише якщо трактується як пробіл, а він ним не є. Intl.Segmenter з granularity: 'word' коректно повертає don't як один сегмент-слово.",{"question":2309,"answer":2387},"Відфільтруй результати свого сегментатора чи регулярки так, щоб лишилися тільки сегменти з принаймні однією літерою Unicode. З Intl.Segmenter перевіряй segment.isWordLike === true — API автоматично позначає пунктуацію й пробіли як несловесні сегменти. З \u002F\\p{L}+\u002Fgu за визначенням матчаться лише послідовності літер, тож числа й окрема пунктуація виключаються.","\u002Farticles\u002Fhow-to-count-words-javascript\u002Fhero.webp",{},"\u002Fuk\u002Fhow-to-count-words-javascript","2026-05-12",{"title":5,"description":2371},"uk\u002Fhow-to-count-words-javascript",[2395,2396,2397,2398,2399],"javascript","кількість слів","intl segmenter","unicode","обробка тексту","X_Htc5QTN23c0sLZq-yY6fbrZuSJWKuAUm83mCH9qZ0",1782712870691]