🌸I could be bounded in a nutshell and count myself a king of infinite space.
特别鸣谢:木芯工作室 、Ivan from Russia
测试对象的属性
要检查某个对象是否具有一个属性,你可以使用 .hasOwnProperty()
方法。 根据对象是否具有该属性,someObject.hasOwnProperty(someProperty)
返回 true 或 false。
示例
function checkForProperty(object, property) { return object.hasOwnProperty(property); } checkForProperty({ top: 'hat', bottom: 'pants' }, 'top'); // true checkForProperty({ top: 'hat', bottom: 'pants' }, 'middle'); // false
第一个 checkForProperty 函数返回 true,第二个返回 false。
修改函数 checkObj 以检查传给函数参数的对象 obj 是否包含传给函数参数的属性 checkProp。 如果在 obj 中找到传给 checkProp 的属性,则返回该属性的值。 如果没有,则返回 Not Found。
操作复杂对象
有时你可能希望将数据存储在一个灵活的数据结构(Data Structure)中。 JavaScript 对象是一种灵活的数据结构。 它可以储存字符串(strings)、数字(numbers)、布尔值(booleans)、数组(arrays)、函数(functions)和对象(objects)以及这些值的任意组合。
这是一个复杂数据结构的示例:
const ourMusic = [ { "artist": "Daft Punk", "title": "Homework", "release_year": 1997, "formats": [ "CD", "Cassette", "LP" ], "gold": true } ];
这是一个包含一个对象的数组
。 该对象有关于专辑的各种元数据(metadata
)。 它也有一个嵌套的 formats 数组。 可以将专辑添加到顶级数组来增加更多的专辑记录。 对象将数据以一种键 - 值对的形式
保存。 在上面的示例中,“artist”: “Daft Punk” 有一个键为 artist 值为 Daft Punk 的属性。
**提示:**数组中有多个 JSON 对象的时候
,对象与对象之间要用逗号隔开。
abbr. 基于 JavaScript 语言的轻量级的数据交换格式(JavaScript Object Notation)
const myMusic = [ { "artist": "Billy Joel", "title": "Piano Man", "release_year": 1973, "formats": [ "CD", "8T", "LP" ], "gold": true }, { "artist": "qk", "title": "hiphop", "release_year": 2023, "formats": [ "CD", "8T", "LP" ], "gold": true } ];
访问嵌套对象
我们可以通过连续使用点号表示法和方括号表示法来访问对象的嵌套属性。
这是一个嵌套对象:
const ourStorage = { "desk": { "drawer": "stapler" }, "cabinet": { "top drawer": { "folder1": "a file", "folder2": "secrets" }, "bottom drawer": "soda" } }; ourStorage.cabinet["top drawer"].folder2; ourStorage.desk.drawer;
ourStorage.cabinet[“top drawer”].folder2 将会是字符串 secrets,并且 ourStorage.desk.drawer 将会是字符串 stapler。
访问嵌套数组
在之前的挑战中,我们学习了在对象中嵌套对象和数组。 与访问嵌套对象类似,数组的方括号可以用来对嵌套数组进行链式访问。
下面是访问嵌套数组的例子:
const ourPets = [ { animalType: "cat", names: [ "Meowzer", "Fluffy", "Kit-Cat" ] }, { animalType: "dog", names: [ "Spot", "Bowser", "Frankie" ] } ]; ourPets[0].names[1]; ourPets[1].names[0];
ourPets[0].names[1] 应该是字符串 Fluffy, 并且 ourPets[1].names[0] 应该是字符串 Spot。
记录集合
你将创建一个帮助维护音乐专辑集的函数。 这个集合是一个包含多个相册的对象,这些相册也是对象。 每张专辑在集合中以唯一的 id 作为属性名来表示。 在每个专辑对象中,有各种描述专辑信息的属性。 并非所有专辑都有完整的信息。
updateRecords 函数有 4 个参数,即以下参数:
records - 一个包含多个专辑的对象
id - 一个数字,代表 records 对象中特定的专辑
prop - 一个字符串,代表相册属性名称
value - 一个字符串,包含用来更新相册属性的信息
使用下面的规则完成函数来修改传递给函数的对象。
你的函数必须始终返回整个 records 对象。
如果 value 是空字符串,从专辑里删除指定的 prop。
如果 prop 不是 tracks,并且 value 不是一个空字符串,将 value 赋给那个专辑的 prop。
如果 prop 是 tracks 并且 value 不是一个空字符串,你需要更新专辑的 tracks 数组。 首先,如果专辑没有 tracks 属性,赋予它一个空数组。 然后添加 value 作为专辑的 tracks 数组的最后一个项目。
注意: 将 recordCollection 对象的副本用于测试。 你不应该直接修改 recordCollection 对象。
// 设置 const recordCollection = { 2548: { albumTitle: 'Slippery When Wet', artist: 'Bon Jovi', tracks: ['Let It Rock', 'You Give Love a Bad Name'] }, 2468: { albumTitle: '1999', artist: 'Prince', tracks: ['1999', 'Little Red Corvette'] }, 1245: { artist: 'Robert Palmer', tracks: [] }, 5439: { albumTitle: 'ABBA Gold' } }; // 只修改这一行下面的代码 function updateRecords(records, id, prop, value) { if(value===""){ delete records[id][prop]; }else if(prop==="tracks"){ records[id][prop]=records[id][prop]||[]; records[id][prop].push(value); }else { records[id][prop]=value } return records; } updateRecords(recordCollection, 5439, 'artist', 'ABBA');
// 设置 const recordCollection = { 2548: { albumTitle: 'Slippery When Wet', artist: 'Bon Jovi', tracks: ['Let It Rock', 'You Give Love a Bad Name'] }, 2468: { albumTitle: '1999', artist: 'Prince', tracks: ['1999', 'Little Red Corvette'] }, 1245: { artist: 'Robert Palmer', tracks: [] }, 5439: { albumTitle: 'ABBA Gold' } }; // 只修改这一行下面的代码 function updateRecords(records, id, prop, value) { if(value===""){ delete records[id][prop]; }else if(prop!=="tracks"&&value!==""){ records[id][prop]=value; }else if(prop==="tracks"&&value!==""){ if(records[id].hasOwnProperty("tracks")===false){ records[id][prop]=[]; } records[id][prop].push(value); } return records; } updateRecords(recordCollection, 5439, 'artist', 'ABBA');
短路或
JS逻辑运算符 || (比如let a = b || c)
JavaScript
1、JS中的||符号:
运算方法:
只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值。
只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。
比如:var a= b || c
这相当于一个赋值语句,如果b的值被转换为false,那么就把c的值赋给a,否在就把b的值赋给a
javascript中以下值会被转换为false
false
undefined
null
0
-0
NaN
“”
‘’
for 循环
你可以使用循环多次执行相同的代码。
JavaScript 中最常见的循环就是 for,它可以循环指定次数。
for 循环中的可选三个表达式用分号隔开:
for (a; b; c),其中a为初始化语句,b为条件语句,c 是最终的表达式。
初始化语句只会在执行循环开始之前执行一次。 它通常用于定义和设置你的循环变量。
循环条件语句会在每一轮循环的开始前执行,只要条件判断为 true 就会继续执行循环。 当条件为 false 的时候,循环将停止执行。 这意味着,如果条件在一开始就为 false,这个循环将不会执行。
终止循环表达式在每次循环迭代结束, 在下一个条件检查之前时执行,通常用来递增或递减循环计数。
在下面的例子中,先初始化 i = 0,条件 i < 5 为 true 时,进入循环。 每次循环后 i 的值增加 1,然后执行终止循环条件表达式 i++。
const ourArray = []; for (let i = 0; i < 5; i++) { ourArray.push(i); }
ourArray 现在的值为 [0, 1, 2, 3, 4]。
使用递归代替循环
递归是函数调用自身的操作。 为了便于理解,有如下任务:计算数组内元素前 n 的元素乘积。 使用 for 循环, 可以这样做:
function multiply(arr, n) { let product = 1; for (let i = 0; i < n; i++) { product *= arr[i]; } return product; }
下面是递归写法,注意代码里的 multiply(arr, n) == multiply(arr, n - 1) * arr[n - 1]。 这意味着可以重写 multiply 以调用自身而无需依赖循环。
function multiply(arr, n) { if (n <= 0) { return 1; } else { return multiply(arr, n - 1) * arr[n - 1]; } }
递归版本的 multiply 详述如下。 在 base case 里,也就是 n <= 0 时,返回 1。 在 n 比 0 大的情况里,函数会调用自身,参数 n 的值为 n - 1。 函数以相同的方式持续调用 multiply,直到 n <= 0 为止。 所以,所有函数都可以返回,原始的 multiply 返回结果。
注意: 递归函数在没有函数调用时(在这个例子是,是当 n <= 0 时)必需有一个跳出结构,否则永远不会执行完毕。
写一个递归函数,sum(arr, n),返回递归调用数组 arr 从前 n 个元素和。
function sum(arr, n) { // 只修改这一行下面的代码 if (n<=0){ return 0; }else{ return sum(arr,n-1)+n } // 只修改这一行上面的代码 }
function sum(arr, n) { // 只修改这一行下面的代码 if (n<=0){ return 0; }else{ return sum(arr,n-1)+arr[n-1] } // 只修改这一行上面的代码 }
小案例
// 设置 const contacts = [ { firstName: "Akira", lastName: "Laine", number: "0543236543", likes: ["Pizza", "Coding", "Brownie Points"], }, { firstName: "Harry", lastName: "Potter", number: "0994372684", likes: ["Hogwarts", "Magic", "Hagrid"], }, { firstName: "Sherlock", lastName: "Holmes", number: "0487345643", likes: ["Intriguing Cases", "Violin"], }, { firstName: "Kristian", lastName: "Vos", number: "unknown", likes: ["JavaScript", "Gaming", "Foxes"], }, ]; function lookUpProfile(name, prop) { // 只修改这一行下面的代码 for(let i=0; i<contacts.length;i++){ if(contacts[i].firstName===name){ if(contacts[i].hasOwnProperty(prop)){ return contacts[i][prop]; }else{ return "No such property"; } } } return "No such contact"; // 只修改这一行上面的代码 } lookUpProfile("Akira", "likes");
使用 JavaScript 生成随机分数
随机数非常适合用来创建随机行为。
在 JavaScript 中,可以用 Math.random()
生成一个在0(包括 0)到 1(不包括 1)之间的随机小数。 因此 Math.random() 可能返回 0,但绝不会返回 1。
提示:使用赋值运算符存储值这一节讲过,所有函数调用将在 return 执行之前结束,因此我们可以 return(返回)Math.random() 函数的值。
使用 JavaScript 生成随机整数
你可以用 Math.random() 生成随机的小数,但有时你需要生成随机的整数。 下面的流程将给你一个小于 20 的随机整数:
用 Math.random() 生成一个随机小数。
把这个随机小数乘以 20。
用 Math.floor() 向下取整,获得它最近的整数。
记住 Math.random() 永远不能完全返回 1,所以不可能实际得到 20,因为你正在用 Math.floor() 四舍五入。 这个流程将给你一个从 0 到 19 的随机整数。
把操作连起来,代码类似于下面:
Math.floor(Math.random() * 20);
你将调用 Math.random(),把结果乘以 20,然后把值传给 Math.floor(),向下取整获得最近的整数。
生成某个范围内的随机整数
你可以在从零到给定数字的范围内生成随机整数。 你也可以为此范围选择一个不同的较小数字。
你将把最小的数字定义为 min,把最大的数字定义为 max。
这个公式将生成一个从 min 到 max 的随机整数。 仔细看看并尝试理解这行代码到底在干嘛:
Math.floor(Math.random() * (max - min + 1)) + min
创建一个函数 randomRange,参数为 myMin 和 myMax,返回一个在 myMin(包括 myMin)和 myMax(包括 myMax)之间的随机整数。
使用 parseInt 函数
parseInt() 函数解析一个字符串返回一个整数。 下面是一个示例:
const a = parseInt("007");
上述函数将字符串 007 转换为整数 7。 如果字符串中的第一个字符不能转换为数字,则返回 NaN
。
在 convertToInteger 函数中使用 parseInt() 将字符串 str 转换为一个整数,并返回这个值。
使用 parseInt 函数并传入一个基数
parseInt() 函数解析一个字符串并返回一个整数。 它还可以传入第二个参数,指定了字符串中数字的基数。 基数可以是 2 到 36 之间的整数。
函数调用如下所示:
parseInt(string, radix);
这是一个示例:
const a = parseInt("11", 2);
变量 radix 表示 11 是在二进制系统中。 这个示例将字符串 11 转换为整数 3。
使用多个三元运算符
在之前的挑战中,你使用了一个条件运算符。 你也可以将多个运算符串联在一起以检查多种条件。
下面的函数使用 if,else if 和 else 语句来检查多个条件:
function findGreaterOrEqual(a, b) { if (a === b) { return "a and b are equal"; } else if (a > b) { return "a is greater"; } else { return "b is greater"; } }
以上函数可以使用多个三元运算符重写:
function findGreaterOrEqual(a, b) { return (a === b) ? "a and b are equal" : (a > b) ? "a is greater" : "b is greater"; }
如上文所示,对多个三元运算符进行每个条件都是单独一行的格式化被认为是最佳做法
。 使用多个三元运算符而没有适当的缩进可能会使您的代码难以理解。 例如:
function findGreaterOrEqual(a, b) { return (a === b) ? "a and b are equal" : (a > b) ? "a is greater" : "b is greater"; }
在 checkSign 函数中使用多个三元运算符来检查数字是正数 (“positive”)、负数 (“negative”) 或零 (“zero”),使用 findGreaterOrEqual 里面的建议缩进格式。 函数应返回 positive、negative 或 zero。
使用递归创建一个倒计时
// 只修改这一行下面的代码 function countdown(n){ return n<1?[]:[n].concat(countdown(n-1)) } // 只修改这一行上面的代码
在上一个挑战中,你学习了怎样用递归来代替 for 循环。 现在来学习一个更复杂的函数,函数返回一个从 1 到传递给函数的指定数字的连续数字数组。
正如上一个挑战提到的,会有一个 base case。 base case 告诉递归函数什么时候不再需要调用其自身。 这是简单 情况,返回得到的值。 还有 recursive call,继续用不同的参数调用自身。 如果函数无误,一直执行直到 base case 为止。
比如,如果想写一个递归函数,返回一个数字 1 到 n 的连续数组。 这个函数需要接收一个参数 n 代表最终数字。 然后会持续的调用自身,传入一个比 n 更小的值一直到传入的值是 1 为止。 函数如下:
function countup(n) { if (n < 1) { return []; } else { const countArray = countup(n - 1); countArray.push(n); return countArray; } } console.log(countup(5));
值 [1, 2, 3, 4, 5] 将显示在控制台中。
起初,这似乎是违反直觉
的,因为 n 的值递减,但是最终数组中的值却递增。 之所以发生这种情况,是因为在递归调用返回之后,才调用 push。 在将 n pushed 进数组时,countup(n - 1) 已经调用赋值成功并返回了 [1, 2, …, n - 1]。
已经定义了一个函数 countdown,函数有一个参数(n)。 函数应该基于参数 n 递归调用返回 n 到 1 的连续数字的数组。 如果函数以小于 1 的参数调用,函数应该返回空数组。 比如,用 n = 5 调用函数应该返回数组 [5, 4, 3, 2, 1]。 函数必需使用递归函数调用自身,不能使用任何形式的循环。
Use Recursion to Create a Countdown 1.9k
Solutions
Solution 1 (Click to Show/Hide)
function countdown(n) { if (n < 1) { return []; } else { const arr = countdown(n - 1); arr.unshift(n); return arr; } }
Solution 2 (Click to Show/Hide)
function countdown(n) { if (n < 1) { return []; } else { const arr = countdown(n - 1); arr.splice(0, 0, n); return arr; } }
Solution 3 (Click to Show/Hide)
function countdown(n){ return n < 1 ? [] : [n].concat(countdown(n - 1)); }
Solution 4 (Click to Show/Hide)
function countdown(n){ return n < 1 ? [] : [n, ...countdown(n - 1)]; }
js 扩展运算符(…)的用法
在日常开发中,我们在看js代码时,经常会看到(…)这样的符号。这里介绍一下它的含义和作用。
定义:
扩展运算符(…)是ES6的语法,用于取出参数对象的所有可遍历属性,然后拷贝到当前对象之中。
基本用法
let person = {name: "Amy", age: 15} let someone = { ...person } someone // {name: "Amy", age: 15}
特殊用法
数组
由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] }; foo // {0: "a", 1: "b", 2: "c"} 空对象
如果扩展运算符后面是一个空对象,则没有任何效果。
let a = {...{}, a: 1} a // { a: 1 }
Int类型、Boolen类型、undefined、null
如果扩展运算符后面是上面这几种类型,都会返回一个空对象,因为它们没有自身属性。
// 等同于 {…Object(1)}
{…1} // {}
// 等同于 {…Object(true)}
{…true} // {}
// 等同于 {…Object(undefined)}
{…undefined} // {}
// 等同于 {…Object(null)}
{…null} // {}
字符串
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象
{…‘hello’}
// {0: “h”, 1: “e”, 2: “l”, 3: “l”, 4: “o”}
对象的合并
let age = {age: 15}
let name = {name: “Amy”}
let person = {…age, …name}
person; // {age: 15, name: “Amy”}
注意事项
自定义的属性和拓展运算符对象里面属性的相同的时候:
自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person = {name: “Amy”, age: 15};
let someone = { …person, name: “Mike”, age: 17};
someone; //{name: “Mike”, age: 17}
自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
let person = {name: “Amy”, age: 15};
let someone = {name: “Mike”, age: 17, …person};
someone; //{name: “Amy”, age: 15}
使用递归来创建一个数字序列
接着上一个挑战,有另外一个机会来用递归函数解决问题。
已经定义好了 rangeOfNumbers 函数,包含两个参数。 函数应该返回一个连续数字数组,startNum 参数开始 endNum 参数截止。 开始的数字小于或等于截止数字。 函数必需递归调用自身,不能使用任意形式的循环。 要考虑到 startNum 和 endNum 相同的情况。
function rangeOfNumbers(startNum, endNum) { if(startNum>endNum){ return []; }else{ const arr=rangeOfNumbers(startNum+1,endNum); arr.unshift(startNum); return arr; } };
function rangeOfNumbers(startNum, endNum) { return endNum < startNum ? [] : rangeOfNumbers(startNum, endNum - 1).concat(endNum); }
Solution 3 (Click to Show/Hide)
function rangeOfNumbers(startNum, endNum) { return endNum < startNum ? [] : [...rangeOfNumbers(startNum, endNum - 1), endNum]; }