JS数组基本方法

  • 1. instanceof能够正确的判断对象的类型,其内部机制是经过判断对象的原型链中是否是能找到类型的prototype。
let a = ['a','b','c','d','e','f']
console.log(a instanceof Array) // true
复制代码
  • 2. Array.isArray()
let a = ['a','b','c','d''e','f']
Array.isArray(a) // true
复制代码
  • 3. Object.prototype.toString.call()
let a = ['a','b','c','d''e','f']
Object.prototype.toString.call(a) // [object Array]
复制代码
  • 4. join() 方法将一个数组(或类数组对象)的全部元素连成一个字符串并返回这个字符串。且不会改变原数组。默认链接符为“,”
var elements = ['A', 'B', 'C'] 

console.log(elements.join()) // A,B,C

console.log(elements.join('')) // ABC

console.log(elements.join('-')) // A-B-C

复制代码
  • 5. reverse() 方法将数组中元素的位置颠倒。此方法会改变原数组。
var elements = ['A', 'B', 'C'] 
elements.reverse()
console.log(elements) // ['C','B','A']
复制代码
  • 6. sort() 方法用于对数组排序,并返回数组。默认的排序顺序是根据字符串Unicode码点。此方法会改变原数组。

sort()方法与reduce()方法结合可用于数组去重。后端

var array1 = [1, 30, 4, 21];
array1.sort();
console.log(array1);// [1, 21, 30, 4]
复制代码
  • 7. concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
var array1 = ['a', 'b', 'c']
var array2 = ['a', 'e', 'f']

console.log(array1.concat(array2)) // ["a", "b", "c", "a", "e", "f"]
复制代码
  • 8. slice(begin,end) 方法返回一个新数组,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。 返回一个含有提取元素的新数组。

描述:slice 不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:数组

1.若是该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。若是被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。promise

2.对于字符串、数字及布尔值来讲(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另外一个数组。浏览器

若是向两个数组人一中添加了新元素,则另个一不会受到影响。bash

var arr = [1, 2, 3, 4, 5]
arr.slice(0) // [1, 2, 3, 4, 5]
arr.slice(1, 2) // [2]
arr.slice(1, 3) // [2, 3]

浅拷贝的例子:
var myHonda = { color: 'red', wheels: 4, engine: { cylinders: 4, size: 2.2 } };
var myCar = [myHonda, 2, "cherry condition", "purchased 1997"];
var newCar = myCar.slice(0, 2);

console.log('myCar = ' + JSON.stringify(myCar));  //  [{color: 'red', wheels: 4, engine: {cylinders: 4, size: 2.2}}, 2,
       'cherry condition', 'purchased 1997']
console.log('newCar = ' + JSON.stringify(newCar)); // [{color: 'red', wheels: 4, engine: {cylinders: 4, size: 2.2}}, 2]
console.log('myCar[0].color = ' + JSON.stringify(myCar[0].color)); // red
console.log('newCar[0].color = ' + JSON.stringify(newCar[0].color)); // red

myHonda.color = 'purple';
console.log('The new color of my Honda is ' + myHonda.color); // The new color of my Honda is purple

console.log('myCar[0].color = ' + myCar[0].color); // purple
console.log('newCar[0].color = ' + newCar[0].color); // purple

复制代码

浅拷贝:上面的demo浅拷贝值值拷贝了数组的第一层,没有进行深层的拷贝。相似于Object.assign()app

var arr = [1, 3, 4, 5]
arr.splice(1,0,2) // []
console.log(arr) // [1,2,3,4,5]
arr.splice(4,1) // [5]
console.log(arr) // [1,2,3,4] 删除5

var months = ['Jan', 'March', 'April', 'June'];
months.splice(1, 0, 'Feb');
console.log(months); // ['Jan', 'Feb', 'March', 'April', 'June']
months.splice(4, 1, 'May') // ['Jan', 'Feb', 'March', 'April', 'May']

复制代码

语法:dom

开始位置  整数,表示要移除的数组元素的个数  要添加进数组的元素
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
复制代码
var a = [1,2,3,4,5,6]
for(var i = 0; i < a.length; i++) {
   // 在删除的过程当中数组的index也在动态变化
    a.splice(i,1)
}
console.log(a) // [2,4,6]
复制代码

Array.prototype.slice()与Array.prototype.splice()的区别函数

Array.prototype.slice()与Array.prototype.splice()的区别

slice: 原意为一块面包切下来的薄片。引伸意思为总体中的一部分.
结合数组的定义,此接口是从数组中切下来的一部分,或者一片,可能片的长度有点大就称为一段...

