跳至主要內容

引用类型

Yang大约 22 分钟JavaScript

引用类型

  • 引用类型的值(对象)是引用类型的一个实例
    • 新对象是使用 new 操作符后跟一个构造函数来创建的
      • 构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的
  • 引用类型是一种数据结构,在技术上讲是一门面向对象的语言
    • 不具备传统面向对象语言所支持的类和接口等基本结构

Object 类型

  • 创建 Object 实例的方法
    • new Object
    • 字面量表示法
      • 不会调用 Object 构造函数
  • 访问对象属性方法
    • 点表示法
    • 方括号表示法
      • 可通过变量访问

Array 类型

  • 有序列表
  • 每一项可以保存任何类型的数据
  • 数组大小可以动态调整,即可以随数据添加自动增长以容纳新数据
  • 创建数组方法
    • new Array :var colors = new Array();
    • 字面量表示法 [ ]

检测数组

  • instanceof 操作符
    • instanceof 是假定单一的全局环境,如果网页中包含多个框架,那实际上就存在两个以上不同的全局环境,就会有两个不同版本的 Array 构造函数,如果从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数
  • Array.isArray() 方法
    • if ( Array.isArray(value) )

转换方法

  • toString:调用数组的 toString() 方法会返回数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串
    • 会调用数组每一项的 toString() 方法
  • valueOf:调用数组的 valueOf() 方法反悔的还是数组
  • toLocalString:经常返回与 toString() 和 valueOf() 方法相同的值,会调用数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串
    • 会调用数组每一项的 toLocalString() 方法

数组方法

栈方法
  • push():接受任意数量参数,逐个添加到数组末尾,并返回修改后的数组长度
  • pop():从数组末尾移除最后一项,减少数组的 length 值,并返回移除的项
队列方法
  • shift():移除数组中第一个项,并返回该项,同时使数组长度减 1
  • unshift():在数组前端添加任意个项并返回新数组的长度
重排序方法
  • reverse():翻转数组(不灵活)
  • sort()
    • 接收数组,数组的每一项进行 toString() 转换为字符串,然后去比较得到的字符串
      • 即使每一项都是数值,比较的也是字符串,10 会位于 5 的前面(不是最佳方案)
    • 接受一个比较函数作为参数
      • 比较函数接受两个参数
        • 如果第一个参数应该位于第二个之前则返回一个负数
        • 如果两个参数相等则返回 0
        • 如果第一个参数应该在第二个之后则返回一个正数
操作方法
  • concat():基于当前数组的所有项创建一个新数组
    • 这个方法会先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组
      • 没有传递参数的情况下,仅复制当前数组并返回副本
      • 穿第一个或多个数组时,该方法会将这些数组中的每一项都添加到结果数组中
      • 如果传递的值不是数组,这些值会被简单的添加到结果数组的末尾
  • slice():基于当前数组中的一个或多个项创建一个新数组
    • 接受一个或两个参数:即返回项的起始和结束位置
      • 只有一个参数时,返回从该参数指定位置开始到当前数组末尾的所有项
      • 有两个参数时,返回起始位置和结束位置之间的项(不包括结束位置的项)
      • 如果改参数中有负数,则用数组长度加上该数老确定相应的位置
        • 例如一个包含五项的数组上调用 slice(-2,1) 与 slice(3,4) 结果是相通的
        • 如果结束位置小于起始位置,则返回空数组
      • 该方法不会影响原始数组
  • splice()
    • 三种使用方式
      • 删除元素:可以删除任意数量的项,需要指定两个参数:要删除的第一项的位置和要删除的项数
        • 只传一个参数怎会删除从这个位置开始到最后的所有元素
      • 插入元素:向指定位置插入任意数量的项
        • 需要提供 3 个参数
          • 起始位置
          • 0(要删除的项数)
          • 要插入的项,如果需要插入多个项,可以再传入第四、第五以致任意多个项
      • 替换元素:向指定位置插入任意数量的项,且同时删除任意数量的项
        • 需要提供 3 个参数
          • 起始位置
          • 要删除的项数
          • 要插入的任意数量的项
    • 该方法始终会返回一个数组,其中包含从原始数组中删除的项
      • 没有删除任何项,则返回一个空数组
