ES6 常用语法和开发总结(万字长文)

可紊 提交于 2020-05-04 09:45:51

ES6 常用语法和开发总结

前言

阮一峰老师的 ES6 入门教程 看了好几遍,发现每次看完都感觉有很多地方不懂,越看越焦虑,现在稍微明白了,这可能是大多数人追求的完美主义,希望每件事情都搞懂心理才踏实。其实,是没必要的,根据”二八定律“,我们只要掌握其中的 20% 常用知识和语法去解决开发中的 80% 问题就够了,剩下不懂的地方可以通过阅读相关文档和资料解决。更重要的原因是前端技术更新迭代如此之快,我们也要跟上步伐,把有限的时间放在正确的事情上,更何况我们还有诗和远方。所以在这里总结一下 ES6 常用语法和开发过程中遇到的一些坑,然后继续前行。

let 声明 VS const 声明

相同点

  • 没有变量提升,必须先声明后使用,否则报错
  • 形成块级作用域,暂时性死区
  • 不能重复声明
  • 在全局声明,不能挂载在window上

不同点

  • const 声明变量时需要赋初始值
  • const 声明的基本类型数值不能被修改,声明的引用类型可以修改属性,但是不能重新赋值改变引用的地址
  • 使用 const 程序执行效率更快

console.log(a) // 报错,Uncaught ReferenceError: a is not defined
let a = 1
let a = 2 // 报错,Uncaught SyntaxError: Identifier 'a' has already been declared

if (true) { // 形成块级作用域
  var b = 33
  let c = 22
}
console.log(b) // 33
console.log(c) // Uncaught ReferenceError: c is not defined

var name = '张三'
let lisi = '李四'
console.log(window.name) // 张三
console.log(window.lisi) // undefined

const d = '11'
const obj = {name: 33}

d = '22' // Uncaught TypeError: Assignment to constant variable.
obj.name = '2323' // {name: 2323}
obj = {} // Uncaught TypeError: Assignment to constant variable

复制代码

解构赋值

  • 数组解构赋值
  • 对象解构赋值
  • 字符串解构赋值
  • 函数参数解构赋值

解构赋值使用场景

  • 交换变量的值
  • 从函数返回多个值
  • 函数参数解构
  • 提取 JSON 数据
  • 遍历 Map 结构
  • 模块导入解构
let x = {
  name: 33
}
ley y = [33, 22]

//交换值
[x, y] = [y, x]
console.log(x) // [33, 22]

function fn() {
  ...
  return {name, value}
}

const {name, value} = fn() // 提取函数多个返回值

function getValue({name, value, ...}) { // 参数解构赋值
  ...
  return value
}

let {name, data } = {name, '33', value: 22, data: [3, 2, 4]} //对象解构赋值,提取 JSON数据

function fn () {
   for(let [k, v] of map.entries()) { // [k, v] map 解构赋值
      ...
   }
}

import {module1, module2, ... } from './filename.js' // 模块解构赋值

复制代码

注意事项

  • 解构赋值变量重命名。有时候我们需要转换一下后台返回的键名,这时这个地方用的上

  • 函数参数对象解构赋值时,我们需要给它默认值,这时如果调用函数不传对象就会报错,解决方法是初始化一个空对象


let {x: width, y: height} = {x: '220px', y: '100px'}  // 重命名
console.log(width, height) // 220px 100px

function fn ({name='', value=0}) {
  ...
}
fn() // Uncaught TypeError: Cannot read property 'name' of undefined

// 加一个空对象
function fn ({name='', value=0} = {}) {
  ...
}
复制代码

字符串新增方法

  • includes:返回布尔值,判断字符串是否包含某串字符。接受两个参数,第二个参数是起始的位置

  • startsWith:返回布尔值,判断字符串开头是否包含某串字符

  • endsWith:回布尔值,判断字符串结尾是否包含某串字符

  • repeat:返回一个新的字符串,将原字符串重复 n 次

  • padStart:返回新的字符串,在字符串前面补全,接受两个参数,第一个参数补全字符串长度,第二个参数补全的字符

  • padEnd::返回新的字符串,在字符串后面补全,接受两个参数,第一个参数补全字符串长度,第二个参数补全的字符

  • trimStart:去除前面空格

  • tiemEnd:去除后面空格

  • matchAll: 返回一个正则表达式在当前字符串的所有匹配


let str = 'abcdef'
str.includes('bc') // true
str.includes('ss') // false

str.repeat(2) // "abcdefabcdef"
str.repeat(0) // ''
str.repeat(-1) // 报错,Uncaught RangeError: Invalid count value

str.padStart(10, 1) // "1111abcdef"
str.padEnd(10, 0) // "abcdef0000"

