freeCodeCamp 旅途6 - 正则表达式、调试、数据结构
正则表达式
正则表达式是表示搜索模式的特殊字符串。也被称为“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"]
。
匹配出现零次或多次的字符:执行该操作的字符叫做asterisk
或star
,即*
。
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" 值,即:false
,0
,""
(空字符串),NaN
,undefined
和null
。
捕捉函数调用后缺少的左括号和右括号:
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'));