------------------------------------------------------------------------
第二部分:元语言下的编程基本方法
------------------------------------------------------------------------
三、代码组织形式
---------------------
1. 物理代码块(代码文本、代码行)
---------------------
代码到底应该如何组织?如果我们认为代码是由序列调用的例程构成的,那么我们可以把代码的
形式回溯到很“远古”的时代,例如:
======
function func1() {
}
function func2() {
}
function main() {
func1();
func2();
}
main();
======
简单地说,就是我们可以通过函数来组织代码块,并通过函数的连续调用完成运算。那么,函数
又是如何“组织成代码块”的呢?
这个过程被分解为三个部分,也就是:
- body : 函数的代码体
- name : 函数名
- param : 函数参数表
其基本规则为:
======
function [name]([param]) {
}
======
QoBean中的Block()这个函数,用于从代码文本中分离上述三个部分。Block()可以处理任何一个
符合JavaScript语法的函数(的文本)。例如(param返回数组, 其它两种情况返回文本字符串):
======
function foo(a,b,c) {
// ..
}
alert( Block(foo, 'body') );
alert( Block(foo, 'name') );
alert( Block(foo, 'param') ); // 返回一个数组,可以直接用作字符串连接
======
Block的上述三种参数,用于获取一个函数(的文本格式)中三个部分的物理代码块。
2. 逻辑代码块(闭包、全局闭包)
---------------------
一个函数如果不是局部的,就是全局的。局部的,就是指被物理上包含在其它函数内部的函数。
基于这样的规则,显然一个(全局的)语句中包括的函数会是全局的(而非局部的)。例如:
======
if (true) {
// 下面的函数是全局的(或属于外一层函数闭包)
function() {
// ...
}
}
======
由于逻辑代码块事实上就两种,因此Block()提供了两种方式来实现它:
======
// 得到一个匿名的全局函数
f1 = Scope(func, 'anonymous');
// 得到一个匿名的局部函数(在当前闭包内)
f2 = eval( Scope(func, 'scope') );
======
这两种方式得到的新函数都以是func为参考的,也就是说复制了一个一模一样的函数——但改
变了它们的闭包作用域。如下例:
======
var msg = 'in global.';
var ff = function() {
var msg = 'in ff.';
return function() {
alert(msg);
}
}()
// show 'in ff.'
ff();
function myFunc() {
var msg = 'in myFunc.';
// show 'in myFunc';
var f = eval( Scope(ff, 'scope') );
f();
// show 'in global.'
var f = Scope(ff, 'anonymous');
f();
}
// test
myFunc();
======
3. Scoped(),绑定到指定的作用域(全局或指定对象的闭包)
---------------------
原则上,Scope()的处理对象是函数,其结果或者是一个代码文本,或者是一个函数闭包。但如果
你试图将一个函数绑定到一个对象的闭包中,那么你需要用Scoped()这个函数,其含义是“作用
域化”。返回一个函数,该函数位于指定的对象或全局的闭包中。Scope()与Scoped()的区别在于:
- Scope()用于根据函数func,返回新函数(或用于eval()的函数文本);
- Scoped()用于将函数绑定到目标对象obj,返回绑定后的函数;
绑定到指定作用域是一个有趣的功能,例如:
======
var aData = 'global data';
var obj = {
aData: 'object data'
}
myFunc = function() {
alert(aData);
}
myFunc2 = Scoped(obj, myFunc);
myFunc(); // show: 'global data';
myFunc2(); // show: 'object data';
======
四、重写的基础技术与思想
---------------------
1. 重写
---------------------
QoBean认为一个项目中的代码,是通过开发人员不断地在一个基础代码上繁殖衍生而得到的。这个所
谓的基础代码的最原始的、基底性的版本,就是下面这样一个函数:
======
function() {
}
======
其最终执行的方法为:
======
void function() {
}();
======
其繁殖衍生的过程就称为“重写”。例如:
======
// 原始版本
x = function() {
}
// 重写1
old_x = x;
x = function() {
old_x();
...
}
// 重写2
old_x = x;
x = function() {
...
old_x();
}
...
// 执行
void x();
======
2. JS中的重写方法
---------------------
JS中本身就具有多种重写的方法,例如编译期重写与执行期重写(注意,编译期的重写是由编译器根
据代码上下文中的标识符,及其出现的次序来决定的。它的效果不能由用户代码来改变):
======
function func() {
}
// 下面的重新声明,其“重写”效果是在编译期发生的
function func() {
}
// 下面的赋值操作,是一种在执行期发生的“重写”效果
func = function() {
}
======
3. 保留旧有代码的两种基本方法
---------------------
在执行期的重写中,可以保留旧有的代码。例如:
======
func = function(foo) { //<-- 2. 使用形式参数foo来持有旧的函数
return function() { //<-- 3. 返回的匿名函数将重写func(),且仍可以通过foo来访问旧函数
// ...
}
}(func); //<-- 1. 传入旧的func函数引用
======
这种技术非常类似于桌面软件开发中的HOOK(钩子)技术,因此在这里我们将它使为“Hook重写”。
还有一种重写技术,也可以做到上述的“保留旧有代码”的效果。他利用了JS代码可以序列化的特性:
======
function attach_code(func, code) {
var _r_codebody = /[^{]*\{([\d\D]*)\}$/;
func = func.toString().replace(_r_codebody, '$1')
return new Function(func + code);
}
func = attach_code(func, '...');
======
上述代码的基本思想在于通过"new Function()"来得到新的函数,而旧的函数被序列化为一个字符串,
作为新的代码的一部分。这个过程中,_r_codebody的作用是获取一个函数的body区——函数代码块。
从这个视角来看,下面的函数:
======
function hi() {
alert('hi');
}
======
其实相当于如下代码重写过程(给一个空函数内部attach一行代码):
======
hi = new Function;
hi = attach_code(hi, "alert('hi');");
======
4. 通过链接代码来组织更大的代码块(或函数)
---------------------
源于函数可以被序列化的特性。attach_code()第二个参数的代码文本,也可以视为第一个函数的body
区。也就是说:
======
a_func = function() {
//...
};
hi = attach_code(hi, _get_code_body(a_func));
======
而从这里,我们也得到一个启发,如下代码:
======
function func1() {
// func1 ...
}
function func2() {
// func2 ...
}
func1();
func2();
======
其实执行效果相当于把func1()与func2()合并起来,而合并的效果也就是:
======
func = func1 + func2; // <-- attach_func(func1, func2);
func();
======
如果attach_func()函数可以联合足够多的函数,我们也就可以拼合足够多的代码块——并让他足够复杂。
attach_func()的实现思想与上述过程是直接相关的:
======
function getCodeBody(func) {
var _r_codebody = /[^{]*\{([\d\D]*)\}$/;
return func.toString().replace(_r_codebody, '$1')
}
function attach_code(f1, f2) {
return getCodeBody(f1) + (f2 ? attach_code.apply(null, [].slice.call(arguments, 1)) : '');
}
function attach_func() {
return new Function(attach_code.apply(null, arguments));
}
// sample
func = attach_func(func1, func2);
alert(func);
======
五、QoBean中的重写技术
---------------------
1. QoBean所面临的更复杂的问题
---------------------
QoBean提供了Block()函数来取得一个function中的各个部分,同时也提供了Scoped()函数来产生指定
闭包环境下的函数。这也就意味着,我们可以在任意位置,通过上述的方法来“重写出”一个新的函
数——这个函数是象打补丁的过程一样,一次次增量叠加起来的。
有趣的是,如同上面所分析的,QoBean认为开发人员在书写原始代码的过程中的行为,的确就是这样
一个过程:不断地改写一个函数代码块的内部,或创建新的函数代码块——然后连接它们。所以,我
们可以写出任意大的程序来,本质上也就是我们在不停地用Block、Scope、Scope、Block……
这个过程在QoBean来说,叫做“编织(weave)”。Weave()这个函数用于简化“不停地Block、Scope”
这一过程。该过程首先包括了一个简单的逻辑:
======
function Weave(where, code) {
var source = Block(this);
code = source.replace(where, code);
return Function.apply(null, Block(this, 'param').concat(code));
}
======
也就是说,你可以为任何一个函数使用Weave()功能,以使得它可以在指定位置“where”增加一段代
码。这个指定位置可以是一个正则表达式、或者一个字符串。所以上一小节中的例子可以这样实现:
======
func = Weave.call(func1, /$/, Block(func2, 'body'));
alert(func);
======
同时,QoBean对这个过程进行了扩展,使得它可以支持:
- 在Weave()处理的函数中,支持函数形式参数;
- 在代码中可以使用'$&'等正则表达式中允许的替换匹配。
这个过程,通过Weave()函数自身的一些编织过程来实现——也就是Weave()自身其实被重写过一次。
2. 两种重写方法的选择
---------------------
“Weave重写”与“Hook重写”各有应用的场合。一般来说,Hook重写更简洁也更容易让人接受,因
为它毕竟是JS自身支持的一种重写方法。但是它存在一个致命的问题——改变了调用栈:
======
function func() {
f();
}
function f() {
alert(arguments.callee.caller); //显示调用栈上的函数
}
// 1. 正常情况:显示调用者函数func()
func();
// 重写1次
f = function(foo) {
return function() {
return foo.apply(this, arguments);
}
}(f);
/* 重写n次
f = function(foo) {
...
}(f);
*/
// 2. 异常情况:重写后,显示Hook程序,而不是调用者函数func()
func();
======
“Weave重写”的确没有这个问题,但它要求重写时对被重写代码有充分的了解——Hook重写则没
有这个要求。Weave重写至少需要了解被重写代码所在闭包的位置——全局的或局部的。这是因为
Weave重写后将产生一个函数,而这个函数(默认)是全局的,例如:
======
f = 'global';
function X() {
var f = function() { };
f = Weave.call(f, /$/, 'alert(f)');
f();
}
X();
======
当你需要它是某个局部的函数时,你不得不重新创建一个(当前闭包内的)scope。例如:
======
f = 'global';
function X() {
var f = function() { };
f = eval(Block(Weave.call(f, /$/, 'alert(f)'), 'scope'));
f();
}
X();
======
由于对闭包位置有显性的要求,因此Weave()重写事实上并不太适合于函数内的重写——当然如
果你总是确知这些信息的话,又另当别论了。所以,Weave()重写通常只用在全局的(匿名或具名
的)函数中——能较为方便地控制闭包的位置。
3. 模拟形式参数表(arugments)的问题
---------------------
当然,我们可以随时通过apply/call方法来传递一个参数表。——尽管我们看起来也可以通过对
象闭包来模拟参数表中的形式参数,例如:
======
function func(x, y) {
alert(x + y)
}
o = {x:100, y:100};
f = new Function(Block(func, 'body'));
func = Scope(o, f);
func();
======
但其中的一个严重的问题是,在通过Block()构造的这个函数f()内部,使用arguments时并不能访
问到对象o中的成员——而在正常情况下,arugments与形式参数是存在对应关系的。所以事实上在
使用Scope()方法时,我们无法通过对象闭包来模拟参数表。
六、基本的元类声明,与元类系统的实现
---------------------
1、Meta.js中的元类系统声明
---------------------
QoBean声明了基本的元类系统,它的声明非常简单:
======
// Constructors-Meta, return TMyObject.Create. It's abstracted define.
MetaObject = Function;
// Classes-Meta, return TMyObject. It's abstracted define.
MetaClass = Function;
======
也就是说,对于QoBean来说,元类和元对象都是函数——按照前面所讲述的,也就是说是“执行
体”。QoBean规定了这些执行体的返回结果,也就是:
======
MetaClass()将返回一个类类型(函数),它声明了一个“基于类的对象系统”的组织形式;
MetaObject()将返回一个构造器函数,它可以放在TMyObject.Create成员中,用于构建一个对象实例。
======
Meta.js中,上述两个声明只是抽象声明,并没有实现,也没有描述任务逻辑细节。这也是元语言系
统的一个特点:元语言是最基底的声明、描述或实现。它约定了范围与边界,但不一定去直接实现它。
这有点像架构师的工作。呵呵。
2、Class.js中的实现
---------------------
Class.js中实现了一个类注册函数,用于将一个普通的函数注册为一个“类类型”,或者说它是通过
类注册的方式,构建了基于类的对象系统。
类注册函数为Class(),它描述了类注册以及从类构建一个对象实例的基本方法。包括四个主干步骤:
======
1、通过MetaClass()取得一个新的类,即cls = MetaClass(AConstructor);
2、设定原构造器的原型继承关系,即AConstructor.prototype = Parent.Create.prototype;
3、初始化类,即Initializtion(cls);
4、重写原来的构造器,即AConstructor = cls.Create
======
在第3步的初始化类过程中,存在四个小的分支步骤:
======
1、填写用于类系统识别的类信息;
2、从MetaObject()中取得一个元对象构造器到cls.Create,以支持“对象创建”的行为;
3、重写上述构造器cls.Create的prototype属性,以支持“对象继承”的能力;
4、重写上述prototype的constructor属性,以支持对象的“构造器(外部)链回溯”。
======
QoBean中的Class.js代码也很简单,就如下几行:
======
// class register util
function Class(Parent, Name) {
var Constructor = eval(Name);
var cls = new MetaClass(Constructor);
// parent-class link and prototype set.
Parent && (Constructor.prototype = Parent.Create.prototype);
// the qomo classes system.
// Initializtion -->
((Constructor = cls(Constructor), // step one
cls.Create = new MetaObject(cls) // step two
).prototype = Constructor // step three
).constructor = cls.Create; // step four
// rewrite constructor by 'Name'
(Name instanceof Function) || eval(Name + '= cls.Create');
return cls;
}
======
类注册过程的更多说明可以参见QoBean项目发起时的一篇文章:
http://blog.csdn.net/aimingoo/archive/2007/12/31/2006534.aspx
3、Object.js中的实现
---------------------
我们看到,Meta.js定义了元语言级别的“类、对象”的抽象,而Class.js中则描述了类和对象的
逻辑关系。然而MetaClass()和MetaObject()并没有具体的实现——也就是说,到现在为止我们得
到的仍然只是“具有逻辑关系的抽象系统”。我们必须要说明“实体系统是怎样的”,也就是要
说明“元类如何创建类、元对象如何创建对象(构造器)”。
而这部分就是在Object.js中实现的。其结果则是完成了全部的类系统,在装载完Object.js之后,
QoBean的类、类类型系统也就构建完成了,这个类类型的基本使用与Qomo历来的版本是完全一致
的。也就是下面的基本模式:
======
// 声明(类似普通构造器函数)
function MyObject() {
}
// 注册
TMyObject = Class(TObject, 'MyObject');
// 使用
obj = TMyObject.Create();
// or
obj = new MyObject();
======
QoBean中的Object.js代码也很简单,就如下几行:
======
// rewrite meta system
MetaClass = Meta(function(fromSource) {
return new Function('Base', 'return new Base');
}, MetaClass);
MetaObject = Meta(function(fromSource) {
var atom = {};
return function() {
if (this instanceof Function) return new arguments.callee(atom, arguments);
if (this.Create) this.Create.apply(this, arguments[0]===atom ? arguments[1] : arguments);
}
}, MetaObject);
======
七、QoBean中,从元系统开始的系统结构栈
---------------------
QoBean的元系统只有Meta.js一个文件,约70行代码。
QoBean的基本对象系统需要载入(参考QoBean代码包中的t_Object.html):
Meta.js
Class.js
Object.js
后两个文件不超过30行代码。整个的基本语言系统在100行JS代码以内。
如果更加增强的、完整的QoBean类类型系统,需要按如下栈载入JS文件:
Meta.js
Class.js
Namespace.js
ObjectEx.js
TObject.js
通过这个代码栈,可以支持Qomo 2.0版本中的全部对象特性。包括get/setter,
以及Inherted继承,以及匿名类类型等。此外,通过TObject.js,支持了Txxxx
风格的类类型继承描述。这些内容,可以参考QoBean代码包中的:
t_ObjectEx.html
t_ObjectEx2.html
QoBean当前版本已经支持了Qomo 2.0底层中的全部语言特性,包括接口、切面等等。
这些完整的特性可以通过如下栈来装载:
<!-- Qomo V2版本的JS增强和(切面的)切点
../trunk/Framework/RTL/JSEnhance.js
../trunk/Framework/RTL/JoPoints.js
-->
Intfs/QomoIntfs.js
Intfs/Interface.js
Meta.js
Class.js
Namespace.js
ObjectEx.js
Intfs/ClassIntf.js
TObject.js
<!-- Qomo V2版本中的切面
../trunk/Framework/RTL/Aspect.js
-->
这个示例可以参考QoBean代码包中的:
t_ObjectEx3.html
QoBean内部版本已经可以完全使用QoBean来替代Qomo V2的语言内核了。通过实测,
上述的新的内核,比Qomo V2旧的版本减少了1/3的代码,且同时提高了1/3的执行
性能。所以,我们在这个节点上发布了QoBean Beta 1.0。
分享到:
相关推荐
元计算技术人员为大家介绍ELAB2.0系统有限元语言介绍及理念
文件夹内文件覆盖至安装目录。...拥有全面的教、学、模、练体系,其中包括会计从业、初级职称、计算机一级MS、二级MS、二级C语言、二级C++、二级JAVA、二级Access、二级VB、二级VF等多个模拟系统。
为linux系统设计一个简单的二级文件系统。要求做到以下几点: (1)可以实现下列几条命令(至少4条); login 用户登陆 dir 列文件目录 create 创建文件 delete 删除文件 open 打开文件 close 关闭文件 read...
《构建多语言AI智能客服系统:基于PHP的在线客服源码解析》 在现代商业环境中,高效的客户服务是提升用户体验和企业竞争力的关键因素之一。随着人工智能技术的发展,AI智能客服系统已经成为许多企业的首选解决方案...
### 国际化多语言系统开发手册 #### 概述 随着全球化进程的加快,越来越多的企业需要面对跨国界的业务挑战,特别是在物流行业中,能够支持多种语言的信息系统变得尤为重要。《国际化多语言系统开发手册》主要介绍...
《王爽汇编语言(第二版)》是学习汇编语言的经典教材,它深入浅出地介绍了计算机系统的基础知识和汇编语言编程技术。这本书主要分为几个部分,涵盖了从基本概念到高级应用的广泛内容。 首先,书中从计算机硬件结构...
同时,掌握VBA编程语言也是提高系统个性化能力的关键。通过学习"工作管理系统操作说明.pptx"中的指南,你可以了解到系统的具体操作步骤和技巧。 总的来说,"EXCEL实现工作管理系统"是利用现代技术改进传统工作方式...
用汇编语言解一元二次方程 600行源代码 已经成功运行
二、选择系统语言 在Language Support选项卡中,用户可以选择自己的系统语言。默认情况下,系统语言为英语,但用户可以根据自己的需求选择其他语言,例如汉语、法语、德语等。在本文档中,我们将选择汉语(中国)...
Rust程序设计语言第二版.epub格式Rust程序设计语言第二版.epub格式
获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言获取系统语言
基于java语言开发的图书管理系统基于java语言开发的图书管理系统基于java语言开发的图书管理系统基于java语言开发的图书管理系统基于java语言开发的图书管理系统基于java语言开发的图书管理系统基于java语言开发的...
《汇编语言》第二版是计算机科学领域中一本经典的教材,主要讲解了低级编程语言——汇编语言的基础知识和应用。汇编语言是一种符号化的机器语言,它与硬件紧密相连,提供了对计算机硬件直接控制的能力。对于计算机...
java语言实现的网上选课系统源码.zipjava语言实现的网上选课系统源码.zipjava语言实现的网上选课系统源码.zipjava语言实现的网上选课系统源码.zipjava语言实现的网上选课系统源码.zipjava语言实现的网上选课系统源码...
使用C#语言开发的学生信息管理系统使用C#语言开发的学生信息管理系统使用C#语言开发的学生信息管理系统使用C#语言开发的学生信息管理系统使用C#语言开发的学生信息管理系统使用C#语言开发的学生信息管理系统使用C#...
Rust的设计理念融合了底层语言对系统资源管理的控制能力以及高级语言的抽象优势。这种设计使得Rust非常适合在需要高性能代码的应用场景中使用,比如系统编程、嵌入式系统、高性能计算以及Web开发。 Rust语言的内存...
二、开发语言选择——C++ C++是一种静态类型、编译式的通用编程语言,以其高效、灵活和强大的功能著称。在文件管理系统中,C++的面向对象特性使得我们可以设计出清晰的类结构,将文件、目录、存储空间等抽象为对象...
把Java编程语言更加系统的学好有什么方法.pdf把Java编程语言更加系统的学好有什么方法.pdf把Java编程语言更加系统的学好有什么方法.pdf把Java编程语言更加系统的学好有什么方法.pdf把Java编程语言更加系统的学好有...