返回介绍

3.4 CoffeeScript

发布于 2023-05-19 13:36:37 字数 13672 浏览 0 评论 0 收藏 0

最近,JavaScript 的发展十分惊人,有一种语言试图借 JavaScript 之威风争得一席之地,下面我们就来介绍一下这种语言——CoffeeScript。

最普及的语言

世界上的编程语言种类相当多,据说有成千上万种。要说其中最普及的,或者换个说法,其引擎被安装数量最多的语言,恐怕非 JavaScript 莫属了。

最近的计算机用户都不大会去编程了,但几乎所有人都会访问网站吧。访问网站,甚至已经成为“上网”的代名词。现在世上几乎所有的 Web 浏览器都内置了 JavaScript 引擎。PC 上的 Internet Explorer、Firefox、Safari、Chrome 等自不必说,就连智能手机甚至是非智能手机的浏览器上都装上了 JavaScript 引擎。

随着移动设备的兴起,尤其是考虑到非智能手机的普及率,完全可以断言 JavaScript 就是最普及的语言。而正是因为有了如此之高的普及率,才进一步推动了其重要性的不断上升。

被误解最多的语言

另一方面,JavaScript 也可以说是被误解最多的语言。

JavaScript 是由原 Netscape Communications 公司的布兰登·艾克,于 1995 年开发的一种用于扩展浏览器功能的编程语言。最初它被命名为 LiveScript,但当时正好是美国 Sun Microsystems 公司(现被 Oracle 公司收购)的 Java 方兴未艾之际,再加上 Netscape 和 Sun 之间有业务上的合作,因此为了在市场宣传上更有冲击力,就改名为 JavaScript 了。JavaScript 中大量使用了花括号,看上去和 Java 有点像,但其语言核心意义的部分和 Java 是完全不同的,因此这个名字便成了招致重大误解的元凶。

在 JavaScript 还没成名的时候,就经常听到类似“JavaScript 就是 Java 吧”这样的说法,还有很多人认为只要学会 Java 也就学会了 JavaScript。

JavaScript 本来的目的,是为了编写点击网页按钮时所需的一些简单的处理逻辑,由这一点又招致了第二个误解——JavaScript 是只能完成简单工作的简易语言。然而实际上则出乎意料,JavaScript 是一种设计良好的语言,它拥有基于原型的面向对象功能,可以将函数作为对象来使用,在此基础上还提供了正式的闭包功能。由于它可以进行函数型编程,因此从语言的语义上来看,有接近 Scheme 的一面。

利用 JavaScript 的良好设计,微软公司实现了动态网页 DynamicHTML,像 Google 地图这样大量运用 JavaScript 的网站也开始不断出现,这让人们对于 JavaScript 的印象发生了转变。Google 地图是 Ajax(Asynchronous JavaScript and XML,异步 JavaScript 与 XML)编程风格的先驱。如今,使用 JavaScript 制作视觉特效,以及用 Ajax 实现无页面迁移的网站,已经一点都不稀奇了。

当初,JavaScript 作为 Netscape Navigator 浏览器内置的客户端语言问世,后来又逐渐内置到其他一些浏览器中。然而,由于各公司对 JavaScript 的实现是在参考 Netscape 的基础上独自开发的,因此浏览器之间的兼容性很低,这让程序员感到十分痛苦。早期的 JavaScript 程序员,需要运用各种各样的方法来判断浏览器类型,为了回避兼容性问题而做出很大的努力。在 1997 年,由 ECMA 1规范作为“ECMAScript”实现标准化以来,这一问题得到了很大的改善。即便如此,依然还有一些人在使用着老版本的 Internet Explorer,因此大家还没有完全从兼容性问题中解放出来。

1 ECMA是一个信息和电信标准组织,原名“欧洲计算机制造商协会”(European Computer Manufacturer Assosications),后来随着该组织的国际化,虽沿用 ECMA 这个名称(一般称为 ECMA International),但已经不代表之前的全称了。该组织以 JavaScript 为基础制定的标准化规范“ECMAScript”编号为 ECMA-262,JavaScript 是该规范的兼容和扩展版本。

