JavaScript基础篇(五) 数组

本文整理灵感来源掘金大佬的:数据结构 - 数组。感谢大佬的整理和分享,此文章主要记录数组的主要 API 和自己的一些见解,仅供自己学习参考!!!说实话数组对于 JavaScript 来说绝对是万丈高楼的地基,重要性不言而喻,话不多说开整。

数组的创建


let a = []
let b = new Array()

一般是以上两种,都是用来创建一个空数组。如果我们想初始化一个有长度的数组,并且数组一开始就有值,如下栗子:

let a = [1, 2, 3]; // [1, 2, 3]
let b = new Array(3); // [undefined, undefined, undefined]
let c = new Array([1, 2, 3]); // [1, 2, 3]
let d = new Array(3).fill(1); // [1, 1, 1]
let e = new Array(3).fill([]); // [Array(0), Array(0), Array(0)]
let f = Array.from({ length : 5}, function() {  // [1, 1, 1, 1, 1]
    return 1
})

前面三个估计大家都看得懂,后面三个可能大部分没见过,都属于 ES6 新增的创建方式,这里了解即可,具体应用可以在实际开发中碰到在深入了解,其实基本用的也很少~~~

如何访问数组


我们平时使用的 [1, 2, 3] 这种形式,称为一维数组。而如果数组中嵌套数组,每嵌套多一层,就加一个维度。记住数组的下标是从 0 开始的。如果某个程序猿跟你表白说你是它心中第 0 位的人……你还是拒绝 “他” 吧,已经走火入魔没救了。

  • 一维数组
let a = [1, 2, 3];
console.log(arr[0]); // 1
console.log(arr[1]); // 2
console.log(arr[2]); // 3
  • 二维数组
let arr = [[1, 2, 3], [4, 5]]
for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr[i].length; j++) {
    console.log(arr[i][j])
  }
}
  • 三维数组
let arr = [[1, 2, [3, 4]], [5, 6]]
for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr[i].length; j++) {
    for (let k = 0; k < arr[i][j].length; k++) {
      console.log(arr[i][j][k])
    }
  }
}

更高维度的数组访问依次类推即可!但是这里延伸一个问题,多维数组的访问异常麻烦,开发中我们可能经常会遇到这种需求:将多维数组扁平化处理为一维数组?如何实现呢,可以自己先研究研究,这里会将代码放在 forEach() 这个 API 中,毕竟咱要先记录数组的一些主要方法在去研究如何扁平化,循序渐进+延伸思考才是程序猿精神!!!

数组的增删改查


我们知道:数组是最简单的内存数据结构,而增删改查在任何一种数据结构中都是基础。比如前端:DOM 树形结构的增删改查,比如服务端:SQL 的增删改查。

我们了解数组增删改查的同时,应该代入自己更深入的思考,使用数组提供给我们的 API 实现功能的同时,要同时去了解它的返回值是什么,是否对原数组造成影响。

  • 数组的新增

push() 方法:可向数组的末尾添加一个或多个元素,并返回新的长度,常用语法为:arr.push(newelement1,newelement2,....,newelementX),使用该方法会改变原数组。

let arr = [1, 2, 3, 4]
console.log(arr.push(5)) // 5
console.log(arr) // [1, 2, 3, 4, 5]
console.log(arr.push(6,7,8)) // 8
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8]

unshift() 方法:可向数组的开头添加一个或更多元素,并返回新的长度,常用语法为:arr.unshift(newelement1,newelement2,....,newelementX),使用该方法会改变原数组。

let arr = [1, 2, 3, 4]
console.log(arr.unshift(5)) // 5
console.log(arr) // [5, 1, 2, 3, 4]
console.log(arr.unshift(6,7,8)) // 8
console.log(arr) // [6, 7, 8, 5, 1, 2, 3, 4]

当然掘金博主的文章里还介绍了几种,如:arr[arr.length] = 5 或者 arr[0] = 1 这种;亦或者 splice()concat() 等操作也可以实现新增。前者其实也是数组的一些小技巧应用,后者其实这些 API 有自己更深层次的应用,日常开发中用的最多的新增操作还是 push()。学而思学而用才是我们循序进步的基础!!!

  • 数组的删除

pop() 方法:删除数组的最后一个元素并返回被删除的元素,使用该方法会改变原数组。

let arr = [1, 2, 3, 4, 5, 6]
console.log(arr.pop()) // 6
console.log(arr) // [1, 2, 3, 4, 5]
// 尝试传参
let arr = [1, 2, 3, 4, 5]
arr.pop(3) // 传参无效,只会删除数组的最后一个元素
console.log(arr) // [1, 2, 3, 4]

shift() 方法:把数组的第一个元素从其中删除,并返回第一个元素的值,使用该方法会改变原数组。

let arr = [1, 2, 3, 4, 5]
console.log(arr.shift()) // 1
console.log(arr) // [2, 3, 4, 5]
// 传参情况和 pop() 类似,不接受传参

上面两个删除的方式只能适用比较傻瓜的场景,所以数组也给我们提供了万金油的删改 API,接下来认识老油条:slice() 和 splice() ,日常开发中非常常用的两个 API,因为它俩实在太优秀了,所以我必须在这里写一段话~~~

slice() 方法:可从已有的数组中返回选定的元素。这里要注意:使用 slice() 会返回一个新的数组而不是修改原数组。

不得不说,这个方法牛逼哄哄!数组的拷贝、删除都可以通过这个方法来实现,具体功能取决于 () 里面传递参数的个数。

