有些知识不总结,永远不是自己的。
这篇文章记录了工作中用到的一些js基础知识。很基础,也很重要。


Array的map方法

12.25

map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。


① map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。


② callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。


③ 如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,或者赋值为 null 或 undefined,则 this 指向全局对象。


④ map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。


⑤ 使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。

以上描述来自MDN–Array.prototype.map()

写这篇文章之前,还没有认真阅读过,但是我当把这段描述贴上后,发现接下来的内容都没必要写了-_-#。。MDN大法好。。。既然起了笔,就继续写下去吧。。

示例1:

orders.map((obj, i) => {
  obj.key = i + 1
  obj.num = i + 1
  obj.operate_status = obj.operate_status.length > 10 ? '' : obj.operate_status
  obj.status = obj.status.length > 10 ? '' :obj.status
})

正如描述中第④条所述。上面这段代码是将一个对象数组进行后续处理。给这个对象数组每一项加上key和num的字段,修改它的operate_status和status字段。

(因为这个orders是后台传过来的信息,用在react表格中,需要加上key,然后表格需要一个序号。后面的两个status,后台有时候会返回一段很长的乱码,所以加上了判断)

!!! 又是写出来才发现,这个例子用map方法实现需求其实不好,因为map方法是返回一个新数组,这里只是为了修改原数组,用forEach方法更合适。已在项目代码中改为forEach,看来这篇总结是很有价值的~~哈哈哈

示例2:

const select_ids = this.selected_rows.map(obj => obj.order_id )

如描述①。上面的代码从select_rows这个对象数组中,提取出了key为order_id的值,返回了一个新的数组并赋值给select_ids

这段代码利用es6的箭头函数,显得十分简洁。

示例3:(先写这么多,加班去了,圣诞快乐)
10.27

orders.map(obj => {
  return {
    order_name: obj.name,
    order_time: obj.create_at
  }
})

上面的代码从orders里提取出name,create_at字段,组成了一个新的对象数组:

[
  { order_name: '', order_time: '' },
  { order_name: '', order_time: '' }
]

可以和示例1,示例2进行对比,就能更加深刻的理解map方法,以及ES6箭头函数的使用方法。



用遍历判断空对象

12.26

场景:(这段可以不看)

从后台得到这样一个的数据:

const data = {
  order_items: [],
  id: '',
  extra: {},
  operators: []
}

这个时候需要在react中根据order_itemsoperators渲染出一些信息,可能会在render()方法中这么写

this.state.order_items.map(obj => {...})
this.state.operators.map(obj => {...})

这样需要根据后台返回的数据设置许多state的默认值,然后需要用setState()方法设置许多state,很麻烦:

constructor() {
  this.state = {
    order_items: [],
    operators: []
  }
}

componentDidMount() {
  httpRequest().then(res =>
      this.setState({
        order_items: res.order_items,
        operators: res.operators
      })
    )
}

如果想只在state里设置一个data,然后只需要用setState({data})来控制data的改变,进而控制render渲染,这个时候就需要:

constructor() {
  this.state = {
    data: {}
  }
}

componentDidMount() {
  httpRequest().then(res =>
      this.setState({data})
    )
}

render() {
  return() {
    <div>
      {
        this.state.data.order_items.map(obj => {...})
      }
    </div>
  }
}

这么写会报错!因为第一次render的时候,this.state.date{}
可以这样解决:

this.state = {
  data: {
    order_items: [],
    operators: []
  }
}

但是这么做和感觉和之前的并没有差别。


这个时候,需要判断一下这个data对象是不是{},下面是正文。。。。。

通常基础不扎实的(such as me),会这么写。。:

if (data) {}

或者这么写

if (data.length) {}

或者这么写

if (data === {}) {}

或者用什么typeofinstanceof之类之类。。。最后发现,然并卵。。一脸蒙逼。。

下面是正确的打开方式

ES5方法:

function isEmptyObject(obj) {
  for (var x in obj) {
    return false;
  }
  return true;
}

if (isEmptyObject(data)) {}

ES6方法:

if (Object.keys(data).length) {}

上面两种方法其核心都是对对象进行遍历。Object.keys()解释如下:

Object.keys 返回一个所有元素为字符串的数组,其元素来自于从给定的对象上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。

下面再列举一些遍历对象属性的方法(详细解释戳这里):

  • Object.getOwnPropertyNames(obj)
  • Object.getOwnPropertySymbols(obj)
  • Reflect.ownKeys(obj)

此外,与Object.keys() 相关的方法是Object.value()Object.entries()
他们返回都是对象非继承的所有可遍历的属性或属性值的数组
具体差别是,keys方法返回属性的数组,value方法返回属性值的数组,entries方法返回键值对的数组。


下面继续废话。。。
之前的代码,现在改为:

constructor() {
  this.state = {
    data: {}
  }
}

componentDidMount() {
  httpRequest().then(res =>
      this.setState({ data })
    )
}

render() {
  let data
  if (Object.keys(this.state.data).length) {
    data = this.state.data
  }
  return (
    <div>
      {
        data &&
        data.order_items.map()
      }

      {
        data &&
        data.operators.map()
      }
    </div>
  )
}

项目中的这个问题目前用这种方法解决(现在我觉得是比较好的方法了)。。之后发现更好的解决方式,再继续在下面废话吧。。。。

3.22补充:ES7中有个关于null传导运算符的提案,提供了一个判断对象属性是否存在的简写方法,具体在阮老师ECMAScript6入门9.12中提到。
因此,上面的短路判断就可以改写为data?.order_items



用短路操作判断

1.14

&&||属于布尔运算符,所谓布尔运算符,就会讲表达式转换为布尔值

