标签

ES6

2015年版的ECMAScript规范,现在是一个标准(ECMAScript 2015)。

ES6
查看更多相关内容
前端5月28日 03:36
some、every、find、filter、map、forEach 有什么区别?这 6 个方法是 JavaScript 数组最常用的迭代方法,面试几乎必考。核心区别在于**返回值类型**和**是否短路**,按返回值分三类记忆最清晰。 ## 一、遍历类(无返回值) ### forEach 纯遍历,对每个元素执行回调,返回值永远是 `undefined`。 - **不能中断**:`return` 只跳过当前回调,`break` 语法不支持,想中途退出只能用 `try/catch` 抛异常(不推荐) - **不支持异步**:回调里写 `async/await` 不会等待 Promise,因为 `forEach` 不关心返回值 ```javascript const list = [1, 2, 3]; list.forEach(item => console.log(item)); // 1, 2, 3 // return 只跳过当次,不会中断循环 ``` ## 二、返回新数组 ### map 每个元素经回调映射后返回**等长新数组**,不改变原数组。 ```javascript const nums = [1, 2, 3]; const doubled = nums.map(n => n * 2); // [2, 4, 6] ``` ### filter 返回**满足条件的元素**组成的新数组,长度可能小于原数组,不改变原数组。 ```javascript const nums = [1, 2, 3, 4, 5]; const big = nums.filter(n => n > 3); // [4, 5] ``` ## 三、返回布尔值或单个元素 ### find 返回**第一个**满足条件的元素,找到即停止遍历(短路)。找不到返回 `undefined`。 ```javascript const users = [{id: 1, name: 'A'}, {id: 2, name: 'B'}]; users.find(u => u.id === 2); // {id: 2, name: 'B'} ``` ### some 有**任意一个**满足条件就返回 `true`,找到即短路。全不满足返回 `false`。**空数组返回 `false`**。 ```javascript [1, 2, 3].some(n => n > 2); // true [1, 2, 3].some(n => n > 5); // false [].some(n => n > 0); // false ``` ### every **所有元素**都满足条件才返回 `true`,遇到不满足即短路。**空数组返回 `true`**(空真逻辑 vacuous truth)。 ```javascript [1, 2, 3].every(n => n > 0); // true [1, 2, 3].every(n => n > 1); // false [].every(n => n > 0); // true(空真) ``` ## 四、对比速查表 | 方法 | 返回值 | 是否短路 | 空数组返回 | 链式调用 | 修改原数组 | |------|--------|----------|-----------|---------|-----------| | forEach | undefined | 否 | undefined | 否 | 否 | | map | 新数组 | 否 | [] | 是 | 否 | | filter | 新数组 | 否 | [] | 是 | 否 | | find | 单个元素/undefined | 是 | undefined | 否 | 否 | | some | boolean | 是 | false | 否 | 否 | | every | boolean | 是 | true | 否 | 否 | ## 五、高频追问 ### map 和 forEach 怎么选? 需要返回新数组用 `map`,纯副作用(如 console.log、DOM 操作)用 `forEach`。关键区别:`map` 可链式调用,`forEach` 返回 `undefined` 不可链式。 ### some 和 includes 有什么区别? - `includes(val)` 判断数组是否包含某个**具体值**,用严格相等(`===`)比较 - `some(fn)` 判断是否有元素满足**自定义条件** - `includes` 只能判断值存在性,`some` 可以写任意判断逻辑 ```javascript [1, 2, 3].includes(2); // true [1, 2, 3].some(n => n > 2); // true [{a: 1}].includes({a: 1}); // false(引用不同) [{a: 1}].some(o => o.a === 1); // true ``` ### 这些方法支持异步回调吗? 都不原生支持。`forEach` 里写 `async/await` 不会等待 Promise resolve。需要异步迭代用 `for...of` + `await` 或 `Promise.all` + `map`。 ```javascript // 错误:forEach 不会等待 async ids.forEach(async id => { const data = await fetch(id); // 并发执行,不会依次等待 }); // 正确方式1:for...of for (const id of ids) { const data = await fetch(id); } // 正确方式2:Promise.all + map(并行) const results = await Promise.all(ids.map(id => fetch(id))); ``` ### find 和 filter 怎么选? 只需第一个匹配用 `find`(性能更好,短路),需要所有匹配用 `filter`。 ### reduce 为什么没列进来? `reduce` 是这 6 个方法的基础——`map`、`filter`、`some`、`every`、`find` 都可以用 `reduce` 实现。面试中常追问 reduce 的用法,但 reduce 更偏向"累加器"模式,功能更强大也更复杂,属于另一个考点的范畴。
前端5月28日 03:36
ES5 和 ES6 有什么区别?ES6(ES2015)是 JavaScript 历史上最大的一次版本更新,面试中这道题考查的是你对 JS 语言演进的理解深度。回答的关键不是罗列特性,而是讲清楚**每个变化解决了什么问题**。 ## 变量声明:从 var 到 let/const ES5 只有 `var`,存在两大问题: ```js // 问题1:变量提升 console.log(a); // undefined(不会报错,但容易出 bug) var a = 1; // 问题2:无块级作用域 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // 3, 3, 3 } ``` ES6 用 `let`/`const` 解决了这两个问题: ```js // let 有块级作用域 + 暂时性死区 console.log(b); // ReferenceError(声明前访问直接报错) let b = 1; for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // 0, 1, 2 } // const 不可重新赋值(但对象属性仍可修改) const obj = { a: 1 }; obj.a = 2; // OK obj = { a: 2 }; // TypeError ``` **面试要点**:`const` 保证的是绑定不可变,不是值不可变。想冻结对象用 `Object.freeze()`。 ## 函数:箭头函数与 this 绑定 ES5 中 `this` 指向取决于调用方式,经常需要 `var self = this` 或 `.bind(this)`: ```js // ES5 var obj = { name: 'ES5', say: function() { var self = this; setTimeout(function() { console.log(self.name); // 必须用 self/cache }, 0); } }; // ES6 — 箭头函数继承外层 this const obj2 = { name: 'ES6', say() { setTimeout(() => { console.log(this.name); // 直接用 this }, 0); } }; ``` **注意**:箭头函数没有自己的 `arguments`、`super`、`new.target`,不能用作构造函数。 ## 字符串:模板字符串 ```js // ES5 var greeting = 'Hello, ' + name + '! You are ' + age + ' years old.'; // ES6 const greeting = `Hello, ${name}! You are ${age} years old.`; ``` 模板字符串支持多行、变量插值、标签模板,彻底告别字符串拼接。 ## 解构赋值与展开运算符 ```js // 对象解构 const { name, age } = user; // 数组解构 const [first, ...rest] = [1, 2, 3, 4]; // first=1, rest=[2,3,4] // 展开运算符 — 浅拷贝与合并 const copy = [...arr]; const merged = { ...defaults, ...config }; ``` 解构让数据提取更简洁,展开运算符替代了 `Object.assign` 和 `concat` 的大多数场景。 ## 类与继承:class 语法 ```js // ES5 — 构造函数 + 原型链 function Animal(name) { this.name = name; } Animal.prototype.speak = function() { return this.name + ' makes a sound'; }; // ES6 — class 语法 class Animal { constructor(name) { this.name = name; } speak() { return `${this.name} makes a sound`; } } class Dog extends Animal { speak() { return `${this.name} barks`; } } ``` `class` 本质是原型继承的语法糖,但有行为差异:内部默认严格模式、方法不可枚举、必须用 `new` 调用。 ## 模块系统:import/export ```js // ES5 — CommonJS(Node.js) const module = require('./module'); module.exports = { foo }; // ES6 — ES Modules import { foo } from './module'; export const bar = 1; export default function() {} ``` ES Modules 是静态的,支持 Tree Shaking;CommonJS 是动态的,运行时加载。现代项目(Vite/Webpack)均以 ESM 为优先。 ## 异步编程:Promise 与 async/await ```js // ES5 — 回调地狱 getData(function(a) { getMore(a, function(b) { getEvenMore(b, function(c) { console.log(c); }); }); }); // ES6 — Promise 链式调用 getData() .then(a => getMore(a)) .then(b => getEvenMore(b)) .then(c => console.log(c)); // ES8 — async/await(同步写法) const a = await getData(); const b = await getMore(a); const c = await getEvenMore(b); ``` Promise 解决了回调地狱,async/await 让异步代码看起来像同步,是面试高频追问点。 ## 新数据结构与 API | 特性 | 用途 | |------|------| | `Map` | 键值对集合,键可以是任意类型(Object 的键只能是字符串/Symbol) | | `Set` | 去重数组:`[...new Set(arr)]` | | `WeakMap/WeakSet` | 键是弱引用,适合缓存和关联私有数据,不阻止 GC | | `Symbol` | 创建唯一标识符,用于私有属性和内置协议 | | `Proxy/Reflect` | 拦截对象操作(Vue 3 响应式核心) | | `Generator/Iterator` | 可暂停函数,`for...of` 遍历统一接口 | ## 追问 ### ES6 之后还有什么重要的新特性? | 版本 | 关键特性 | |------|----------| | ES7 | `Array.prototype.includes`、指数运算符 `**` | | ES8 | `async/await`、`Object.values/entries` | | ES9 | `Promise.finally`、异步迭代 `for await...of` | | ES10 | `flat/flatMap`、`Object.fromEntries` | | ES11 | `??`(空值合并)、`?.`(可选链)、`Promise.allSettled` | | ES12 | `replaceAll`、逻辑赋值 `||=` `&&=` `??=` | | ES13 | `at()`、`Object.hasOwn`、Top-level await | ### let/const 和 var 最大的实际区别? 1. **块级作用域** — 解决 for 循环闭包问题 2. **暂时性死区** — 声明前访问报 ReferenceError,var 是 undefined 3. **不可重复声明** — 同一作用域内 let/const 不能重复声明同名变量 4. **const 不可重新赋值** — 但对象/数组内容仍可修改 ### class 只是语法糖吗? 基本是。`class` 编译后就是原型链模式(构造函数 + prototype + Object.create)。但有几个行为差异: - class 内部默认严格模式 - class 方法不可枚举(`for...in` 遍历不到) - 只能用 `new` 调用(有 `new.target` 检查,直接调用报错) - `extends` 内部用 `Object.create` 设置原型链,比 ES5 手动写更规范 ### 面试回答策略 面试官问这道题,不是让你背特性列表。推荐的回答结构: 1. **一句话概括**:ES6 让 JS 从脚本语言变成工程化语言 2. **按类别讲 3-4 个重点**,每个说清楚"ES5 什么问题 → ES6 怎么解决" 3. **追问时深入**:挑一个你最熟悉的特性展开(如 class 的原型链原理、Promise 的微任务机制)
前端5月28日 03:35
ES6 中的 Map 和原生的 Object 有什么区别?Map 和 Object 都能存键值对,但 Map 是专门为"字典"场景设计的,解决了 Object 做字典时的几个硬伤。 **键的类型**:Object 的 key 只能是字符串或 Symbol,数字 1 和字符串 "1" 是同一个 key。Map 的 key 可以是任意类型——对象、函数、NaN 都行,用 SameValueZero 算法比较(NaN 等于 NaN)。 **原型链污染**:Object 有原型链,`obj.__proto__`、`obj.toString` 这类属性名会冲突。`Object.create(null)` 能规避,但写法不直觉。Map 天然没有这个问题。 **大小**:Map 有 `size` 属性直接取。Object 要 `Object.keys(obj).length`。 **顺序**:Map 严格按插入顺序迭代。Object 在 ES6 后基本也按插入顺序,但整数 key 会被提前排列,容易踩坑。 **遍历**:Map 直接 `for...of` 或 `forEach`。Object 要先转数组(`Object.entries()`)或用 `for...in`(还会遍历原型链)。 **性能**:频繁增删键值对时 Map 更快。Object 在 V8 中对连续整数 key 有快属性优化,但这种优化对字典场景没帮助。 **序列化**:`JSON.stringify` 能直接处理 Object。Map 不行,需要先转成数组或对象。 ```js const m = new Map(); const obj = {}; m.set(obj, 'value'); // 对象做 key,Object 做不到 m.set(1, 'num'); m.set('1', 'str'); // 1 和 '1' 是不同 key console.log(m.size); // 3 ``` 一句话:需要字典数据结构时优先用 Map,需要 JSON 序列化或简单配置对象时用 Object。 ## 追问 ### WeakMap 和 Map 有什么区别? WeakMap 的 key 必须是对象,值任意。key 是弱引用——被 GC 回收后对应条目自动消失。不可迭代(没有 `size`、`forEach`、`keys()`),因为条目随时可能被回收。 | | Map | WeakMap | |---|---|---| | key 类型 | 任意 | 仅对象 | | 引用方式 | 强引用 | 弱引用 | | 可迭代 | 是 | 否 | | size | 有 | 无 | | 典型场景 | 字典存储 | 关联私有数据 | ### 项目里 WeakMap 用在什么地方? Vue 3 的响应式系统用 WeakMap 存对象 → 依赖关系,对象被销毁时依赖自动清理,不会内存泄漏。另一个常见场景:给 DOM 节点绑定额外数据,节点移除后数据自动释放。 ### Object.create(null) 能替代 Map 吗? 能解决原型链污染问题,但解决不了键类型限制、size 获取、顺序保证、迭代便利性。Map 是更完整的方案。 ### Map 的 key 用 NaN 会怎样? Map 用 SameValueZero 算法比较键,NaN 等于 NaN,所以 NaN 可以正常作为 key,且不会重复。Object 中 NaN 作为 key 会被转成字符串 "NaN",行为一致,但 Map 的语义更明确。
前端5月28日 03:29
ES6 类继承中 super 关键字的作用是什么?`super` 在 ES6 类继承中有两种用法:**作为函数调用**和**作为对象引用**。核心要点是——`super()` 调用父类构造函数,`super.method()` 调用父类原型方法,`super.staticMethod()` 在静态方法中调用父类静态方法。 ## super() 作为函数调用 在子类 `constructor` 中,`super()` 调用父类构造函数。ES6 的继承机制规定:父类负责创建 `this` 对象,子类负责在此基础上添加属性。因此 `super()` 必须在 `this` 之前调用,否则会抛出 `ReferenceError`。 ```javascript class Parent { constructor(name) { this.name = name; } } class Child extends Parent { constructor(name, age) { super(name); // 必须先调 super,否则下面用 this 会报错 this.age = age; } } ``` 如果子类没有显式定义 `constructor`,引擎会自动插入一个默认的 `constructor(...args) { super(...args); }`。 ## super.method() 作为对象引用 在子类普通方法中,`super` 指向父类的 `prototype`,可以调用父类原型上的方法: ```javascript class Parent { greet() { return 'hello from Parent'; } } class Child extends Parent { greet() { return super.greet() + ' and Child'; } } new Child().greet(); // "hello from Parent and Child" ``` ## 静态方法中的 super 在子类静态方法中,`super` 指向父类本身(而非 `prototype`),因此可以调用父类的静态方法: ```javascript class Parent { static create() { return new this(); } } class Child extends Parent { static create() { return super.create(); // 调用 Parent.create() } } ``` ## super 的内部指向总结 | 使用场景 | super 指向 | |---------|-----------| | `super()` 在 constructor 中 | 父类构造函数 | | `super.method()` 在普通方法中 | `Parent.prototype` | | `super.method()` 在静态方法中 | 父类本身(`Parent`) | | `super.x = value` | 触发父类原型上的 setter(如果有) | ## 追问:子类 constructor 为什么必须先调 super? ES6 类的继承与 ES5 的寄生组合继承有本质区别。ES5 中是先创建 `this`(子类自己的对象),再用 `Parent.apply(this)` 借用父类构造函数挂属性。ES6 反过来了——由父类构造函数先创建并初始化 `this`,子类再修改。这个顺序由 `new.target` 控制:当 `new Child()` 执行时,`new.target` 是 `Child`,但 `this` 的创建权在 `Parent` 那里。`super()` 执行后 `this` 才可用。 ## 追问:super.x = value 有什么陷阱? 给 `super` 的属性赋值时,并不会像直觉那样去修改父类原型上的属性。实际行为是:如果父类原型上定义了该属性的 `setter`,赋值操作会触发那个 `setter`,`this` 指向当前子类实例;如果没有 `setter`,则相当于直接在 `this` 上创建属性: ```javascript class Parent { set x(val) { console.log('setter called with', val); } } class Child extends Parent { setX() { super.x = 42; // 触发 Parent.prototype 的 setter } } new Child().setX(); // "setter called with 42" ``` ## 追问:ES6 继承与 ES5 原型继承的区别 | | ES5 寄生组合继承 | ES6 class 继承 | |--|----------------|---------------| | this 创建 | 子类先创建 this,再借用父类 | 父类构造函数创建 this | | super | 无,用 Parent.call(this) | super() 必须 | | 原型链 | 手动 Object.create(Parent.prototype) | extends 自动建立 | | 静态方法 | 不会继承 | 自动继承 | | new.target | 不存在 | 控制实例化行为 |
前端5月27日 01:17
Promise 和 async/await 和 Callback 有什么区别?三个阶段的异步方案,层层递进: **Callback**:把后续操作作为回调函数传给异步操作。问题是回调地狱——多层嵌套横向增长,错误处理每个回调都得单独处理。 **Promise**:把回调包装成对象,链式 `.then()` 解决横向嵌套,`.catch()` 统一处理错误。但长链仍不够直观,且 `.then()` 里不能直接用 `try-catch`。 **async/await**:Promise 的语法糖。`async` 函数返回 Promise,`await` 暂停执行等结果。写法就是同步代码的样子,错误用 `try-catch`。本质还是 Promise——`await` 的值就是 `.then()` 回调的参数。 ```javascript // 三个方案的同一操作 // Callback getData((err, data) => { if (err) return; process(data); }); // Promise getData().then(process).catch(handleError); // async/await try { const data = await getData(); process(data); } catch { handleError(); } ``` ## 追问 ### async/await 怎么处理并发请求? `Promise.all([fetch1, fetch2])` 配合 `await`。不要写成 `await fetch1(); await fetch2()`——这样是串行的,第二个请求等第一个完成才发。 ### async 函数返回的 Promise 和普通 Promise 有区别吗? 没有本质区别。async 函数内部抛错等于 reject,return 值等于 resolve。唯一注意的是:async 函数返回的 Promise 是原生 Promise,即使你 return 的是一个 thenable 对象,也会自动包裹成 Promise。
前端5月27日 01:16
module.exports 和 exports 的区别是什么?export 和 export default 的区别是什么?两对概念,一个在 CommonJS,一个在 ESModule。 **module.exports vs exports(CommonJS)**: - `module.exports` 是真正的导出对象。`exports` 只是 `module.exports` 的引用(`const exports = module.exports`) - 给 `exports` 赋新值会断开引用,导出失败;`module.exports` 赋新值可以 - 安全做法:只添加属性用 `exports.foo = bar`,需要替换整个导出用 `module.exports = foo` ```javascript // 正确 module.exports = { a: 1 }; exports.b = 2; // 错误 — exports 被重新赋值,断开引用 exports = { a: 1 }; // module.exports 还是 {} ``` **export vs export default(ESModule)**: - `export` 是命名导出,可以有多个。导入时用 `{ name }` 且名字必须匹配 - `export default` 是默认导出,每个模块只有一个。导入时可以取任意名字 - 一个模块可以同时有命名导出和默认导出 ```javascript // 导入区别 import { foo } from './a'; // 命名导出 import foo from './a'; // 默认导出 import foo, { bar } from './a'; // 两者都有 ``` ## 追问 ### 为什么 export default 导入可以随意命名? 因为默认导出本质上导出的是 `{ default: value }` 这个特殊 key。`import x from` 就是取 `default` key 的值。因此也叫 default import。 ### 项目中应该优先用命名导出还是默认导出? 命名导出更好——IDE 自动补全、refactor 改名时更安全、Tree-Shaking 友好。默认导出适合"这个模块只有一个主要导出"(如一个组件、一个工具函数)。但争议是社区级的,没有绝对的优劣。
前端5月27日 01:16
var、let、const 之间的区别是什么?三个维度的区别: **作用域**:var 是函数作用域,let/const 是块级作用域。`{ }` 内部用 let 声明的变量,括号外访问不到。 **变量提升**:var 有提升且初始化为 undefined(声明前访问得到 undefined)。let/const 也有提升但存在暂时性死区(TDZ)——声明前访问直接 ReferenceError。 **重复声明**:var 可重复声明(后覆盖前),let/const 在同一作用域不能重复声明。 **const 额外特性**:声明时必须初始化,且不能重新赋值。但对象和数组的属性可以修改(const 锁的是绑定,不是值)。 ```javascript // var:函数作用域,讨厌的经典 bug for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i)); // 3 3 3 } // let:块级作用域,每次迭代创建新绑定 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i)); // 0 1 2 } ``` ## 追问 ### 为什么 let 能解决 for 循环的回调/闭包问题? var 是整个 for 循环共享一个变量,循环结束后 i 是最终值。let 每次循环迭代都会创建一个新的绑定,每个 setTimeout 捕获的 i 是不同的绑定。即使循环结束后,这些绑定的值仍然保留着当时的 i。 ### const 声明的对象属性为什么可以修改? const 锁定的是变量名到值的绑定关系——"这个变量名不能指向别的值"。对象属性是变量指向的内存地址内部的变更,不改变绑定关系。
前端5月27日 01:16
WeakSet、WeakMap 和 Set、Map 之间的区别是什么?核心区别就一条:**Weak 版本的 key(或元素)是弱引用,不阻止垃圾回收。** **Set vs WeakSet**: - Set 元素可以是任何类型;WeakSet 元素只能是对象 - Set 可迭代(`forEach`、`size`、`keys`);WeakSet 不可迭代 - Set 中对象被引用着,即使对象其他地方不再使用也不会被 GC;WeakSet 中对象没有其他引用时会被回收 **Map vs WeakMap**: - Map 的 key 可以是任何类型;WeakMap 的 key 只能是对象 - Map 可迭代;WeakMap 不可迭代 - WeakMap 条目会随 key 对象被 GC 而自动清除 WeakMap 典型场景:Vue 3 的响应式依赖追踪、存储 DOM 节点的关联数据、为第三方对象附加元数据而不造成内存泄漏。WeakSet 用得少——需要标记"这个对象我见过"但不想阻止它被 GC 时用。 ## 追问 ### 为什么 WeakMap 没有 size 属性? 因为 WeakMap 中条目可能随时被 GC 回收,size 值是瞬时的、不可靠的。如果 JS 引擎提供了 size,开发者的代码里依赖了这个值,但下一秒 GC 跑了一次值变了——这种不可预测性比没有 size 更糟糕。 ### WeakMap 和 Map 在内存管理上有什么区别? Map 的 key 被引用着,即使这个 key 对象在别处都已不使用,Map 里的引用也会阻止 GC——内存泄漏风险。WeakMap 的 key 是弱引用,如果 key 对象没有其他强引用了,GC 可以回收,对应的 WeakMap 条目自动消失。
前端2月7日 16:44
说一下 splice 和 slice 的功能用法`splice()` 和 `slice()` 都是 JavaScript 中用来处理数组的方法,但它们的功能和用法有所不同。 ### splice() `splice()` 方法通过删除或替换现有元素或在数组中添加新元素来改变数组的内容。其基本语法如下: ```javascript array.splice(start[, deleteCount[, item1[, item2[, ...]]]]) ``` - **start**: 指定修改的开始位置(数组索引)。 - **deleteCount**: (可选)整数,表示要从数组中删除的元素数量。 - **item1, item2, ...**: (可选)要添加进数组的新元素。 **示例**: ```javascript let myArray = ['a', 'b', 'c', 'd']; myArray.splice(1, 2, 'x', 'y'); // 从索引1开始删除2个元素,并添加'x'和'y' console.log(myArray); // 输出: ['a', 'x', 'y', 'd'] ``` ### slice() `slice()` 方法则返回一个新的数组,包含从开始到结束(不包括结束)选择的数组的一部分。原始数组不会被修改。其基本语法如下: ```javascript array.slice(begin[, end]) ``` - **begin**: 提取起始处的索引(从该索引开始提取元素)。 - **end**: (可选)提取结束处的索引(到该索引之前的元素会被提取)。 **示例**: ```javascript let myArray = ['a', 'b', 'c', 'd']; let newArray = myArray.slice(1, 3); // 提取从索引1到索引2的元素 console.log(newArray); // 输出: ['b', 'c'] console.log(myArray); // 原数组不变,输出: ['a', 'b', 'c', 'd'] ``` 总结来说,`splice()` 是一个可以在任何位置添加或删除元素的方法,这会改变原数组,而 `slice()` 用于创建一个新的数组,包含原数组的一部分,原数组不会改变。