Karakter apa yang dikelompokkan dengan Array.from?

38

Saya telah bermain-main dengan JS dan tidak tahu bagaimana JS memutuskan elemen mana yang akan ditambahkan ke array yang dibuat saat menggunakan Array.from(). Sebagai contoh, emoji 👍 berikut memiliki nilai length2, karena terdiri dari dua titik kode, tetapi, Array.from()memperlakukan dua titik kode ini sebagai satu, memberikan array dengan satu elemen:

const emoji = '👍';
console.log(Array.from(emoji)); // Output: ["👍"]

Namun, beberapa karakter lain juga memiliki dua titik kode seperti karakter ini षि(juga memiliki a .length2). Namun, Array.fromjangan "mengelompokkan" karakter ini dan malah menghasilkan dua elemen:

const str = 'षि';
console.log(Array.from(str)); // Output: ["ष", "ि"]

Pertanyaan saya adalah: Apa yang menentukan apakah karakter dipecah (seperti pada contoh dua) atau diperlakukan sebagai satu elemen tunggal (seperti pada contoh satu) ketika karakter terdiri dari dua titik kode?

Shnick
sumber
5
Lihatlah pasangan pengganti UTF-16 ...
Jonas Wilms
1
Saya memiliki kekhawatiran tentang polyfill MDN dari Array.from, yang memiliki perilaku yang berbeda: -s
Ele
1
@Ele itu hanya menganggap objek dengan length. Iterator atau bahkan Settidak bekerja dengan itu
adiga

Jawaban:

26

Array.frompertama-tama mencoba untuk memanggil iterator dari argumen jika ada, dan string memang memiliki iterator, jadi ia memanggil String.prototype[Symbol.iterator], jadi mari kita mencari cara kerja metode prototipe. Ini dijelaskan dalam spesifikasi di sini :

  1. Biarkan O menjadi? RequireObjectCoercible (nilai ini).
  2. Biarkan S? ToString (O).
  3. Kembali CreateStringIterator (S).

Melihat ke atas pada CreateStringIteratorakhirnya membawa Anda ke 21.1.5.2.1 %StringIteratorPrototype%.next ( ), yang artinya:

  1. Biarkan cp! CodePointAt (s, posisi).
  2. Biarkan resultString menjadi nilai String yang berisi cp. [[CodeUnitCount]] unit kode berturut-turut dari awal dengan unit kode pada posisi indeks.
  3. Set O. [[StringNextIndex]] ke posisi + cp. [[CodeUnitCount]].
  4. Kembalikan CreateIterResultObject (resultString, false).