此方法不会改变原数组。是浅拷贝(只拷贝了一层)。

说明:
1.浅拷贝
2.slice()无参数,则拷贝整个数组
3.slice(begin),只有开始入参,即end省略,则表示从begin到arr.length元素都取得。
4.slice(begin,end)取得下标为[begin, end-1]的元素
5.begin或end为负值,则表示从数组后端开始计数

splice: 原意为拼接、结合、粘接(胶片、磁带等)。将影片或者绳子,使用胶结或者绞接的方式,将其链接起来,其链接部位称为。有一部《SPLICE》的电影(中文名:人兽杂交)。这个故事, 是说两个科学家, 违反科学道德, 利用人类基因和兽类基因, 作杂交试验, 生产出一个新的物种, 这个物种就是 杂交(splice), 此杂交是基因序列的 拼接, 进而产生杂交的结果, 产生我的不人,兽不兽的物体。 胶结处、拼接处,即为splice



splice:改变现有数组内容,其功能包括删除元素和添加元素。

说明:
1.splice方法改变现有数组内容
2.splice能够删除元素和添加元素。删除和添加 正体现了拼接的要素, 删除-破坏了现有数组,破坏以后须要进行修补,

修补的结果就是拼接新的数组段,splice 拼接新的数组段, 合并到现有数组中。

这两个步骤, 多么相似 电影人兽杂交中基因拼接的标准步骤 (挖掉人类基因中坏的部分, 在这个位置上不上兽类的良好基因), 怀疑js这个接口的定义这, 是收到splice这个电影的启发。 不然按照英文定义, splice只有拼接的含义, 没有挖掉的含义, 不该该在此接口中实现挖掉的功能。
3.splice(begin, count), 表示从begin位置开始删除count个元素。
 begin为负值表示从数组尾部向前计数。 若是 count大于数组的从begin位置计算的元素个数, 则到结束全部的元素都将被删除
4.splice(begin,0,itema),表示在begin位置后面添加itema元素
5.splice(begin,1,itema,itemb),全功能用法,在begin位置开始删除一个元素,而后在删除元素的位置替换上元素  itema 和 itemb。
6.splice返回删除元素的数组。



复制代码
  • 10. push() 方法将一个或多个元素添加到数组的末尾,并返回改数组的新长度。
var arr = [1,3,5,7]
arr.push(8) // [1,3,5,7,8]
复制代码
  • 11. unshift() 方法将一个或多个元素加到数组的开头,并返回该数组的新长度。
var arr = [1, 2, 3]
console.log(arr.unshift(4, 5))  // 5
console.log(arr)  [4, 5, 1, 2, 3]
复制代码
  • 12. pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
var arr = [1, 2, 3]
console.log(arr.pop())  // 3
复制代码
  • 13. shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
var arr = [1, 2, 3]
console.log(arr.shift())  // [2,3]
复制代码
  • 14. 遍历

for in测试

var arr = [1, 2, 3]
for (var i in arr) {
  console.log(arr[i]) 
}
// 1 
// 2
// 3
var obj = {a:1; b:2; c:3}
for (var i in arr) {
    console.log(obj[i])
}
// 1
// 2
// 3
复制代码

for of 循环可使用的范围包括数组、Set 和 Map 结构、某些相似数组的对象等。ui

const arr = [1, 2, 3];
for(let v of arr) {
  console.log(v); 
}
// 1
// 2
// 3
复制代码

map() 方法建立一个新数组,其结果是该数组的每个元素都调用一个提供的函数后返回的结果。map()方法在必定程度上就是为生成新数组而生的

var arr = [1,2,3]
var mapArr = arr.map(x => x * 3)
console.log(mapArr) // [1,6,9]

var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9]
复制代码

forEach() 方法对数组的每个元素执行一次提供的函数。

var arr = [1,2,3]
arr.forEach(function(item,index,arr){
    console.log(`arr[${index}]=${item}`)
})
// arr[0]=1
// arr[1]=2
// arr[2]=3
复制代码

将for循环改成forEach

const items = ['item1', 'item2', 'item3']
const copy = []
// for 循环
for (let i = 0; i< items.length; i++) {
    copy.push(items[i])
}
// forEach
items.forEach(function(item){
    copy.push(item)
})
复制代码

若是数组在迭代时被修改了,则其它元素会被跳过

let words = ['one','two','tree','four']
words.forEach(function(word) {
    console.log(word)
    if (word === 'two') {
        words.shift()
    }
})
// one
// two
// four
当到达包含值"two"的项时,整个数组的第一个项被移除了,这致使全部剩下的项上移一个位置。由于元素"four"如今在数组更新前的位置,"tree"会被跳过。forEach()不会在迭代以前建立数组的副本。
复制代码

