5

我最近有必要在 javascript 中动态地重写一个 javascript 函数。我做这件事的轻松程度,以及它的乐趣,让我感到震惊。

在这里,我有一些 HTML:

<div id="excelExport1234" 
     onclick="if(somestuff) location.href='http://server/excelExport.aspx?id=56789&something=else'; else alert('not important');"
  >Click here to export to excel</div>

而且我无法更改输出的 HTML,但我需要向该链接添加一个额外的参数。我开始考虑它,并意识到我可以这样做:

excelExport = $('excelExport1234');
if (needParam)
        eval('excelExport.onclick = ' + excelExport.onclick.toString().replace("excelReport.aspx?id", "excelReport.aspx?extraParam=true&id") + ';');
else
        eval('excelExport.onclick = ' + excelExport.onclick.toString().replace("extraParam=true&", "") + ';');

它就像一个冠军!excelExport.onclick 返回一个函数对象,我将其转换为字符串,并对其进行一些字符串操作。由于它现在是“function() { ... }”的形式,我只是返回并将其分配给 dom 对象的 onclick 事件。不得不使用 eval 有点难看,但是 AFAIK 没有一个 javascript 函数构造函数可以接受一串代码并将其很好地转换为对象。

无论如何,我的意思不是我超级聪明(我不是),我的意思是这很酷。而且我知道 javascript 不是唯一可以做到这一点的语言。我听说 lisp 多年来一直为此目的使用宏。除了真正的 grok 宏,你需要真正 grok lisp,我不 grok 它,我只是'有点明白'。

所以我的问题是:你可以(轻松地)用哪些其他语言动态重写函数,你能给我看一个简单的例子吗? 我想看看你还能在哪里做到这一点,以及它是如何做到的!

(另外,我不知道要标记什么,所以我随机猜测)

4

12 回答 12

9

LISP 是这方面的终极语言。LISP 函数是实际的 LISP 列表,这意味着您可以像操作任何其他数据结构一样操作 LISP 源代码。

这是一个非常简单的例子,说明它是如何工作的:

(define hi 
  (lambda () (display "Hello World\n")))
;; Displays Hello World
(hi)
(set! hi
      (lambda () (display "Hola World\n")))
;; Displays Hola World
(hi)

然而,这在函数是一等对象的任何语言中都是可能的。LISP 的这种语法的强大功能最有趣的展示之一就是它的宏系统。我真的觉得我不能公正地讨论这个话题,所以如果你有兴趣,请阅读这些链接:

http://en.wikipedia.org/wiki/Macro_(computer_science)#Lisp_macros

http://cl-cookbook.sourceforge.net/macros.html

于 2009-07-13T13:25:32.243 回答
3

我想这取决于您将什么定义为“轻松动态重写”。例如,在 .Net 中,您有 Func 类型和 lambdas,它们允许您将函数定义为变量或临时匿名函数,例如。

int[] numbers = {1, 2, 3, 4, 5};

Func<int[], int> somefunc;
if (someCondition) 
{
   somefunc = (is => is.Sum());
} else {
   somefunc = (is => is.Count());
}

Console.WriteLine(somefunc(numbers).ToString());

上面是一个非常人为的例子,要么计算整数数组中的项目,要么求和,然后使用动态创建的函数来满足一些任意条件。

注意 - 请不要指出这些事情可以在没有 lambda 的情况下轻松完成(他们显然可以)我只是想写一个非常简单的例子来演示 C# 中的概念

于 2009-07-13T13:31:21.080 回答
2

自修改代码也称为退化代码。这通常被认为是一件坏事,并且它曾经是高级语言的目标,以防止它被轻松编写。

这是来自维基百科条目:

一些人认为自我修改代码是一种不好的做法,它使代码更难阅读和维护。然而,在某些情况下,自我修改仍然被认为是可以接受的,例如当子例程指针被动态改变时——即使效果几乎与直接修改相同。

于 2009-07-13T13:25:55.050 回答
2

Scheme 允许你这样做。

(define (salute-english name) (display "Hello ") (display name))
(define (salute-french nom) (display "Salut ") (display nom))

现在您通过将salute变量分配给正确的函数来重新定义函数,或者salute-englishsalute-french,如下所示:

(define salute salute-english)