第一种情况:不传参,此时会对原数组进行深拷贝并生成一个新数组,即如果我们修改新数组的值不会影响原数组。深拷贝浅拷贝搞不明白的可以瞅瞅我写的:JavaScript基础篇(一)

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice()
newArr[0] = 6
console.log(arr) // [1, 2, 3, 4, 5]
console.log(newArr) // [6, 2, 3, 4, 5]

第二种情况:传入一个参数且为正值,此时将原数组从下标为传递的参数值开始一直截取到最后。

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice(1)
console.log(newArr) // [2, 3, 4, 5]

第三种情况:传入一个参数且为负值,此时将截取原数组末尾的元素,传递的参数为多少,就在后面截取多少个。

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice(-2)
console.log(newArr) // [4, 5]

第四种情况:传入两个参数,第一个参数为数组切割的起始值,第二个参数为数组切割的结束值。记住切割的小秘诀:包头不包尾,例如(1,4)就是下标为 1 的元素就要算进去,但是下标为 4 的元素不会算进去。

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice(1, 4)
console.log(newArr) // [2, 3, 4]

slice() 基本的使用场景就是上面这些,最后总结一下它的语法:arr.slice(beginSlice, endSlice)

参数 是否必填 参数描述
beginSlice 从该索引(以 0 为基数)处开始提取原字符串中的字符。
endSlice 结束位置(以 0 为基数),如果不传,默认到数组末尾。

我看 w3school 中好像规定 beginSlice 为必传参数,但是写法中确实可以不传,如不传会进行深拷贝生成一个新的数组,个人感觉应该是该方法的基础函数中做了传参的兼容处理,其实不一定要传。(本理解仅供自己参考)

splice() 方法:向/从数组中添加/删除项目,然后返回被删除的项目。

相较于 slice() 多了一个 p,它和 slice() 一样也是个万金油的方法,它能适用于新增、修改、删除这些场景。但是这里一定要记住它和 slice() 区别最大的一点:

splice():所进行的操作会影响到原数组
slice():所进行的操作不会影响到原数组

第一种情况:不传参,自身返回一个空数组,原数组不改变。官方同样也规定了第一个参数和第二个参数为必传项,此处主要是和 slice() 做对比演示。(不推荐)

let arr = [1, 2, 3, 4, 5]
console.log(arr.splice()) // []
console.log(arr)

第二种情况:传一个参数,当 splice() 传入一个参数时,它的基础函数应该做了判断,第二个参数不写应该是第一个参数后面的元素全部删除。并且同 slice() 一样,它也支持传递负数,即从项目的末尾位置开始删除,执行 splice() 返回被删除的元素集合。该方法会改变原数组。如下栗子:

let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(3)) // [4, 5]
console.log(arr) // [1, 2, 3]
// 如果传递负数要刷出 4 和 5
console.log(arr.splice(-2)) // [4, 5]
console.log(arr) // [1, 2, 3]

第三种情况:传两个参数,第一个参数代表要删除项目的位置(使用负数可从数组结尾处规定位置。),第二个参数代表要删除的项目数量。官方有规定使用该方法必须传入两个参数,当然只传递一个参数也不会报错,不过可能严谨性不够。

let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 2)) // [2, 3]
console.log(arr) // [1, 4, 5]
// 第一个参数为负值的情况
console.log(arr.splice(-1, 1)) // [5]
console.log(arr) // [1, 2, 3, 4]

第四种情况:传入三个参数,前面基本都是删除,但是如果我们传入三个参数,该方法又解锁了新增和修改两个功能,有木有觉得很巧妙,来看看栗子:

// 新增操作
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 0, 6)) // []
console.log(arr) // [1, 6, 2, 3, 4, 5]
// 同时新增多个元素
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 0, 6, 7, 8, 9)) // []
console.log(arr) // [1, 6, 7, 8, 9, 2, 3, 4, 5]
// 修改操作
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 1, 6)) // [2]
console.log(arr) // [1, 6, 3, 4, 5]
// 修改+新增双合一
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 1, 6, 7, 8, 9)) // [2]
console.log(arr) // [1, 6, 7, 8, 9, 3, 4, 5]

看了上面的栗子有木有觉得恍然大悟(有点懵逼),简单总结:

  • 第二个参数为 0 即代表新增,将第三个(及其后面的参数) 追加到数组的下标(第一个参数)元素后面去,自身返回一个空数组,同时该操作会改变原素组。
  • 第二个参数为 1 即代表修改,要修改的元素为数组的下标(第一个参数)元素,将其改为第三个参数(及其后面的参数),自身返回被修改的元素集合(数组),同时该操作会改变原素组。

用法其实也不复杂,可能有点绕,我们也做个表来对该方法做个总结:

参数 是否必填 参数描述
index 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany 必需。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, ..., itemX 可选。向数组添加的新项目。

对比 pop()shift()slice()splice() 应用场景和主要区别?
pop() 和 shift():使用场景类似,一个尾部删除,一个头部删除,两者删除都返回被删除的元素,返回类型为被删除的元素自身的类型。
slice() :可以用来做深拷贝、也可以用来删除数组中的元素,该方法最大的特点就是它自身返回生成一个新的数组且所有操作都不影响原数组。
splice():一个更全面的 API,集合了修改、删除、新增功能,自身返回类型同样是一个数组。唯一的副作用可能就是所有操作都会影响原数组。

  • 数组的修改

最简单场景的修改,不借助任何 API 的方式:

let arr = [1, 2, 3, 4, 5]
// 通过索引下标修改元素
arr[0] = 6
console.log(arr) // [6, 2, 3, 4, 5]