对于数组和可迭代对象的遍历方法,咱们须要从不一样的维度进行对比,方法的功能性,方法的应用场景,方法的兼容性,方法的效率,方法的返回值以及是是否改变原始数组。这些方法是如何实现的,而且考虑低版本浏览器的兼容性。 forEach()与map()every()与some(),filter()与find()、findIndex(),keys()、values()与entries(),reduce()与reduceRight()

js遍历方法

ES5方法:

forEach arr.forEach(fn,thisArg) 返回undefined

map arr.map(fn,thisArg) 返回新数组

every arr.every(fn,thisArg) 返回true或false,一假即为假

some arr.some(fn,thisArg) 返回true或false,一真即为真

filter arr.filter 返回新数组

reduce 返回一个值

reduceRight 返回一个值

for-in 循环获得key

ES6方法

for-of 用于循环数组,循环获得value

find & findIndex arr.find(fn,thisArg),arr.findIndex(fn,thisArg) 返回数组中第一个知足条件的元素(元素的索引),若是没有则返回undefined(或-1)

entries arr.entries() 返回一个数组迭代器对象

keys arr.keys() 返回一个数组索引的迭代器

values arr.values() 返回一个数组迭代器对象,该对象包含数组中每一个索引的值

Symbol.iterator 返回一个数组迭代器对象 arrSymbol.interator

  • 15. filter() 方法建立一个新数组,此新数组包含经过所提供函数实现的测试的全部元素。
var arr = [1,2,3]
const result = arr.filter(x => x >1)
console.log(result) // [2,3]
复制代码
  • 16. some() 方法测试数组中的某些元素是否经过由提供的函数实现的测试(1真即真)。数组中的每个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(便可转换为布尔值 true 的值)。若是找到了这样一个值,some 将会当即返回 true。不然,some 返回 false。
var arr = [1,2,3]
var even = arr.some(x => x > 5) // false
复制代码
  • 17. every() 方法测试数组的全部元素是否都经过了指定函数的测试(一假即为假)。
var arr = [1,2,3]
var passed = arr.every(x => x < 5) // true
复制代码
  • 18. reduce() 方法对累计器和数组中的每一个元素(从左到右)应用一个函数,将其简化为单个值。accumulator(累加器),currentValue(当前值)。此方法必须传入两个参数。

reducer函数接收4个参数:

  • 1.Accumulator (acc) (累计器)
  • 2.Current Value (cur) (当前值)
  • 3.Current Index(idx) (当前索引)
  • 4.Source Array(src) (源数组)

语法

arr.reduce(callback[, initialValue])
复制代码

参数:

  • callback 执行数组中每一个值的函数,包含四个参数:1.Accumulator (acc) (累计器) 2.Current Value (cur) (当前值) 3.Current Index(idx) (当前索引)(可选) 4.Source Array(src) (源数组)(可选)
  • initialValue (可选) 做为第一次调用callback函数时的第一个参数的值。若是没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce将报错。 reduce为数组中的每个元素执行callback函数,不包括数组中被删除或从未被赋值的元素。
var arr = [1,2,3,4]
var reducer = arr.reduce((accumulator,currentValue) => accumulator * currentValue) // 24
复制代码

数组求和

var sum = arr.reduce((accumulator,currentValue) => accumulator + currentValue) // 10
复制代码

找出数组中最大值

var max = arr.reduce((accumulator,currentValue) => accumulator > currentValue ? accumulator : currentValue) // 4
复制代码

找出数组中最小值

var min = arr.reduce((accumulator,currentValue) => accumulator < currentValue ? accumulator : currentValue) // 1
复制代码

累加对象数组里的值

let initialValue = 0
let sum = [{x: 1},{x: 2},{x: 3}].reduce((accumulator, currentValue) => accumulator + currentValue.x), initialValue)
console.log(sum) // 6
复制代码

将二维数组转化为一维

let flattened = [[0,1],[2,3],[4,5]].reduce((acc,cur) => acc.concat(cur), []) // [0,1,2,3,4,5]
复制代码

计算数组中每一个元素出现的次数

let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
let countedNames = names.reduce(function (allNames, name) { 
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {})
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
复制代码

按属性对object分类

const people = [
 { name: 'Alice', age: 21 },
 { name: 'Max', age: 20 },
 { name: 'Jane', age: 20 }
]

function groupBy (objectArray, property) {
    return objectArray.reduce(function (acc, obj) {
      let key = obj[property]
      if (!acc[key]) {
          acc[key] = []
      }
      acc[key].push(obj)
      return acc
    },{})
}
let gruupedPeople = groupBy(people, 'age')
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }
复制代码