位置方法
  • indexOf()
    • 接收两个参数
      • 要查找的项和(可选的)表示查找起点位置的索引
      • 从数组的开头(位置 0)开始向后查找
      • 返回要查要的项在数组中的位置,没找到的情况下返回 -1
      • 比较时采用的是全等操作符
  • lastIndexOf()
    • 接收两个参数
      • 要查找的项和(可选的)表示查找起点位置的索引
      • 从数组的末尾开始向前查找
      • 返回要查要的项在数组中的位置,没找到的情况下返回 -1
      • 比较时采用的是全等操作符
迭代方法
  • 每个方法都接受两个参数
    • 要在每一项上运行的函数
    • (可选的)运行该函数的作用域对象 - 影响 this 的值
  • 传入这些方法中的函数会接收三个值
    • 数组项的值
    • 该项在数组中的位置(索引)
    • 数组对象本身
  • 5 个迭代方法及作用(这几个方法都不会修改数组中包含的值)
    • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true ,则返回 true
      • 用于查询数组中的项是否满足某个条件
    • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true ,则返回 true
      • 用于查询数组中的项是否满足某个条件
    • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组
      • 返回数组中满足函数中条件的项
    • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
      • 返回在原始数组中对应项上运行传入函数的结果的数组
    • forEach():对数组中的每一项运行给定函数,该方法没有返回值
      • 本质上与 for 循环迭代数组一样
缩小方法
  • reduce()reduceRight()
    • 都会迭代数组的所有项,然后构建一个最终返回的值
    • reduce() 方法从数组第一项开始,逐个遍历到最后
    • reduceRight() 方法从数组最后一项开始,向前遍历到第一项
    • 都接受两个参数
      • 在每一项上调用的函数
        • 函数接收四个参数(前一个值、当前值、项的索引、数组对象)
        • 函数返回的任何值都会作为第一个参数自动传给下一项
        • 第一次迭代发生在数组的第二项上,此时第一个参数是数组的第一项,第二个参数是数组的第二项
      • (可选的)用作第一个调用函数的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

Date 类型

  • 使用 UTC(国际协调时间)1970 年 1 月 1 时午夜(零时)开始经过的毫秒数来保存日期,能精确到 1970 年 1 月 1 日之前货之后的 285616 年
  • 创建一个日期对象,使用 new 操作符和 Date 构造函数
    • 如果第一个值是数值,Date 构造函数就会假设该值是日期中的年份,第二个是月份
  • 根据特定的日期或者时间创建日期对象,必须传入该日期的毫秒数,为了简化这一过程,ECMAScript 提供了两个方法 Date.parse() 和 Date.UTC()
    • Date.parse() :接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数,字符串形式如下面几种方法所示
      • "月/日/年",如 6/13/2004
      • "英文月名 日,年",如 January 12,2004
      • "英文星期几 英文月名 日 年 时:分:秒 时区",如 Tue May 25 2004 00:00:00 GMT-0700
      • "YYYY-MM-DDTHH:mm:ss.sssZ", 如 2004-05-25T00:00:00 (兼容 ECMAScript5 才支持)
      • 如果传入的字符串不能表示日期,则会返回 NaN
      • 实际上如果直接将表示日期的字符串传给 Date 构造函数,也会在后台调用 Date.parse()
    • Date.UTC() :返回表示日期的毫秒数,与 Date().parse() 在狗键值时使用不同的信息
      • 参数分别是 年份、基于 0 的月份(一月是 0、二月是 1,以此类推)、月中的哪一天(1-31)、小时数(0-23)、分钟、秒以及毫秒数
        • 只有两个参数(年和月)是必填的,如果没有提供天数则设天数为 1,如果省略其他参数,则统统设为 0
    • Date.now():返回表示调用这个方法时的日期和时间的毫秒数