使用 splice() 进行修改,删除里面详细讲解了这个方法,这里就不赘述了!我看掘金的博主大大在修改这个模块下面整理了 filter() 方法,该方法主要的功能其实是对数组进行过滤处理,并且该方法不会改变原数组,而是生成一个新的数组,所以感觉定义在修改这里是不是有点勉强~~~

  • 数组的查询

查询我们肯定要知道查询条件是什么,所以其实数组查询一般都是通过遍历数组,匹配对应的查询条件来实现的。其实这个方法也比较多,例如刚刚说的 filter() 数组过滤处理,也算是一种简单查询;通过 map() 进行遍历匹配也可以算查询;还有 find()findIndex(),感觉这些 API 名字取得也都比较应景;还有 indexOf()lastIndexOf() 等等,这里我们也简单拿几个栗子来演示:

indexOf()方法:可返回数组中某个指定的元素位置,常用语法为:array.indexOf(item,start)

  • item:必须。查找的元素。
  • start:可选的整数参数。规定在数组中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。
  • 返回值:Number 类型,元素在数组中的位置,如果没有搜索到则返回 -1。
const arr = [1, 2, 3, 4, 5, 1, 3, 4, 2, 5]
// 数组中有很多个 1,返回的是第一个 1 出现的位置索引
console.log(arr.indexOf(1)) // 0
// 第二个参数表示从数组中的第五个元素开始查找,所以索引为 0 的 1 被自动忽略
console.log(arr.indexOf(1, 4)) // 5

lastIndexOf()方法:返回一个指定的元素在数组中最后出现的位置,从该字符串的后面向前查找。常用语法为:array.lastIndexOf(item,start)

  • item:必须。查找的元素。
  • start:可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的最后一个字符处开始检索。
  • 返回值:Number 类型,元素在数组中的位置,返回元素在数组中最后一次出现的索引位置,如果没有搜索到则返回 -1
const arr = [1, 2, 3, 4, 5, 1, 3, 4, 2, 5]
// 返回数组中最后一个 5 所在的位置索引
console.log(arr.lastIndexOf(5)) // 9
console.log(arr.lastIndexOf(9)) // -1
console.log(arr.lastIndexOf(4, 2)) // -1
console.log(arr.lastIndexOf(4, 4)) // 3

有没有感觉 lastIndexOf(4, 4) 的第二个参数很奇怪,其实它的第二个参数代表数组中的元素截取点,如 arr.lastIndexOf(4, 2) 就是数组中的前三位里面 [1, 2, 3] 里面有没有 4 这个元素所以返回 -1,而 arr.lastIndexOf(4, 4) 就是数组中的前五位里面 [1, 2, 3, 4, 5] 中包含了 4 且它在数组中的的索引位置为 3 所以返回 3。

includes() 方法:判断一个数组是否包含一个指定的值,如果是返回 true,否则false。常用语法:arr.includes(searchElement, fromIndex)

  • searchElement:必须。需要查找的元素值。
  • fromIndex:可选。从该索引处开始查找 searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 0。
  • 返回值:Boolean 类型,如果找到指定值返回 true,否则返回 false。
const arr = [1, 2, 3, 4, 5, 1, 3, 7, 4, 2, 5]
console.log(arr.includes(2)) // true
// 数组中前2个元素是否包含2,1为数组截取的下标索引,即会截取[1, 2]在进行判定
console.log(arr.includes(2, 1)) // true
// 第二个参数为负数时-2代表截取的数组长度而不是索引,即会截取[2, 5]来进行判定
console.log(arr.includes(2, -2)) // true

当然,如果我们打算用 indexOf() 或者 lastIndexOf() 或者 includes() 来对一些复杂的数组结构进行查找肯定是不行的,就比如下面这个栗子:

const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]

如果我们此时去判断 list 数组中是否有 zhangsan,我们首先尝试用 indexOf() 的方式来验证一下:

list.indexOf('zhangsan') // -1

果然不出所料,这个办法是不行的!此时我们就需要对数组进行遍历,然后对每个对象进行匹配查找,来看看 find() 这个语义话的方法吧:

const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const res = list.find(item => {
  return item.name === 'zhangsan'
})
console.log(res) // {id: 1, name: "zhangsan"}

我们这里先不纠结 find() 的规则,因为它本质上是遍历数组中的每一项然后去根据条件匹配,而数组中的遍历方式有非常多 API,稍后我们会对这些 API 逐个分析比较其优劣势,这里先只演示查找的用法。上面的栗子中我们根据 name==='zhangsan' 查找对了它所在的元素对象,这里安利一个箭头函数返回值的写法,就是箭头函数中如果后面直接跟判断条件返回的话不需要写 return,可以直接使用下面这种写法:

// 一行搞定,直接跟判断条件这样写默认直接返回
const res = list.find(item => item.name === 'zhangsan')
console.log(res) // {id: 1, name: "zhangsan"}

再来看看 findIndex() 这个方法,多么语义化的 API 啊,看名字就知道是找对应条件元素的索引,直接上栗子:

const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const res = list.findIndex(item => item.name == 'wangwu')
// wangwu 在数组中的第三个元素对象里面,其索引下标为 2
console.log(res) // 2

当然,正如我们前面所说的,其实数组的查询有很多个 API 可以用,毕竟查询无非就是遍历数组进行条件匹配,而数组遍历的 API 真的太多了,基础的查找方法就介绍上面 5 种,接下来让我们一起认识数组遍历吧!!!

数组的遍历


