关于 JavaScript 中的字符串 part2
首先
我整理了一些上次泄露的字符串方法和JSON
。
这是参考资料。
(《信串展》1至4)
方法
String.prototype.padStart()
如果目标字符串未达到指定长度,则将另一个字符串添加到指定长度。
// Syntax
str.padStart(targetLength[, padString])
以下是来自 MDN 的示例。
// console.log('123'.padStart(5, '0')); // '00123'
const fullNumber = '012345678910';
const lastFourDigits = fullNumber.slice(-4);
let maskedNumber = lastFourDigits.padStart(fullNumber.length, '*');
console.log(maskedNumber); // ********8910
感觉有点欠缺,就用正则表达式来练习。
function maskedNum(str, unmaskedLength) {
if (/^\d+$/.test(str)) {
return '*'.repeat(str.length - unmaskedLength) + str.slice(-(unmaskedLength));
}
return 'Invalid input';
}
console.log(maskedNum('012345678910', 4)) // ********8910
console.log(maskedNum('a12345678910', 4)) // Invalid input
String.prototype.padEnd()
它的工作原理与padStart()
几乎相同。从末尾追加字符串,直到长度足够。
// Syntax
str.padEnd(targetLength[, padString])
......但有些东西缺乏灵活性,它变成了另一种练习。
// console.log('abcdef'.padEnd(10, '.')); // abcdef....
function bannerString(str, targetLength) {
if ((str.length - 1) <= (targetLength - 3)) {
return str + '.'.repeat(3);
}
return str.slice(0, targetLength - 3) + '.'.repeat(3);
}
let input = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
console.log(bannerString(input, 30));
// Lorem ipsum dolor sit amet,...
console.log(bannerString(input, 60));
// Lorem ipsum dolor sit amet, consectetur adipiscing elit....
console.log(bannerString(input, 90));
// Lorem ipsum dolor sit amet, consectetur adipiscing elit....
String.prototype.trimStart() & String.prototype.trimEnd() & String.prototype.trim()
删除开头/结尾/两端的换行符和空格。
// Syntax
str.trimStart() // start from left
str.trimEnd() // end up right
str.trim() // trim head and tail
let str = ' Lorem 123 ';
console.log(str.length);
// 16
console.log(str.trimStart(), str.trimStart().length);
// Lorem 123 11
console.log(str.trimEnd(), str.trimEnd().length);
// Lorem 123 15
console.log(str.trim(), str.trim().length);
// Lorem 123 10
let str = '\t\f\r\n\v\u0020\u00a0\u3000\u2028\u2029Lorem\t\f\r\n\v\u0020\u00a0\u3000\u2028\u2029';
console.log(str.length);
// 25
console.log(str.trimStart().length);
// 15
console.log(str.trimEnd().length);
// 15
console.log(str.trim().length);
// 5
我在上一篇文章中总结了一点关于换行符和空白字符的内容。
迭代器
正常的 for 循环以Code Unit
为单位处理字符串,因此它无法正确处理两个Code Unit
的组合,例如代理对或0xffff
之外的Unicode。
在这里,我希望for...of
和Array.from()
以Code Point
为单位识别字符串。 (如果我找到其他方法,我会再次更新。)
对于...的
let str = '?????';
for (let item of str) {
console.log(item.codePointAt(0));
}
// 127822
// 127815
// 127822
// 129365
// 127826
数组.from()
function countCodePoints(str, target) {
let count = 0;
Array.from(str, (x) => {
if (x === target) count += 1;
})
return count;
}
console.log(countCodePoints(str, '?')); // 2
JSON
用于数据交换的通用格式对象。 (这是我的解释。现实更复杂。)
JSON
字符串被称为对象、数组、数字、字符串、布尔值和 null
被序列化/编组的对象。
方法
JSON.stringify()
将参数转换为 JSON
格式化文本(字符串),
JSON.parse()
从 JSON
文本转换为 JavaScript 中的相应值。
// Syntax
JSON.stringify(value[, replacer[, space]])
// note
value: Object, Array, Number, String, boolean, null
replacer: Array of properties / function replacer(key, value)
space: space characters for formatting (space <= 10)
JSON.parse(text, reviver)
// note
text: JSON-string
reviver: function replacer(key, value)
NaN
、Infinity
等不能为字符串的数字被强制转换为null
。- 跳过函数属性、符号键和值、包含
undefined
的属性。
JSON.stringify() - 值
let jsonArr = JSON.stringify(['1', 2, 'true', false, null, NaN, Infinity]);
console.log(jsonArr);
// ["1",2,"true",false,null,null,null]
console.log(JSON.parse(jsonArr));
// [
// '1', 2,
// 'true', false,
// null, null,
// null
// ]
let jsonObj = JSON.stringify({
a: '1',
b: 2,
c: 'true',
d: false,
e: null,
f: NaN,
g: Infinity,
h: {
a: '5'
},
i: [4, 5, 6],
myMethod() {
console.log('myMethod');
},
[Symbol('id')]: 123,
j: Symbol('foo'),
k: undefined
})
console.log(jsonObj);
// {"a":"1","b":2,"c":"true","d":false,"e":null,"f":null,"g":null,"h":{"a":"5"},"i":[4, 5, 6]}
console.log(JSON.parse(jsonObj));
/*
{
a: '1',
b: 2,
c: 'true',
d: false,
e: null,
f: null,
g: null,
h: { a: '5' },
i: [ 4, 5, 6 ]
}
*/
即使对象是从对象包装函数返回的,JSON.stringify()
也可以转换为 JSON
字符串。 JSON
String 丢弃包装器并再次使用JSON.parse()
转换为相应的值。
// object wrapper => json => corresponding value
// console.log(typeof (new Number(5)));// object
let num = JSON.stringify(new Number(5));
console.log(num);
// 5 // string
console.log(JSON.parse(num));
// 5 // number
// console.log(typeof (new String('abc'))); // object
let str = JSON.stringify(new String('abc'));
console.log(str);
// "abc" // string with double quotes
console.log(JSON.parse(str));
// abc // string
JSON.stringify() - 替换
如果将具有循环结构/循环引用的对象原样转换为JSON
,则会发生错误。
let room = {
number: 10
}
let guests = {
title: 'Party',
participants: ['Taro', 'Jiro']
};
guests.place = room;
room.occupiedBy = guests;
console.log(guests.place, room.occupiedBy);
/*
<ref *1> {
number: 10,
occupiedBy: {
title: 'Party',
participants: [ 'Taro', 'Jiro' ],
place: [Circular *1]
}
} <ref *1> {
title: 'Party',
participants: [ 'Taro', 'Jiro' ],
place: { number: 10, occupiedBy: [Circular *1] }
}
*/
console.log(JSON.stringify(guests));
// TypeError: Converting circular structure to JSON
如果您传递一个避免循环引用JSON.stringify
的第二个参数的属性数组,JSON
将毫无问题地工作(也应用了嵌套属性)。
let room = {
number: 10
};
let guests = {
title: 'Party',
participants: [{ name: 'Taro' }, { name: 'Jiro' }],
place: room
};
room.occupiedBy = guests;
console.log(JSON.stringify(guests, ['title', 'participants']));
// {"title":"Party","participants":[{},{}]}
console.log(JSON.stringify(guests, ['title', 'participants', 'name', 'place', 'number']));
// {"title":"Party","participants":[{"name":"Taro"},{"name":"Jiro"}],"place":{"number":10}}
console.log(JSON.stringify(guests, ['title', 'number', 'name', 'place', 'participants']));
// {"title":"Party","place":{"number":10},"participants":[{"name":"Taro"},{"name":"Jiro"}]}
(第二个参数的数组中JSON
的内部顺序会根据数组而变化。)
尽管只要循环引用属性不冲突,它就可以正常工作。
console.log(JSON.stringify(guests, ['occupiedBy']));
// {}
console.log(JSON.stringify(guests, ['title', 'occupiedBy']));
// {"title":"Party"}
console.log(JSON.stringify(guests, ['place', 'occupiedBy']));
// TypeError: Converting circular structure to JSON
为了避免循环引用,让undefined
忽略JSON
。
console.log(JSON.stringify(guests, (key, value) => {
return (key === 'occupiedBy') ? undefined : value;
}));
// {"title":"Party","participants":[{"name":"Taro"},{"name":"Jiro"}],"place":{"number":10}}
我用这个例子作为参考。当我输出replacer
的第二个参数value
并仔细观察时,它按顺序访问了属性并取出了所有嵌套的部分。有了这个,我以某种方式理解了一个数组被放在第一个参数key
中,并且只取出了一个特定的属性,如果没有指定嵌套属性,它将被忽略。
JSON.stringify() - 空格
为了便于阅读,JSON.stringify()
使用指定的数字(最多 10 个)或字符串(最多 10 个长度)作为缩进的第三个参数。
let user = {
name: 'Taro',
age: 26,
roles: {
isAdmin: false,
isEditor: true
}
};
console.log(JSON.stringify(user));
// {"name":"Taro","age":26,"roles":{"isAdmin":false,"isEditor":true}}
console.log(JSON.stringify(user, null, 2));
/*
{
"name": "Taro",
"age": 26,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
toJSON()
对于对象,它的位置类似于toString()
。 JSON.stringify()
使用时调用。
let room = {
number: 23,
};
let meetup = {
title: "Conference",
room
};
console.log(JSON.stringify(room)); // {"number":23}
console.log(JSON.stringify(meetup)); // {"title":"Conference","room":{"number":23}}
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
room
};
console.log(JSON.stringify(room)); // 23
console.log(JSON.stringify(meetup)); // {"title":"Conference","room":23}
JSON.parse()-str
JSON
是一个字符串。
× '{name: "Taro"}'
○ '{"name": "Taro"}'
× '{"surname": 'Yamada'}'
○ '{"surname": "Yamada"}'
× '{'isAdmin': false}'
○ '{"isAdmin": false}'
× '{"birthday": new Date(1995, 6, 6)}'
○ '{"birthday":"1995-07-05T16:00:00.000Z"}'
× '{"friends": "[0, 1, 2, 3]"}' // value is string
○ '{"friends": [0, 1, 2, 3]}'
× '{"family": "{"father": "...", "mother": "..."}"}'
○ '{"family": {"father": "...", "mother": "..."}}'
JSON.parse() - 复活者
reviver()
是 (key, value)
一个有两个参数的函数。
reviver()
可以操作成 JSON
字符串。
let str = '{"name": "Taro", "birthday": "1995-07-05T16:00:00.000Z"}';
let person = JSON.parse(str, (key, value) => {
if (key === 'birthday') return new Date(value);
return value;
});
console.log(`${person.name}'s birthday is ${person.birthday.toDateString()}`);
// Taro's birthday is Thu Jul 06 1995
reviver()
适用于嵌套属性以及 replacer()
。
实践
我写了这个例子。
let room = {
number: 10
};
let guests = {
title: 'Party',
participants: [{ name: 'Taro' }, { name: 'Jiro' }],
place: room
};
room.occupiedBy = guests;
guests.self = guests;
console.log(JSON.stringify(guests, (key, value) => {
// console.log(key)
return (key !== '' && value === guests) ? undefined : value;
}))
// {"title":"Party","participants":[{"name":"Taro"},{"name":"Jiro"}],"place":{"number":10}}
下面是key
的输出。
title
participants
0
name
1
name
place
number
occupiedBy
self
第一个key
拉出""
({"": guests}
),以此类推直到嵌套属性。
JSON 中的换行符/终止符
我很好奇如何处理换行符和空格。
JSON
如果您尝试解析包裹在类似字符串的结构(双引号)中的换行符/空格字符,则会发生错误。
console.log(JSON.parse('"foo"')); // foo
console.log(JSON.parse('"\u0009"'));
// SyntaxError: Unexpected token in JSON at position 1
当我将其设为属性值时,它的处理没有问题。
const json = JSON.stringify({
horizontalTab: '\u0009',
verticalTab: '\u000b',
formFeed: '\u000c',
carriageReturn: '\u000d',
lineFeed: '\u000a',
whiteSpace: '\u0020',
nonBreakingSpace: '\u00a0',
ideographicSpace: '\u3000',
lineSeparator: '\u2028',
paragraphSeparator: '\u2029',
reverseSolidus: '\u005c'
});
console.log(json);
/*
{"horizontalTab":"\t","verticalTab":"\u000b","formFeed":"\f","carriageReturn":"\
r","lineFeed":"\n","whiteSpace":" ","nonBreakingSpace":" ","ideographicSpace":"
","lineSeparator":"","paragraphSeparator":"","reverseSolidus":"\\"}
*/
console.log(JSON.parse(json));
// {
// horizontalTab: '\t',
// verticalTab: '\x0B',
// formFeed: '\f',
// carriageReturn: '\r',
// lineFeed: '\n',
// whiteSpace: ' ',
// nonBreakingSpace: ' ',
// ideographicSpace: ' ',
// lineSeparator: '',
// paragraphSeparator: '',
// reverseSolidus: '\\'
// }
(从 ES2019 开始,\u2028
、\u2029
可以存储在 JSON
字符串和属性中。)
如果不在字符串的两端,换行符和空白字符都可以视为普通空格,
如果放在两端,则变为SyntaxError
。对于隐式字符串 ''
和模板文字都是如此。
let str = '"abc\u2028def\u2029ghi"';
console.log(JSON.parse(str));
// abc def ghi
str = `"abc\u2028def\u2029ghi"`
console.log(JSON.parse(str));
// abc def ghi
// newline/terminator at the head or tail
str = '"\u0009abc\u2028def\u2029ghi"';
console.log(JSON.parse(str));
// SyntaxError: Unexpected token in JSON at position 1
str = '"abc\u2028def\u2029ghi\u000b"';
console.log(JSON.parse(str));
/*
SyntaxError: Unexpected token
in JSON at position 12
*/
str = `"\u0009abc\u2028def\u2029ghi"`
console.log(JSON.parse(str));
// SyntaxError: Unexpected token in JSON at position 1
str = `"abc\u2028def\u2029ghi\u000b"`
console.log(JSON.parse(str));
/*
SyntaxError: Unexpected token
in JSON at position 12
*/
用户收到的String
可能是换行和空白字符混合的状态,所以用trim()
或replace()
正则表达式搜索,将两端的空白字符截断,再转换为JSON
我试着写如何重写。
let json = '"\u2028abc\u2028def\u2029ghi\u2029"';
// trim()
function trimmedJsonString(json) {
let str = json.slice(1, json.length - 1);
return `"${str.trim()}"`;
}
console.log(JSON.parse(trimmedJsonString(json)));
// abc def ghi
// replace()
function replacedJsonString(json) {
let str = json.slice(1, json.length - 1);
return `"${str.replace(/^\s+|\s+$/, '')}"`;
}
console.log(JSON.parse(replacedJsonString(json)));
// abc def ghi
function replacedBySpace(json) {
let str = json.slice(1, json.length - 1);
return `"${str.replace(/\s+/g, ' ')}"`;
}
console.log(JSON.parse(replacedBySpace(json)));
// abc def ghi
尽管您可能希望保留每个换行符/空格字符。
let json = '"\u2028\u2028abc\u2028def\u2029ghi\u2029\u2029"';
function escapedJsonString(json) {
let str = json.slice(1, json.length - 1);
return `"${str.
replace(/\u2028/g, '\\u2028').
replace(/\u2029/g, '\\u2029')}"
`;
}
console.log(JSON.parse(escapedJsonString(json)));
// abc def ghi
JSON中的unicode
标准JSON
数据必须以 UTF-8 编码以进行数据交换。例如U+D800
-U+DFFF
不是代理对或无效对(不存在对应字符)的单个代码点不符合 UTF-8 标准。
JavascriptJSON.stringify()
现在将它们作为字符串输出而不转义,并且不进行进一步处理。
// well-formed JSON.stringify
console.log(JSON.stringify('\ud834')); // "\ud834"
console.log(JSON.stringify('\udf06\ud834')); // "\udf06\ud834"
console.log(JSON.stringify('\ud834\udf06')); // "?"
console.log(JSON.parse(JSON.stringify('\ud834\udf06'))); // ?
console.log(JSON.stringify('\ud834') === '"\\ud834"'); // true
console.log(JSON.stringify('\udf06') === '"\\udf06"'); // true
- 代理对:
U+10000
-U+10FFFF
- 高代理:
U+D800
-U+DBFF
- 低代理:
U+DC00
-U+DFFF
let str = '\ud834\udf06\ud834abc\ud834\udf06';
console.log(JSON.stringify(str, (key, value) => {
return value.replace(/[\ud800-\udfff]/g, '')
}));
// "abc"
(这是我的想法的注释。)
用正则表达式排除代理真的好吗?如果它是一个有效的代理对,它可以被正确转换,我认为在某些情况下需要存在特殊的代码字符,但我不能用正则表达式完全得到它。
代码点是问题所在。在正则表达式中,可以提取并处理不正确的大小写组合,但是在连续码点\ud834\udf06\ud834
中,\ud834\udf06
是正确的组合,而\udf06\ud834
是错误的,所以如果排除它,@ 987654420@是因为无法配对,只能是bug。
所以我觉得有必要把它处理一次代理。
// surrogate pair in string
let str = '\ud834\udf06\ud834abc\ud834\udf06';
function findSurrogatePair(str) {
return Array.from(str, (item) => {
return (item.codePointAt() > 65535) ?
`0x${item.codePointAt().toString(16)}/` : item
}).join('');
}
// JSON.stringify()
let json = JSON.stringify(str, (key, value) => {
let fixedString = findSurrogatePair(value);
return fixedString.replace(/[\ud800-\udfff]/g, '');
});
console.log(json);
// "0x1d306/abc0x1d306/"
// JSON.parse()
let jsonToString = JSON.parse(json, (key, value) => {
return value.replace(/0x[a-f0-9]{5,6}\//g, (item) => {
let str = item.slice(0, item.length - 1);
return String.fromCodePoint(str);
})
});
console.log(jsonToString);
// ?abc?
// surrogate pair in obj
let obj = {
string1: '?abc?',
string2: '\ud834\udf06\ud834abc\ud834\udf06'
}
let json = JSON.stringify(obj, (key, value) => {
if (key !== '' && typeof value === 'string') {
let fixedObj = findSurrogatePair(value);
return fixedObj.replace(/[\ud800-\udfff]/g, '');
}
return value
});
console.log(json);
// {"string1":"0x1d306/abc0x1d306/","string2":"0x1d306/abc0x1d306/"}
let jsonToObj = JSON.parse(json, (key, value) => {
if (key !== '' && typeof value === 'string') {
return value.replace(/0x[a-f0-9]{5,6}\//g, (item) => {
let str = item.slice(0, item.length - 1);
return String.fromCodePoint(str);
})
}
return value;
});
console.log(jsonToObj);
// {string1: '?abc?', string2: '?abc?'}
下面是重写的函数。
// surrogate pair in string
function surrogateStringToJSON(str) {
let fixedString = Array.from(str, (item) => {
return (item.codePointAt() > 65535) ?
`0x${item.codePointAt().toString(16)}/` : item
}).join('').replace(/[\ud800-\udfff]/g, '');
return JSON.stringify(fixedString);
}
console.log(surrogateStringToJSON(str))
// "0x1d306/abc0x1d306/"
function surrogateJSONToString(json) {
let fixedJson = json.replace(/0x[a-f0-9]{5,6}\//g, (item) => {
let str = item.slice(0, item.length - 1);
return String.fromCodePoint(str);
})
return JSON.parse(fixedJson);
}
console.log(surrogateJSONToString(surrogateStringToJSON(str)))
// ?abc?
// surrogate pair in obj
function surrogateObjToJSON(obj) {
return JSON.stringify(obj, (key, value) => {
if (key !== '' && typeof value === 'string') {
return Array.from(value, (item) => {
return (item.codePointAt() > 65535) ?
`0x${item.codePointAt().toString(16)}/` : item
}).join('').replace(/[\ud800-\udfff]/g, '');
}
return value
})
}
console.log(surrogateObjToJSON(obj))
function surrogateJSONToObj(json) {
return JSON.parse(json, (key, value) => {
if (key !== '' && typeof value === 'string') {
return value.replace(/0x[a-f0-9]{5,6}\//g, (item) => {
let str = item.slice(0, item.length - 1);
return String.fromCodePoint(str);
})
}
return value;
})
}
console.log(surrogateJSONToObj(surrogateObjToJSON(obj)))
我觉得我已经使用了我现在的能力,尽管我认为它并不令人满意。 .
下一次,我想总结一下模板文字和关注点。
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308626159.html