日期时间组件方法

  • getTime():返回表示日期的毫秒数,与 valueOf() 方法返回的值相同
  • getFullYear():取得四位数年份
  • getMonth():返回日期中的月份,0 表示一月,11 表示 12 月
  • getDate():返回日期月份中的天数(1-31)
  • getDay():返回日期中的星期的星期几(0 表示星期日,6 表示星期日)
  • getHours():返回日期中的小时数(0-23)
  • getMinutes():返回日期中的分钟数(0-59)
  • getSeconds():返回日期中的秒数(0-59)
  • getMilliseconds():返回日期中的毫秒数

RegExp 类型(未完成)

  • ECMAScript 通过 RegExp 类型来支持正则表达式。
  • 使用下面的语法可以创建一个正则表达式
    • var exoression = / pattern / flags
      • pattern:可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。
      • 每个正则表达式都可带有一个或多个标志( flags ),用以标明正则表达式的行为。
      • 正则表达式的匹配模式支持下列三个标志
        • g:表示全局(global)模式,即模式将被应用与所有字符串,而非在发现第一个匹配项时立即停止
        • i:表示不区分大小写模式,即在确定匹配项时忽略模式与字符串的大小写
        • m:表示多行模式,即在到达一行文本末尾时还会继续查找下一行中是否讯在于模式匹配的项
      • 一个正则表达式就是一个模式与上述三个标志的组合体
      • 模式中所有的元字符都必须转义, 正则表达式中的元字符包括:()
      • 例子
        • var pattern1 = /at/g:匹配字符串中所有 “at” 的实例
        • vat pattern2 = /[bc]at/i]:匹配第一个 “bat” 或 "cat",不区分大小写
        • var pattern3 = /.at/gi:匹配所有以 “at” 结尾的 3 个字符的组合,不区分大小写

Function 类型

  • 没有重载,同名函数会直接覆盖
  • 函数声明与函数表达式
    • 优先读取函数声明,并使其在执行任何代码之前可用(可以访问)
    • 函数表达式必须等到解释器执行到他所在的代码行才会被解释执行
    • 作为值的函数
<script>
  // 调用例子
  function callSomeFunction(someFunction, someArgument) {
    return someFunction(someArgument)
  }
  function add10(num) {
    return num + 10
  }
  var result1 = callSomeFunction(add10, 10)
  function getGreeting(name) {
    return 'Hello, ' + name
  }
  var result2 = callSomeFunction(getGreeting, 'Nicholas')
  alert(result1)
  alert(result2)

  // 根据对象属性对数组进行排序
  let data = [
    {
      name: '孙振洋',
      gender: '男',
      age: 25
    },
    {
      name: '喜小乐',
      gender: '男',
      age: 18
    },
    {
      name: '高越',
      gender: '女',
      age: 26
    }
  ]
  function creatComparisonFunction(propertyName) {
    return function (object1, object2) {
      let value1 = object1[propertyName]
      let value2 = object2[propertyName]
      if (value1 > value2) {
        return 1
      } else if (value1 < value2) {
        return -1
      } else {
        return 0
      }
    }
  }
  data.sort(creatComparisonFunction('age'))
  console.log(data)
</script>
  • 函数的内部属性
    • 函数内部,有两个特殊的对象:argumentsthis
      • arguments:类数组对象,包含传入函数中的所有参数
        • 这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数
      • this:是函数据以执行的环境对象,或者也可以说是 this 值(当在网页的全局作用域中调用函数时,this 对象的引用就是 window)
<script>
  // callee
  function outer() {
    inner()
  }
  function inner() {
    console.log(inner.caller)
    console.log(inner.caller.caller)
  }
  outer()