遍历不会,学了也废~~~感觉 JavaScript 中用的最多的就是遍历,不管是数组遍历还是对象遍历,基本开发中各种数据的处理都离不开他们,这里我们就罗列一些常用的数组遍历的 API,写一写,记一记,代码真容易!!!

find() 方法

返回数组中满足提供的测试函数的第一个元素的值,没有则返回 undefined。常用语法为:arr.find(callback[, thisArg])。该方法为 ES6 新增方法。

  • callback:在数组每一项上执行的函数,接收三个参数:
    element:必填,当前遍历到的元素
    index:可选,当前遍历到的索引
    array:可选,数组本身
  • thisArg:可选,执行回调时用作 this 的对象
  • 返回值:数组中第一个满足所提供测试函数的元素的值,没有则返回 undefinedfind() 方法不会改变原数组。
// 返回值测试
const array = [5, 12, 8, 130, 44]
console.log(array.find(item => item > 10)) // 12 返回数组中第一个符合条件的值
// 常用语法测试
const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const list1 = [
  { id: 10, name: 'zhaoliu' }
]
function findName(list) {
  return list.find(function (item, index, elem) {
    // item 就是 list 数组中的每一项
    // index 就是每一项的索引 
    // elem 就是 list 本身
    console.log(item, index, elem)
    console.log(this) // [{ id: 10, name: 'zhaoliu' }]
    console.log(this[0].id) // 10
  }, list1) // list1 就是函数中的 this 指向,无则使用 undefined
}
findName(list)

活学活用小测试:寻找数组中的质数,如果找不到质数则返回 undefined。质数是啥......只能被1和自身整除的数就是质数,比如 3,只有 1 和 3 能整除,而 4 有 1、2、4 都能整除,那么 3 就是质数,4就是合数。好吧,原理摸清楚了,该写代码了:

const arr = [4, 6, 9]
const arr1 = [4, 11, 6, 8, 9, 10]
function isPrime(item, index, array) {
  let start = 2
  while (Math.sqrt(item) >= start) {
    if (item % start++ < 1) {
      return false
    }
  }
  return item > 1
}
console.log(arr.find(isPrime)) // undefined
console.log(arr1.find(isPrime)) // 11
findIndex() 方法

返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。常用语法为:arr.findIndex(callback[, thisArg])。该方法为 ES6 新增方法。

  • callback:针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入下面三个参数:
    element:当前函数
    index:当前元素的索引
    array:调用 findIndex 的数组
  • thisArg:可选,执行回调时作为 this 对象的值。
  • 返回值:数组中通过提供测试函数的第一个元素的索引。否则,返回 -1,findIndex() 不会修改所调用的数组。
// 返回值测试
const array = [5, 12, 8, 130, 44]
console.log(array.findIndex(item => item > 10)) // 1 满足条件的第一个元素为 12,其索引为 1。
// 常用语法测试,基本和 find() 差不多,直接借用过来了
const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const list1 = [
  { id: 10, name: 'zhaoliu' }
]
function findName(list) {
  return list.findIndex(function (item, index, elem) {
    // item 就是 list 数组中的每一项
    // index 就是每一项的索引 
    // elem 就是 list 本身
    console.log(item, index, elem)
    console.log(this) // [{ id: 10, name: 'zhaoliu' }]
    console.log(this[0].id) // 10
  }, list1) // list1 就是函数中的 this 指向,无则使用 undefined
}
findName(list)

小测试:查找数组中收个质数元素的索引,如找不到质数,则返回-1。

const arr = [4, 6, 9]
const arr1 = [4, 11, 6, 8, 9, 10]
function isPrime(item, index, array) {
  let start = 2
  while (Math.sqrt(item) >= start) {
    if (item % start++ < 1) {
      return false
    }
  }
  return item > 1
}
console.log(arr.findIndex(isPrime)) // -1
console.log(arr1.findIndex(isPrime)) // 1
map() 方法

返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。该方法按照原始数组元素顺序依次处理元素。map() 不会对空数组进行检测, map() 不会改变原始数组。常用语法为:arr.map(function(currentValue,index,arr), thisValue)

  • function(currentValue, index,arr):必须。函数,数组中的每个元素都会执行这个函数
    currentValue:必须。当前元素的值
    index:可选。当前元素的索引值
    arr:调用 map 方法的数组
  • thisValue:可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。
    如果省略了 thisValue,或者传入 null、undefined,那么回调函数的 this 为全局对象。
  • 返回值:一个由原数组每个元素执行回调函数的结果组成的新数组。
// 返回值测试
const numbers = [1, 4, 9]
const roots = numbers.map(Math.sqrt)
console.log(roots) // [1, 2, 3]
console.log(numbers) // [1, 4, 9]
// 使用 map 重新格式化数组中的对象
const kvArray = [{ key: 1, value: 10 },
{ key: 2, value: 20 },
{ key: 3, value: 30 }];
// 其实只有 obj 有用,只是将所有可选参数罗列出来
const reformattedArray = kvArray.map((obj, index, array) => {
  let rObj = {}
  rObj[obj.key] = obj.value
  return rObj
})
console.log(reformattedArray) // [{1: 10}, {2: 20}, {3: 30}]

通常情况下,map 方法中的 callback 函数只需要接受一个参数,就是正在被遍历的数组元素本身。但这并不意味着 map 只给 callback 传了一个参数。这个思维惯性可能会让我们犯一个很容易犯的错误。参考下例:

['1', '2', '3'].map(parseInt) 返回结果是什么?

const res = ['1', '2', '3'].map(parseInt)
console.log(res) // [1, NaN, NaN]