复制代码

正则表达式

方法

  • match:匹配成功返回数组,匹配不成功返回null

  • replace:替换字符串,返回一个新的字符串

  • search:匹配返回第一个索引的位置,匹配不成功返回 -1

  • split:返回数组

  • exec:匹配成功返回数组,匹配不成功返回null

  • test:返回布尔值 boolean

数值扩展

方法

  • Number.isInteger:判断是否是整数
  • Number.isSafeInteger:判断是否是安全整数
  • Number.isNaN: 判断是否是NaN 数值
  • Math.trunc():去除一个数的小数部分

函数

函数拓展

  • 函数默认参数

  • rest 参数

  • 函数 length 属性。它表示函数参数的个数,如果参数设置默认值,则 length 减 1;rest 参数也不会计入 length 属性

  • 箭头函数注意事项

    • 函数体内的 this 对象,就是定义时所在的对象
    • 箭头函数不可以当作构造函数使用,不可以使用 new 命令
    • 不可以使用 arguments 对象
    • 不可以使用 yield 命令
    • 箭头函数不适合使用场景:1、定义对象的方法;2、监听事件回调
  • 尾调用优化

  • 尾递归


function fn(a, b, c) {
...
}
fn.length // 3

function fn(a, b=1, c=2) {
...
}
fn.length // 1

