关于 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...ofArray.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)
  • NaNInfinity等不能为字符串的数字被强制转换为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