数组去重

let arr = [1,2,1,2,3,5,4,5,3,4,3,4,4,4,4]
let result = arr.sort().reduce((init, current)=> {
  if(init.length===0 || init[init.length-1] !== current) {
      init.push(current)
  }
  return  init
},[])
console.log(result) // [1,2,3,4,5]
复制代码

编写一个函数 unique(arr),返回一个去除数组内重复的元素的数组。例如:

unique([0, 1, 2, 2, 3, 3, 4]) // => [0, 1, 2, 3, 4]
unique([0, 1, '1', '1', 2]) // => [0, 1, '1', 2]

const unique = (arr) => {
    let result = arr.reduce((init, current) => {
        if (init.indexOf(current) === -1) {
            init.push(current)
        }
        return init
    },[])
    return result
}

var testArr = [1, '1', 1, '1', '1', '2', 2, 2, '2', 3, 3, 3]
unique(testArr)
复制代码
const unique = (arr) => [...new Set(arr)]
复制代码

数组去重

function merge(arr) {
    if (!Array.isArray(arr) || arr.length == 0 ) return []
    
    var ret = []
    
    for (var i = 0; i < arr.length; i++) {
        // 或者 arr.indexOf(arr[i]) == i)
        if (ret.indexOf(arr[i] == -1) {
            ret.push(arr[i])
        }
    }
    return ret
}
复制代码

按顺序运行Promise

/**
 * Runs promises from array of functions that can return promises
 * in chained manner
 *
 * @param {array} arr - promise arr
 * @return {Object} promise object
 */
function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input)
  );
}

// promise function 1
function p1(a) {
new Promise(resolve, reject) {
    resolve(a * 5)
 }
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
 return a * 3;
}

// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10)
  .then(console.log);   // 1200
复制代码

功能型函数管道

// Building-blocks to use for composition
const double = x => x + x;
const triple = x => 3 * x;
const quadruple = x => 4 * x;

// Function composition enabling pipe functionality
const pipe = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
);

// Composed functions for multiplication of specific values
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

// Usage
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240
复制代码

Polyfill

if (!Array.prototype.reduce) {
    Object.defineProperty(Array.prototype, 'reduce', {
        value: function(callback) {
            if (this === null) {
                throw new TypeError('Array.prototype.reduce ' + 
          'called on null or undefined' )
            }
            
            if (typeof callback !== 'function') {
                throw new TypeError( callback +
          ' is not a function')
            }
            
            // 1. Let O be ? ToObject(this value).
            var o = Object(this)
            
            // 2. Let len be ? ToLength(? Get(O, "length")).
           var len = o.length >>> 0; 
           
           // Steps 3, 4, 5, 6, 7      
          var k = 0; 
          var value;
    
          if (arguments.length >= 2) {
            value = arguments[1];
          } else {
            while (k < len && !(k in o)) {
            k++; 
        }

        // 3. If len is 0 and initialValue is not present,
        //    throw a TypeError exception.
        if (k >= len) {
          throw new TypeError( 'Reduce of empty array ' +
            'with no initial value' );
        }
          value = o[k++];
        }
        
        // 8. Repeat, while k < len
          while (k < len) {
            // a. Let Pk be ! ToString(k).
            // b. Let kPresent be ? HasProperty(O, Pk).
            // c. If kPresent is true, then
            //    i.  Let kValue be ? Get(O, Pk).
            //    ii. Let accumulator be ? Call(
            //          callbackfn, undefined,
            //          « accumulator, kValue, k, O »).
            if (k in o) {
              value = callback(value, o[k], k, o);
            }
    
            // d. Increase k by 1.      
            k++;
          }
      
           // 9. Return accumulator.
          return value;
        }
    })
}
复制代码

若是您须要兼容不支持Object.defineProperty的JavaScript引擎,那么最好不要 polyfill Array.prototype方法,由于你没法使其成为不可枚举的。

  • 19. reduceRight() 方法接收一个函数做为累加器和数组的每一个值(从右到左)将其减小为单个值。
const arr = [[1,2],[3,4],[5,6]]
const result = arr.recudeRight((accumulator,currentValue) => accumulator.concat(currentValue))
console.log(result) // [5,6,3,4,1,2]
复制代码

reduce()与reduceRight()的区别

var a = ['1', '2', '3', '4', '5']; 
var left  = a.reduce(function(prev, cur)      { return prev + cur; }); 
var right = a.reduceRight(function(prev, cur) { return prev + cur; }); 