我们期望输出 [1, 2, 3], 而实际结果是 [1, NaN, NaN]。我估计第一眼看都会有点懵,为啥会这样返回,我们可以将其进行拆解:

// 因为 map 方法本身会返回一个新的数组,拆解出来后其实就是分别返回
// parseInt('1', 0) 1
// parseInt('2', 1) NaN
// parseInt('3', 2) NaN
['1', '2', '3'].map((num, index) => {
  return parseInt(num, index)
})

所以其实这里的问题变成了 parseInt(num, index) 的值返回的是一个什么结果,我们可以去查一下 parseInt 的用法:

parseInt() 函数可解析一个字符串,并返回一个整数,常用语法:parseInt(string, radix)

参数 描述
string 必需。要被解析的字符串。
radix 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。<br />如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。<br />如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。

好吧,扩展了解一下!!!MDN 上关于这个问题有更深入的剖析,感兴趣的可以去瞅瞅,此理解仅供参考~~~

forEach() 方法

用于调用数组的每个元素,并将元素传递给回调函数。该方法对于空数组是不会执行回调函数的。常用语法为:arr.forEach(function(currentValue, index, arr), thisValue)。使用该方法不会改变原数组。

  • function(currentValue, index,arr):必须。函数,数组中每个元素需要调用的函数
    currentValue:必须。当前元素
    index:可选。当前元素的索引值
    arr:当前元素所属的数组对象。
  • thisValue:可选。当执行回调函数 callback 时,用作 this 的值。
  • 返回值:undefined
// 返回值测试
const arr = ['a', 'b', 'c']
const res = arr.forEach(item => console.log(item)) // a b c
console.log(res) // undefined

除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果你需要中止或跳出循环,forEach() 方法不是应当使用的工具。还有如果数组中出现空值或者 undefined 时执行会被跳过,如下栗子:

function logArrayElements(element, index, array) {
  console.log('a[' + index + '] = ' + element);
}
// 注意索引 2 被跳过了,因为在数组的这个位置没有项
[2, 5, , 9].forEach(logArrayElements);
// logs:
// a[0] = 2
// a[1] = 5
// a[3] = 9

延伸思考,通过 forEach() 实现多维数组扁平化?

function flatten(arr) {
  let result = []
  arr.forEach(item => {
    if (Array.isArray(item)) { // 判断遍历的子元素是否仍是数组
      // 使用递归对子元素进行重复执行
      result.push(...flatten(item))
    } else {
      result.push(item)
    }
  })
  return result
}
const arrs = [1, 2, 3, [4, 5, [6, 7], 8, 9]]
const arr = flatten(arrs)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

代码比较简单,不过最新的 ES2019 语法推出了一个数组拍平的 API,可以直接拍平数组,如下栗子:

let arr = [1, 2, 3, [4, 5, 6, [7, 8, 9]], 10].flat()
console.log(arr) //[1, 2, 3, 4, 5, 6, Array(3), 10]

没有彻底怕平,是 bug 吗,其实不是,flat(num)num 可选值默认是拍平 1 层,如果是三维数组可传入 2 即拍平两层,依此类推~~~

let arr = [1, 2, 3, [4, 5, 6, [7, 8, 9]], 10].flat(2)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filter() 方法

创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。 filter() 不会对空数组进行检测。filter() 不会改变原始数组。该方法为 ES6 新增方法。常用语法为:array.filter(function(currentValue,index,arr), thisValue)

  • function(currentValue, index,arr):必须。函数,数组中的每个元素都会执行这个函数
    currentValue:必须。数组中当前正在处理的元素。
    index:可选。当前元素的索引值。
    arr:当前元素所属的数组对象。
  • thisValue:可选。当执行回调函数 callback 时,用作 this 的值。
  • 返回值:一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。。
// 返回值测试
function isBigEnough(item) {
  return item>= 10
}
const arr = [12, 5, 8, 130, 44]
const filterArr = arr.filter(isBigEnough)
console.log(arr) // [12, 5, 8, 130, 44]
console.log(filterArr) // [12, 130, 44]

我们也可以使用 filter() 根据搜索条件来过滤数组中的内容,如下栗子:

const fruits = ['apple', 'banana', 'grapes', 'mango', 'orange']
function filterItems(query) {
  return fruits.filter(item => {
    // 将所有子项转化成小写在进行比对
    return item.toLowerCase().indexOf(query.toLowerCase()) > -1
  })
}
const filterArr = filterItems('ap')
console.log(filterArr)

小练习:通过 filter() 实现数组去重?

function unique(arr) {
  const res = []
  arr.filter(item => {
    if (res.indexOf(item) < 0) {
      res.push(item)
    }
  })
  return res
}
const arr = [10, 20, 30, 20, 50, 40, 30, 60, 20]
const filterArr = unique(arr)
console.log(filterArr) // [10, 20, 30, 50, 40, 60]

知识进阶:使用 Set 实现数组去重~~~

function unique(arr) {
  const set = new Set(arr)
  return [...set]
}
const arr = [10, 20, 30, 20, 50, 40, 30]
const res = unique(arr)
console.log(res) // [10, 20, 30, 50, 40]
every() 方法

测试一个数组内的所有元素是否都能通过某个指定函数的测试,它返回一个布尔值。如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。如果所有元素都满足条件,则返回 true。 every() 不会对空数组进行检测。every() 不会改变原始数组。该方法为 ES6 新增方法。常用语法为:array.every(function(currentValue,index,arr), thisValue)

  • function(currentValue, index,arr):必须。函数,数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 falsy 的元素。如果发现了一个这样的元素,every 方法将会立即返回 false 并结束执行。
    currentValue:必须。当前元素的值。
    index:可选。当前元素的索引值。
    arr:当前元素所属的数组对象。
  • thisValue:可选。当执行回调函数 callback 时,用作 this 的值。
  • 返回值:布尔值。如果所有元素都通过检测返回 true,否则返回 false。