最后的误解是关于性能。JavaScript 的变量和表达式没有类型信息,具备动态性质,因此其性能和 Java 等静态类型语言相比具有劣势。实际上,早期的 JavaScript 引擎在实现上并没有过于追求性能,然而,随着 JavaScript 应用范围的扩大,这一点也得到了巨大的改善。

显著高速化的语言

作为编程语言来说,经常被关注的一点,就是同样的算法用各种不同的语言实现的时候,相互之间有多少性能上的差异。这一点上,一般认为采用能获得更多性能改善信息的静态类型,且以编译器作为引擎的语言性能比较高,例如 C++ 和 Java 等。

然而从根本上讲,性能应该与引擎的性质有关,而和语言的种类是无关的。因此,像 JavaScript 是动态语言因此速度慢这种印象并非普遍事实,而是由该语言的引擎在实现上做出了多大的努力而决定的。

的确,早期的 JavaScript 引擎性能并不算高。然而随着 JavaScript 被广泛使用,其重要性也跟着提高,对 JavaScript 引擎的投资也得到了扩大,各种高速引擎相继问世。刚刚诞生之际的 Java,由于需要通过字节码解释器来工作,和 C++ 等原生语言相比速度慢了不少,甚至有人说:“这种东西完全不能用。”但仅仅过了不久,Java 的性能就得到了大幅度的改善,现在在某些情况下,甚至能够实现超越 C++ 等语言的性能,这和 JavaScript 现象十分类似。

最近的 JavaScript 引擎中,由于采用了 JIT、特殊化、分代垃圾回收等技术,在动态语言中已经可以归入速度最快的级别了。

JIT 是 Just In Time Compiler 的缩写,指的是在程序运行时将其编译为机器语言的技术。由于编译为机器语言的程序可以以 CPU 原本的速度来运行,因此能够克服解释器所带来的劣势。JIT 在 JVM(Java Virtual Machine,Java 虚拟机)中也得到了运用。

所谓特殊化,指的是一种在将函数转换为内部表达时所运用的技术。通过假定参数为特定类型,事先准备一个特殊化的高速版本,在函数调用的开头先执行类型检查,当前提条件成立时直接运行高速版本。动态语言运行速度慢的理由之一,就是因为在运行时需要伴随大量的类型检查,而通过特殊化则可以回避这一不利因素。

分代垃圾回收,是一种对不再使用的对象进行回收的垃圾回收(Garbage collection)算法。垃圾回收有一些比较普通的方法,如标记清除法。这种方法对由程序(变量等)引用的对象进行递归式扫描,标记出“存活对象”,并认为剩下的对象将来不再被访问,将其作为“死亡对象”进行回收。这种方法的缺点是,程序中生成的对象数量越多,为了找到存活对象所需的扫描次数就越多。如果运行时间的很大一部分都消耗在垃圾回收上的话,性能就会降低。JavaScript 开发的程序,随着规模的扩大,对象数量也跟着增加,采用标记清除法所带来的性能下降问题也就愈发显著。

要改善这个问题,其中一个方法就是分代回收。在大部分程序中都存在这样一种趋势,即所生成的对象的一大半都只被使用很短的一段时间就不再被访问了,而存活下来的一部分对象,却拥有非常长的寿命。在分代回收中,将对象划分为新生代和老生代(根据情况还可能划分更多的代)两个组。其中对新生代频繁进行扫描,而对老生代只偶尔进行扫描,从而减少了整体的扫描次数。

由于上述这些技术的运用,JavaScript 得以在为数不多的动态语言中跻身速度最快的行列。Ruby 当然也被超越了,感到相当寂寞呢。

对 JavaScript 的不满

那么,虽然 JavaScript 人气如此之高,使用又如此广泛,但随着用户数量的增加,还是招致了越来越多的不满。

JavaScript 从语法和语义上来看都非常简单,基本上是一种非常优秀的语言。然而,它的语法有些过于简单了,有很多人对程序容易变得冗长感到不满。多年以来,我一直主张过于简单的语言一定不会让程序员开心,因此这一不满也可以说是应验了我的观点吧。

为了让语言功能变得更加丰富,出现了一些如 prototype.js、jQuery 之类的库,其中增加了一些方法,让 JavaScript 的对象用起来有 Ruby 的感觉。当然,这些库所提供的功能并不仅限于此。

