`
Lich_Ray
  • 浏览: 163381 次
  • 性别: Icon_minigender_1
  • 来自: 南京
文章分类
社区版块
存档分类
最新评论

无类语言的OOP(JavaScript描述)

阅读更多
本文以 JavaScript 语言为例,介绍了无类面向对象语言中实现各种面向对象概念的方法。值得注意的是,下面所说的并非“奇技淫巧”,其中的大部分都是计算机科学家们在设计无类语言时就已经确立了的模式,少部分是我借鉴其它语言的经验已经对前辈们思想的理解给出了完备化技术。
阅读本文至少需要对 JavaScript 语言“特别”的对象机制以及函数的运行上下文有所了解。如果您还对 JavaScript 对象知之甚少,可以查看附件中我翻译的 ECMA262 v3 中 4.2.1 Object 这一节;如果对 Lambda 演算不了解,建议去看 SICP

一. 基础:
建立类。只需声明一个函数作为类的构造函数即可。
function Light (light) {
	//填充对象属性
	this.light = light ? light : 0
	this.state = false
	
	//对象方法。
	//放心,JavaScript 没傻到给每个对象都真去分配一个函数的地步
	this.turnOn = function () {
		this.state = true
	}
}

创建实例。通过下面的代码创建一个新的电灯:
new Light(100) instanceof Light
js> true

这个新的电灯现在是匿名的,接下来可以在任何表达式中使用它。当然,最常用的做法是把一个名字绑定上这个对象。
访问实例属性。
//访问属性
new Light(100).light
js> 100
anOnLight = new Light()
//调整属性
anOnLight.state = true

匿名类。顾名思义,这个类没有名字(精确的说是构造函数没有名字)。就像这样:
aLight = new (function (light){
	this.light = light ? light : 0
	this.state = false
)(90)

类属性;类函数。顾名思义,一个类自身绑定的属性、函数,被所有类的实例可见,但不可直接使用。
//类属性
Light.SIZE = 5
//类函数
Light.newInstence = function (arg) {
	//这么简单的 Factory 模式
	//this 指向函数运行所在名字空间的上级
	return new this(arg)
}

想利用实例使用类的属性用下面的办法。函数调用类似:
anOnLight.constructor.SIZE
js> 5

类方法。真正意义上的“方法”
Light.prototype.turnOff = function () {
	this.state = false
}
anOnLight.turnOff()
anOnLight.state
js> false


二. 进阶
单继承。一个类扩展另一个类的所有能力。
function PhilipLight (price) {
	this.price = price
}
//事实上是建立了一个匿名的 Light 实例,然后将其能力反映给 PhilipLight
//飞利浦灯泡的亮度默认为100。这种继承模式很有意思。
PhilipLight.prototype = new Light(100)
myLight = new PhilipLight(12)
myLight.price
js> 12
//类方法照用。对象方法也照用。
myLight.turnOn()
myLight.state
js> true

可以把单继承作为一个 Object 类的能力保留下来,如果不强求默认值的话:
//把那些垃圾的库抛在脑后,让它们见识见识什么叫优雅。
Object.prototype.extend = function (aClass) {
	this.prototype = new aClass
}
PhilipLight.extend(Light) //No problem

多继承。我可以很明白的说,JavaScript 办不到。因为想在单继承链上实现多继承是不可能的。不过,这并不是说 JavaScript 面向对象机制不能达到多继承那样的表现力:装饰模式、Mixin 这些更强大的机制都是能办到的。
Mixin。漂亮地实现 Mixin 的前提是访问拦截器(getter 和 setter)。JavaScript 1.6 之前没有这种东西,需要修改编程习惯——这不是我们想要的。JavaScript 1.7 中加入的只是对特定消息的访问拦截器(现已在出现在 1.5 C 实现中)支持所以我们只能稍微改变一下编程风格。先说明一下如何对某个对象应用其它类的函数。
泛型。JavaScript 1.5 中,我们可以用函数对象的 call() 方法或 apply() 方法对该对象应用来自其它类的函数:
//Light 也是一种商品
function Product (price) {
	this.price = price
	//买 num 件商品需要的钱
}
Product.prototype.buySetOf = function (num) {
	return this.price * num
}
//那么对于同样有 price 属性的飞利浦灯泡,我们可以这样计算买10个灯泡要多少钱:
Product.prototype.buySetOf.call(myLight, 10)
js> 120
//apply 的第二个参数是被 call 的参数列表
Product.prototype.buySetOf.apply(myLight, [10])
js> 120

类的半自动混合。
Object.prototype.mixin = function (aClass) {
	//这里用到的技术下文中讲解
	this.prototype.app = function (func, args) {
		//func 是消息字符串
		if (this[func] != undefined)
			return (this[func].apply(this, args))
		return (aClass.prototype[func].apply(this, args))
	}
}
PhilipLight.mixin(Product)
myLight = new PhilipLight(12)
myLight.app('buySetOf', [10])
js> 120

对象的半自动混合。对象当成另一个对象使用,类似的方法:
Object.prototype.able = function (anObject) {
	this.app = function (func, args) {
		//func 是消息字符串
		if (this[func] != undefined)
			return (this[func].apply(this, args))
		return (anObject[func].apply(this, args))
	}
}
//这个用法弱智了点,但确实能说明问题
myLight.able(new Product)
myLight.app('buySetOf', [10])
js> 120


三. 补完
这一章讲解 4P 的实现。
包(package)没什么好说的,通读一遍 Prototype.js,看看作者是如何使用 JavaScript 对象描述程序结构的,就什么都知道了。这可比什么 interface 强多了。
公有(public)权限。Pass.
受保护的(protected)权限。如果你使用了 JavaScript 对象来描述程序结构,那么,其中每个类中的函数会自然获得 protected 权限——因为,使用它们都需要包名或者 with 语句。
私有(private)权限。不像 Python 等等语言,它们事实上是不存在的私有权限;JavaScript 使用 Lambda 演算中的逃逸变量原理实现私有权限。换个例子:
function Desk (height) {
	//对于一个符合标准的实现,这里的 var 关键字可以省略
	var height = height ? height : 0
	var weight = 0
	//下面的东西对于 Java 程序员来说很熟悉 :)
	this.getHeight = function () {
		return height
	}
	this.setHeight = function (num) {
		height = num
	}
}
deak = new Desk(34)
deak.getHeight()
34
deak.setHeight(45)
deak.getHeight()
45
desk.height
ReferenceError line 1:desk.height is not defined

此时的 height 就是逃逸变量,从 Desk 函数中以作为对象上绑定的函数的环境上绑定的变量“逃”了出来(这句话有些拗口,不过的确如此)。对于直接由构造函数参数引入的变量,也可以作为私有属性。类似的,还可以有私有函数——直接将函数定义写入构造函数即可。

四. 小结
以 Self、JavaScript 为代表的无类语言在用函数式风格解释面向对象思想方面作出了巨大进步,无论是灵活性还是强大程度都不是那些关键字一大堆的语言可与之相媲美的。如果我有空,可能还会来介绍一点 E 语言方面的思想,那才是真正无敌的无类语言啊。
  • 4.2.1.zip (9.9 KB)
  • 描述: ECMA-262 翻译片段。
  • 下载次数: 614
分享到:
评论
26 楼 Sean220 2007-11-10  
charon 写道
貌似很多开源包和很多人都实现了自己的extends/mixins或者类似的方法.有些在里面还干了一些非常magic的事情。

这个,大概是用基于prototype的语言来实现这些东西的困惑巴。因为没有在语言级别提供直接的支持,于是大家都百花齐放,或者说的难听一点就是各奔东西了。直接的结果是某些开源包互相之间有冲突,不能拿来一齐使用(我有近半年没用过javascript了,或者现状已经改变了?社区有了大一统的做法?)

另外,这里还差一个多态的实现,虽然简单,但称不上优雅。也一齐补全了吧


为何说差一个多态实现?对于弱类型的OO语言来说,根本无需类型检查,不是更好更原生的多态支持么?



25 楼 kaki 2007-11-03  
楼主只有18岁有这个造诣实在是令人佩服!!

感慨。
24 楼 Beag.Ye 2007-10-22  
实现多继承是可以的,但想基于自有机制是不可能的。不安全的多继承,还是免了吧。
PS: 楼上用到什么JS解释器,是firebug吗?
23 楼 afcn0 2007-10-20  
"此时的 height 就是逃逸变量,从 Desk 函数中以作为对象上绑定的函数的环境上绑定的变量“逃”了出来(这句话有些拗口,不过的确如此)。"你解释的不是很清楚,其实就是scope chain,每个function都有自己的scope如果其中又定义了function,就会引发scope chain,scope chain当中只要chain下面有一个函数的引用还可获取(不管是timeout还是放到全局变量),那chain向上所有scope当中变量全部不释放,但是js有一点处理的不是很好的就是,如果你没有显示声明一个function的参数名,完全使用arguments获得,那在scope chain当中arguments将被覆盖,chain以上无法获得,不过Rhino实现当中__parent__好象就是找scope chain链表对象的,但是标准上面和原形链一样这些都是隐式的,不可获取的,lambda演算是个什么东西?
22 楼 zdzwsz 2007-10-18  
请版主删除
21 楼 afcn0 2007-10-16  
>>> Object.prototype.mix=function(sub){sub.call(this);var temp=Object.extend({},sub.prototype);temp.__proto__=this.__proto__;this.__proto__=temp}
function()
>>> function test(){this.a=123}
>>> test.prototype.b=345
345
>>> a={}
Object
>>> a.mix(test)
>>> a
Object a=123 b=345
>>> function test2(){this.c=678}
>>> test2.prototype.test3=789
789
>>> a.mix(test2)
>>> a
Object a=123 c=678 test3=789 b=345
>>> test2
test2()
>>> test2.prototype
Object test3=789
>>> test.prototype
Object b=345
写了个mix方法,我觉得基本实现多继承,不知道楼主怎么看待,我觉得多继承是可能的
20 楼 bencode 2007-09-19  
一般是这样的写法

var A = function(...) {
    this.field = ...; // 对象的数据,每个对象都有自己的属性值
    ...
}
A.prototype.sayHello = function() {    // 对象的方法
    ...
}
19 楼 hax 2007-08-18  
每一次都会创建不同的函数对象,这是跑不了的了。但是所有这些函数对象是有共同点的,函数代码是一样的,仅仅是scope不一样,因此js引擎是可以做优化的。

所以上面的例子里,从节约内存的角度来说应该写成
Light.prototype.turnOn = function () {...}

但是,就算现在的写法,也不必过分紧张,因为函数执行代码还是一份。
18 楼 kedou 2007-08-18  
Lich_Ray 写道

建立类。只需声明一个函数作为类的构造函数即可。
function Light (light) {
	//填充对象属性
	this.light = light ? light : 0
	this.state = false
	
	//对象方法。
	//放心,JavaScript 没傻到给每个对象都真去分配一个函数的地步
	this.turnOn = function () {
		this.state = true
	}
}




我之前看过一些相关资料,说
this.trunOn=function(){
     this.state=true;     
}

每次创建一个Light时,会重复生成函数,为每个Light对象都创建建立的函数.所以这样会比较浪费内存!!
一直在我人印象都是这样,刚刚看了本文后,有一点动摇,究竟是会为this.state=function(){this.state=true}是每创建一个对象时为其创建独立的函数,还是所有该对象共享该函数呢???
17 楼 yao 2007-07-18  
nihongye 写道
引用
# //把那些垃圾的库抛在脑后,让它们见识见识什么叫优雅。 
# Object.prototype.extend = function (aClass) { 
#     this.prototype = new aClass 
# } 
# PhilipLight.extend(Light) //No problem


不如
Object.prototype.extend = function (object) { 
    this.prototype = object
}
来得直接,完全的无类。



很多书上说用一个父类的实例去替换子类的prototype。另外,只用prototype chain的方式实现的是单继承。
16 楼 nihongye 2007-07-01  
引用
# //把那些垃圾的库抛在脑后,让它们见识见识什么叫优雅。 
# Object.prototype.extend = function (aClass) { 
#     this.prototype = new aClass 
# } 
# PhilipLight.extend(Light) //No problem


不如
Object.prototype.extend = function (object) { 
    this.prototype = object
}
来得直接,完全的无类。
15 楼 sp42 2007-06-21  
相关文章:
http://www.iteye.com/topic/85966
关于JavaScript的 貌似类(pseudo-classes)----不吐不快
14 楼 charon 2007-06-15  
Lich_Ray 写道

那当然是私有变量,而且还保证了不知道细节的外部定义函数再绑定也是白搭。真是比 private 还 private。
deak.getAgain = function () {
    return height
}
js> deak.getAgain()
js: ReferenceError line 2:height is not defined





faint.兄弟在开玩笑吧。
这个不仅仅是外部函数,内部函数也不行啊。除非这个内部函数是定义在那个逃逸变量的作用域范围内。
用现在时髦的说法,俺们都流行open class了,难道这些open上去的方法就不允许访问源定义中的私有变量?
不过这么做确实是私有变量里面的私有变量。说白了就是揣个引用到处跑啊。
13 楼 charon 2007-06-14  
表达式中不能出现本级空间中同名变量这个问题,官方的说法貌似是不能给中间作用域的变量直接赋值。或者说,所有赋值左端的自由变量都只能是最内层作用域的. python的作用域规则确实比较诡异,2.1之前还要差。
只有采用变通的办法, 搞个可读写的object。比如var=[x],则就可以在内层空间这么搞var[0] = var[0] + 10。
12 楼 Lich_Ray 2007-06-14  
好好好,不跟你们争关于多态的东西了,行不?
JavaScript 中的实例属性 __proto__ 指向其构造函数的 prototype,constructor 指向构造函数,不要自己写多态方法调用方法,要用哪个直接指定就可以了;如果一定要写,用类似文中 Mixin 用的技术外部调用多态方法也是可以的。

引用
而且,就继承而言,javascript中的分类也是五花八门,也许拿到一个js库,第一个任务就是理解它的继承模型http://ajaxpatterns.org/Javascript_Inheritance

你发的那个网址所说的我只了解一部分,仔细看了一下。
我所说的令人讨厌的是 Direct Inheritance,因为它只是在一味地模仿 Java,而且模仿地还不像,对于我这样成天用 this 在执行上下文中跳来跳去的用户还有严重的 Bug;它要求你必须中规中矩,改变代码风格。
其它方法中 Bound Inheritance 是个 Direct 的保守版本,不过我也不能接受。
剩下来的几种方式不知道你仔细阅读了代码没有,本质是一样的,基本上可以做到不影响代码风格(因为最终效果都是一样的,估计你也看出来了,我可以选择使用,想不用时也不会出问题);Reference Inheritance 其实只是把普通的 prototype 继承消息封装了起来,可扩展性受限制,不知是出于何种想法。

那当然是私有变量,而且还保证了不知道细节的外部定义函数再绑定也是白搭。真是比 private 还 private。
deak.getAgain = function () {
    return height
}
js> deak.getAgain()
js: ReferenceError line 2:height is not defined


确实,把 class 定义在局部空间中可以达到类似的效果,只不过,Python 在理解局部变量的时候,重绑定上级空间中的变量时,表达式中不能出现本级空间中同名变量——看到这个是不是很晕?而且,你打算用什么把 class 从函数空间中返回出来?难道说:
def MyClass ():
    private_val = 12
    class _noname_:
        def get_val (self):
            return private_val
    # 很有意思,可以考虑做一成个“私有类容器”//smile
    return _noname_
>>> obj = MyClass()()
>>> obj.get_val()
12

我觉得我真的很有毅力……
11 楼 charon 2007-06-14  
再提个问题
LZ这个逃逸变量法实现私有权限的方式,是内在不一致的。或者说实现的并不是真正的私有变量.
就LZ对Python提的8个问题,其中有三个是:
引用

...
Python 的类能在声明完成之后不声明额外名字给类添加方法吗?
Python 的对象能在声明完成之后不声明额外名字给自己添加方法吗?
....
Python 的类能真正支持私有属性吗?

姑且认为LZ是确认Javascript能够实现这些特性的。
但是,LZ的私有变量的实现,直接与前两个问题冲突。而且即便是声明额外的名字,也不能解决这个冲突。
在体外声明的方法,根本无法直接访问体内定义的私有变量。
因此这个逃逸法定义的实际上并不是私有变量。或者说是以牺牲内在一致性为代价的私有变量。

顺便说一下,如果这么做也算是私有变量的话,python照样也可以实现,只要把class定义在一个非全局环境中即可。
10 楼 potian 2007-06-13  
dogstar 写道
well done,继续写一些好文章。

charon 写道
貌似很多开源包和很多人都实现了自己的extends/mixins或者类似的方法.有些在里面还干了一些非常magic的事情。

这个,大概是用基于prototype的语言来实现这些东西的困惑巴。因为没有在语言级别提供直接的支持,于是大家都百花齐放,或者说的难听一点就是各奔东西了。直接的结果是某些开源包互相之间有冲突,不能拿来一齐使用(我有近半年没用过javascript了,或者现状已经改变了?社区有了大一统的做法?)

另外,这里还差一个多态的实现,虽然简单,但称不上优雅。也一齐补全了吧


javascript应该算是小巧灵性。这些灵活就注定了他不太容易大规模工业生产。比如java。
为什么非要写的那么复杂呢?我在写javascript的时候,顶多就用到一些对象之类的,什么继承之类的考虑都不考虑。写java不也是一个接口,一个实现。连继承都很少有。(不要告诉我,我写的应用太小,哈哈)。所以,语言本身探讨可以,但不要过多吹毛求疵了。


或许OO程序中继承、多态所占的相关代码并不是很多,但往往是整个系统最闪光的部分

譬如DP整本书在教你少用继承,但如果没有继承和多态,那所有的DP就化为乌有了



9 楼 charon 2007-06-13  
Lich_Ray 写道

PS: 多态对于 JavaScript 来说自动存在。看标准理解点运算符和访问运算符 [] 的行为(自动查找继承链);附件中也有一点浅显的介绍。从我写的 Mixin 代码中也能看出来:PhilipLight 不就是多个 price 属性吗?本来跟 Product 类要求的 price 并不相关,但照用不误。


你这个例子中的price不算是多态,只能算是覆盖了。怎么说你的this.price只能有一个。

问题的关键是你需要找到父类的同名函数。比如父类中有methodA,子类中也复写了相同签名的methodA,通常的要求是子类的methodA在干自己的私活之前必须还能调用父类的methodA(最典型的是在构造函数中,但构造函数又是个特例,各种语言都会对它做一些特别的照顾).
当然,有很多种简单的方法可以做到这一点,但是都需要做额外(相对于基于Class的语言是不必要的)的工作。

而且,就继承而言,javascript中的分类也是五花八门,也许拿到一个js库,第一个任务就是理解它的继承模型http://ajaxpatterns.org/Javascript_Inheritance


8 楼 dennis_zane 2007-06-13  
多谢,我确实没去读过ECMAScript的规范,对javascript的学习也仅限于纯粹介绍JS的书,被误导了!-_-
7 楼 Lich_Ray 2007-06-13  
dennis_zane 写道
第一个例子中在构造函数中创建的函数,很多javascript的书都说这样创建的每个对象都有自己的函数版本,比如这里的Light的每个对象都有自己的turnOn,楼主说“JavaScript 没傻到给每个对象都真去分配一个函数的地步“,这一点如何证明?

没好好学编译原理或者没好好看 ECMA-262 吧?
看看下面这一段,摘自 ECMA-262 13 章的一段:
引用
Two uses of the FunctionBody grammar production are defined to be equated when one of the following is true:
    * Both uses obtained their FunctionBody from the same location in the source text of the same ECMAScript program. This source text consists of global code and any contained function codes according to the definitions in 10.1.2.
    * Both uses obtained their FunctionBody from the same location in the source text of the same call to eval (15.1.2.1). This source text consists of eval code and any contained function codes according to the definitions in 10.1.2

这一段确定符合这些条件的函数,必须被认为是同一个函数。下一段中的注释说明了这有什么用(前文在讲解创建函数的算法步骤):
引用
Step 1 allows an implementation to optimise the common case of a function A that has a nested function B where B is not dependent on A. In this case the implementation is allowed to reuse the same object for B instead of creating a new one every time A is called. Step 13 makes this optimisation optional; an implementation that chooses not to implement it will go to step 2.

两个字:优化。既然两个函数“相同”,那就保留一个呗!

PS: 多态对于 JavaScript 来说自动存在。看标准理解点运算符和访问运算符 [] 的行为(自动查找继承链);附件中也有一点浅显的介绍。从我写的 Mixin 代码中也能看出来:PhilipLight 不就是多个 price 属性吗?本来跟 Product 类要求的 price 并不相关,但照用不误。

相关推荐

    matlab中绿色的代码什么意思-JavaScriptOOP:如何以及何时在JavaScript中使用OOP?

    另一个指标是TypeScript的出现,TypeScript是Microsoft编写的JS的超集,并支持OOP功能:类,名称空间,模块,静态类型,泛型。 在这个简短的概述中,我将回答问题:如何,为什么以及何时在JavaScript中使用OOP? ...

    JavaScript中的 new 命令

    面向对象编程(Object Oriented Programming,缩写为 OOP)...JavaScript 语言使用构造函数(constructor)作为对象的模板。 “构造函数”,就是专门用来生成实例对象的函数。 它就是对象的模板,描述实例对象的基本结

    JavaScript-Advanced:这些是本学期我解决的一些问题

    通过“ JavaScript高级”,学生可以深入了解JavaScript语言,例如高级语法,使用矩阵,对象,类和高级功能。 学生还将学习更复杂的概念,例如函数上下文,显式绑定,事件循环。 本课程将进一步发展他们的算法思维。 ...

    nest:一个渐进式的Node.js框架,用于在TypeScript和JavaScript(ES6,ES7,ES8)之上构建高效,可伸缩的企业级服务器端应用程序:rocket:

    它使用现代JavaScript,并使用构建(保留与纯JavaScript的兼容性),并结合了OOP(面向对象编程),FP(功能编程)和FRP(功能React编程)的元素。 在,Nest使用 ,但还与广泛的其他库(例如,从而允许轻松使用大量...

    redefine:用于智能对象属性定义的轻量级实用程序

    redefine.js 一个轻量级但功能强大的ES5实用程序。 过时的 是定义类的最新,最能证明未来,积极维护且...在JS世界中确实很常见的一件事,如果不熟悉ES5,那么一件事情就不那么容易解决:来自其他OOP语言JavaScript类概

    UFSC计算机工程:我的项目在毕业时开发。完成。但是文件丢失。

    我将按学期分开课堂上开发的项目第一学期编程逻辑第二学期编程语言I第三学期C中的数据结构第四学期关于人工智能I的项目Cpp中的编程语言II OOP FFT / DFT 算法设计与分析第五学期关于人工智能II的项目控制基础蓝光第...

    prototype-speed-test

    我将自己描述为一个中级JavaScript开发人员,并且一直在尝试使用这种可爱的语言来学习尽可能多的东西。 我很快发现自己想要对我的网站尽可能有效地使用面向对象编程。 尽管对于小型站点,使用极端DRY和抽象代码的...

    simongomes:GItHub资料描述

    语言: PHP , JavaScript , C++ , Java , OOP 前端: HTML5 , CSS3 , Vue , React 后端: Laravel , WordPress , Express , Node 数据库: MySql 技术实践:敏捷,测试驱动开发 :hugging_face: 谢谢...

    写给大家看的面向对象编程书(第3版).[美]Matt Weisfeld(带详细书签).pdf

    6.1.8 创建类模型来描述系统 85 6.2 案例研究:blackjack示例 86 6.2.1 使用CRC卡 87 6.2.2 明确blackjack类 88 6.2.3 明确类的职责 90 6.2.4 UML用例:明确协作关系 95 6.2.5 第一轮CRC卡 98 6.2.6 UML类图...

    matlab代码做游戏-CV:简历

    我不仅对编码充满热情,而且对使用TDD,OOP,MVC等以正确的方式进行编码充满热情。 我学习很快,并且能够相对容易地选择新语言。 我认为,良好的用户界面对于交付真正满足最终用户需求的产品至关重要。 专案 项目 ...

    asp.net知识库

    完整的在.net后台执行javascript脚本集合 ASP.NET 中的正则表达式 常用的匹配正则表达式和实例 经典正则表达式 delegate vs. event 我是谁?[C#] 表达式计算引擎 正式发布表达式计算引擎WfcExp V0.9(附源码) 运算...

Global site tag (gtag.js) - Google Analytics