所谓的短路操作指的是:
且运算符(&&)会返回第一个布尔值为false的运算对象的值,其后的运算对象会被忽略。如果每个运算对象的布尔值都是true,则返回最后一个运算对象的值(不是布尔值)。

或运算符(||)会返回第一个布尔值为true的运算对象的值,其后的运算对象会被忽略。如果每个运算对象的布尔值都是false,则返回最后一个运算对象的值(不是布尔值)。

jQuery的源码中,用了很多&&来代替if语句

i && doSomething();

if (i) {
  doSomething();
}

一般来说,这两种写法是等价的。但是有一点不同的是,既然&&是个布尔运算符,那么它就会返回一个布尔值为false的值。当在不需要返回值的时候,还是用if语句比较稳妥。或者用三元运算符表示

i ? doSomething() : null

||运算符同理也可以代替if语句,不过常用来给变量设置默认值。



arguments对象相关

5.1

  1. arguments对象可以为参数赋值,但是严格模式不允许这么做。
  2. Es6的rest参数,即...变量名,可以代替arguments获取函数其余的参数。
  3. 与arguments对象不同的是,rest参数中的变量就是一个数组,可以在函数中直接用数组的方法,而不用之前用arguments时将类数组转换数组的操作。
  4. 将类数组转换为数组有以下几种常用写法
    • Array.prototype.slice.call(arguments)
    • [].slice.call(arguments)
    • Array.from(arguments)
    • [...arguments]
      ``


数组的push、concat与扩展操作

5.1

这里只是讨论三者在数组合并方面的不同。
将以下合并到一个数组d中:
const a = [1, 2, 3]; const b = '123'; const c = 123;

  1. 用push
    const d = []; d.push(...a, b, c);
  2. 用扩展运算符
    const d = [...a, b, c]
  3. 用concat
    coust d = [].concat(a, b, c)

乍一看没什么大区别,但是,要是有一个变量,它的类型是不定的,比如可能是a,b,c中任意一个,我们就只能用concat来操作了。

具体的问题,总结一下

  1. push以一个数组为参数时,会把这个数组整体作为一个数组的一项,所以需要用扩展运算符才能达到concat的效果。
  2. 单纯的扩展运算符操作number类型的会报错,操作string类型会把字符串分成单个字符。

所以,对于合并数组这个需求,更健壮的实现还是要用concat



数组reduce方法的应用

5.24

提到 reduce 方法,第一印象应该就是它的累加作用 [1, 2, 3, 4, 5].reduce((a, b) => a + b); // 15

后来接触了函数式编程的知识,进一步认识到 reduce 方法是一个纯的方法,和map一样不会有副作用,符合函数式编程的思想。

但是在实践过程中,对 reduce 方法理解不够深入,使用的场景并不是很多。下面做个总结,以便更好的理解。

场景1:利用 reduce 第二个参数来从数组中生成新对象

reduce 第二个参数可以理解为一个初始值,当这个初始值存在时,reduce 方法会把这个初始值作为第一次回调函数返回的值(即回调函数中的第一个参数),回调函数从数组的第0项开始处理(第二的参数为array[0], 第三个参数为0)。如果没有这个初始值,reduce 方法的回调函数则会从数组的第1项开始处理(第二的参数为array[1], 第三个参数为0,第一个参数则为array[0])。

比如有这样一个数据

1
2
3
4
5
const people_arr = [
{ id: 2017001, name: '张三' },
{ id: 2017002, name: '李四' },
{ id: 2017003, name: '路人甲' }
];

我们想得到这样的数据

1
2
3
4
5
const people_obj = {
2017001: '张三',
2017002: '李四',
2017003: '路人甲'
};

之前我一直是这么做的

1
2
const people_obj = {};
people_arr.forEach(people => people_obj[people.id] = people.name);

现在利用 reduce 可以这样做

1
2
3
4
const prople_obj = people_arr.reduce((obj, key) => {
obj[key.id] = key.name;
return obj;
}, {});

这样的写法相对与之前,形式上复杂一点,但是语义化更好,可维护性更高。之前的写法中,对需要得到的变量(people_obj)的定义和生成是分开的,要是还有其他逻辑代码,就显得很复杂,可读性很差,而且是不纯的。用了reduce就可以避免这些情况。

场景2:利用 reduce 第二个参数来从对象数组中生成符合条件的新对象数组

比如有这样一个数据

1
2
3
4
5
const people_arr = [
{ id: 2017001, first_name: '张', last_name: '三', is_male: true },
{ id: 2017002, first_name: '李', last_name: '四', is_male: false },
{ id: 2017003, first_name: '路人', last_name: '甲', is_male: true }
];

我们想得到这样的数据

1
2
3
4
const male_arr = [
{ id: 2017001, name: '张三' },
{ id: 2017003, name: '路人甲' }
];

之前我是这么写的

1
2
3
4
5
6
7
const male_arr = [];
people_arr.forEach(people =>
people.is_male && male_arr.push({
id: people.id,
name: people.first_name + people.last_name
})
);

这里虽然是生成新数组,但用map就需要这样写:

1
2
3
4
5
6
7
8
const male_arr = people_arr.map(people => {
if(people.is_male) {
return ({
id: people.id,
name: people.first_name + people.last_name
})
}
}).filter(o => !!o);

因为map的特点,如果不加filter,male_arr会有一项是undefined。

当用了reduce以后,之前的写法就显得比较low了。

1
2
3
4
5
6
const male_arr = people_arr.reduce((arr, people) => (
people.is_male ? arr.concat({
id: people.id,
name: people.first_name + people.last_name
}) : arr
), []);