CoffeeScript

于是,为了解决对 JavaScript 语法上的不满,CoffeeScript 做出了尝试。CoffeeScript 是由 Jeremy Ashkenas 开发的。Ashkenas 拥有多种编程语言的经验,还开发过用于从 Ruby 访问视觉 设计语言 Processing 2的 Ruby Processing。

2 Processing 是一种用于视觉设计和绘图的开源编程语言(及 IDE 开发环境),发布于 2001 年。

也许是出于这样的背景,CoffeeScript 在语法上貌似受 Ruby 和 Python 的影响很大。两者相比的话,应该还是受 Python 影响更大一些。

所谓 CoffeeScript,一言以蔽之,就是用 Javascript 实现的用于编写 JavaScript 的方便语言。CoffeeScript 是一种可以弥补 JavaScript 缺点和不满的、拥有独自语法的编程语言,和 JavaScript 之间完全没有兼容性。然而,CoffeeScript 程序在运行前需要被编译为 JavaScript,然后作为 JavaScript 程序来运行。也就是说,虽然程序看上去完全不同,但其语义的部分却是完全相同的。

进一步说,CoffeeScript 的编译器是用 JavaScript 编写的。也就是说,只要有 JavaScript, CoffeeScript 编写的程序就可以在浏览器上直接运行。很多语言都因为无法在客户端使用,从而不得不转向服务器端环境,而这一性质可以说是 CoffeeScript 的一个巨大优势。

基于这些优势,以及我们后面要介绍的 CoffeeScript 所具有的其他优秀性质,Ruby on Rails 从 3.1 版本开始,正式采纳了 CoffeeScript。

安装方法

CoffeeScript 的安装方法有好几种,在 Ubuntu 等 Debian 系 Linux 环境中,可以像平常一样作为软件包进行安装。

$ sudo apt-get install coffeescript
或者,可以使用 node.js(参见 6.4 节),通过它的软件包系统 npm 来进行安装。

$ sudo npm install coffee-script
除此之外的情况,则可以从 http://coffeescript.org/ 下载 tar.gz 文件。

安装完毕之后就可以使用 coffee 命令了。输入 coffee -h 可以显示命令行选项一览。

基本的用法:

$ coffee 程序.coffee
(CoffeeScript 程序一般用 .coffee 作为扩展名)可以直接运行文件中保存的 CoffeeScript 程序。要将 CoffeeScript 程序编译为 JavaScript,可以使用“-c”选项。

$ coffee -c 程序.coffee
结果就会输出一个扩展名替换为 .js 的文件,即编译后的 JavaScript 程序。

声明和作用域

我自己几乎没有用 JavaScript 编程的经验,不过听身为 JavaScript 程序员的好友说,对 JavaScript 的不满之一,就是它的变量声明和作用域。

在 JavaScript 中,局部变量需要用保留字“var”进行显式声明,如果不小心忘记声明,这个变量就会变成全局变量。由于全局变量在任何地方都可以访问和修改,于是就变成一个孕育 bug 的可怕温床。

在 CoffeeScript 中,对这一点进行了反省,对于变量引用的规则做出了一些修改。首先,变量的声明不需要用 var,而是通过赋值来进行。在函数中第一个赋值语句被视为对局部变量的声明,这一点与 Ruby 和 Python 十分相似。例如:

foo = 42
在 CoffeeScript 中只是一个单纯的赋值语句,但编译为 JavaScript 后,则变成了:

var foo;
foo = 42;
CoffeeScript 减少了声明,看上去更加简洁。

由于 CoffeeScript 中通过赋值语句会将所有的变量都声明为局部变量,因此要创建全局变量是不可能的。和 JavaScript 不同,CoffeeScript 中位于顶层的变量不是全局变量,而是局部变量(除非用“-b”选项进行显式指定)。

此外,由于不存在对局部变量的显式声明,因此当外侧作用域中存在同名变量时,则以外侧变量优先。如果无意中使用了同名变量,则有可能产生难以发现的 bug。Ruby 中也有同样的问题,但在 Ruby 1.9 之后版本中,可以通过对代码块作用域固有的局部变量进行显式声明来回避这一问题。

CoffeeScript 中可以在变量名前面加上 @ 来进行引用,这相当于:

