`
步行者
  • 浏览: 167598 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

再论闭包

    博客分类:
  • JAVA
阅读更多

--再论闭包 --

 

 

下面这个例子 引自 《Groovy in action》 第五章。

 

一段 Java 代码,

interface ResourceUser {
    void use(Resource resource)
}
resourceHandler.handle(new ResourceUser(){
    public void use (Resource resource) {
        resource.doSomething()
    }
});

 

下面是一段等价 Groovy 的代码。

resourceHandler.handle { resource -> resource.doSomething() }

 

惊叹吧 ?  8行JAVA代码居然被 Groovy 如此 easy 地实现了。

惊叹! 惊叹之余再来反思。。。

 

(1),为何能如此简单(Groovy):因为它在使用闭包。

        Groovy中 行为元素(函数,过程)作为一级元素。

        像resource -> resource.doSomething() 就是一个匿名函数。

        参数 为 resource,返回值 为 resource.doSomething()。

 

(2),为何如此复杂(Java):因为它在模拟闭包。

        函数咱没有,那就包装一下,模拟一下,但好像臃肿了点。。

 

(3),易理解吗?

       Groovy 资源处理器 (名词:resourceHandler)处理 (动词:handle)一个函数 (匿名函数:resource ->  

                      resource.doSomething())。

       模拟闭包的Java 资源处理器 (名词:resourceHandler)处理 (动词:handle)一个资源用户 (名词:new

                      ResourceUser(){...})。

       资源处理器应该只处理资源啊。。。

 

(4), 重新造句 (Java)

       OO的Java 资源处理器 (名词:resourceHandler)处理 (动词:handle)一个资源 (名词:resource)。

 

new ResourceHandler(){
	protected void handle(Resource resource){
		//doSomething ....
		resource.doSomething();
		//doSomething ....
	}
}.handle(request);

 

 

个人观点

 

一种功能如果可以用FP中的闭包来实现,

那它必然也可以用OO中的对象来实现, 而且更贴近于人的思维。

有必要用对象模拟闭包吗?个人感觉没必要,

用对象模拟的闭包 来实现一种功能,感觉相比上诉两种情况更复杂。

 

 

分享到:
评论
64 楼 icewubin 2009-08-18  
火星叔叔马丁 写道
话说ruby还真是lisp一脉相传下来的
ruby受到lisp影响 其实要超过受到perl的影响

我知道啊,正因为我知道我才这么说的。

不过有人大言不惭的说Lisp是银弹,我还是比较佩服的(不会没看出我在说反话吧,哪有什么银弹语言),既然都是银弹了,还要其他语言干啥啊?
63 楼 icewubin 2009-08-18  
Juiz 写道
icewubin 写道
照你的逻辑,Java早该灭亡了,有Lisp还要Java干嘛?Lisp是银弹啊?

你的逻辑中有一个很严重的误导就是,程序复杂以及代码可读性差是由于Java本身过于复杂造成的,而实际上绝大多数复杂性是由于固有的业务复杂性造成的,而不是语言本身。语言本身的提高只能缓解一定的编程复杂度,但是不能减少任何业务本来固有的复杂度,不存在银弹的。


你的意思是“不能减少任何业务本来固有的复杂度”,就不能减少程序员的劳动量?

引入一个好的语言特性,在代码中就能造成线性的差别(可读性和代码量上),连带影响 debug、测试代码量、影响架构、影响交流成本,差异到了工程中就是到指数级了,根本不是引入一个库/框架能比的。

当然糟糕的语言特性也会造成反效果罢了。

Lisp 是银弹有什么错的吗?

http://page.mi.fu-berlin.de/prechelt/Biblio/jccpprt_computer2000.pdf

如果Lisp是银弹的话,为什么狼都还活着呢?
如果Lisp是银弹的话,是不是ruby之类的动态语言都能算是银弹么?
62 楼 iaimstar 2009-08-18  
蜀黍你乱入了。。
消亡的话,java早就消亡了,在很早以前,我就貌似听说过java,c++已死的言论
觉得也合理
但是消失还差的很远
现在都有人用COBOL~~但是cobol早就没有生命力了
61 楼 iaimstar 2009-08-18  
说java消失的应该去面壁思过
60 楼 icewubin 2009-08-18  
mathgl 写道
icewubin 写道
Juiz 写道
其实 java 不是真正的面向对象,充其量只是“面向类”,java 的啰嗦语法很容易导致冗长重复,继而产生连锁效应:
需要借助 IDE 生成代码,而生成代码的可读性就更差了;
长代码导致出错增加、重构困难、bug 迷雾;
代码越长,要求的注释越多;
为了分而治之,衍生出各种分层理论,design over code 之类的,但是层数越多,调用栈就越复杂,弹出巨大的 stack trace 你根本弄不清问题在哪 ……

这些问题归结到底,就是欠缺清晰、简洁的表达造成的。

OO 到了极致,从指令流程序变成数据流程序,便成为了函数式语言,这里头的东西几十年就研究通透了 …… 在精巧设计的 lambda 面前,很多杂乱的语言特性如 annotation、for in、try...catch 都变得没有价值,很多庞大的库和大量使用了 xml 的框架都会变成鸡肋 —— 因为用闭包可以更简单的做到。像上面的例子,很多人只看到少了 7 行,其实是少了 7/8 ……

可是现在 java 语言做一点点微小的进步都非常困难 …… 不进步便消亡,只是关系到数量庞大的公司和个人的利益,消亡的过程肯定比较缓慢。

照你的逻辑,Java早该灭亡了,有Lisp还要Java干嘛?


因为使用者众,程序也多,断无一夜之间全部消失之理。

Lisp诞生远比Java早。
59 楼 mathgl 2009-08-18  
icewubin 写道
Juiz 写道
其实 java 不是真正的面向对象,充其量只是“面向类”,java 的啰嗦语法很容易导致冗长重复,继而产生连锁效应:
需要借助 IDE 生成代码,而生成代码的可读性就更差了;
长代码导致出错增加、重构困难、bug 迷雾;
代码越长,要求的注释越多;
为了分而治之,衍生出各种分层理论,design over code 之类的,但是层数越多,调用栈就越复杂,弹出巨大的 stack trace 你根本弄不清问题在哪 ……

这些问题归结到底,就是欠缺清晰、简洁的表达造成的。

OO 到了极致,从指令流程序变成数据流程序,便成为了函数式语言,这里头的东西几十年就研究通透了 …… 在精巧设计的 lambda 面前,很多杂乱的语言特性如 annotation、for in、try...catch 都变得没有价值,很多庞大的库和大量使用了 xml 的框架都会变成鸡肋 —— 因为用闭包可以更简单的做到。像上面的例子,很多人只看到少了 7 行,其实是少了 7/8 ……

可是现在 java 语言做一点点微小的进步都非常困难 …… 不进步便消亡,只是关系到数量庞大的公司和个人的利益,消亡的过程肯定比较缓慢。

照你的逻辑,Java早该灭亡了,有Lisp还要Java干嘛?


因为使用者众,程序也多,断无一夜之间全部消失之理。
58 楼 icewubin 2009-08-18  
Juiz 写道
其实 java 不是真正的面向对象,充其量只是“面向类”,java 的啰嗦语法很容易导致冗长重复,继而产生连锁效应:
需要借助 IDE 生成代码,而生成代码的可读性就更差了;
长代码导致出错增加、重构困难、bug 迷雾;
代码越长,要求的注释越多;
为了分而治之,衍生出各种分层理论,design over code 之类的,但是层数越多,调用栈就越复杂,弹出巨大的 stack trace 你根本弄不清问题在哪 ……

这些问题归结到底,就是欠缺清晰、简洁的表达造成的。

OO 到了极致,从指令流程序变成数据流程序,便成为了函数式语言,这里头的东西几十年就研究通透了 …… 在精巧设计的 lambda 面前,很多杂乱的语言特性如 annotation、for in、try...catch 都变得没有价值,很多庞大的库和大量使用了 xml 的框架都会变成鸡肋 —— 因为用闭包可以更简单的做到。像上面的例子,很多人只看到少了 7 行,其实是少了 7/8 ……

可是现在 java 语言做一点点微小的进步都非常困难 …… 不进步便消亡,只是关系到数量庞大的公司和个人的利益,消亡的过程肯定比较缓慢。

照你的逻辑,Java早该灭亡了,有Lisp还要Java干嘛?Lisp是银弹啊?

你的逻辑中有一个很严重的误导就是,程序复杂以及代码可读性差是由于Java本身过于复杂造成的,而实际上绝大多数复杂性是由于固有的业务复杂性造成的,而不是语言本身。语言本身的提高只能缓解一定的编程复杂度,但是不能减少任何业务本来固有的复杂度,不存在银弹的。
57 楼 json615 2009-08-14  
java 其实应该走像的是 更简单更OO得道路
java 应该像操作系统中的linux 走向简单稳定
菜鸟意见。。。。欢迎攻击。。。
56 楼 fangwei 2009-08-13  
过十年再看那时的OO是什么样吧,觉得现在的争论毫无意义
55 楼 liusong1111 2009-06-29  
<div class="quote_title">步行者 写道</div>
<div class="quote_div">
<p><span>to liusong1111:
    首先谢谢你的回帖!
    前面的所有回复我都认真看过,我不了解ruby,但大约看懂了你的代码,
ruby中的闭包确实可以添加方法,添加属性。</span></p>
<p><span>因为ruby里的闭包被封装成了对象
groovy也一样,</span></p>
<p> </p>
<pre name="code" class="java">println ({it*it} instanceof Object)
//output:
//true</pre>
 
<div><span><span style="white-space: pre-wrap;">对闭包的调用,实际上是对封装闭包的对象方法的调用</span></span></div>
<div><br></div>
<div>
<pre name="code" class="java">def closure = {it*it}

println closure(2)
//  ||
println closure.call(2)</pre>
</div>
<div> </div>
<div>闭包无疑是从FP中引入的概念(计算机领域)</div>
<div>一种编程语言引入它 是因为它的轻巧,简洁。</div>
<div>我想不是因为它具有OO特性吧</div>
<div><br></div>
<div><br></div>
<div><br></div>
</div>
<p> </p>
<p>争论源于“闭包与OO格格不入“的论断。</p>
<p>你上面的逻辑,如同说“对象上的操作,实际上是对封装对象行为的函数的调用“,因此,”面向对象“与”过程式编程“格格不入,“面向对象”没有意义。</p>
<p>最终又归结到信仰问题了。</p>
54 楼 wondery 2009-06-28  
<div class="quote_title">步行者 写道</div>
<div class="quote_div">
<p> </p>
<pre name="code" class="java">interface ResourceUser {
    void use(Resource resource)
}
resourceHandler.handle(new ResourceUser(){
    public void use (Resource resource) {
        resource.doSomething()
    }
});</pre>
<p> </p>
<p><span style="font-size: small;">下面是一段等价<strong><span style="color: #000000;"> Groovy</span> </strong>的代码。</span> </p>
<pre name="code" class="java">resourceHandler.handle { resource -&gt; resource.doSomething() }</pre>
<p> </p>
<p><span style="font-size: small;">惊叹吧 ?  8行JAVA代码居然被 Groovy 如此 easy 地实现了。</span> </p>
<p><span style="font-size: small;">惊叹! 惊叹之余再来反思。。。</span> </p>
<p> </p>
</div>
<p>行数比例貌似 8:1  <img src="/images/smiles/icon_cool.gif" alt=""></p>
<p> </p>
53 楼 damoqiongqiu 2009-06-25  
Saito 写道
  暂时把楼主归为 倒 闭包派。。

  就跟 inter 有 挺穆  跟  倒穆。。

       AC milan 有挺安  跟 倒安一样。。 ms现在还可以投票取消闭包。


  说实话。 Java 7 可能是对Java社区的一次强大冲击。 不亚于Java 5 。 企业级应用里面为什么爱用Java。 首先面向对象。 开源社区足够强大。 语法足够简单。一些大公司他们连 泛型都不用。 1.4用到死。。就别提Java 7 了。

  我始终觉得。 Java老向c#靠拢就走歪了。 盖房子的工人没必要还一身amani。。

相当精辟,大公司都倾向于使用稍微老点的版本。
52 楼 mooniscrazy 2009-06-25  
没用的东西。
不是骂人。
没有本质差别,不过是个匿名的方法而已,加速不了软件的开发,也不可能用这些东西就把一个低级程序员变成高级程序员,顶多少打几行字而已。
因为软件开发,并不取决于打字的速度,所以,语言是否优秀,也不取决于它的语法是否能够少写几个字。
51 楼 步行者 2009-06-25  
<p><span style="">to liusong1111:
    首先谢谢你的回帖!
    前面的所有回复我都认真看过,我不了解ruby,但大约看懂了你的代码,
ruby中的闭包确实可以添加方法,添加属性。</span></p>
<p><span style="">因为ruby里的闭包被封装成了对象
groovy也一样,</span></p>
<p>
</p>
<pre name="code" class="java">println ({it*it} instanceof Object)
//output:
//true</pre>
 
<div><span style=""><span style="white-space: pre-wrap;">对闭包的调用,实际上是对封装闭包的对象方法的调用</span></span></div>
<div><br></div>
<div>
<pre name="code" class="java">def closure = {it*it}

println closure(2)
//  ||
println closure.call(2)</pre>
</div>
<div> </div>
<div>闭包无疑是从FP中引入的概念(计算机领域)</div>
<div>一种编程语言引入它 是因为它的轻巧,简洁。</div>
<div>我想不是因为它具有OO特性吧</div>
<div><br></div>
<div><br></div>
<div><br></div>
50 楼 liusong1111 2009-06-25  
to楼主:icefishc和幸存者都有其它语言的经历(C#、smalltalk、python、perl),而且给出了不少例证,请重点读读幸存者前面的贴:

幸存者 写道
闭包的核心概念就两个:一等函数(即可以作为参数的函数)和可使用外部变量。
没有什么单纯或不单纯的闭包之说,那些东西完全取决于某种语言如何实现闭包,而不取决于闭包本身的概念。
OO从来没有规定函数不能成为一等公民,也没有规定函数一定不能是对象,事实上早在70年代,函数和类在smalltalk中都是对象。说闭包不是对象或函数不能是对象完全没有道理。


赞同这个说法。能不能给闭包自定义属性和方法,能不能继承、多态,取决于这个语言想不想支持,而不是能不能做到的问题。

看ruby的:

# 用变量closure接收一个闭包
def hello(&closure)
  closure.instance_eval do
    # 给它设置属性
    @var = "hello"

    # 给它添加方法
    def x
      @var + " " + self.call
    end
  end
  
  # 调用这个闭包上新加的方法
  closure.x
end

# 向hello传入一个闭包{"world"}
puts hello{
  "world"
}


看你贴过groovy的代码,仔细研究一下groovy,应该能得到相同的结论(通过metaclass等)。closure作为一等公民,可以被一个普通的变量引用、传递。在ruby里意味着它是Proc的一个对象(实例),自然可以调用Proc的方法(比如call)。以java的视角,类是用来组织数据的,不是描述行为。而对于ruby,行为也是数据,也可以用类描述,类也是对象,类的类还是对象,这才是真正的Everything is Object。楼主受java和java所谓的OO的影响太深,评判这些问题时,应该眼光往外多看看,避免思维定势。尤其应该深入学习一门带闭包的语言,看看它到底是怎么用的。

你主贴的第三点:“易理解吗?” 因为举的例子没有什么业务含义,我觉得两个都不易理解。
但你看,java代码里多出来的ResourceUser类、use方法,使它的代码变得冗长,同时,使用者还要多记忆两个名词。相比groovy代码,这两个名词分明是多余的。重复了!坏味道。重构-消除重复。使用闭包可以做到。

如果不在语法级修改,能消除重复也行,问题是,你后面这段代码灵光吗?
引用

new ResourceHandler(){  
     protected void handle(Resource resource){  
        //doSomething ....  
        resource.doSomething();  
        //doSomething ....  
    }  
 }.handle(request); 
49 楼 步行者 2009-06-24  
想说的 前面都说过了 ,有的话说了好几遍
不想重复了

每个人脑子里都有一个闭包,闭包在不同的地方似乎也有不同的定义
我认为 的 闭包在前面陈述过了

不过欢迎大家 把自己脑子里的闭包 陈述出来
把它与 OO 的区别与联系也提供出来供大家参考,供大家讨论

扔砖送花都欢迎,不过要讲清楚,有论据。。。



48 楼 icefishc 2009-06-24  
先放下Java中引入非OO的东西好不好这个话题不谈。
在这个帖子中 大多数反对Java中引入闭包的理由是 "闭包与OO格格不入" 但都没有给出解释来说明为什么"闭包与OO格格不入"
我猜想大概有2点原因
1. 认为闭包不是对象 引入非对象破坏的java的“纯洁性”。  关于这个问题请参考python 和smalltalk对闭包的处理.
2. 认为闭包破坏了对象的封装性.  的确在有些语言中闭包破坏了对象的封装性.
(比如下面这个perl的例子中通过闭包访问了对象中的局部变量。)
package Foo; #define a class in perl
sub new {
  my $class = shift;
  my $self = {};
  bless $self, $class;
  return $self;
}
my $pv = 1;

sub bar(){
  my $self = shift;
  my $do = shift;
  $do->();
}
1; #end

package main;
my $foo = Foo->new;
$foo->bar(sub {print $pv});

但这个功能对于闭包并不是必须的。 去掉这个功能它仍然是闭包。

希望认为闭包不符合oo特性的人可以把理由说出来。
47 楼 icefishc 2009-06-24  
步行者 写道
又提到 闭包是不是对象上来了?

首先幸存者同学没有说闭包就是对象, 只是说这两个概念并不冲突。 是你把问题引到这里的。
再次引用wikipedia中对于闭包的定义
引用

In computer science, a closure is a first-class function with free variables.

按照这个定义 只要有个东西不管它是什么东西只要满足这几个条件 都可以称作闭包。
那么对象可以满足这几个条件么 当然可以。 对象可以当作参数或返回值 对象可以是函数(例如python中的函数) 也可以拥有free variables. 所以对象也可以作为闭包的一种具体存在方式.

步行者 写道

你可以定义闭包的方法吗,你可以定义闭包的属性吗?

为什么不可以呢. 幸存者同学不是已经给出例子了么.

步行者 写道

早都说过对象是状态和行为的封装
闭包是封装状态的行为

什么意思 请给出具体解释或示例

步行者 写道

函数和类在smalltalk中都是对象
那是经过对象封装了

不讨论这句话的对错. 假设是对的那有怎么样.
46 楼 步行者 2009-06-24  
又提到 闭包是不是对象上来了?

你可以定义闭包的方法吗,你可以定义闭包的属性吗?
早都说过对象是状态和行为的封装
闭包是封装状态的行为

函数和类在smalltalk中都是对象
那是经过对象封装了

45 楼 幸存者 2009-06-24  
wikipedia 写道
a closure is a first-class function with free variables

闭包的核心概念就两个:一等函数(即可以作为参数的函数)和可使用外部变量。
没有什么单纯或不单纯的闭包之说,那些东西完全取决于某种语言如何实现闭包,而不取决于闭包本身的概念。
OO从来没有规定函数不能成为一等公民,也没有规定函数一定不能是对象,事实上早在70年代,函数和类在smalltalk中都是对象。说闭包不是对象或函数不能是对象完全没有道理。

相关推荐

    论文研究-基于网络闭包理论的交易型社区网络演化研究.pdf

    论文研究-基于网络闭包理论的交易型社区网络演化研究.pdf, 随着社会化商务时代的到来,交易型社区的发展越来越成为解决社会化商务中经济性与社会性矛盾的关键.但交易型...

    论文研究-闭包算子空间范畴及其性质研究.pdf

    基于对闭包运算的性质研究,引入了闭包算子空间及其之间的连续映射概念,证明了闭包算子空间(对象)及其之间的连续映射(态射)构成范畴(闭包算子范畴)。证明了闭包算子空间范畴中(有限)积和(有限)余积的存在...

    原子闭包系统, 原子闭包算子和原子全蕴含系统

    原子闭包系统, 原子闭包算子和原子全蕴含系统,杨海建,李庆国,本文引进了原子闭包系统,原子闭包算子以及原子全蕴含系统等概念, 研究了它们之间的相互关系, 给出了由原子闭包系统来表示有限原子�

    论文研究-完备剩余格上的蕴涵闭包算子.pdf

    利用蕴涵运算,在完备剩余格上定义了蕴涵闭包算子,给出了它的若干等价刻画及其表示定理。

    三次扩张的整闭包

    三次扩张的整闭包,龚成,,计算有限扩张的整闭包不但是交换代数中的一个核心问题,也很受代数几何以及代数数论发展的推动。 本文在前人研究的基础上,去掉2

    论文研究-求解最小闭包球问题改进的SMO-型算法.pdf

    研究[n]维空间中[m]个点的最小闭包球(MEB)问题。通过结合确定并删除内部点的技术到序列最小最优化(SMO)方法中,提出一种近似求解MEB问题的改进的SMO-型算法。证明了该算法具有线性收敛性。数值结果表明对于一些...

    论文研究-基于闭包的聚类判别方法研究.pdf

    区别于传统的聚类方法,提出了以类为起点,通过构造闭包进行聚类的新方法,并建立了聚类判别模型,此模型给出了对于闭包间的交叉区域的检验点的判别准则。然后针对二维的聚类问题,提出了以最小圆为闭包的聚类判别...

    论文研究-完备剩余格上的蕴涵闭包系统.pdf

    在完备剩余格上引入了蕴涵闭包系统的概念,讨论了蕴涵闭包系统与闭包系统之间的关系。给出了蕴涵闭包系统的一些性质及其表示定理。进一步研究了蕴涵闭包算子和蕴涵闭包系统的关系。

    论文研究-闭包算子与邻域系的广义化.pdf

    通过弱化一般拓扑中的闭包算子公理,引入了广义闭包算子的概念,研究了其与稍早引入的广义邻域系的关系。特别的,证明了二者之间存在着自然的完备格同构。

    论文研究-大数据环境下最小单调约束闭包Hadoop并行关联规则 .pdf

    大数据环境下最小单调约束闭包Hadoop并行关联规则,李春青,李海生,针对传统关联规则算法存在较大规则冗余问题,提出基于最小单调约束闭包Hadoop并行化关联规则。首先,基于闭包算子约束规则等价关系

    论文研究-基于卡诺图确定所有候选码和属性集闭包 .pdf

    基于卡诺图确定所有候选码和属性集闭包,张亦舜,,关系模式设计过程中,确定所有候选码及任意属性集的闭包是最基本的计算。本文证明此两问题等价于函数依赖图中质蕴含的确定和判断

    关于凸分析中代数闭包和向量闭包的概念研究 (2013年)

    研究了线性空间中代数闭包和向量闭包的关系,给出了凸集的代数闭包是凸集的新证明,利用代数闭包与向量闭包的关系,得到了一个涉及向量闭包的分离定理。

    基于三元闭包和会员闭包的社区发现算法研究 (2014年)

    随着网络的发展和人们沟通方式的扩展,社交网络影响了人们的生活,改变了人们传播与分享消息的方式,吸引了越来越多的人关注和研究社交网络。社交网络即社交网络服务,源自英文 SNS(social network service)的翻译,...

    代数交半格和代数并半格的闭包系统表示

    代数交半格和代数并半格的闭包系统表示,吴明渊,李庆国,Domain理论中,各类格序结构的集合表示是一个重要研究课题。一个众所周知的结论是代数闭包系统能表示代数格。本文研究代数闭包系统�

    论文研究-直觉模糊关系再研究.pdf

    对直觉模糊关系进行了再研究, 提出了直觉模糊关系的核与闭包的概念,得到了直觉模糊关系的十四集定理. 首先,利用直觉模糊逆自反关系、对称关系以及自反关系给出了直觉模糊...

    ch8-关系的闭包和等价关系-集合论1

    3. 设 R’ 集合A 上的自反关系,且R⊆R’, 则对 1. 输入MR 2. 计数器 k 置初值n-1

    On Computing the Transitive Closure of a Relation PDF

    On Computing the Transitive Closure of a Relation 计算二元关系的(自反)传递闭包 编译器设计,有名的论文

    求解最小体积闭包椭球问题的积极集算法 (2015年)

    先建立求解最小体积闭包椭球(MVEE)问题秩-2 更新算法的线性收敛性,然后给出一种简单的积极集策略,每次迭代计算距离当前椭球最远的N个点.结合该策略到秩-2 更新算法中,得到一个求解MVEE问题的积极集算法.数值结果表明...

    机器学习论文

    训练需花费大量时间和空间的问题,提出了基于中心约束最小闭包球的加权多类算法。该算 法首先利用先验知识确定一种新的基于相对紧密度的方法计算样本权值并将其融合到支持向 量机中,然后转化为中心约束的最小闭包球...

Global site tag (gtag.js) - Google Analytics