let obj = {
  name: 'JEFFER',
  getName: () => { // 不适合箭头函数
  console.log(this.name)
}
obj.getName() // undefined

doc.addEventListener('click', () => {
  console.log(this) // 这里的this原本指向dom元素,现在使用箭头函数指向了 window
}

复制代码

数组

  • from:将 ArgumentsDOM 元素集合等伪数组,转化为数组

  • of:将多个数据项向转化为数组

  • find:查找元素,查找成功返回数组元素,查找不到返回 null

  • findIndex:查询下标,查找成功返回数组元素下标,查找不到返回 -1

  • copyWithin:数组内部复制

  • fill:填充数组

  • entries:返回数组下标和元素组成的二维数组

  • keys:返回下标的数组

  • values:返回元素的数组

  • includes:匹配数组项,返回 boolean

  • flat: 数组降维


function fn(a, b,c) {
...
console.log(Array.from(Arguments)) // [a, b, c]
}
Array.of(1, 2, 3) // [1, 2, 3]

let arr = [22, 33, 44, 55]
arr.find((item) => item === '33') //33 
arr.findIndex((item) => item === '33') // 1

复制代码

数组迭代方法

数组迭代方法在函数式编程时非常有用,熟练使用函数式编程能大大提高自己的编程效率,写出的代码也很简洁,易读易维护

  • forEach:遍历数组,中间可以使用 continue 跳过本次循环,在循环中break 关键字是不生效的

  • filter:过滤数组返回一个新数组,不改变原数组。可以进行链式操作

  • map:数组映射,返回一个数组,不改变原数组。可以进行链式操作

  • reduce:数组汇总,返回一个值。接收两个参数,第二个参数接收默认值

  • every:返回一个 boolean 值,遍历结果全部为 true 才为 true,否则返回 false

  • some:返回一个 boolean 值,遍历某次结果返回 true时会立刻结束循环。这个想中间结束循环很有用,不用再借助 break 结束循环,有利于提高执行效率

注意 :在上面迭代方法中返回新数组使用的是浅拷贝,如果数组中的数值是引用类型,即使返回了新数组如果修改其值,该值指向同一个引用地址也会相互影响


let arr = [32,32,2324, 223]

arr.forEach((item, index) => {
  if (...) {
    continue // 满足条件想尽早结束本次循环
   }
})

arr.filter((item, index) => {
  ...
  return item
}).filter(()=>{...}).map(()=>{...}) // 链式操作

arr.map((item, index) => {
  ...
  return item
}).filter(()=>{...}).map(()=>{...}) // 链式操作

arr.reduce((pre, next) { // 汇总
  ...
  return item
}, [])

arr.ervery((pre, next) {
  ...
  return item
})

arr.some((pre, next) {
  if (...) {
    ...
    return true // 结束循环
  }
  ...
  return item
})

复制代码

对象

方法

  • Object.assign():合并对象,浅拷贝,改变原对象

  • Object.keys():返回数组,获取对象的键

  • Object.values():返回数组,获取对象的值

  • Object.entries():返回二维数组,获取对象的键值

  • Object.fromEntries():将二维数组或 map 数据类型转化为对象

  • Object.create():创建对象

  • Object.is():同值相等,NaN等于NaN, 0 不等于 -0

  • Object.getPrototypeOf(obj):获取对象原型

  • Object.setPrototypeOf(obj):设置对象原型

Object.entries()Object.fromEntries() 方法可以相互转换

Object.fromEntries特殊用法,配合 URLSearchParams 对象,将查询字符串转为对象


le obj = { name: '33' }
Object.assign(obj, {value: 11}) // 注意,改变了原对象obj
console.log(obj) // {name, '33', value: 11}

// 二维数组转对象 { foo: "bar", baz: 42 }
Object.fromEntries([ 
  ['foo', 'bar'],
  ['baz', 42]
]) 

// map 转对象
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);
Object.fromEntries(entries) // { foo: "bar", baz: 42 }

// 将 URL 参数转化为对象
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

复制代码

Set 和 Map 数据结构

Set

Set 类似于数组结构,存放唯一值

属性

  • Set.prototype.constructor :构造器
  • Set.prototype.sizeSet实例的成员总数

方法

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。

s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(3) // false

s.delete(2);
s.has(2) // false
复制代码

迭代方法

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// 这样也行
for (let item of set) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

set.forEach((value, key) => console.log(key + ' , ' + value))
// "red", "red"
// "green", "green"
// "blue", "blue"
复制代码

使用场景

  • 可以用来去重,数组去重、字符串去重
  • 实现并集、交集、差集

// 去除数组的重复成员
[...new Set(array)]

// 去除字符串中重复字符
[...new Set('ababbc')].join('')
// "abc"
复制代码
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
复制代码

注意事项

  • 添加 Set 集合数据区分数据类型,5“5” 两个值不一样
  • SetNaN 是相等的,多次添加只添加一个
  • 两个对象总不相等
  • SetArray 可以相互转换

let set = new Set()
set.add(5)
set.add('5')
set.add(NaN)
set.add(NaN)
set.add({})
set.add({}) // 两个对象不一样
set.size // 5

// set 最终结果是
// Set(5) {5, "5", NaN,{},{}}

// 数组转 Set
let set1 = new Set(arr)

// Set 转数据
let arr = [...set]
// 或者
let arr = Array.from(set)

复制代码

Map

Map 类似于对象结构,本质上是键值对,简称 Hash 结构

属性

  • Map.prototype.constructor :构造器
  • Map.prototype.sizeMap实例的成员总数

方法

  • Map.prototype.set(key, value):添加某个键值对,返回 整个Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。(添加多个可以进行链式操作)

  • Map.prototype.get(key):读取key对应的键值,如果找不到key,返回undefined

  • Map.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中

  • Map.prototype.delete(key):删除某个键,返回true。如果删除失败,返回false

  • Map.prototype.clear():清除所有成员,没有返回值。

迭代方法

用法和 Set 一样,就不粘贴代码了

  • Map.prototype.keys():返回键名的遍历器

  • Map.prototype.values():返回键值的遍历器

  • Map.prototype.entries():返回键值对的遍历器

  • Map.prototype.forEach():使用回调函数遍历每个成员,forEach 可以接受第二个参数用来绑定 this 执行上下文

Map 和数组的转换

数组给我们提供了丰富的 API,将 Map 结构转换为数组处理数据更方便


const map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['a', 'b', 'c']

[...map] or [...map.entries] 
// [[1,'a'], [2, 'b'], [3, 'c']]

const map1 = new Map(
  [...map].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

复制代码

Proxy 拦截器

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']

  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。

  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。

  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。

  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。

  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。

  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。

  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。

  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)

  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

Reflect

ReflectES6 为了操作对象而提供的新 API

每个新东西的出现都是为了解决实际问题,Reflect 的出现同样也是为了解决以下问题:

  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

  • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法

方法

  • Reflect.apply(target, thisArg, args):等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数

const ages = [11, 33, 12, 54, 18, 96];

// 旧写法
const youngest = Math.min.apply(Math, ages);

// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
复制代码
  • Reflect.construct(target, args):等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。

  • Reflect.get(target, name, receiver):获取对象的值,例如 target[name], receiver 参数可以修改读取this 上下文(后面一样,就不介绍了)

  • Reflect.set(target, name, value, receiver):设置对象的值 target[name] = value

  • Reflect.defineProperty(target, name, desc):等同于 Object.defineProperty(target, name, desc)

  • Reflect.deleteProperty(target, name):等同于delete target[name],用于删除对象的属性

  • Reflect.has(target, name):等同于name in target里面的in运算符

  • Reflect.ownKeys(target):等同于Object.keys(target)

  • Reflect.isExtensible(target):等同于Object.isExtensible(target)

  • Reflect.preventExtensions(target):等同于Object.preventExtensions(target)

  • Reflect.getOwnPropertyDescriptor(target, name):等同于Object.getOwnPropertyDescriptor(target)

  • Reflect.getPrototypeOf(target):等同于Object.getPrototypeOf(target)

  • Reflect.setPrototypeOf(target, prototype):等同于Object.setPrototypeOf(target)

Promise

promise 是实现异步编程的一种解决方案,可以通过链式操作,解决回调地狱的问题

使用场景

  • 加载图片
  • 封装 ajax 请求接口

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    const image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}
复制代码

