whatwg - Longitud de cadena en bytes en JavaScript
whatwg español (11)
En mi código JavaScript, necesito redactar un mensaje al servidor en este formato:
<size in bytes>CRLF
<data>CRLF
Ejemplo:
3
foo
Los datos pueden contener caracteres Unicode. Necesito enviarlos como UTF-8.
Estoy buscando la forma más cruzada de navegador para calcular la longitud de la cadena en bytes en JavaScript.
Intenté esto para componer mi carga útil:
return unescape(encodeURIComponent(str)).length + "/n" + str + "/n"
Pero no me da resultados precisos para los navegadores más antiguos (o, tal vez, las cadenas en esos navegadores en UTF-16?).
¿Alguna pista?
Actualizar:
Ejemplo: longitud en bytes de la cadena ЭЭХ! Naïve?
ЭЭХ! Naïve?
en UTF-8 es de 15 bytes, pero algunos navegadores reportan 23 bytes en su lugar.
Aquí hay un método independiente y eficiente para contar los bytes UTF-8 de una cadena.
//count UTF-8 bytes of a string
function byteLengthOf(s){
//assuming the String is UCS-2(aka UTF-16) encoded
var n=0;
for(var i=0,l=s.length; i<l; i++){
var hi=s.charCodeAt(i);
if(hi<0x0080){ //[0x0000, 0x007F]
n+=1;
}else if(hi<0x0800){ //[0x0080, 0x07FF]
n+=2;
}else if(hi<0xD800){ //[0x0800, 0xD7FF]
n+=3;
}else if(hi<0xDC00){ //[0xD800, 0xDBFF]
var lo=s.charCodeAt(++i);
if(i<l&&lo>=0xDC00&&lo<=0xDFFF){ //followed by [0xDC00, 0xDFFF]
n+=4;
}else{
throw new Error("UCS-2 String malformed");
}
}else if(hi<0xE000){ //[0xDC00, 0xDFFF]
throw new Error("UCS-2 String malformed");
}else{ //[0xE000, 0xFFFF]
n+=3;
}
}
return n;
}
var s="/u0000/u007F/u07FF/uD7FF/uDBFF/uDFFF/uFFFF";
console.log("expect byteLengthOf(s) to be 14, actually it is %s.",byteLengthOf(s));
Tenga en cuenta que el método puede arrojar un error si una cadena de entrada es UCS-2 con formato incorrecto
Aquí hay una versión mucho más rápida, que no usa expresiones regulares, ni encodeURIComponent:
function byteLength(str) {
// returns the byte length of an utf8 string
var s = str.length;
for (var i=str.length-1; i>=0; i--) {
var code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) s++;
else if (code > 0x7ff && code <= 0xffff) s+=2;
if (code >= 0xDC00 && code <= 0xDFFF) i--; //trail surrogate
}
return s;
}
Aquí hay una comparación de rendimiento .
Simplemente calcula la longitud en UTF8 de cada punto de código Unicode devuelto por charCodeAt (basado en las descripciones de wikipedia de UTF8 y caracteres suplentes UTF16).
Sigue RFC3629 (donde los caracteres UTF-8 tienen como máximo 4 bytes de longitud).
En realidad, descubrí lo que está mal. Para que el código funcione, la página <head>
debe tener esta etiqueta:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
O, como se sugiere en los comentarios, si el servidor envía Content-Encoding
encabezado HTTP Content-Encoding
, debería funcionar también.
Entonces los resultados de diferentes navegadores son consistentes.
Aquí hay un ejemplo:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>mini string length test</title>
</head>
<body>
<script type="text/javascript">
document.write(''<div style="font-size:100px">''
+ (unescape(encodeURIComponent("ЭЭХ! Naïve?")).length) + ''</div>''
);
</script>
</body>
</html>
Nota: sospecho que al especificar cualquier codificación (precisa) se solucionaría el problema de codificación. Es solo una coincidencia que necesito UTF-8.
Esta función devolverá el tamaño de bytes de cualquier cadena UTF-8 que le pase.
function byteCount(s) {
return encodeURI(s).split(/%..|./).length - 1;
}
Esto funcionaría para los caracteres BMP y SIP / SMP.
String.prototype.lengthInUtf8 = function() {
var asciiLength = this.match(/[/u0000-/u007f]/g) ? this.match(/[/u0000-/u007f]/g).length : 0;
var multiByteLength = encodeURI(this.replace(/[/u0000-/u007f]/g)).match(/%/g) ? encodeURI(this.replace(/[/u0000-/u007f]/g, '''')).match(/%/g).length : 0;
return asciiLength + multiByteLength;
}
''test''.lengthInUtf8();
// returns 4
''/u{2f894}''.lengthInUtf8();
// returns 4
''سلام علیکم''.lengthInUtf8();
// returns 19, each Arabic/Persian alphabet character takes 2 bytes.
''你好,JavaScript 世界''.lengthInUtf8();
// returns 26, each Chinese character/punctuation takes 3 bytes.
Me tomó un tiempo encontrar una solución para React Native, así que lo pondré aquí:
Primero instale el paquete de buffer
:
npm install --save buffer
A continuación, use el método de nodo:
const { Buffer } = require(''buffer'');
const length = Buffer.byteLength(string, ''utf-8'');
No hay forma de hacerlo en JavaScript de forma nativa.
Si conoce la codificación de caracteres, puede calcularla usted mismo.
encodeURIComponent
asume UTF-8 como la codificación de caracteres, por lo que si necesita esa codificación, puede hacer,
function lengthInUtf8Bytes(str) {
// Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
var m = encodeURIComponent(str).match(/%[89ABab]/g);
return str.length + (m ? m.length : 0);
}
Esto debería funcionar debido a la forma en que UTF-8 codifica secuencias de múltiples bytes. El primer byte codificado siempre comienza con un bit alto de cero para una secuencia de un solo byte, o un byte cuyo primer dígito hexadecimal es C, D, E o F. El segundo byte y los siguientes son aquellos cuyos primeros dos bits son 10 Estos son los bytes adicionales que desea contar en UTF-8.
La tabla en wikipedia hace más claro
Bits Last code point Byte 1 Byte 2 Byte 3
7 U+007F 0xxxxxxx
11 U+07FF 110xxxxx 10xxxxxx
16 U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
...
Si, en cambio, necesitas entender la codificación de la página, puedes usar este truco:
function lengthInPageEncoding(s) {
var a = document.createElement(''A'');
a.href = ''#'' + s;
var sEncoded = a.href;
sEncoded = sEncoded.substring(sEncoded.indexOf(''#'') + 1);
var m = sEncoded.match(/%[0-9a-f]{2}/g);
return sEncoded.length - (m ? m.length * 2 : 0);
}
Otro enfoque muy simple que usa Buffer
(solo para NodeJS):
Buffer.from(string).length
Para la codificación UTF-8 simple, con compatibilidad ligeramente mejor que TextEncoder
, Blob hace el truco. Sin embargo, no funcionará en navegadores muy antiguos.
new Blob(["😀"]).size; // -> 4
Pasaron los años y hoy en día puedes hacerlo de forma nativa
(new TextEncoder(''utf-8'').encode(''foo'')).length
Tenga en cuenta que aún no es compatible con IE (o Edge) (puede usar un polyfill para eso).
Puedes intentar esto:
function getLengthInBytes(str) {
var b = str.match(/[^/x00-/xff]/g);
return (str.length + (!b ? 0: b.length));
}
Esto funciona para mi.