// 返回值测试
function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough);   // false
[12, 54, 18, 130, 44].every(isBigEnough); // true
some() 方法

测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回一个布尔值。该方法会依次执行数组的每个元素,如果有一个元素满足条件,则表达式返回 true , 剩余的元素不会再执行检测。如果没有满足条件的元素,则返回false。some() 不会对空数组进行检测。some() 不会改变原始数组。该方法为 ES6 新增方法。常用语法为:array.some(function(currentValue,index,arr),thisValue)

  • function(currentValue, index,arr):必须。函数,数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 truthy 的元素。如果发现了一个这样的元素,some 方法将会立即返回 true 并结束执行。
    currentValue:必须。当前元素的值。
    index:可选。当前元素的索引值。
    arr:当前元素所属的数组对象。
  • thisValue:可选。当执行回调函数 callback 时,用作 this 的值。
  • 返回值:布尔值。数组中有至少一个元素通过回调函数的测试就会返回true;所有元素都没有通过回调函数的测试返回值才会为false。
// 检测在数组中是否有元素大于 10
function isBiggerThan10(element, index, array) {
  return element > 10;
}

[2, 5, 8, 1, 4].some(isBiggerThan10);  // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true

有没有发现 some()every() 很相似,这两个方法的目的都是为了检测数组中的成员是否满足所给定的条件,只是检测规则不一致。

大概总结了一下,常用的遍历 API 应该就是上面这些.....不知道有没有遗漏,感觉开发中主要用到的也就是这些了吧,做个小总结:

find()findIndex() 方法应该是对应的。find() 返回数组中满足提供的测试函数的第一个元素的值,findIndex() 返回数组中满足提供的测试函数的第一个元素的索引。

map()forEach() 方法应该是对应的,都是对数组进行循环遍历。不同的是 forEach() 返回值为 undefined,而 map() 的返回值是一个由原数组每个元素执行回调函数的结果组成的新数组。

every()some() 方法应该是对应的。这两个方法的目的都是为了检测数组中的成员是否满足所给定的条件,只是 every() 需要数组中所有元素都满足条件才返回 truesome() 只需要数组中一个元素满足条件就返回 true

其它常用 API


不知不觉感觉大部分 API 已经都写到了,当然还有一些非常常用的 API,例如数组合并、数组排序等,

concat() 方法

用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。常用语法为:array1.concat(array2,array3,...,arrayX)

  • array2, array3, ..., arrayX:必需。该参数可以是具体的值,也可以是数组对象。可以是任意多个。
  • 返回值:返回一个新的数组。
// 基础应用
var num1 = [1, 2, 3],
    num2 = [4, 5, 6],
    num3 = [7, 8, 9];
var nums = num1.concat(num2, num3);
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

其实开发中 concat() 方法用的非常之多,这里延伸拓展用 concat() 进行数组拍平,前面使用 forEach()flat() 都做过数组拍平,让我们来看看 concat() 如何实现的呢~~~

首先使用 concat() 拍平二维数组,当然此方法无法拍平更高维度的数组,如下栗子:

let arr = [1, 2, 3, 4, [5, 6], 7]
let newArr = [].concat.apply([], arr)
console.log(newArr) // [1, 2, 3, 4, 5, 6, 7]

小伙伴肯定会好奇,说好的用 concat() 来实现二维数组拍平,你用个 apply() 搞的我一脸懵逼,也没看懂为什么~~~如果我们直接使用 concat() 也是可以的,但是需要做如下处理:

let newArr = [].concat(1, 2, 3, 4, [5, 6], 7)
console.log(newArr) // [1, 2, 3, 4, 5, 6, 7]

前面说了,concat() 支持我们传递多个值,可以为数组,也可以直接为值,所以如果我们像上面那样写就可以直接得到一个一维数组,但是 concat() 如果直接合并一个二维数组得到的结果其实还是一个二维数组:

let arr = [1, 2, 3, 4, [5, 6], 7]
let newArr = [].concat(arr)
console.log(newArr) // [1, 2, 3, 4, [5, 6], 7]

所以这里我们使用 apply() 中转一下,因为它本身接受的传值方式就是数组,然后内部会帮我们进行展开,所以最后会得到 [].concat(1, 2, 3, 4, [5, 6], 7) 这种结构,即达到我们的需求。那么我们就可以利用这个特性,加上递归来进行多维数组的拍平,如下栗子:

const arr = [1, 2, 3, [4, 5, 6, [7, [8], 9]], 10]
function flatten(arr) {
  const isDeep = arr.some(item => item instanceof Array)
  // 如果彻底拍平,所有子元素都不是数组,直接返回该数组
  if (!isDeep) {
    return arr
  }
  // 重复进行拍平
  const res = [].concat.apply([], arr)
  return flatten(res)
}
const newArr = flatten(arr)
console.log(newArr)
reduce() 方法

对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。该方法为 ES6 新增方法。常用语法为:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

  • function(total,currentValue, index,arr):必需。用于执行每个数组元素的函数。
    total:必需。初始值, 或者计算结束后的返回值。
    currentValue:必需。当前元素。
    currentIndex:可选。当前元素的索引。
    arr:可选。当前元素所属的数组对象。
  • initialValue:可选。作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。
  • 返回值:返回计算结果。