this.变量名
的缩写。对实例变量的引用使用“@”这一点和 Ruby 很像呢。

此外,变量名等末尾还可能出现“?”。这种写法乍一看好像也是从 Ruby 来的,但实际上意思完全不同。Ruby 中如果在方法名末尾加上“?”,表示该方法是一个谓词方法(返回真假值的方法)。而在 CoffeeScript 中,变量名后面加上“?”则表示“该变量为 null 和 undefined 以外的值”。

因此,从这个概念进行类推:

a ? b
表示当 a 为 null 或 undefined 时则为 b,而:

a?()
表示当 a 为 null 或 undefined 时则为 undefined,否则将 a 作为函数进行调用,而:

a?.b
则表示“当 a 为 null 或 undefined 时则为 undefined,否则引用 a 中的 b 这一属性”。

例如,将“a?.b”编译为 JavaScript 后如图 1 所示。undefined 的检查方法非常简单,很容易理解。由于有很多方法在出错或遇到异常时会返回 null 和 undefined,如果使用这个功能的话,可以在出错时跳过后面的处理逻辑,从而让程序变得更加简洁。

typeof a === "undefined" || a == undefined ? undefined : a.b;
图 1 “a?.b”的编译结果

CoffeeScript 也支持多重赋值,如:

[a, b] = [1, 2]
则表示将 a 赋值为 1,将 b 赋值为 2。和 Ruby 不同的是,不仅是数组,连字典(map)也可以进行展开式的多重赋值,如:

{a, b} = {a: 3, b: 4}
表示将 a 赋值为 3,将 b 赋值为 4。此外,还可以指定变量名,如:

{a:foo, b:bar} = {a: 3, b: 4}
表示将 foo 赋值为 3,将 bar 赋值为 4。

多重赋值看似简单,其实编译为 JavaScript 之后会变得相当复杂(图 2)。

var _a, _b, a, b, bar, foo;
// [a,b] = [1,2]
_a = [1, 2];
a = _a[0];
b = _a[1];

//{a:foo, b:bar} = {a: 3, b: 4}
_b = {
  a: 3,
  b: 4
};
foo = _b.a;
bar = _b.b;
图 2 多重赋值的编译结果

分号和代码块

个人认为,CoffeeScript 最重要的改善点,就是上面讲到的对声明的省略以及对全局变量问题的解决。然而,看了 CoffeeScript 所编写的程序之后,给我留下最深印象的却并非是上面这一点,而是对分号的省略,以及通过缩进来表现代码块。

在 CoffeeScript 中,像 Python 一样是通过缩进来表现代码块的。例如,匿名函数可以这样写:

(a) ->
console.log(a)
a * a
在不必每次都写 function 的同时,还可以将多行的匿名函数用非常简洁的方式表达出来。由于 JavaScript 是将函数作为对象来对待的,因此可以使用高阶函数的编程风格,但匿名函数的表达十分繁琐,经常让人感到非常痛苦。而且,CoffeeScript 中最后一个被求值的表达式会自动成为返回值,和必须写 return 的 JavaScript 相比,程序的表达更加简洁。

值得注意的是,在将包括代码块在内的值作为参数的情形。同样是用缩进来表现代码块的 Python 中,创建匿名函数的 lambda 表达式中,函数体只能采用单一的表达式,而要将多行函数作为对象来使用,则必须先作为局部作用域进行命名和定义,这个规则显得相当麻烦。

作为后起之秀,CoffeeScript 自然考虑到了这个问题,只要用括号整个括起来,就可以当做表达式来使用了。例如,像下面这样:

something(((a)->
  console.log(1)
  a * a), 2)
缩进表现的代码块不仅可以用于匿名函数,对 if 和 while 结构同样有效。例如:

if cond()
  1
else
  2
这样的块状结构,当程序体只有一行时就可以在一行中进行表达,如:

sq = (a) -> a*a
或者是:

a = if cond() then 1 else 2
正如上述例子中所示,CoffeeScript 中的 if 语句实际上是一个表达式,可以返回值。因此,“~?~:~”这样的三项操作符就没有必要使用,作废了。

省略记法

正如缩进表现的代码块一样,CoffeeScript 的设计方针是让表达尽量简洁。例如,JavaScript 中为了分隔语句而必须使用分号,在 CoffeeScript 中则完全不需要使用分号。

