for...in...遍历数组的问题
for..in主要是用来遍历对象的,也能遍历数组和字符串。
因为for..in会把对象原型上的原型属性都遍历出来,所以如下案例中,会多出现一项函数。
Array.prototype.searchItem = function(value){ //函数已被简化
return right;
}
var a = [1, 2, 3, 4];
for(var i in a){
console.log(a[i]);
}
// 1
// 2
// 3
// 4
// function (value){
// return right;
// }
// 输出的结果中,多出了一行,这一行是一个函数,不是我们定义在数组中的值。到此这个问题就出来了。这个真的是你的本意吗?答案是否定的。
// 综上所述,用for...in...在通常情况下确实可以正确运行,但是如果我们的代码时放到别人的环境中也想跑,那请不要使用for...in...来循环数组
同样的用forEach去遍历,就只会遍历出a对象上的属性(即a数据内的数据)
a.forEach(item=>{
console.log(item)
})
// 1
// 2
// 3
// 4
for...in 和 forEach的区别
for...in主要遍历对象(的属性名),也能遍历数组和字符串
let o = {a:2};
for(let key in o){
console.log(key)
}
// a
遍历字符串的话,key就是字符串的索引,value就是字符串的值
但是forEach只能遍历数组,forEach里面有个回调函数,这个回调函数有三个值
callbackFn(element,index,array)
-
element- 数组中正在处理的当前元素。
-
index- 数组中正在处理的当前元素的索引。
-
array- 调用了
forEach()的数组本身。
- 调用了
MDN介绍:
forEach()方法对数组的每个元素执行一次给定的函数。
语法
forEach(callbackFn)
forEach(callbackFn,thisArg)
这个callBackFn会为数组中每个元素执行的函数,并会丢弃它的返回值。
意思就是,这个forEach不需要return,也不会有return值,可以理解为单纯地就是用来处理或改变数组中的每一项元素。
与 map() 不同,forEach() 总是返回 undefined,而且不能继续链式调用。其典型的用法是在链式调用的末尾执行某些操作。
forEach对于空数组是不会执行回调函数的。
forEach 循环本身不支持 continue 和 break语句的
如果想实现continue效果,可以使用 return。
如果要实现break效果,建议使用 every 和 some 循环
数组方法集锦
map方法
map: map循环返回一个经过调用函数处理后的新的数组
map 循环不会对空数组进行检测
map 循环必须 return
map 循环不会修改原数组
map 循环接受三个参数,[第一参数]为数组中的每一项,[第二参数]为数组的下标,[第三个参数]为你要遍历的数组本身。第二和第三参数都是可选的
map 循环会针对每一项都进行循环,如果跳过则会返回 undefined
let aNewArr = aArr.map((item, index, arr) => {
return item;
});
跳过循环的示例: 这种写法不会通过eslint的校验,这里只是做个案例说明
let aNewArr = aArr.map((item, index, arr) => {
if (item > 3) {
return item;
}
});
console.log(aNewArr);
// \[undefined, undefined, undefined, 4, 5, 6, 7]
some方法
some: some 循环查找数组中任意符合条件的元素并返回boolean值,当数组中有任意元素符合条件就返回 true 否则返回 fasle
some 循环接受三个参数,[第一参数]为数组中的每一项,[第二参数]为数组的下标,[第三个参数]为你要遍历的数组本身。第二和第三参数都是可选的
some 循环会依次执行数组的每一个元素
如果有一个元素满足条件,则返回 true,且剩余的元素不会再执行检测 即 循环结束
some 循环不会对空数组进行检测
some 循环不会改变原数组
let bFlag1 = aArr.some((item, index, arr) => {
return item > 3
});
console.log(bFlag1);
// true
let bFlag2 = aArr.some((item, index, arr) => {
return item > 9
});
console.log(bFlag2);
// false
reduce方法
reduce: reduce() 循环接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
arr.reduce((prev,cur,index,arr) => { ... }, init);arr 表示原数组
prev 表示上一次调用回调时的返回值,或者初始值 init
cur 表示当前正在处理的数组元素
index 表示当前正在处理的数组元素的索引,若提供 init 值,则索引为0,否则索引为1
init 表示初始值
let iTotal = aArr.reduce((total, item, index, arr) => {
return total + item;
}, 0);
console.log(iTotal);
// 28
let iTotal = aArr.reduce((total, item, index, arr) => {
return total + 5;
}, 0);
console.log(iTotal);
// 35
// 数组项的最大值
let iMax = aArr.reduce((prev, next)=>{
return Math.max(prev, next);
})
console.log(iMax);
// 7
// 数组去重
var aNewArr = [1, 2, 3, 2, 3, 4, 5, 3, 7, 6].reduce((prev, next) => {
prev.indexOf(next) === -1 && prev.push(next);
return prev;
},[]);
console.log(aNewArr);
// [1, 2, 3, 4, 5, 7, 6]
// 数组拼接
let aNewArr = [[0, 1], [2, 3], [4, 5]].reduce(( accumulator, currentValue ) => {
return accumulator.concat(currentValue);
}, []);
console.log(aNewArr);
// [0, 1, 2, 3, 4, 5]
filter方法
filter: filter() 循环返回一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
filter 循环接受三个参数,[第一参数]为数组中的每一项,[第二参数]为数组的下标,[第三个参数]为你要遍历的数组本身。第二和第三参数都是可选的
filter 循环不会对空数组进行检测
filter 循环不会改变原数组
let aNewArr = aArr.filter((item, index, arr) => { return item > 3 });
console.log(aNewArr);
// [4, 5, 6, 7]
ES6新增操作数组的方法
find: 传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索
let aArr = [1, "2", 3, 3, "2"]
console.log(aArr.find(n => typeof n === "number"))
// 1
findIndex(): 传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索
let aArr = [1, "2", 3, 3, "2"]
console.log(aArr.findIndex(n => typeof n === "number"))
// 0
fill():用新元素替换掉数组内的元素,可以指定替换下标范围。
let aArr = [1, "2", 3, 3, "2"]
aArr.fill("aa", 1, 4);
console.log(aArr);
// [1, "aa", "aa", "aa", "2"]
copyWithin():选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围
语法:arr.copyWithin(target, start, end)
let aArr = [1, 2, 3, 4, 5]
console.log(aArr.copyWithin(3))
// [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2
from : 将类似数组的对象(array-like object)和可遍历(iterable)的对象转为真正的数组
let aBar = ["a", "b", "c"];
Array.from(aBar);
// ["a", "b", "c"]
Array.from('foo');
// ["f", "o", "o"]
of: 用于将一组值,转换为数组。这个方法的主要目的,是弥补数组构造函数 Array() 的不足。因为参数个数的不同,会导致 Array() 的行为有差异
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
entries() 返回迭代器:返回键值对
let aArr = ['a', 'b', 'c'];
for(let v of aArr.entries()) {
console.log(v)
}
// [0, 'a'] [1, 'b'] [2, 'c']
values() 返回迭代器:返回键值对的value
let aArr = ['a', 'b', 'c'];
for(let v of aArr.values()) {
console.log(v)
}
//'a' 'b' 'c'
keys() 返回迭代器:返回键值对的key
const arr = ['a', 'b', 'c'];
for(let v of arr.keys()) {
console.log(v)
}
// 0 1 2
includes: 判断数组中是否存在该元素,参数:查找的值、起始位置
var a = [1, 2, 3];
a.includes(2); // true
a.includes(4); // false
隐式转换问题
看到了这一条规范
- 2.1.16 [建议]:转换成boolean时,使用!!
let iNum = 3.14;
let bNum = !!iNum;
console.log(bNum) //true
好奇为什么会这样,写了几个输出
let n = 134;
console.log(!n);//false
console.log(!!n);//true
这个!!的原理在于,当!跟上一个数字的时候,作用是把这个数隐式转换为Boolean值false,再次!取反则变为了true
附上关于隐式转换的具体知识
使用对象字面量创建对象
在创建对象的时候,最好用字面量的方式,比起new一个对象来说,性能会更好一点
{}是引擎直接解释的,new的方式要调用一个Object内部构造器,所以使用字面量要略微快一点点
// good
let oObj = {};
// bad
let oObj = new Object();
Object.prototype.hasOwnProperty()
根据MDN描述:
hasOwnProperty()方法返回一个布尔值,表示对象自有属性(而不是继承来的属性)中是否具有指定的属性。
- 2.1.24[建议]:for in 遍历对象时, 使用 hasOwnProperty 过滤掉原型中的属性
在之前写深拷贝的函数的时候,其实也遇到了这一行代码Object.prototype.hasOwnProperty.call(oObj, key)
let oObj = {
name: 'someone',
age: 28
};
let oNewInfo = {};
for (let key in oObj) {
if (Object.prototype.hasOwnProperty.call(oObj, key)) {
oNewInfo[key] = oObj[key];
}
}
判断我们遍历的这个对象属性是不是在这个对象自身身上的,如果是,就进行拷贝操作,如果发现这个对象属性是从原型上继承下来的或其他,那么就不执行拷贝操作
清空数组的方式
推荐用.length = 0
let arr = [1,2,3];
arr.length = 0;
console.log(arr); //[]
减少DOM操作
DOM操作比较消耗性能
比如创建列表,有两种推荐的方式:
- 在循环体中 createElement 并 append 到父元素中
- 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML
v-for和v-if
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中,将会影响速度,尤其是当之需要渲染很小一部分的时候,所以,不推荐v-if和v-for同时使用
<template>
<ul>
<!-- 不推荐:如下:100个user中只需要使用一个数据,也会循环整个数组 -->
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
<!-- 使用推荐方式:使用计算属性先过滤数据,再渲染 -->
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
computed: {
// 使用计算属性先过滤数据,再渲染
activeUsers: () => {
return this.users.filter((user) => {
return user.isActive
})
}
}
}
</script>
当时不是面试啥公司的时候,问到了这个v-for和v-if的优先级、以及能不能一起使用,如果场景要求下我们必须会同时用到v-for和v-if,那么我们怎么去解决这个问题呢
用计算属性去替代v-if,过滤后再放到v-for中去循环
学会巧用computed,比如说下面的场景:
<template>
<ul>
<!-- 不推荐-->
<!-- 必须是男孩并且超过18并且健康,除非是个神童也可以做这份工作 -->
<li v-if="(isBoy && isAbove18 && isHealthy) || isRemarkable">做这项工作</li>
<!-- 推荐 -->
<li v-if="bCanDoThisWork">做这项工作</li>
</ul>
</template>
<script>
export default {
computed: {
// 使用计算属性
bCanDoThisWork: () => {
// 必须是男孩并且超过18并且健康,除非是个神童也可以做这份工作
return (isBoy && isAbove18 && isHealthy) || isRemarkable;
}
}
}
</script>