// 基础应用
const arr = [1, 2, 3, 4, 5]
const res = arr.reduce((total, item) => {
  return total + item
}, 6)
console.log(res) // 21

说实话,这个方法现在经常看到一些地方用到,但是还是有点迷,可能是以前没用过......感觉 MDN 对于这个方法讲的挺详细的,大家可以移步过去瞅瞅。这里摘两个好理解又实用的栗子:

// 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
var countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  } else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
console.log(names) // { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

这里其实主要用了 initialValue 这个空对象来实现叠加,其实叠加的步骤如下:

{}
{'Alice': 1}
{ 'Alice': 1, 'Bob': 1}
{ 'Alice': 1, 'Bob': 1, 'Tiff': 1}
{ 'Alice': 1, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
{ 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

再来看一个小栗子,按属性对 Object 进行分类:

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];
function groupBy(arr, property) {
  return arr.reduce((acc, obj) => {
    let key = obj[property]
    if (!acc[key]) {
      acc[key] = []
    }
    acc[key].push(obj)
    return acc
  }, {})
}
const res = groupBy(people, 'age')
console.log(res)
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }

感觉看着代码理解起来很容易,但是自己思考和实现思路可能又转不太过来.........不过感觉这个方法确实挺有用的。

from() 方法

从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。该方法为 ES6 新增方法。常用语法为:Array.from(object, mapFunction, thisValue)

  • Object:必需,要转换为数组的对象。
  • mapFunction: 可选,数组中每个元素要调用的函数。
  • thisValue:可选,映射函数(mapFunction)中的 this 对象。
  • 返回值:一个新的数组实例。

该方法用的应该算比较多的,我感觉日常开发中可能主要用于将 arguments 转化成数组。回顾将伪数组转化为数组的方式:

// 获取元素结合,得到一个伪数组,有 length 属性确不能调用数组的方法
const img = document.querySelectorAll('img')
console.log(img instanceof Array) // false
// 使用 Array.from
const imgArr = Array.from(img)
console.log(imgArr instanceof Array) // true
// 使用 slice
const imgArr = [].slice.call(img)
console.log(imgArr instanceof Array) // true
// 使用扩展运算符
const imgArr = [...img]
 console.log(imgArr instanceof Array) // true

再来看下 from() 其它基础用法:

// 从 String 生成数组
Array.from('foo') // [ "f", "o", "o" ]
// 数组去重可以使用 new Set()
const set = new Set(['foo', 'bar', 'baz', 'foo'])
Array.from(set) // [ "foo", "bar", "baz" ]
// 传入第二个参数 mapFunction
console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6]
toString() 方法

返回一个字符串,表示指定的数组及其元素。常用语法为:arr.toString()

  • 返回值:一个表示指定的数组及其元素的字符串。

这个方法比较简单,但是也比较常用,这里就简单罗列一下,看个栗子:

const array1 = [1, 2, 'a', '1a']
console.log(array1.toString()) // "1,2,a,1a"
join() 方法

将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。常用语法为:arr.join(separator)

  • separator:可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。
  • 返回值:一个所有数组元素连接的字符串。如果 arr.length 为0,则返回空字符串。

这个方法和 toString() 一样比较简单,也比较常用,这里也简单罗列一下,看个栗子:

var a = ['Wind', 'Rain', 'Fire'];
var myVar1 = a.join();      // myVar1的值变为"Wind,Rain,Fire"
var myVar2 = a.join(', ');  // myVar2的值变为"Wind, Rain, Fire"
var myVar3 = a.join(' + '); // myVar3的值变为"Wind + Rain + Fire"
var myVar4 = a.join('');    // myVar4的值变为"WindRainFire"
reverse() 方法

将数组中元素的位置颠倒,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。该方法会改变原数组。常用语法为:arr.reverse()

  • 返回值:颠倒后的数组。
let arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]
sort() 方法

原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的。常用语法为:array.sort(sortfunction)

  • sortfunction:可选。规定排序顺序。必须是函数。
  • 返回值:Array。数组在原数组上进行排序,不生成副本。
// 注意,如果直接使用 sort() 并不会得到我们想要的排序结果
const array1 = [1, 30, 4, 21, 100000]
array1.sort()
console.log(array1) // [1, 100000, 21, 30, 4]

如果你打算使用 sort() 直接进行数字排序,可能会大失所望,因为它的排序规则并不是按照数字大小来的.......此时我们可以巧妙使用如下方法:

var numbers = [1, 30, 4, 21, 100000]
// 升序排列 箭头函数返回值简写
numbers.sort((a, b) => a - b)
console.log(numbers) // [1, 4, 21, 30, 100000]
// 降序排列
numbers.sort((a, b) => b - a)
console.log(numbers) // [100000, 30, 21, 4, 1]

日常开发中,可能会碰到要求我们使用商品金额排序或者商品库存排序或者商家距离排序,模拟演示实现该功能?

const friuts = [
  {
    name: 'apple',
    price: 18.5,
    count: 10,
    juli: 10
  },
  {
    name: 'pear',
    price: 1.5,
    count: 5,
    juli: 20
  },
  {
    name: 'banana',
    price: 20.5,
    count: 20,
    juli: 5
  },
]
// 对象排序的 key 值和是否升序还是降序
function sortExp(key, isAsc) {
  return function (x, y) {
    return (x[key] - y[key]) * (isAsc ? 1 : -1)
  }
}
// 按价格升序
friuts.sort(sortExp('price', true))
console.log(JSON.stringify(friuts))
// 按价格降序
friuts.sort(sortExp('price', false))
console.log(JSON.stringify(friuts))
// 按库存升序
friuts.sort(sortExp('count', true))
console.log(JSON.stringify(friuts))
// 按库存降序
friuts.sort(sortExp('count', false))
console.log(JSON.stringify(friuts))
// 按距离升序
friuts.sort(sortExp('juli', true))
console.log(JSON.stringify(friuts))
// 按距离降序
friuts.sort(sortExp('juli', false))
console.log(JSON.stringify(friuts))