函数调用中包围参数的括号,当只有一个参数时也可以省略。不过,当一个参数都没有的时候,就无法区分到底是调用函数呢,还是对函数对象进行引用。因此这种情况下,在调用函数时,还是要加上 ()(图 3)。

# 参数的括号可以省略
console.log("hello")
console.log "hello"
a = -> 1
a   # 返回1的函数对象
a() #     调用函数,返回1
图 3 函数调用的情况

CoffeeScript 中对象的括号也是可以省略的(图 4)。

# JavaScript的写法
obj = {a:1, b:2}
# 省略括号
obj = a:1, b:2
# 用换行和缩进来表现
# 逗号也省略了
obj =
  a:1
  b:2
图 4 对象的括号可以省略

字符串

CoffeeScript 的字符串也很有讲究。首先,Ruby 中也具备的表达式嵌入功能。如下所示,在字符串中用“#||”包围起来的表达式,它的值会被嵌入到字符串中。

name = "Matz"
console.log "Hello #{name}"
此外,还可以像 Python 一样,用三重引号来表示跨行字符串(图 5)。

# 换行被忽略,值为ab
console.log "a
b"
# 换行有效,值为
#   a
#   b
console.log """a
b
"""
图 5 三重引号表示的字符串

三重引号在需要将类似 XML 这样的长字符串写入程序中的情况下非常有用。有趣的是,在这里 CoffeeScript 是从 Ruby 和 Python 中平等地借鉴它们的功能呢。

话说,CoffeeScript 的注释也和 Ruby、Python 一样是用“#”开头的(JavaScript 是“//”),从三重引号进行类推,“###”就表示直到下一个“###”为止的多行内容全部为注释。

数组和循环

CoffeeScript 中的数组也很有讲究。不过很遗憾,数组没办法像对象一样省略外侧的括号。

ary = [
 1
 2
]
数组也有省略记法,比如看上去很像 Ruby 的范围表达式:

[1..4]
这种写法表示“从 1 到 4”,编译成 JavaScript 结果如下:

[1,2,3,4]
不过,如果范围两端的任一端使用变量的话,编译出来就会变成图 6 这样复杂的结果。

# a=4; [1..a]
var a, _i, _results;
a = 4;
(function() {
  _results = [];
  for (var _i = 1;
     1 <= a ? _i <= a : _i >=a;
     1 <= a ? _i++ : _i--) {
    _results.push(_i);
  }
  return _results;
}).apply(this);
图 6 数组范围表达式编译结果

大家对 JavaScript 数组的一个不满,就是针对其内容的循环比较难写(图 7)。于是,在 CoffeeScript 中,for~in~ 循环为数组专用,而对于对象成员的访问,则使用另一种 for~of~ 循环来实现。为了让大家理解它们的区别,我们将图 8 中的 CoffeeScript 程序编译成 JavaScript 的结果显示在图 9 中。

// (a) 本来是想获取数组的内容
var ary, i;
ary = [7,8,9,0];
for (i in ary) {
  console.log(i);
}
// 结果显示的不是内容而是索引

// (b) 要获取数组的内容应使用如下方法
var _i;
for (_i = 0, _len = a.length; _i < _len; _i++) {
  i = a[_i];
  console.log(i);
}
// 这样才能真正显示数组的内容

// (c) for~in~原本是面向对象的
var obj;
obj = {foo: 1, bar: 2};
for (i in obj) {
  console.log(i);
}
// 可以取得对象的成员名称
图 7 JavaScript 的数组循环

ary = [7,8,9,0];
obj = {foo: 1, bar: 2};
# 数组用循环(显示其元素)
for i in ary
  console.log i

# 对象是无法循环的
# 因为它不是数组
for i in obj
  console.log i

# for~of相当于JavaScript的for~in~
# 显示成员名称
for i of obj
  console.log i

# 显示索引
for i of ary
  console.log i
图 8 CoffeeScript 的 for 循环