</script>
  • ECMAScript 5 也规范了另一个函数对象的属性:caller

    • 这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null
    • 严格模式下 arguments.callee 会导致错误
    • ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined
    • 定义这个属性是为了分清 arguments.caller 和 函数的 caller 属性,以上变化都是为了加强这门语言的安全性,这样第三方代码库就不能在相同的环境里窥视其它代码了
    • 严格模式还有一个限制:不能为函数的:caller 属性复制,否则会导致 错误
  • 函数属性和方法

    • 每个函数都包含两个属性:lengthprototype
      • length:表示函数希望接收的命名参数的个数,
      • prototype
        • 每个函数都包括两个非继承而来的方法:apply()call(),这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 的值。
          • apply() :接受两个参数,一个是在其中运行函数的作用域,另一个是参数数组。 其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象
          • call():与 apply() 方法的作用基本相同,它们的区别仅在于接收参数的方式不同。第一个参数 this 值没有变化,变化的是其余参数都是直接传递给函数。换句话说,在使用 call() 方法时,传递给函数的参数据需逐个列举出来
<script>
  var color = 'red'
  var obj = {
    color: 'blue'
  }
  var str1 = 'color'
  var str2 = ':'

  function sayColor(str1, str2) {
    console.log(str1 + str2 + this.color)
  }

  sayColor.apply(window, [str1, str2]) // color:red
  sayColor.apply(obj, [str1, str2]) //color:blue
  sayColor.call(window, str1, str2) //color:red
  sayColor.call(obj, str1, str2) // color:blue
</script>
  • bind():创建一个函数的实例,其 this 值会被绑定到传给 bind() 函数的值
<script>
  var color = 'red'
  var obj = {
    color: 'blue'
  }

  function sayColor() {
    console.log(this.color)
  }
  var sayColorStr = sayColor.bind(window)
  var sayColorObj = sayColor.bind(obj)
  sayColorStr() // red
  sayColorObj() // blue
</script>
  • 每个函数继承的 toLocaleString()toString() 方法始终返回函数的代码。
    • 返回代码的格式因浏览器而异,有的返回的代码与源代码中的函数代码一样,而有的则返回函数代码的内部表示,即由解释器删除了注释并对某些代码做了改动后的代码。由于这些差异,我们无法根据这两个方法来实现任何功能,不过这些信息在调试代码时很有用。另一个继承的 valueOf() 方法同样也只返回函数代码。

基本包装 类型

Boolean 类型

  • 重写了 valueOf() 方法,返回基本类型值 true 或 false
  • 重写了 toString() 方法,返回字符串 “true” 和 “false”
  • 建议永远不要使用 Boolean 对象

Number 类型

  • 重写了 valueOf() 方法,返回对象表示的基本类型的数值
  • 另外两个方法则返回字符串形式的数值
  • 将数值格式化为字符串的方法
    • tofixed():按照指定的小数位(0~20)返回数值的字符串表示(如果数值本身包含的小数位比指定的还多,那么接近指定的最大小数位的值就会舍入)
    • toExponential():该方法会返回以指数表示法(也成 e 表示法)表示的数值的字符串形式,与 tofixed() 一样,toExponential() 也接受一个参数,而且该参数也是指定输出结果中的小数位数
    • toPrecision():该方法可能会赶回固定格式大小( fixed )格式,也可能返回指数格式,接受一个参数,即表示数值的所有数字位数(不包括指数部分 1~21)
  • 仍然不建议直接实例化 Number 类型

String 类型

  • String 对象的方法也可以在所有基本的字符串值中访问到。其中,继承的 valueOf()、toLocaleString()、toString() 方法都返回对象所表示的基本字符串的值
  • 即使字符串中包含双字节字符(不是占一个字节的 ASCLL 字符),每个字符仍算一个字符
字符方法
  • chartAt()charCodeAt(),这两个方法都接受一个参数,即基于 0 的字符串位置。
    • charAt() 方法以单字符字符串形式返回给定位置的那个字符
    • charCodeAt() 方法返回字符编码
    • str[index] 的形式也可访问字符串中的特定字符(IE8 以前版本的 IE 浏览器不支持)