Inilah CodeUnitCountyang Anda minati. Nomor ini berasal dari CodePointAt :

  1. Biarkan dulu menjadi unit kode pada posisi indeks dalam string.
  2. Biarkan cp menjadi titik kode yang nilai numeriknya lebih dulu.
  3. Jika pertama bukan pengganti terkemuka atau pengganti tambahan, maka

    Sebuah. Kembalikan Catatan { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }.

  4. Jika pertama adalah trailing surrogate atau posisi +1 = ukuran, maka

    Kembalikan Catatan { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  5. Biarkan kedua menjadi unit kode pada posisi indeks + 1 dalam string.

  6. Jika yang kedua bukan pengganti tambahan, maka

    Sebuah. Kembalikan Catatan { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.

  7. Setel ke! UTF16DecodeSurrogatePair (pertama, kedua).

  8. Kembalikan Catatan { [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }.

Jadi, ketika iterasi dengan string Array.from, ia mengembalikan CodeUnitCount 2 hanya ketika karakter yang dimaksud adalah awal dari pasangan pengganti. Karakter yang ditafsirkan sebagai pasangan pengganti dijelaskan di sini :

Operasi tersebut menerapkan perlakuan khusus untuk setiap unit kode dengan nilai numerik dalam rentang inklusif 0xD800 hingga 0xDBFF (didefinisikan oleh Standar Unicode sebagai pengganti utama , atau lebih resmi sebagai unit kode pengganti tinggi) dan setiap unit kode dengan nilai numerik dalam rentang inklusif 0xDC00 hingga 0xDFFF (didefinisikan sebagai pengganti trailing, atau lebih formal sebagai unit kode pengganti rendah) menggunakan aturan berikut ..:

षि bukan pasangan pengganti:

console.log('षि'.charCodeAt()); // First character code: 2359, or 0x937
console.log('षि'.charCodeAt(1)); // Second character code: 2367, or 0x93F

Tapi 👍karakternya adalah:

console.log('👍'.charCodeAt()); // 55357, or 0xD83D
console.log('👍'.charCodeAt(1)); // 56397, or 0xDC4D

Kode karakter pertama '👍'adalah, dalam hex, D83D, yang berada dalam jangkauan 0xD800 to 0xDBFFsurrogate terkemuka. Sebaliknya, kode karakter pertama 'षि'jauh lebih rendah, dan tidak. Jadi 'षि'mendapat terpisah, tetapi '👍'tidak.

षिterdiri dari dua karakter yang terpisah: , Devanagari Surat Ssa , dan ि, Devanagari Vokal Sign saya . Ketika bersebelahan dalam urutan ini, mereka digabungkan secara grafis menjadi satu karakter secara visual, meskipun terdiri dari dua karakter yang terpisah.

Sebaliknya, kode karakter 👍 hanya masuk akal ketika bersama sebagai mesin terbang tunggal. Jika Anda mencoba menggunakan string dengan salah satu titik kode tanpa yang lain, Anda akan mendapatkan simbol omong kosong:

console.log('👍'[0]);
console.log('👍'[1]);

Performa Tertentu
sumber
10
Saya pikir, walaupun sebagian besar benar, bermanfaat, dan dengan kutipan yang diberikan dengan hati-hati, jawaban ini gagal menjelaskan dengan jelas perbedaan utama antara dua kasus: dari sudut pandang Unicode, षिsebenarnya adalah dua karakter dengan titik kode berbeda digabungkan untuk membentuk satu mesin terbang (satu karakter abstrak , sebagaimana dipahami oleh manusia). Ini berbeda dengan 👍emoji, yang merupakan karakter lengkap di dalam dan dari dirinya sendiri, meskipun titik kodenya cukup tinggi sehingga harus dipisah menjadi pasangan pengganti. Saya percaya klarifikasi yang dapat membantu ini (jika tidak berharga) menjawab banyak.
badak
Secara khusus, konsonan ष (ṣ) dan vokal ि (i) secara grafis bergabung ke dalam suku kata षि (ṣi)
Amadan
@CertainPerformance Hanya ada satu titik kode di "👍". Ini menunjukkan terminologi dalam jawaban ini mungkin salah.
Ben Aston
13

UTF-16 (pengkodean yang digunakan untuk string dalam js) menggunakan unit 16bit. Jadi setiap unicode yang dapat direpresentasikan menggunakan 15 bit direpresentasikan sebagai satu titik kode, yang lainnya sebagai dua, yang dikenal sebagai pasangan pengganti . The iterator dari string iterates atas poin kode.

UTF-16 di Wikipedia

Jonas Wilms
sumber
8

Ini semua tentang kode di belakang karakter. Beberapa dikodekan dalam dua byte (UTF-16) dan ditafsirkan Array.fromsebagai dua karakter. Harus memeriksa daftar karakter:

http://www.fileformat.info/info/charset/UTF-8/list.htm

http://www.fileformat.info/info/charset/UTF-16/list.htm

function displayHexUnicode(s) {
  console.log(s.split("").reduce((hex,c)=>hex+=c.charCodeAt(0).toString(16).padStart(4,"0"),""));
}

displayHexUnicode('षि');

console.log(Array.from('षि').forEach(x => displayHexUnicode(x)));


function displayHexUnicode(s) {
  console.log(s.split("").reduce((hex,c)=>hex+=c.charCodeAt(0).toString(16).padStart(4,"0"),""));
}

displayHexUnicode('👍');

console.log(Array.from('👍').forEach(x => displayHexUnicode(x)));


Untuk fungsi yang menampilkan kode hex:

Javascript: String Unicode ke hex

Grégory NEUT
sumber