ES6进阶知识一
目录
三、迭代器(Iterator)与生成器(Generator)
一、ES6构建工具与模块化
1.1.构建工具
ES6的构建工具包括Gulp、Babel、Webpack等。
这些工具可以帮助开发者将ES6代码编译为ES5代码,以便在旧版浏览器上运行。
它们还支持代码监听、打包、压缩等功能,提高开发效率和代码质量。
1.1.1.Webpack
Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。它会根据模块的依赖关系进行静态分析,然后将这些模块打包成一个或多个 bundle。
安装 Webpack
在项目根目录下运行以下命令来安装 Webpack 及其 CLI 工具:
npm install --save-dev webpack webpack-cli
配置 Webpack
在项目根目录下创建一个 webpack.config.js
文件,并添加以下内容:
const path = require('path');
module.exports = {
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, 'dist') // 输出路径
},
module: {
rules: [
{
test: /\.js$/, // 匹配所有 .js 文件
exclude: /node_modules/, // 排除 node_modules 目录
use: {
loader: 'babel-loader', // 使用 Babel 加载器
options: {
presets: ['@babel/preset-env'] // 配置 Babel 预设
}
}
}
]
}
};
使用 Webpack
在 package.json
文件的 scripts
部分添加一个构建脚本
"scripts": {
"build": "webpack --config webpack.config.js"
}
然后运行以下命令来构建项目:
npm run build
1.1.2.Babel
Babel 是一个广泛使用的 JavaScript 编译器,可以将 ES6+ 代码转换为向后兼容的 JavaScript 代码,以便在旧版浏览器或环境中运行。
安装 Babel
在项目根目录下运行以下命令来安装 Babel 及其预设:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
配置 Babel
在项目根目录下创建一个 .babelrc
文件,并添加以下内容:
{
"presets": ["@babel/preset-env"]
}
或者使用 babel.config.json
文件(两者选其一):添加内容一样
{
"presets": ["@babel/preset-env"]
}
1.2.ES6模块化
ES6 引入了模块化编程的概念,允许我们将代码拆分成多个模块,每个模块只关注自己的功能,并通过 import
和 export
关键字来实现模块之间的通信。
1.命名导出导入
导出模块
在 src/math.js
文件中定义一些数学函数,并使用命名导出:
// src/math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
导入模块
在 src/index.js
文件中导入 math.js
模块中的函数
// src/index.js
import { add, subtract } from './math.js';
console.log(add(2, 3)); // 输出: 5
console.log(subtract(5, 3)); // 输出: 2
2. 默认导出与导入
导出模块
在 src/utils.js
文件中定义一个工具函数,并使用默认导出:
// src/utils.js
export default function logMessage(message) {
console.log(message);
}
导入模块
在 src/index.js
文件中导入 utils.js
模块中的默认函数:
// src/index.js
import logMessage from './utils.js';
logMessage('Hello, ES6 Modules!'); // 输出: Hello, ES6 Modules!
1.3.完整案例展示
1. 项目结构
my-es6-project/
├── dist/
│ └── bundle.js (构建后生成的文件)
├── node_modules/ (安装的依赖包)
├── src/
│ ├── index.js
│ ├── math.js
│ └── utils.js
├── .babelrc (Babel 配置文件)
├── package.json (项目配置文件)
└── webpack.config.js (Webpack 配置文件)
2. 代码实现
src/math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
src/utils.js
export default function logMessage(message) {
console.log(message);
}
src/index.js
import { add, subtract } from './math.js';
import logMessage from './utils.js';
console.log(add(2, 3)); // 输出: 5
console.log(subtract(5, 3)); // 输出: 2
logMessage('Hello, ES6 Modules!'); // 输出: Hello, ES6 Modules!
.babelrc
{
"presets": ["@babel/preset-env"]
}
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
};
package.json
{
"name": "my-es6-project",
"version": "1.0.0",
"description": "A demo project for ES6 modules and build tools.",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js"
},
"devDependencies": {
"@babel/core": "^7.x.x",
"@babel/cli": "^7.x.x",
"@babel/preset-env": "^7.x.x",
"webpack": "^5.x.x",
"webpack-cli": "^4.x.x"
}
}
3. 构建与运行
在项目根目录下运行以下命令来构建项目
npm run build
构建成功后,将在 dist
目录下生成一个 bundle.js
文件。你可以通过创建一个 HTML 文件来引用这个构建后的文件,并在浏览器中查看输出结果
二、高阶异步编程模式
2.1.任务队列与微任务
1.任务队列(Task Queue):
通常包含宏任务(MacroTask),如setTimeout
、setInterval
、I/O操作等。
宏任务之间的执行间隔可能会受到浏览器渲染和其他因素的影响。
2.微任务队列(Microtask Queue):
包含微任务(MicroTask),如Promise的回调、MutationObserver
等。
微任务的优先级高于宏任务,会在当前宏任务执行完毕后立即执行,直到微任务队列为空。
3.宏任务与微任务的执行顺序
console.log('宏任务1');
setTimeout(() => {
console.log('宏任务2');
}, 0);
Promise.resolve().then(() => {
console.log('微任务1');
});
console.log('宏任务3');
执行结果:
宏任务1
宏任务3
微任务1
宏任务2
解释:
首先执行同步代码(宏任务1和宏任务3)。
然后执行微任务(微任务1),因为微任务的优先级高于宏任务。
最后执行宏任务队列中的setTimeout
回调(宏任务2)。
2.2.自定义事件与事件总线
自定义事件和事件总线是JavaScript中用于组件或对象间通信的重要机制。
1.自定义事件:
允许在对象或应用程序的不同部分之间传递消息和数据。
可以通过CustomEvent
构造函数或Event
构造函数(在某些情况下)来创建自定义事件。
2.事件总线模式:
是一种松耦合的通信方式,允许不同的模块或组件通过事件进行通信,而无需直接相互引用。
使用事件总线可以减少模块之间的依赖,提高代码的可维护性和可扩展性。
在Vue等前端框架中,自定义事件和事件总线得到了广泛的应用。例如,在Vue中,子组件可以通过$emit
触发自定义事件,父组件则可以通过v-on
或@
语法来监听这些事件,从而实现组件间的通信。
3.使用事件总线进行模块间通信
假设我们有两个模块moduleA
和moduleB
,它们需要通过事件总线进行通信。
// 事件总线类
class EventBus {
constructor() {
this.events = {};
}
subscribe(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
publish(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(...args));
}
}
}
// 创建事件总线实例
const eventBus = new EventBus();
// moduleA
function moduleA() {
eventBus.subscribe('dataReceived', (data) => {
console.log('Module A received data:', data);
});
}
// moduleB
function moduleB() {
setTimeout(() => {
eventBus.publish('dataReceived', { key: 'value' });
}, 1000);
}
// 初始化模块
moduleA();
moduleB();
执行结果:
在1秒后,控制台会输出“Module A received data: { key: 'value' }”。
解释:
moduleA
通过eventBus.subscribe
订阅了dataReceived
事件。
moduleB
在1秒后通过eventBus.publish
发布了dataReceived
事件,并传递了一个数据对象。
moduleA
的回调函数被调用,并接收到了传递的数据
2.3.异步迭代与生成器的高级用法
异步迭代器和生成器是ES6中引入的高级异步编程特性,它们允许开发者以更直观、更优雅的方式处理异步操作。
-
异步迭代器:
- 允许在异步操作中逐个处理数据项,而不会阻塞程序的执行。
- 它们通过
Symbol.asyncIterator
方法实现,可以被for await...of
循环使用。
-
异步生成器函数:
- 可以暂停和恢复执行,以异步方式生成数据。
- 它们使用
async function*
声明,并通过yield
关键字返回数据。
异步迭代器和生成器在处理大量数据或复杂异步操作时非常有用。例如,它们可以用于读取大型文件、处理网络请求流等场景。通过异步迭代器和生成器,开发者可以更加灵活地控制异步操作的执行流程,从而提高代码的可读性和可维护性。
3.示例
使用异步迭代器处理网络请求流
假设我们需要从一个API端点获取分页数据,并逐个处理这些数据项
async function* fetchData(url, pageSize) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}&pageSize=${pageSize}`);
const data = await response.json();
if (data.length === 0) break; // 假设返回的数据为空数组时表示没有更多数据
for (const item of data) {
yield item;
}
page++;
}
}
(async () => {
const dataStream = fetchData('https://api.example.com/items', 10);
for await (const item of dataStream) {
console.log('Received item:', item);
// 在这里处理每个数据项,例如保存到数据库或更新UI
}
console.log('All data processed.');
})();
执行结果:
控制台会逐个输出从API获取的数据项,并在处理完所有数据后输出“All data processed.”。
解释:
fetchData
是一个异步生成器函数,它使用fetch
函数从API获取分页数据。
在while
循环中,它不断请求下一页的数据,直到返回的数据为空数组。
对于每一页的数据,它使用for...of
循环逐个yield
出数据项。
在异步函数块中,我们使用for await...of
循环来消费这个异步生成器,并逐个处理从API获取的数据项。
三、迭代器(Iterator)与生成器(Generator)
3.1.迭代器(Iterator)
1.定义:
迭代器是一种对象,它提供了一种顺序访问集合中每个元素的方式,而不暴露集合内部的表示。
迭代器的主要方法是next()
,每次调用该方法都会返回一个包含value
和done
属性的对象。value
表示当前元素的值,done
表示是否已经遍历完所有元素。
2.实现原理:
在需要进行遍历操作时,通过调用集合对象上的Symbol.iterator
方法获取到该集合对象对应的默认迭代器。
在每次调用next()
方法时,迭代器会执行相应的操作,并返回一个包含value
和done
属性的对象。
如果done
为false
,则表示还有更多的元素需要遍历,此时value
属性表示当前遍历到的值。
如果done
为true
,则表示已经遍历完所有元素,此时value
属性为undefined
。
3.使用场景:
迭代器提供了一种统一的遍历机制,使得我们可以使用相同的方式来访问不同类型的数据结构。
无论是数组、字符串、Set、Map还是自定义对象,只要实现了迭代器接口,就可以使用for...of
循环或者手动调用next()
方法来进行遍历。
4.示例:
let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
3.2.生成器(Generator)
1.定义:
生成器是一种特殊的函数,它可以通过yield
关键字来暂停函数的执行,并返回一个包含value
和done
属性的对象。
生成器函数使用function*
语法进行定义。
2.实现原理:
当调用生成器函数时,实际上并不会立即执行函数体内部的代码,而是返回一个迭代器对象,该迭代器对象实现了next()
方法。
每次调用next()
方法时,生成器会从上一次暂停的位置继续执行代码,直到遇到下一个yield
关键字或者函数结束。
3.使用场景:
生成器提供了一种更灵活、更可控的方式来处理异步编程。
通过使用yield
关键字,我们可以在函数执行过程中暂停和恢复,并且可以将异步操作以同步方式编写和理解。
4.示例:
function* generatorFunc() {
yield 'Hello';
yield 'World';
}
let generator = generatorFunc();
console.log(generator.next()); // { value: 'Hello', done: false }
console.log(generator.next()); // { value: 'World', done: false }
console.log(generator.next()); // { value: undefined, done: true }
3.3.迭代器与生成器的关系
生成器本身就是一个迭代器,它返回的迭代器对象实现了迭代器接口,因此可以使用next()
方法进行遍历。
生成器通过yield
关键字实现了函数的暂停和恢复,这使得它在处理异步编程时具有更大的灵活性。
3.4.总结
迭代器为JavaScript提供了一种统一的遍历机制,使得我们可以使用相同的方式来访问不同类型的数据结构。
生成器则通过yield
关键字实现了函数的暂停和恢复,为异步编程提供了更自然和易于理解的方式。
在实际开发中,迭代器与生成器经常结合使用,以实现更复杂的迭代和异步逻辑。
四、代理(Proxy)与反射(Reflect)
4.1.代理(Proxy)
- 定义与功能:
- 代理是ES6引入的一种元编程机制,允许开发者拦截并自定义目标对象的操作。
- 通过使用Proxy构造函数,开发者可以创建一个代理对象,该对象将拦截对目标对象的读取、写入、函数调用等操作。
- 创建与使用:
- 要创建一个代理对象,需要使用Proxy构造函数,并传入两个参数:目标对象(target)和处理程序(handler)。
- 处理程序是一个对象,其方法被称为陷阱(trap),用于指定拦截后的行为。
- 常用陷阱方法:
get(target, property, receiver)
:拦截对目标对象属性的读取操作。set(target, property, value, receiver)
:拦截对目标对象属性的写入操作。apply(target, thisArg, argumentsList)
:拦截对目标对象的函数调用操作。construct(target, argumentsList, newTarget)
:拦截对目标对象的构造函数调用操作。- 其他陷阱方法还包括
has
、deleteProperty
、getOwnPropertyDescriptor
等。
- 示例
const target = { name: 'John', age: 30 };
const handler = {
get(target, property) {
console.log(`Getting property: ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Setting property: ${property} = ${value}`);
target[property] = value;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出: Getting property: name John
proxy.age = 35; // 输出: Setting property: age = 35
4.2.反射(Reflect)
- 定义与功能:
- 反射是ES6引入的一个内置对象,提供了一套与JavaScript内置操作直接对应的静态方法。
- 这些方法的设计目的是为了使操作对象的行为变得更为清晰、更易于使用,并在某些情况下提供更好的错误处理机制。
- 常用方法:
Reflect.get(target, propertyKey[, receiver])
:获取目标对象上指定属性的值。Reflect.set(target, propertyKey, value[, receiver])
:设置目标对象上指定属性的值。Reflect.defineProperty(target, propertyKey, attributes)
:在目标对象上定义一个属性。Reflect.deleteProperty(target, propertyKey)
:尝试删除目标对象上的指定属性。Reflect.apply(target, thisArgument, argumentsList)
:调用目标函数。Reflect.construct(target, argumentsList[, newTarget])
:使用给定的参数列表调用构造函数并创建一个新实例。- 其他方法还包括
Reflect.has
、Reflect.ownKeys
、Reflect.isExtensible
等。
- 特点:
- Reflect对象的方法与Object对象上的同名方法相对应,但Reflect方法通常返回一个布尔值以表示操作是否成功,而不是静默失败。
- Reflect方法在遇到非法操作时会抛出适当的异常,如TypeError或RangeError,而不是默默地失败。
- 示例:
const obj = { a: 1, b: { c: 2 } };
console.log(Reflect.get(obj, 'a')); // 输出: 1
Reflect.set(obj, 'a', 2);
console.log(obj.a); // 输出: 2
4.3.代理与反射的关系
代理和反射是协调合作的关系。代理用于拦截对象的内置操作,并注入自定义的逻辑;而反射则向外界暴露了一些底层操作的默认行为,使得开发者可以更方便地访问和修改这些行为。
在代理的陷阱方法中,通常会使用Reflect对象的方法来执行原始的、未被拦截的操作。这样,开发者就可以在自定义逻辑前后添加额外的行为,而不会破坏原始的操作。
五、元编程与装饰器
5.1.元编程
-
Symbol:
- 引入了一种新的原始数据类型,用于创建唯一的标识符。
- Symbols 可以被用作对象的键,且保证不会和现有的字符串键冲突。
- Symbols 提供了一种隐藏层,使得对象可以拥有不可迭代且不能被现有反射工具获取的属性。
-
Reflect:
- 是一个新的全局对象,提供了大量有用的内省(introspection)方法。
- Reflect 囊括了所有JavaScript引擎内部专有的“内部方法”,现在被暴露为了一个单一、方便的对象。
- 通过Reflect对象,开发者可以更容易地访问和操作对象的底层信息。
-
Proxy:
- 允许开发者创建对象的代理,并拦截和自定义对目标对象的操作。
- 通过Proxy,开发者可以实现对对象行为的动态修改和扩展。
5.2.装饰器
装饰器是ES7(虽然目前在标准中尚未正式定稿,但已被广泛使用)提出来的一种语法特性,它允许你在类、类方法、类属性等声明前面添加特殊的修饰符,以此来修改他们的行为。装饰器本质上是一个高阶函数,用于对类的定义、方法、属性进行修饰。
-
装饰器的语法:
- 使用
@
符号作为前缀,后面紧跟装饰器函数的名称。 - 装饰器函数接受特定的参数,如目标对象、属性名、属性描述符等,并返回一个新的属性描述符或进行其他操作。
- 使用
-
装饰器的应用:
- 类装饰器:应用于整个类,可以修改类的构造函数或添加新的静态方法。
- 方法装饰器:应用于类的方法,可以修改方法的行为或添加额外的逻辑。
- 属性装饰器:应用于类的属性,可以修改属性的特性或添加元数据。
-
装饰器的用途:
- 日志记录:为类或方法自动添加日志输出,便于调试。
- 权限控制:控制方法的访问权限,检查调用者的身份。
- 缓存功能:对函数调用结果进行缓存,避免重复计算。
- 性能监控:通过装饰器监控函数执行的性能数据。
- 自动绑定:为类方法自动绑定
this
,避免在不同上下文中丢失this
指向。 - 代码复用:通过装饰器复用代码,不必重复编写逻辑。
- 提升可读性:通过分离业务逻辑与装饰器逻辑,提升代码可读性和可维护性。
-
装饰器的使用示例:
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with`, args);
return original.apply(this, args);
};
return descriptor;
}
class Example {
@log
sayHello(name) {
return `Hello, ${name}`;
}
}
const example = new Example();
example.sayHello('World'); // 输出: Calling sayHello with [ 'World' ] Hello, World
在这个例子中,log
装饰器为sayHello
方法添加了日志输出功能,每次调用sayHello
时都会记录参数信息。
六、Generator 函数与class
6.1.Generator 函数
Generator 函数允许你暂停和恢复函数的执行
1. 定义 Generator 函数
Generator 函数使用 function*
语法来定义,而不是普通的 function
关键字。
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
2. 调用 Generator 函数
调用 Generator 函数不会立即执行其代码,而是返回一个迭代器对象。
const gen = myGenerator();
3. 使用 next()
方法
通过调用迭代器对象的 next()
方法,可以逐步执行 Generator 函数中的代码。每次调用 next()
方法时,Generator 函数会从上次暂停的地方继续执行,直到遇到下一个 yield
表达式或函数结束
console.log(gen.next().value); // 输出: 1
console.log(gen.next().value); // 输出: 2
console.log(gen.next().value); // 输出: 3
console.log(gen.next().done); // 输出: true
每次调用 next()
方法时,该方法返回一个对象,该对象有两个属性:
value
:yield
表达式的值(如果函数已结束,则为 undefined
)。
done
:一个布尔值,表示 Generator 函数是否已经执行完毕。
4. 示例:使用 Generator 函数处理异步操作
Generator 函数特别适合用于处理异步操作,因为它允许你暂停和恢复函数的执行,而不需要嵌套回调函数或 Promise 链。以下是一个简单的示例,展示了如何使用 Generator 函数和 Promise
处理异步操作:
function fetchData(url) {
return new Promise((resolve, reject) => {
// 模拟异步数据获取
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
function* asyncFlow() {
const data1 = yield fetchData('https://api.example.com/data1');
console.log(data1);
const data2 = yield fetchData('https://api.example.com/data2');
console.log(data2);
}
function runGenerator(generator) {
const it = generator();
function handleResult(result) {
if (result.done) {
return;
}
result.value.then(
(value) => {
const nextResult = it.next(value);
handleResult(nextResult);
},
(error) => {
console.error(error);
}
);
}
handleResult(it.next());
}
runGenerator(asyncFlow);
在这个示例中,asyncFlow
是一个 Generator 函数,它使用 yield
来暂停执行并等待异步操作的结果。runGenerator
函数负责运行这个 Generator 函数,并在每次 yield
表达式的结果解决后继续执行。
5. 注意事项
Generator 函数返回的是一个迭代器对象,而不是普通的函数返回值。
Generator 函数中的 yield
表达式可以返回任何值,包括对象、数组、Promise 等。
Generator 函数允许你使用 yield*
表达式来委托另一个 Generator 函数或可迭代对象。
6.2.class
class
语法被引入,为JavaScript提供了更接近传统面向对象编程的语法糖。尽管JavaScript本质上仍然是基于原型的继承,但class
语法使得定义和继承类变得更加直观和易于理解。
1. 定义类
使用class
关键字来定义一个类。类体中包含构造器(constructor
)和方法。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
2. 创建类的实例
使用new
关键字来创建类的实例。
const person1 = new Person('Alice', 30);
person1.greet(); // 输出: Hello, my name is Alice and I am 30 years old.
3. 继承
使用extends
关键字来实现类的继承。子类可以重写父类的方法,或者添加新的方法。
class Employee extends Person {
constructor(name, age, jobTitle) {
super(name, age); // 调用父类的构造器
this.jobTitle = jobTitle;
}
greet() {
super.greet(); // 调用父类的方法
console.log(`I am an ${this.jobTitle}.`);
}
}
const employee1 = new Employee('Bob', 40, 'Engineer');
employee1.greet();
// 输出:
// Hello, my name is Bob and I am 40 years old.
// I am an Engineer.
4. 静态方法
使用static
关键字来定义静态方法。静态方法属于类本身,而不是类的实例。
class MathUtils {
static add(a, b) {
return a + b;
}
}
console.log(MathUtils.add(2, 3)); // 输出: 5
5. Getter 和 Setter
在类中可以使用get
和set
关键字来定义属性的getter和setter方法。
class Rectangle {
constructor(width, height) {
this._width = width;
this._height = height;
}
get area() {
return this._width * this._height;
}
set area(value) {
// 注意:这里通常不会直接设置面积,因为面积是由宽度和高度决定的。
// 但为了演示,我们可以抛出一个错误。
throw new Error('Area is a read-only property.');
}
// 可以设置宽度和高度
set width(value) {
this._width = value;
}
get width() {
return this._width;
}
// 同理,可以设置和获取高度
set height(value) {
this._height = value;
}
get height() {
return this._height;
}
}
const rect = new Rectangle(10, 5);
console.log(rect.area); // 输出: 50
// rect.area = 100; // 这将抛出错误
rect.width = 20;
console.log(rect.area); // 输出: 100
注意事项
尽管ES6引入了class
语法,但JavaScript的类并不完全等同于其他面向对象语言中的类。例如,JavaScript的类没有传统的“类型”或“接口”的概念,也没有访问修饰符(如private
、protected
等,尽管在后续版本中引入了私有字段)。
类的继承是基于原型的,这意味着子类会继承父类的原型链上的属性和方法。
在类中,this
的绑定是自动的,与在函数或方法中使用this
时需要小心的情况不同。
class
语法为JavaScript提供了更清晰、更易于理解的面向对象编程方式,但它仍然是基于JavaScript原型继承机制的语法糖。
亲爱的友友们~~码字不易,给孩子点点赞呗
原文地址:https://blog.csdn.net/m0_64455070/article/details/143742747
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!