字符串操作方法
  • contact():用于将一个或多个字符串拼接起来,返回拼接得到的新字符串
  • slice()substr()substring()
    • 这三个方法都会返回被操作字符串的一个子字符串
    • 都接受一或两个参数,第一个参数指定子字符串的开始位置
    • slice()substring() 的第二个参数指定的是子字符串最后一个字符后面的位置
    • substr() 的第二个参数指定的是返回字符个数
    • 如果没有给这些方法传递第二个参数,则将字符串的长度作为结束位置
    • contact() 方法一样,这三个函数不会修改字符串本身的值,只是返回一个基本类型的字符串值,对原始字符串没有任何影响
    • 当传入参数存在负值时
      • slice() :将传入的负值与字符串的长度相加
      • substr():将负的第一个参数加上字符串的长度,将负的第二个参数转换为 0
      • substring():将所有负值参数都转换为 0,该方法会将较小的数作为开始位置,将较大的数作为结束位置
字符串位置方法
  • indexOf()lastIndexOf()
    • 在一个字符串中搜索给定字符串,然后返回字符串位置,如果没有找到该字符串,则返回 -1
    • indexOf() 从字符串的开头向后搜索字符串
    • lastIndexOf() 从字符串的末尾向前搜索字符串
    • 这两个方法都可以接受第二个参数,表示从字符串的哪个位置开始搜索
      • indexOf() 从该参数指定的位置向后搜索,忽略该位置之前的所有字符串
    • lastIndexOf() 从该参数指定的位置向前搜索,忽略该位置之后的所有字符串
trim() 方法
  • 创建一个字符串的副本删除前置及后缀的所有空格,然后返回结果
字符串大小写转换方法
  • toLowerCase():将字符串转化为小写
  • toLocaleLowerCase():将字符串转化为小写
  • toUpperCase:将字符串转化为大写
  • toLocaleUpperCase():将字符串转化为大写
  • toLocaleLowerCase()toLocaleUpperCase() 是针对特定地区实现。对有些地区来说,针对地区方法与其通用方法得到的结果相同,但少数语言(如土耳其语)会为 Unicode 大小写转换应用特殊的规则,这时候就必须使用针对地区的方法来保证实现正确的转换。
  • 在不知道自己的代码将在哪种语言环境中运行的情况下,使用针对地区的方法更稳妥一些
字符串的模式匹配方法(未完成)
  • match():在字符串上调用这个方法,本质上与调用 RegExp 的 exec() 方法相同
    • 只接收一个参数,要么是一个正则表达式,要么是一个 RegExp 对象
localeCompare() 方法
  • 比较两个字符串,并返回下列值中的一个
    • 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况返回 -1,具体的值要视实现而定)
    • 如果字符串等于字符串参数,则返回 0
    • 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况返回 1,具体的值同样要视实现而定)
fromCharCode() 方法
  • 接受一或多个字符编码,然后将它们转换成一个字符串
  • 从本质上来看,这个方法与实例方法 charCodeAt() 执行的是相反的操作

单体内置对象

  • ECMA-262 对内置对象的定义是:由 ECMAScript 实现提供的、不依赖宿主环境的对象,这些对象在 ECMAScript 程序执行之前就已经存在了。”意思就是说,开发人员不必显式的实例化内置对象,因为它们已经实例化了。
  • ObjectArrayStrinfgGlobalMath
Global 对象
  • ECMAScript 中最特别的一个对象,因为不管你从什么角度上看,这个对象都是不存在的。
  • ECMAScript 中的 Global 对象在某种意义上是最为一个终极的 “兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法,事实上没有全局变量或者全局函数,所有在全局作用于中定义的函数和方法都是 Global 对象的属性
Global 对象包含的一些方法

