前端重点开发规范,快来趁机偷偷学八股!

216 阅读8分钟

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>