当然,排序并不只有 sort排序,随便百度都有 冒泡排序插入排序希尔排序 等等多种排序方法。这里简单罗列两种。

  • 冒泡排序

什么叫冒泡?类似于水中冒泡,较大的数沉下去,较小的数慢慢冒起来,假设从小到大,即为较大的数慢慢往后排,较小的数慢慢往前排。所以冒泡的实现原理其实就是:

数组中有 n 个数,比较每相邻两个数,如果前者大于后者,就把两个数交换位置;这样一来,第一轮就可以选出一个最大的数放在最后面;那么经过 n-1(数组的 length - 1) 轮,就完成了所有数的排序。

知道了冒泡的基本原理,那么我们先来摸清楚数组中相邻两个数的位置交换如何实现,看如下代码:

// 我们希望 arr 中的 1 和 2 交换位置
const arr = [1, 2]
// 定义中转变量,将第一个值赋给它,第二个值赋给第一个值,在将中转变量赋给第二个值从而完成交换。
let temp = arr[0]
arr[0] = arr[1]
arr[1] = temp
console.log(arr) // [2, 1]

好吧,前奏已经铺垫好,我们先来实现第一轮,找出数组中的最大数,并把它放到数组的最后面。

const arr = [3, 4, 1, 2]
for (let i = 0; i < arr.length - 1; i++) {
  // 如果前一个数大于后一个数,就交换两个数的位置
  if (arr[i] > arr[i + 1]) {
    let temp = arr[i]
    arr[i] = arr[i + 1]
    arr[i + 1] = temp
  }
}
console.log(arr) // [3, 1, 2, 4]

看上面的代码,我们已经成功完成了第一轮,将最大的数字 4 放在了最后面。那我们直接将这个操作重复 n-1 轮( n 为数组的长度)。此时我们在上面循环的外层直接在套一层循环。

const arr = [3, 4, 1, 2]
// 总共会执行的轮数
for (let j = 0; j < arr.length - 1; j++) {
  // 每一轮前后两个数都进行比较
  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] > arr[i + 1]) {
      let temp = arr[i]
      arr[i] = arr[i + 1]
      arr[i + 1] = temp
    }
  }
}
console.log(arr) // [1, 2, 3, 4]

排序成功,有没有发现如果将其拆解,其实理解起来一点都不复杂......那么还有没有啥遗留的问题可以优化呢?我们知道在每一轮的比较过程中,我们都已经将最大的数放到了最后,所以我们在内层遍历的时候,因为最后面一个数已经是最大的了,我们是不是不用将最后一个数来进行比较。并且我们内外两层循环都要重复计算 arr.length 的长度,但是数组的长度本身是固定不会改变的,我们是否可以把它抽离到循环的外面赋值给一个常量从而减少计算的性能消耗。好的,考虑的不错,你已经是一个优秀的 boy 了,于是我们代码优化如下:

const arr = [3, 4, 1, 2, 30, 21, 100000]
// 数组长度抽离成常量
const length = arr.length - 1
for (let j = 0; j < length; j++) {
  // 这里要根据外层for循环的 j,逐渐减少内层 for循环的次数
  for (let i = 0; i < length - j; i++) {
    if (arr[i] > arr[i + 1]) {
      let temp = arr[i]
      arr[i] = arr[i + 1]
      arr[i + 1] = temp
    }
  }
}
console.log(arr) // [1, 2, 3, 4, 21, 30, 100000]

冒泡排序比较稳定简单,但是因为两层嵌套循环,所以一般处理的数据量不宜过大。如果遇到大量数据的排序可以选择快速排序。

  • 快速排序

感觉快速排序可以看 阮一峰老师的快排js实现 ,讲的还是很明白的,主要思想为一下三步:

1、选择数组中间数作为基数,并从数组中取出此基数;
2、准备两个数组容器,遍历数组,逐个与基数比对,较小的放左边容器,较大的放右边容器;
3、递归处理两个容器的元素,并将处理后的数据与基数按大小合并成一个数组,返回。

整体实现代码栗子:

const arr = [2, 4, 3, 4, 6, 3, 2, 5, 6, 2, 3, 6, 5, 4]
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr
  }
  // 基准位置,理论上可以任意选取
  let pivotIndex = Math.floor(arr.length / 2)
  // 基准值
  let pivot = arr.splice(pivotIndex, 1)[0]
  let left = []
  let right = []
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }
  // 使用递归不断重复这个过程,就可以得到排序后的数组。
  return quickSort(left).concat([pivot], quickSort(right))
}
const res = quickSort(arr)
console.log(res)

通过阮老师对快排的文章,确实简单易懂,感觉大佬果然是大佬,膜拜一下~~~当然面试中如果碰到排序要求感觉还是可以使用冒泡排序和 sort() 排序,这两个比较简单,也容易理解......更多算法练习可以参考 leedcode

感觉这篇文章真是大杂烩,整理了好几天,中间还休了一个八天的十一假期,出去浪了~~~写到这里感觉也差不多可以停了,后续遇到相关数组问题在进行补充。如果文中有不对的地方或者理解有误的地方欢迎大家提出并指正。每一天都要相对前一天进步一点,加油!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279