(define (redefined-the-salute-function language)
  (if (eq? language 'french)
      (set! salute salute-french)
      (set! salute salute-english)))

更一般的函数式编程语言允许您这样做,或者函数是一流的值。函数可以被操纵、传递,有时也可以赋值给变量等等。然后列表包括:LispSchemeDylanOCamlSML。一些具有一流功能的语言包括PythonRubySmalltalk,我认为是Perl

请注意,当您拥有可以交互式键入程序的交互式语言时,必须可以重新定义函数/方法:REPL 必须能够做到这一点,以防万一您碰巧重新键入已定义函数的定义.

于 2009-07-13T13:50:54.690 回答
2

我认为大多数动态语言都是如此。这是 Python 中的一个示例

定义 f(x):
    打印 x

def new_function(x): print "hello", x

f("世界")    
f = new_function
f("世界")

输出是

世界
你好世界

我认为应该谨慎使用这种技术

于 2009-07-13T13:39:35.110 回答
1

您可以在 C++ 中执行此操作,但这并不容易、不安全或不推荐。

  1. 生成源代码的文本
  2. 调用编译器(fork & exec)来构建动态库。在 gcc 中,您可以在标准输入上传递要编译的源代码,它不必在文件中。
  3. 加载库(Windows 上的 LoadLibrary(),Linux 上的 dlopen())
  4. 获取指向您想要的任何函数的函数指针(Windows 上的 GetProcAddress(),Linux 上的 dlsym())
  5. 如果你想替换现有的函数,如果它是一个虚函数,你可以修改 v-table 以指向新函数(这部分尤其是一个充满危险的可怕想法)。v-table 的位置或它的格式不是 C++ 标准的一部分,但我使用的所有工具链本身都是一致的,所以一旦你弄清楚它们是如何做到的,它可能不会休息。
于 2009-07-13T14:41:56.097 回答
1

我以前在 TCL 一直这样做,轻而易举,效果很好。我可以通过网络调查一些东西的接口,然后动态创建一个定制的接口来访问和控制东西。例如,您可以从通用 SNMP 库创建自定义 SNMP 接口。

我没有使用过它,但是 C# 有一些内置的支持来生成它自己的字节码,这相当令人印象深刻。

我也在 C 中做过这种事情,但它是不可移植的,几乎不值得麻烦。这是一种有时用于“自我优化”代码以生成适当的 C 函数以优化处理给定数据集的技术。

于 2009-07-13T13:24:10.327 回答
1

在 Perl 中很容易。

*some_func = sub($) {
    my $arg = shift;
    print $arg, "\n";
};
some_func('foo');

Re Sam Saffron 的请求:

*hello_world = sub() {
    print "oops";
};
hello_world();
*hello_world = sub() {
    print "hello world";
};
hello_world();
于 2009-07-13T13:25:49.073 回答
0

在 PLSQL 中:

create or replace procedure test
as
begin
 execute immediate '
create or replace procedure test2
as
begin
  null;
end;
 ';
end;
/
于 2009-07-13T14:30:59.300 回答
0

这是 Python 中的其他内容(除了 luc 的答案),我不推荐,只是为了展示它 - 有 exec,它可以执行一个字符串,您可以将其构建为任何代码......

此处显示的 I/O 来自 Python 2.5.2 解释器会话。只是一些构造字符串以从子字符串执行的简单示例(>>> 是解释器提示)...

>>> def_string = 'def my_func'
>>> param_string_1 = '():'
>>> param_string_2 = '(x):'
>>> do_string_1 = '  print "Do whatever."'
>>> do_string_2 = '  print "Do something with", x'
>>> do_string_3 = '  print "Do whatever else."'
>>> do_string_4 = '  print "Do something else with", x'
>>> def_1 = '\n'.join([def_string+param_string_1, do_string_1, do_string_3])
>>> print def_1
def my_func():
  print "Do whatever."
  print "Do whatever else."
>>> exec def_1
>>> my_func()
Do whatever.
Do whatever else.
>>> def_2 = '\n'.join([def_string+param_string_2, do_string_2, do_string_4])
>>> print def_2
def my_func(x):
  print "Do something with", x
  print "Do something else with", x
>>> exec def_2
>>> my_func('Tom Ritter')
Do something with Tom Ritter
Do something else with Tom Ritter
>>> 
于 2009-09-04T22:21:21.520 回答
0

Ruby中的琐碎:

def hello_world; puts "oops"; end
hello_world
# oops
def hello_world; puts "hello world"; end
hello_world
# hello world

当然,这个例子很无聊:

require "benchmark"
# why oh _why 
class Object
  def metaclass; class << self; self; end; end
  def meta_eval &blk; metaclass.instance_eval &blk; end
end

class Turtle
end

def make_it_move(klass)
  klass.send(:define_method, :move) { |distance|
    puts "moving #{distance} meters"
    sleep(0.1 * distance)
  }
end

make_it_move(Turtle)

turtle = Turtle.new
turtle.move(1)
# moving 1 meters

def profile(instance, method)
  instance.meta_eval do
    m = instance_method(method)
    define_method method do |*a|
      puts "Benchmarking #{instance.class} #{method}"
      puts Benchmark.measure {
        m.bind(instance).call(*a)
      }
    end
  end
end

profile(turtle, :move)

turtle.move(10)
# Benchmarking Turtle move
# moving 10 meters
#  0.000000   0.000000   0.000000 (  1.000994)


Turtle.new.move(3)
# moving 3 meters 

上面的代码:

  1. 定义一个空白类
  2. 向它添加一个方法
  3. 抓取一个实例
  4. 在该实例上拦截该方法
于 2009-09-05T13:17:34.710 回答
0

许多语言都支持更改函数的功能,而且它并不像您想象的那么复杂。在函数式语言中,函数是值,函数名是绑定到它们的符号,就像任何变量一样。如果语言允许您将符号重新分配给不同的功能,这是微不足道的。

我认为更有趣的功能是能够获取函数的源代码(toString如上)并从字符串创建新函数(eval在本例中)。

于 2009-09-05T14:51:30.587 回答