freeCodeCamp旅途

freeCodeCamp 旅途6 - 正则表达式、调试、数据结构

2019-05-27  本文已影响0人  HJSD

正则表达式

正则表达式是表示搜索模式的特殊字符串。也被称为“regex”或“regexp”,它们可以帮助程序员匹配、搜索和替换文本。

使用测试方法

如果你想要在字符串"The dog chased the cat"中匹配到"the"这个单词,你可以使用如下正则表达式:/the/。注意,正则表达式中不需要引号。
JavaScript 中有多种使用正则表达式的方法。测试正则表达式的一种方法是使用.test()方法。.test()方法会把你编写的正则表达式应用到一个字符串(即括号内的内容),如果你的匹配模式成功匹配到字符,则返回true,反之,返回false

let testStr = "freeCodeCamp";
let testRegex = /Code/;
testRegex.test(testStr);  // Returns true

//   同时用多种模式匹配文字字符串,使用 | 操作符来匹配多个规则 
let petString = "James has a pet cat.";
let petRegex = /dog|cat|bird|fish/;   
let result = petRegex.test(petString);

匹配时忽略大小写,使用标志(flag)来匹配大小写 -- i

let myString = "freeCodeCamp";
let fccRegex = /freecodecamp/i; 
let result = fccRegex.test(myString);

提取匹配项

使用.match()方法来提取你找到的实际匹配项,在括号内传入正则表达式。只能提取或搜寻一次匹配模式。

"Hello, World!".match(/Hello/);  // Returns ["Hello"]
let ourStr = "Regular expressions";
let ourRegex = /expressions/;
ourStr.match(ourRegex);   // Returns ["expressions"]

//   全局匹配
let testStr = "Repeat, Repeat, Repeat";
let repeatRegex = /Repeat/g;
testStr.match(repeatRegex);  // Returns ["Repeat", "Repeat", "Repeat"]

用通配符.匹配任何内容:

let humStr = "I'll hum a song";
let hugStr = "Bear hug";
let huRegex = /hu./;
humStr.match(huRegex); // Returns ["hum"]
hugStr.match(huRegex); // Returns ["hug"]

使用字符集搜寻具有一定灵活性的文字匹配模式。字符集允许你通过把它们放在方括号([])之间的方式来定义一组你需要匹配的字符串。

let bigStr = "big";
let bagStr = "bag";
let bugStr = "bug";
let bogStr = "bog";
let bgRegex = /b[aiu]g/;
bigStr.match(bgRegex); // Returns ["big"]
bagStr.match(bgRegex); // Returns ["bag"]
bugStr.match(bgRegex); // Returns ["bug"]
bogStr.match(bgRegex); // Returns null

匹配字母表中的字母:在字符集中,你可以使用连字符-)来定义要匹配的字符范围。

let catStr = "cat";
let batStr = "bat";
let matStr = "mat";
let bgRegex = /[a-e]at/;
catStr.match(bgRegex); // Returns ["cat"]
batStr.match(bgRegex); // Returns ["bat"]
matStr.match(bgRegex); // Returns null

//  使用连字符(-)匹配字符范围并不仅限于字母。它还可以匹配一系列数字。
let jennyStr = "Jenny8675309";
let myRegex = /[a-z0-9]/ig;  // matches all letters and numbers in jennyStr
jennyStr.match(myRegex);