var ary, i, obj, _i, _j, _len, _len2;
ary = [7, 8, 9, 0];
obj = {
  foo: 1,
  bar: 2
};
for (_i = 0, _len = ary.length; _i < _len; _i++) {
  i = ary[_i];
  console.log(i);
}
for (_j = 0, _len2 = obj.length; _j < _len2; _j++) {
  i = obj[_j];
  console.log(i);
}
for (i in obj) {
  console.log(i);
}
for (i in ary) {
  console.log(i);
}
图 9 图 8 程序的编译结果

JavaScript 是基于原型的面向对象语言,因此并不像基于类的语言一样,具备直接支持类定义和方法定义等功能的语法。另一方面,JavaScript 虽然提供了用于从原型生成新对象的 new 语句,但不知为何,作为原型的却是函数对象,总是让人感觉怪怪的。

虽然这也可以说是一种策略吧,不过作为长期以来习惯了基于类的面向对象语言的人来说,多少会觉得痛苦。因此,CoffeeScript 中提供了 class 语句,可以做到看上去像是基于类的面向对象语言。实际上,新版本的 JavaScript 中也提供了 class 语句,但出于兼容性上的考虑,CoffeeScript 并没有直接使用 JavaScript 的 class 语句。

CoffeeScript 的 class 定义如图 10 所示。和 CoffeeScript 的简洁相比,编译为 JavaScript 之后的结果就显得十分复杂。当然,这是让 JavaScript 硬生生配合 CoffeeScript“面子工程”的结果,也许并不能说是一种公平的比较吧。

class Person
  # 构造方法
  # Ruby的initialize,Python的__init__
  constructor: (name) -> @name = name

# 继承
class SalaryMan extends Person
  constructor: (name, @salary) ->
    # 调用超类的方法
  super(name)
  earn: => console.log "you earn #{@
salary} YEN a month."
salaryman = new SalaryMan("Matz", 100)
图 10 CoffeeScript 的类定义

图 10 中还有一些很有意思的地方,比如在子类的方法中可以像 Ruby 一样使用 super,以及在方法参数中加上“@”就可以不必通过显式赋值来对实例变量进行初始化。

此外,图 10 中还有一点值得注意。在 SalaryMan 类的 earn 方法定义中,用于函数对象的箭头不是“->”而是“=>”。在 CoffeeScript 中,“=>”被称为胖箭头(fat arrow)。

JavaScript 中,目前是通过 this 来表达上下文的,说实话,this 会在哪一个时间点绑定什么这一点有些难以理解。尤其是在事件回调等情况下,在被调用的函数中,this 到底指向哪里,不实际试验一下的话是想象不出来的。用胖箭头定义的函数对象中,其上下文固定为局部上下文;而作为方法进行定义时,this 永远指向其接收器。这样一来关于 this 的麻烦也就消除了。

还有一点,在图 10 的程序中没有体现,那就是类方法究竟应该如何定义。我们可以利用在

class 实体中 this 绑定为正在被定义的类这一点,使用“@”记法即可。即:

class Foo
  @number = 0
  @inc: => @number++
  constructor: ->
    Foo.inc()
    console.log Foo.number
在这里,@number 是类对象 Foo 的实例变量,@inc 是类方法。要调用类方法,需要像这样:

Foo.inc()
显式用类名来进行调用。需要注意的是,类对象的实例变量在创建子类时会被复制,但并不共享。也就是说,即使:

class Bar extends Foo
Bar.inc()
Foo 的实例变量也不会发生变化。

小结

在这里我们无法涵盖 CoffeeScript 的全部特性,除了上面提到的之外,还有很多十分方便的功能。CoffeeScript 给人的印象是,在发挥 JavaScript 优势的同时,为了消除对 JavaScript 的不满,借鉴了 Ruby 和 Python 等多种语言的功能。虽然它比原本过于简单的 JavaScript 更加复杂一些,但我感觉它的语言设计中体现了一种绝妙的平衡感。抛开利害关系来说,我甚至觉得有些地方比 Ruby 更加优秀。

CoffeeScript 的编译器是通过 JavaScript 编写的,编译结果也是 JavaScript,因此只要有 JavaScript 引擎,无论在任何环境下都可以工作,更何况,现在 JavaScript 引擎可以说遍地都是。CoffeeScript 利用这个优势,无论在服务器端还是客户端,今后其应用范围都会越来越广,可以说是将来值得期待的语言之一。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文