封装ajax请求接口


const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});
复制代码

方法

  • Promise.prototype.thenfullfilled 成功状态之后的回调函数

  • Promise.prototype.catch()rejected 错误时的回调函数

  • Promise.prototype.finally():该方法是 ES2018 引入标准,不管是成功或失败都会执行

  • Promise.all():用于将多个 Promise 实例,包装成一个新的 Promise 实例,

const p = Promise.all([promise1, promise2, promise3]).then(res => {});
复制代码

上面promise1promise2promise3 都是Promise 实例,它们都是成功之后才能执行 then 回调函数,返回参数 res 是个数组,可以用来解决同时请求多个接口

  • Promise.race():和上面的 all 方法用法一样,不同的地方在于只要有一个实例执行成功,它就成功了,只要有一个失败它也代表失败了

  • Promise.allSettled():该方法和上面allrace 用法一样,它主要解决的是不管成功或失败都返回,状态总是fulfilled,不会变成rejected,该方法由 ES2020 引入,有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,它就很有用

  • Promise.any():和 race 很相似,不同的地方,是不会因为某个 Promise 变成rejected状态而结束,有点类似于数组中的 Array.prototype.some 方法,该方法目前是一个第三阶段的提案

  • Promise.resolve()Promise 的静态方法,它的使用场景是将现有对象转为 Promise 对象,返回fullfilled成功状态

  • Promise.reject()Promise 的静态方法,它的使用场景是将现有对象转为 Promise 对象,返回rejected失败状态

Async 函数

async 函数也是异步编程的一种解决方案,它和 Promise 不同在于可以使用同步的编码方式实现异步编程,除此之外使用它代替 Promise 多次调用 then 方法也会使代码更容易读懂和解耦

async 函数是 Generator 函数的语法糖,它在此基础上做了几点改进:

  • 内置执行器:async函数自带执行器

  • 更好的语义:asyncawait,比起星号和yield,语义更清楚了

  • 更广的适用性:async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

  • 返回值是 Promise:可以用then方法指定下一步的操作

async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖

基本用法

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
复制代码

使用场景

  • 函数声明可以使用
  • 函数表达式可以使用
  • 对象方法可以使用
  • Class 方法可以使用
  • 迭代数组或对象中使用
  • 并发请求接口数据
// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// 迭代数组
async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

// 双重 async/await 并发请求接口数据
async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

复制代码

Class

class 是一个语法糖,它本身指向一个构造函数

使用场景

  • 使用 class 可以实现面向对象编程
  • 可以使用 extends 继承,实现抽象和代码复用
  • 可以定义内部属性和静态方法,实现封装

Module 模块

作用

模块的作用是可以将一个大的程序拆分成相互依赖的小文件,再用简单的方法拼接起来,可以让程序代码更容易维护、扩展功能、阅读和调试。

ES6 VS 其他模块

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

注意

  • ES6 引入的是只读操作,最好不要修改它的值

基本用法

  • export 命令输出,后面必须是var, let, const 声明的变量、函数或对象

  • export default 输出,后面可以是函数、对象或其他数据类型值,不能有var, let, const 关键字,并且一个文件只能有一个 export default

  • import 命令输入, export 输出的要加大括号,export default 输出的不用加大括号

// a.js
var name = 33
export name; // 报错
export var firstName = 'Michael'; // 正确
export default name // 正确

function fn () {}
export fn; // 报错
export default fn 正确
export function fn () {} // 正确
export default function fn () {} // 正确

export { // 正确
  name as other, // 重命名
  firstName
}

export default { // 正确
  name,
  firstName
}

//-----------------------

// b.js 文件
import { name as username } from './a.js' // export 导出的、重命名
import * as all from './a.js' // 导入所有
import name fromm './a.js // export default 导出的

复制代码

import() 函数 ES2020提案 引入import()函数,支持动态加载模块,import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块

编程风格

块级作用域

  • 声明变量用 let 代替 var,形成块级作用域,没有副作用
  • letconst 优先使用 constconst 声明常量,减少由于修改值出错,同时有利于提高程序的运行效率

函数

  • 能使用箭头函数的地方尽量使用箭头函数
  • 使用函数式编程
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!