本期题目
「 题目 」: 删除给定一维数组(暂不考虑多维)元素中不符合条件的元素,结果返回新的数组
首先,我们在实际开发中会遇到很多处理数组的场景,其实无非就是四个字——"增删查改"。
但是,真的只是简单的增删查改吗?
NO!
如果只是这么简单,那写这篇博文就有点画蛇添足了!
下面让我们分析一下题目所涉及的知识点:
考点思考
- 数组的遍历、处理;
- 遍历:使用循环遍历数组;
- 循环使用 while 即可,结合判断函数的元素判断的返回值;
- 对元素的判断处理;
- 传递一个判断函数(可以灵活定义,拓展性比较好);
- 判断函数:
- 符合条件的返回 true;
- 不符合条件的返回 false
相关回顾
让我们简单回顾一下数组 Array 对象的方法:
- push( ):向数组的末尾添加一个或更多元素,并返回新的长度;
- unshift( ):向数组的开头添加一个或更多元素,并返回新的长度;
pop( ):删除并返回数组的最后一个元素;
shift( ):删除并返回数组的第一个元素;
- concat( ):连接两个或更多的数组,并返回结果;
- join( ):把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分割;
- reverse( ):颠倒数组中元素的顺序;
- sort( ):对数组的元素进行排序;
slice( ):从某个已有的数组返回特定的元素;
splice( ):删除元素,并向数组添加新的元素;
- valueOf( ):返回数组对象的原始值;
- toString( ):把数组转化为字符串,并返回结果;
- toSource( ):返回该对象那个的源码;
- toLocaleString( ):把数组转化为本地数组,并返回结果;
PS:以上数据来自
其次,我们分析一下,题目是对数组元素的删除,有关 Array 对象的删除操作就是以下的四种方法:
pop( ):删除并返回数组的最后一个元素;
shift( ):删除并返回数组的第一个元素;
slice(startIndex,endIndex):从某个已有的数组返回特定的元素;
splice(index,howmany,newitem01,newitem02... ):删除/添加元素,并向数组添加新的元素;
简单分析一下区别:
方法名称 | 方法介绍 | 是否改变原数组 | 返回值 | 备注(适用场景) |
---|---|---|---|---|
pop( ) | 末尾删除 | 会 | 末尾元素 | 获取数组元素 末尾依次删除数组元素 |
shift( ) | 开头删除 | 会 | 开头元素 | 获取数组元素 开头依次删除数组元素 |
slice( ) | 特定截取 | 不会 | 一个新数组 | 返回新的数组,不改变原数组 |
splice( ) | 特定删除 特定添加 | 会 | 处理后的原数组 | 直接改变原有数组 |
再让我们回顾一下常见的几种循环
JavaScript 常见的三种循环
-
while
-
do while
-
for
while 循环
语法结构与使用步骤:
// 语法结构while(/**条件**/){// 代码块;改变条件}// 使用步骤// 1. 初始化变量// 2. 判断条件// 3. 执行代码块// 4. 改变初始条件// 5. 判断条件// 示例var i=0, _arr=[0,1,2,3,4,5];while(i<=_arr.length-1){ // 做点什么 console.log(i); // 改变初始条件 i--;}// 结果// 0,1,2,3,4,5复制代码
do while 循环
语法结构与使用步骤
// 语法结构do {// 代码块} while (// 条件);// 使用步骤:// 1.初始化变量// 2.无条件执行一次// 3.判断条件// 4.执行代码块// 5.改变初始条件// 6.判断条件// 示例var i=0, _arr=[0,1,2,3,4,5,6,7,8];do { // 做点什么 console.log(_arr[i]); i++;} while(i<=_arr.length)// 结果// 0,1,2,3,4,5,6,7,8复制代码
for 循环
语法结构与使用步骤
// 语法结构for(//初始化变量;//判断条件;//改变初始化变量){ // 代码块}// 使用步骤// 1. 初始化变量(只会执行一次)// 2. 判断条件// 3. 执行代码块// 4. 改变初始化变量(在循环体里面去做这件事,避免死循环)// PS:建议不要嵌套多层循环(三层以上),// 防止处理逻辑复杂,易进去死循环,造成CPU使用率飙升,// 内存泄漏,要考虑代码执行的性能问题// 示例// for循环的较优写法var i, _arr=[1,3,6,12,3,7];for(i=_arr.length;i--;){ // 使用_arr[i]做点什么吧 console.log(_arr[i]);}// 结果// 7,3,12,6,3,1复制代码
- do while 会无条件执行一次,再进行条件判断
- 使用 while 时不知道循环次数,使用 for 时知道循环次数
- while 是条件循环,for 是限定了循环次数
代码实现
结合以上基本知识点回顾与分析,现给出以下的实现方案:
- 定义一个 dropElements 方法:两个传参 arr,judgefn;
- arr:原数组参数;
- judgefn:条件判断函数[灵活定义判断条件,结果 return true/false];
- 对传参 arr,进行代码健壮性测试:类型/空值判断;
- 循环对 arr[0]做判断;
- 当检测到数组元素不符合 judgefn 中的条件时,对数组进行截取操作;
- 截取使用
_arrNew = arr.slice(1); // Array.slice(stratindex,[endindex]);
- 截取后的新数组赋值给中间临时变量
_arrNew
; - 最后 return 处理后的
_arrNew
// 代码如下 :function dropElements(_arr, judgefn) { // arr健壮性 // 1.是否是数组 if (!Array.isArray(_arr)) { // console.error("不是数组,请检查参数!"); return false; // 如果是需要对结果做容错处理,可以直接返回空数组 : []; // return []; } // 2.是否为空,为空时,直接返回原值 else if (_arr.length === 0) { console.debug("数组为空,原值返回!"); return _arr; } // 数组元素判断处理 // 判断参数是否是空数组,同时判断是否符合 judgeefn 的判断条件 // 不是空数组的话,并且不满足条件的话--judgefn(_arr[0])为false时,做截取操作并返回新的数组; let _arrNew; while (_arr.length > 0 && !judgefn(_arr[0])) { _arrNew = _arr.slice(1); } // 返回结果 return _arrNew;}复制代码
结果测试
function dropElements(_arr, judgefn) { // 健壮性 // 1.是否是数组 if (!Array.isArray(_arr)) { // console.error("不是数组,请检查参数!"); return false; } // 2.是否为空,为空,直接返回原值 else if (_arr.length === 0) { console.debug("数组为空,原值返回!"); return _arr; } // 数组元素判断处理 // 判断参数是否是空数组,同时判断是否符合 judgeefn 的判断条件 // 不是空数组的话,并且不满足条件的话--judgefn(_arr[0])为false时,做截取操作并返回新的数组; while (_arr.length > 0 && !judgefn(_arr[0])) { _arr = _arr.slice(1); } // 返回结果 return _arr;}// _arr健壮性测试:String,Object,Array,Boolean,Null,Undefined,Number,Symbol// 测试环境(参数,方法)搭建var _undefined, // 提前定义基本类型Undefined的数值 // 条件判断函数 testJudgeFn = (n) => n >= 3, // 测试数据 testData = [ { type: "string", value: "_string", result: "" }, { type: "object", value: { a: 1, b: 2 }, result: "" }, { type: "array", value: [0, 1, 2, 3, 4, 5, 6, 7, 8], result: "" }, { type: "boolean", value: true, result: "" }, { type: "boolean", value: false, result: "" }, { type: "null", value: null, result: "" }, { type: "number", value: 100, result: "" }, { type: "number", value: 0, result: "" }, { type: "number", value: NaN, result: "" }, { type: "undefined", value: _undefined, result: "" }, ];// 结果测试并打印for (let i = 0, testLength = testData.length; i < testLength; i++) { testData[i].result = dropElements(testData[i].value, testJudgeFn) === false ? `Params Type Error:${testData[i].type}` : dropElements(testData[i].value, testJudgeFn); // 打印结果 console.table(`第${i + 1}次测试结果:`, testData[i].result);}复制代码
贴出测试结果的截图:
对于这个简单的数组问题,你想到了什么?
小总结
你是否会有着以下问题:
- 为什么使用传递一个函数而不是把具体判断条体放进函数体内部?
- 为什么使用 while 循环,而不是 for 循环等其他方式?
- 为什么还要考虑它的传参类型,空值,容错...等等?
简单解答:
- 定义一个判断函数,是为了增加它的判断灵活度,可能它的期望是返回数组中的
字符串
、对象
...; - 循环的使用应该根据实际处理的方式,选择相应的,而不是用到循环就是 for 循环,还有时候 do while,while 循环相比 for 循环更优;
- 对于与一个函数,它的传参多数情况下是不可控制,必要的类型校验,空值校验处理,容错处理,代码的健壮性都需要考虑,只有这样才可以保证代码逻辑的稳定性...
更深的思考:
- 数组的 slice()、splice()用法,还使用在哪些具体的实际应用,它的高级使用有哪些?
- 为什么使用单 var 的变量命名方式,有什么好处?
- 一般我们是如何写 for 循环,如何结合实际场景写一个较优的 for 循环处理逻辑?
- 如何拆分一个功能,实现高内聚低耦合、细分确定功能逻辑的单一职责...