console.log(left);  // "12345"
console.log(right); // "54321"
复制代码
  • 20. 数组交换
let arr = [1,2,3]
复制代码

方法1

建立临时变量

let temp = arr[0]
arr[0] = arr[2]
arr[2] = temp
复制代码

方法2

ES6解构赋值

[arr[0],arr[2]] = [arr[2],arr[0]]
复制代码

方法3

splice方法,此方法会以数组返回原数组被修改的内容。

arr.splice(2,1, ...arr.splice(0,1, arr[2]))
注:arr.splice(0,1, arr[2]) // [1] 此时数组为 [3,2,3]
arr.splice(2,1,1) // 数组交换位置完成 [3,2,1]
复制代码
  • **21.**数组打乱
var arr = [1,2,3,4,5,6]
arr.sort(() => {
    return 0.5 - Math.random()
})
console.log(arr) // [4, 6, 1, 2, 3, 5]
上面的写法并非真正意义上的彻底乱序,在抽奖的需求中,这样写会出大问题。
复制代码

参考

const arr = [1, 2, 3, 4, 5, 6]
for (let i = 1; i < arr.length; i++) {
    const random = Math.floor(Math.random() * (i + 1))
    [arr[i], arr[random]] = [arr[random], arr[i]]
}
复制代码
  • 22. find() 方法返回知足提供的测试函数的第一个元素的值。不然返回undefined
var array1 = [5, 12, 8, 130, 44];

var found = array1.find(function(element) {
  return element > 10;
});

console.log(found); // 12
复制代码
  • 23. Array.from()
Array.from(arrayLike[, mapFunction[, thisArg]])
复制代码
  • arrayLike:必传参数,想要转换成数组的伪数组对象或可迭代对象。
  • mapFunction:可选参数,mapFunction(item,index){...} 是在集合中的每一个项目上调用的函数。返回的值将插入到新集合中。
  • thisArg:可选参数,执行回调函数 mapFunction 时 this 对象。(少用)

生成数字范围

function range(end) {
    return Array.from({ length: end}, (_, index) => index)
}
range(6) // [0, 1, 2, 3, 4, 5]
复制代码

range()函数中,Array.from()提供了相似数组的{length: end},以及一个简单返回当前索引的map函数。将索引值index组合成了一个新的数组。作法很是的巧妙。

Array.from({ length: 6})返回的结果以下:

Array.from({ length: 6}) // [undefined, undefined, undefined, undefined, undefined, undefined]
复制代码

数组去重

因为 Array.from() 的入参是可迭代对象,于是咱们能够利用其与 Set 结合来实现快速从数组中删除重复项。

function unique(array) {
    return Array.from( new Set(array) )
}
unique([1,2,2,4,3,5,3]) // [1,2,4,3,5]
复制代码
  • new Set(array)建立了一个包含数组的集合,并删除重复项

  • 由于 Set 集合是可迭代的,因此可使用 Array.from() 将其转换为一个新的数组。

  • 24.Array.prototype.flatMap()

flatMap()方法首先使用映射函数映射每一个元素,而后将结果压缩成一个新数组。它与map和深度值1的flat几乎相同,但flatMap一般在合并成一种方法的效率稍微高一些。

var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
    // 返回新数组的元素
}[, thisArg]
复制代码

返回值:一个新的数组,其中每一个元素都是回调函数的结果,而且结构深度depth值为1.

var arr1 = [1, 2, 3, 4]
arr1.map(x => [x * 2]) // [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]) // [2, 4, 6, 8]
// 只会将 flatMap 中的函数返回的数组“压平”一层
arr1.flatMap(x => [[x * 2]]) // [ [2], [4], [6], [8]]
复制代码

虽然上面的代码使用 map 和 flatMap 好像均可以,但这只能展现如何使用 flatMap。

因此,为了更好的展现flatMap的做用,咱们将包含几句话的数组拆分红单个汉字组成的新数组。

let arr = ["今每天气不错""","早上好"]
arr.map(s => s.split(""))  // [["今", "天", "天", "气", "不", "错"],[],["早", "上", "好"]]
arr.flatMap(s => s.split('')) // ["今", "天", "天", "气", "不", "错", "早", "上", "好"]
复制代码

等价操做

概括(reduce)与合并 (concat)

var arr1 = [1, 2, 3, 4]
arr1.flatMap(x => [x * 2])
// 等价于
arr1.reduce((acc, x) => acc.concat([x * 2]), []) // [2, 4, 6, 8]
复制代码
相关文章
相关标签/搜索