匹配单个未指定的字符:你可以创建一个你不想匹配的字符集合。这些类型的字符集称为否定字符集,在开始括号后面和不想匹配的字符前面放置插入字符(即^)。
/[^aeiou]/gi匹配所有非元音字符。注意,字符.![@/和空白字符等也会被匹配,该否定字符集仅排除元音字符。

匹配出现一次或多次的字符:使用+符号来检查情况。
/a+/g会在"abc"中匹配到一个匹配项,并且返回["a"]。因为+的存在,它也会在"aabc"中匹配到一个匹配项,然后返回["aa"]。如果它是检查字符串"abab",它将匹配到两个匹配项并且返回["a", "a"]

匹配出现零次或多次的字符:执行该操作的字符叫做asteriskstar,即*

let soccerWord = "gooooooooal!";
let gPhrase = "gut feeling";
let oPhrase = "over the moon";
let goRegex = /go*/;
soccerWord.match(goRegex); // Returns ["goooooooo"]
gPhrase.match(goRegex); // Returns ["g"]
oPhrase.match(goRegex); // Returns null

用惰性匹配来查找字符:贪婪匹配会匹配到符合正则表达式匹配模式的字符串的最长可能部分,并将其作为匹配项返回。另一种方案称为懒惰匹配,它会匹配到满足正则表达式的字符串的最小可能部分。
/t[a-z]*i/应用于字符串"titanic",正则表达式默认是贪婪匹配,因此匹配返回为["titani"]。你可以使用?字符来将其变成懒惰匹配。调整后的正则表达式/t[a-z]*?i/匹配字符串"titanic"返回["ti"]

匹配字符串的开头:使用字符集中的插入符号(^)来创建一个否定字符集,形如[^thingsThatWillNotBeMatched]。在字符集之外,插入符号用于字符串的开头搜寻匹配模式。

let firstString = "Ricky is first and can be found.";
let firstRegex = /^Ricky/;
firstRegex.test(firstString);   // Returns true
let notFirst = "You can't find Ricky now.";
firstRegex.test(notFirst);   // Returns false

匹配字符串的末尾:使用正则表达式的美元符号$来搜寻字符串的结尾。

匹配所有的字母和数字:JavaScript 中与字母表匹配的最接近的字符类是\w,这个缩写等同于[A-Za-z0-9_]

匹配除了字母和数字的所有符号:使用\W搜寻和\w相反的匹配模式。注意,相反匹配模式使用大写字母。此缩写与[^A-Za-z0-9_]是一样的。

let shortHand = /\W/;
let numbers = "42%";
let sentence = "Coding!";
numbers.match(shortHand); // Returns ["%"]
sentence.match(shortHand); // Returns ["!"]

匹配所有数字:数字字符的缩写是\d,这等同于字符类[0-9],它查找 0 到 9 之间任意数字的单个字符。

匹配所有非数字:查找非数字字符的缩写是\D。这等同于字符串[^0-9],它查找不是 0 - 9 之间数字的单个字符。

匹配空白字符:使用\s搜寻空格,此匹配模式不仅匹配空格,还匹配回车符、制表符、换页符和换行符,你可以将其视为与[\r\t\f\n\v]

let whiteSpace = "Whitespace. Whitespace everywhere!"
let spaceRegex = /\s/g;
whiteSpace.match(spaceRegex);   // Returns [" ", " "]

匹配非空白字符:使用\S搜寻非空白字符,其中S是大写。此匹配模式将不匹配空格、回车符、制表符、换页符和换行符。你可以认为这类似于字符类[^\r\t\f\n\v]

let whiteSpace = "Whitespace. Whitespace everywhere!"
let nonSpaceRegex = /\S/g;
whiteSpace.match(nonSpaceRegex).length; // Returns 32

指定匹配的上限和下限:使用加号+查找一个或多个字符,使用星号*查找零个或多个字符。使用数量说明符指定匹配模式的上下限。数量说明符与花括号({})一起使用。

let A4 = "aaaah";
let A2 = "aah";
let multipleA = /a{3,5}h/;
multipleA.test(A4); // Returns true
multipleA.test(A2); // Returns false

//  只指定匹配的下限:在第一个数字后面跟一个逗号即可
let A4 = "haaaah";
let A2 = "haah";
let A100 = "h" + "a".repeat(100) + "h";
let multipleA = /ha{3,}h/;
multipleA.test(A4); // Returns true
multipleA.test(A2); // Returns false
multipleA.test(A100); // Returns true

//  指定匹配的确切数量:只需在大括号之间放置一个数字
let A4 = "haaaah";
let A3 = "haaah";
let A100 = "h" + "a".repeat(100) + "h";
let multipleHA = /a{3}h/;
multipleHA.test(A4); // Returns true
multipleHA.test(A3); // Returns true
multipleHA.test(A100); // Returns false

检查全部或无:使用问号?指定可能存在的元素,这将检查前面的零个或一个元素。你可以将此符号视为前面的元素是可选的。

let american = "color";
let british = "colour";
let rainbowRegex= /colou?r/;
rainbowRegex.test(american); // Returns true
rainbowRegex.test(british); // Returns true

正向先行断言和负向先行断言:先行断言是告诉 JavaScript 在字符串中向前查找的匹配模式。当你想要在同一个字符串上搜寻多个匹配模式时,这可能非常有用。

let quit = "qu";
let noquit = "qt";
let quRegex= /q(?=u)/;
let qRegex = /q(?!u)/;
quit.match(quRegex); // Returns ["q"]
noquit.match(qRegex); // Returns ["q"]

//  先行断言 的更实际用途是检查一个字符串中的两个或更多匹配模式。
let password = "abc123";
let checkPass = /(?=\w{3,6})(?=\D*\d)/;
checkPass.test(password); // Returns true

使用捕获组重用模式:括号()可以用来匹配重复的子字符串。
要指定重复字符串将出现的位置,可以使用反斜杠(\)后接一个数字。这个数字从 1 开始,随着你使用的每个捕获组的增加而增加。这里有一个示例,\1可以匹配第一个组。

let repeatStr = "regex regex";
let repeatRegex = /(\w+)\s\1/;  // 匹配任意两个被空格分割的单词
repeatRegex.test(repeatStr); // Returns true
repeatStr.match(repeatRegex); // Returns ["regex regex", "regex"]

//   使用捕获组搜索和替换
//    .replace()方法来搜索并替换字符串中的文本。.replace()的输入首先是你想要搜索的正则表达式匹配模式,第二个参数是用于替换匹配的字符串或用于执行某些操作的函数。
let wrongText = "The sky is silver.";
let silverRegex = /silver/;
wrongText.replace(silverRegex, "blue");  // Returns "The sky is blue."

//   你还可以使用美元符号($)访问替换字符串中的捕获组。
"Code Camp".replace(/(\w+)\s(\w+)/, '$2 $1');   // Returns "Camp Code"

删除开头和结尾的空白:.trim()方法在这里也可以实现同样的效果。

let hello = "   Hello, World!  ";
let wsRegex = /^\s+|\s+$/g; // 修改这一行
let result = hello.replace(wsRegex,""); // 修改这一行

调试

代码中的错误通常有三种情形:1)语法错误导致程序停止运行,2)代码无法执行或具有意外行为导致运行时错误,以及 3)代码有语义(逻辑)错误,没有实现原来的意图。
语法错误的示例 - 通常会被代码编辑器检测到:

funtion willNotWork( {
  console.log("Yuck");
}   // "function" 关键字拼写错误而且在最后缺少括号

运行时错误的示例 - 通常在程序执行时检测到:

function loopy() {
  while(true) {
    console.log("Hello, world!");
  }
}   // 调用 loopy 函数会进入一个死循环,这可能会导致浏览器崩溃。

语义错误的示例 - 通常在测试代码输出结果时被检测到:

function calcAreaOfRect(w, h) {
  return w + h; // 应该是 w * h
}  // 语法和执行过程都没错,但是结果是错的
let myRectArea = calcAreaOfRect(2, 3);  

使用控制台检查变量值

Chrome 和 Firefox 都有出色的 JavaScript 控制台(也称为 DevTools),可以用来调试你的 JavaScript 代码。

使用 type of 检查变量的类型

当你以 JavaScript 对象(JSON)的形式访问和使用外部数据时尤其要小心。

console.log(typeof ""); // 输出 "string"
console.log(typeof 0); // 输出 "number"
console.log(typeof []); // 输出 "object"
console.log(typeof {}); // 输出 "object"

JavaScript 有六种原始(不可变)数据类型:Boolean,Null,Undefined,Number,String, 和Symbol(ES6 新增)和一种可变的数据类型:Object。注意,在 JavaScript 中,数组在本质上是一种对象

捕获拼错的变量名和函数名:变量或函数名的错写、漏写或大小写弄混都会让浏览器尝试查找并不存在的东西,并报出“引用错误”。JavaScript 变量和函数名称区分大小写。

捕获未闭合的括号、方括号、大括号和引号:要注意的另一个语法错误是所有的小括号、方括号、花括号和引号都必须配对。避免这种错误的一种方法是,一次性输入完这些符号,然后将光标移回它们之间继续编写。

捕捉单引号和双引号的混合用法:只使用一种引号是可以的,你可以使用反斜杠 () 转义字符来转义字符串中的引号。

// 这些是正确的:
const grouchoContraction = "I've had a perfectly wonderful evening, but this wasn't it.";
const quoteInString = "Groucho Marx once said 'Quote me as saying I was mis-quoted.'";
// 这是不正确的:
const uhOhGroucho = 'I've had a perfectly wonderful evening, but this wasn't it.';

捕获使用赋值运算符而不是相等运算符:JavaScript 中的赋值运算符 (=) 是用来为变量名赋值的。并且=====运算符检查相等性(三等号===是用来测试是否严格相等的,严格相等的意思是值和类型都必须相同)。
JavaScript 会把大部分的值都视为true,除了所谓的 "falsy" 值,即:false0""(空字符串),NaNundefinednull

捕捉函数调用后缺少的左括号和右括号:

function myFunction() {
  return "You rock!";
}
let varOne = myFunction; // 将函数赋值给变量
let varTwo = myFunction(); // 将函数返回值 "You rock!"赋给变量

调用函数时,捕获以错误顺序传递的参数:如果参数分别是不同的类型,例如接受数组和整数两个参数的函数,参数顺序传错就可能会引发运行时错误。对于接受相同类型参数的函数,传错参数也会导致逻辑错误或运行结果错误。

捕获使用索引的时候出现的错误:当你试图访问字符串或数组的特定索引(分割或访问一个片段)或循环索引时,有时会出现Off by one errors错误(有时称为 OBOE)。JavaScript 索引从0开始,而不是1,这意味着最后一个索引总会比字符串或数组的长度少 1。如果你尝试访问等于长度的索引,程序可能会抛出“索引超出范围”引用错误或打印出undefined

let alphabet = "abcdefghijklmnopqrstuvwxyz";
let len = alphabet.length;
for (let i = 0; i <= len; i++) {  // 在最后多了一次循环
  console.log(alphabet[i]);
}
for (let j = 1; j < len; j++) {  // 循环少了一次,漏掉了索引 0 处的字符
  console.log(alphabet[j]);
}
for (let k = 0; k < len; k++) {  // 不多不少,这才是正确的
  console.log(alphabet[k]);
}

重新初始化循环中的变量时要小心:

function zeroArray(m, n) {
  // 创建一个具有 m 行和 n 列“零”的二维数组
  let newArray = [];
  let row = [];
  for (let i = 0; i < m; i++) {    // 添加 m 行到 newArray
    row = [];  // 每次寻欢过后需要初始化
    for (let j = 0; j < n; j++) {     // Pushes n 个“零”到当前行以创建列
      row.push(0);
    }    // Pushes 当前里面有 n 个“零”的行到 newArray
    newArray.push(row);
  }
  return newArray;
}

使用有效的终止条件防止无限循环:

function loopy() {
  while(true) {
    console.log("Hello, world!");
  }  //  没有终止循环的条件
}

数据结构

使用数组存储数据集合

数组(Array)数据结构的最简单的实现例子。这是一个一维数组(one-dimensional array),它只有一层,或者说在它里面没有包含其它的数组结构。你可以看到它里面包含了布尔值(booleans)、字符串(strings)、数字(numbers)以及一些其他的 JavaScript 语言中合法的数据类型:

let simpleArray = ['one', 2, 'three’, true, false, undefined, null];
console.log(simpleArray.length);  // 输出 7
//   所有数组都有一个长度(length)属性。你可以简单地使用Array.length方法来访问它。

这是一个多维数组(multi-dimensional Array),或者说是一个包含了其他数组的数组。

let complexArray = [[{one: 1,two: 2},{three: 3,four: 4}],[{a: "a",b: "b"},{c: "c",d: “d”}]];

使用方括号访问数组的内容:要从一个数组中获取一个元素,我们可以在一个数组变量名的后面加一个使用“方括号”括起来的索引。这叫做方括号符号(bracket notation)。

let ourArray = ["a", "b", "c"];
let ourVariable = ourArray[0];   // ourVariable 的值为 "a"
ourArray[1] = "not b anymore";  // ourArray 现在的值为 ["a", "not b anymore", "c"];

使用 push() 和 unshift() 添加项目到数组中

你可以不限次数地往数组中添加元素或者从中移除元素,或者说数组是可变的(mutable)。
push()方法将元素插入到一个数组的末尾,而unshift()方法将元素插入到一个数组的开头。

let twentyThree = 'XXIII';
let romanNumerals = ['XXI', 'XXII'];
romanNumerals.unshift('XIX', 'XX');// 数组现在为 ['XIX', 'XX', 'XXI', 'XXII']
romanNumerals.push(twentyThree);// 数组现在为 ['XIX', 'XX', 'XXI', 'XXII', 'XXIII']

使用 pop() 和 shift() 从数组中删除项目

push()unshift()都分别有一个作用基本与之相反的函数:pop()shift()pop()从数组的末尾移除一个元素,而shift()从数组的开头移除一个元素。

let greetings = ['whats up?', 'hello', 'see ya!'];
greetings.pop();   // 数组现在等于 ['whats up?', 'hello']
greetings.shift();  // 数组现在等于 ['hello']
let popped = greetings.pop();  // 返回 'hello'  greetings 现在等于 []

使用 splice() 删除项目

splice()让我们可以从数组中的任意位置移除任意数量的连续的元素。splice()最多可以接受 3 个参数,但现在我们先关注前两个。splice()接收的前两个参数基于调用splice()数组中元素的索引。

let array = ['today', 'was', 'not', 'so', 'great'];
array.splice(2, 2);// 从第 3 个元素开始,删除 2 个元素  ['today', 'was', 'great']

splice()不仅从被调用的数组中移除元素,还会返回一个包含被移除元素的数组:

let array = ['I', 'am', 'feeling', 'really', 'happy'];
let newArray = array.splice(3, 2);   // newArray 等于 ['really', 'happy']

使用 splice() 增加项目:利用它的第三个参数来向数组中添加元素。第三个参数可以是一个或多个元素,这些元素会被添加到数组中。

function colorChange(arr, index, newColor) {
  arr.splice(index, 1, newColor);
  return arr;
}
let colorScheme = ['#878787', '#a08794', '#bb7e8c', '#c9b6be', '#d1becf'];
colorScheme = colorChange(colorScheme, 2, '#332327');
// 我们移除了 '#bb7e8c' 并在其位置上添加了 '#332327'
// colorScheme 现在等于 ['#878787', '#a08794', '#332327', '#c9b6be', '#d1becf']

使用 slice() 拷贝数组项目

slice()并不修改数组,而是复制或者说提取(extract)给定数量的元素到一个新数组里,而调用方法的数组则保持不变。slice()只接受 2 个输入参数—第一个是开始提取元素的位置(索引),第二个是结束提取元素的位置(索引)。slice 方法会提取直到该索引的元素,但被提取的元素不包括该索引对应的元素。

let weatherConditions = ['rain', 'snow', 'sleet', 'hail', 'clear'];
let todaysWeather = weatherConditions.slice(1, 3);
// todaysWeather 等于 ['snow', 'sleet'];
// weatherConditions 仍然等于 ['rain', 'snow', 'sleet', 'hail', 'clear']

使用扩展运算符复制数组:slice()已经能让我们从一个数组中选择一些元素来复制到新数组中了,而 ES6 中又新引入了一个简洁且可读性强的语法展开运算符(spread operator)...

let thisArray = [true, true, undefined, false, null];
let thatArray = [...thisArray];
// thatArray 等于 [true, true, undefined, false, null]
// thisArray 保持不变,并等于 thatArray

组合使用数组和扩展运算符:展开运算符的另一个大用处是合并数组,或者将某个数组的所有元素插入到另一个数组的任意位置。用传统的语法我们也可以连接两个数组,但只能两个数组首尾相接。而展开语法能使下面的操作变得极其简单:

let thisArray = ['sage', 'rosemary', 'parsley', 'thyme'];
let thatArray = ['basil', 'cilantro', ...thisArray, 'coriander'];
// thatArray 现在等于 ['basil', 'cilantro', 'sage', 'rosemary', 'parsley', 'thyme', 'coriander']

使用 indexOf() 检查元素是否存在

由于数组可以在任意时间被修改或者说被改变(mutated),我们不能保证某个数据在一个给定数组中的位置,甚至不能保证该元素还存在于该数组中。indexOf()方法接受一个元素作为输入参数,并返回该元素在数组中的位置(索引);若该元素不存在于数组中则返回-1

let fruits = ['apples', 'pears', 'oranges', 'peaches', 'pears'];
fruits.indexOf('dates') // 返回 -1
fruits.indexOf('oranges') // 返回 2
fruits.indexOf('pears') // 返回 1,即第一个出现的 'pears' 元素在数组中的索引为 1

使用 For 循环迭代数组的所有项

JavaScript 提供了几个内置的方法,它们以不同的方式遍历数组来获得不同的结果(如every()forEach()map()等等)。而简单的for循环不仅能实现这些功能,而且相比之下也更灵活。

function greaterThanTen(arr) {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] > 10) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
greaterThanTen([2, 12, 8, 14, 80, 0, 1]);  // 返回 [12, 14, 80]

创建复杂的多维数组:数组中的数组还可以包含其他数组,数组中是可以嵌套任意层的数组的。从而数组可以被用来实现非常复杂的叫做多维(multi-dimensional)或嵌套(nested)数组。

let nestedArray = [ // 顶层,或第 1 层——最外层的数组
  ['deep'], // 数组中的数组,第 2 层
  [['deeper'], ['deeper'] // 第 3 层嵌套的两个数组],
  [[['deepest'], ['deepest'] // 第 4 层嵌套的两个数组],
  [[['deepest-est?'] // 第 5 层嵌套的一个数组]]]
];
console.log(nestedArray[2][1][0][0][0]);  // 输出:deepest-est?
nestedArray[2][1][0][0][0] = 'deeper still';
console.log(nestedArray[2][1][0][0][0]);    // 现在输出:deeper still

将键值对添加到对象中

对象(object)本质上是键值对(key-value pair)的集合,或者说,一系列被映射到唯一标识符(叫做属性(property)或者键(key))的数据。

let FCC_User = {  username: 'awesome_coder',  followers: 572,  points: 1741,  completedProjects: 15};
let userData = FCC_User.followers;   // userData 等于 572
let userData = FCC_User['followers'];    // userData 等于 572    等价

数据结构基础:修改嵌套在对象中的对象

let userActivity = {  id: 23894201352,  date: 'January 1, 2017',
  data: {    totalUsers: 51,    online: 42  }};
userActivity.data.online = 45;
console.log(userActivity);

数据结构基础:使用方括号访问属性名称

先计算selectedFood变量的值,并返回foods对象中以该值命名的属性对应的值,若没有以该值命名的属性则会返回undefined

let selectedFood = getCurrentFood(scannedItem);
let inventory = foods[selectedFood];

数据结构基础:使用 delete 关键字删除对象属性

delete foods.apples;

数据结构基础:检查对象是否具有某个属性

JavaScript 为我们提供了两种不同的方式来实现这个功能,一个是hasOwnProperty()方法,另一个是in关键字。

users.hasOwnProperty('Alan');
'Alan' in users;   // 都返回 true

数据结构基础:使用 for...in 语句迭代对象

for (let user in users) {
  console.log(user);
};  // 输出:  Alan  Jeff  Sarah  Ryan

数据结构基础:使用 Object.Keys() 生成对象所有键组成的数组

let users = {
  Alan: {    age: 27,    online: false  },
  Jeff: {  age: 32,    online: true  },
  Sarah: {    age: 48,    online: false  },
  Ryan: {    age: 19,    online: true  }
};
function getArrayOfUsers(obj) {
  return Object.keys(obj);
}
console.log(getArrayOfUsers(users));

数据结构基础:修改存储在对象中的数组

let user = {
  name: 'Kenneth',  age: 28,
  data: {
    username: 'kennethCodesAllDay',
    joinDate: 'March 26, 2016',
    organization: 'freeCodeCamp',
    friends: [      'Sam',      'Kira',      'Tomo'    ],
    location: {      city: 'San Francisco',      state: 'CA',      country: 'USA'    }
  }
};
function addFriend(userObj, friend) {
  userObj.data.friends.push(friend);
  return userObj.data.friends;
}
console.log(addFriend(user, 'Pete'));
上一篇下一篇

猜你喜欢

热点阅读