####### URL 编码方法

  • encodeURI()encodeURIComponent() 方法可以对 URI(通用资源标识符)进行编码以便发送给浏览器
    • 有效的 URI 中不能包含某些字符,例如空格
    • 这两个 URI 编码方法就可以对 URI 进行编码,它们用特殊的 UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解
    • encodeURI() 主要用于整个 URI,而 encodeURIComponent() 主要用于对 URI 中的某一段进行编码。
    • 两个函数主要的区别在于 encodeURI() 不会对本身属于 URI 的特殊字符进行编码,如冒号、正斜杠、问好和井字号,而 encodeURIComponent() 会对它发现的任何非标准字符进行编码
  • encodeURI()encodeURIComponent() 对应的两个方法分别是 decodeURI()decodeURIComponent()
    • 其中 decodeURI() 只能对使用 encodeURI() 替换的字符进行解码,同样的,decodeURIComponent() 能够解码使用 encodeURIComponent() 编码的所有字符,即它可以解码任何特殊字符的编码
eval() 方法
  • 只接受一个参数,即要执行的 ECMAScript(或 JavaScript)字符串
  • 当解析器发现代码中调用 eval() 方法时,它会将传入的参数当做实际的 ECMAScript 语句来解析,然后把执行结果插入到原来的位置。
  • 通过 eval() 执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过 eval() 执行的代码可以引用在包含环境中定义的变量
  • 在 eval() 中创建的任何变量或函数都不会被提升,因为在解析代码的时候,他们被包含在一个字符串中,他们只有在 eval() 执行的时候创建
  • 严格模式下,在外部访问不到 eval() 中创建的任何变量或函数,同样,在严格模式下,为 eval 赋值也会导致错误
Global 对象的属性
  • window 对象 ECMAScript 虽然没有指出如何直接访问 Global 对象,但 Web 浏览器都是将这个全局对象作为 window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了 window 对象的属性
Math 对象
Math 对象的属性
属性说明
Math.E自然对数的底数,即常量 e 的值
Math.LN1010 的自然对数
Math.LN22 的自然对数
Math.LOG2E以 2 为底 e 的对数
Math.LOG10E以 10 为底 e 的对数
Math.PIπ 的值
Math.SQRT1_21/2 的平方根(即 2 的平方根的倒数)
Math.SQRT22 的平方根
Math 对象的方法

####### min()max() 方法

  • 用于确定一组数值中的最小值和最大值,这两个方法都可以接收任意多个数值参数
<script>
  var values = [1, 2, 5, 9, 8, 3, 4, 6, 7]
  var max = Math.max.apply(Math, values)
  console.log(max)
</script>

####### 舍入方法

  • Math.ceil():执行向上舍入,即它总是将数值向上舍入为最接近的整数
  • Math.floor():执行向下舍入,即它总是将数值向下舍入为最接近的整数
  • Math.round():执行标准舍入,即它总是将数值四舍五入为最接近的整数

####### random() 方法

  • 返回介于 0 和 1 之间的一个随机数,不包括 0 和 1
  • 可以用以下公式来从某个整数范围内随机选择一个值
    • 值 = Math.floor( Math.random() * 可能值的总数 + 第一个可能的值 )
<script>
  // 随机取一个整数范围内的一个整数
  function selectFrom(lowerValue, upperValue) {
    var choices = upperValue - lowerValu + 1
    return Math.floor(Math.round() * choices + lowerValue)
  }
</script>
其他方法
方法说明
Math.abs(num)返回 num 的绝对值
Math.exp(num)返回 Math.E 的 num 次幂
Math.log(num)返回 num 的自然数对
Math.pow(num,power)返回 num 的 power 次幂
Math.sqrt(num)返回 num 的平方根
Math.acos(x)返回 x 的反余弦值
Math.asin(x)返回 x 的反正弦值
Math.atan(x)返回 x 的反正切值
Math.stan2(y,x)返回 y/x 的反正切值
Math.cos(x)返回 x 的余弦值
Math.sin(x)返回 x 的正弦值
Math.tan(x)返回 x 的正切值
上次编辑于:
贡献者: sunzhenyang