JavaScript函数命名为参数,甚至是另一种方法

分享于 

14分钟阅读

Web开发

  繁體 雙語

介绍

阅读当前文章标题及其简短描述许多Javascript专家将认为,ECMAScript specificiation并没有提到命名参数,也没有提及命名参数。 已经在许多方面实现了功能 不过,忽略这些方面,我不想重新创建轮子,我只想提出一些有趣的问题。

Background

如上所述,更改JS可以给你一些意外,你的工作可能会被它的他扩展所覆盖。 尽管这些观点是 true 和有效的,但我相信在驱动方面的动机鼓励开发者改进专业,这往往是新的探索性旅程的起点,通常是一个understimated的优点。 还有,在我的情况下 提供给我的不仅仅是一个暗示 Sergey Alexandrovich kryukov 我将从本文的另一篇文章中阅读文章,我建议阅读从不同的角度来看问题。

使用代码

现在我已经准备好向你展示主源代码的样式,并通过代码示例来说明特性实现。 这里 below 你会发现 为了将 Object.seal 扩展处理在运行时处理不可逆行为,argumentify 扩展的源代码可能需要使用 5引入的Object.defineProperty 特性直接将命名参数附加到函数。

为了防止参数名冲突,hasOwnProperty 本机方法用于检查属性的存在。

无论如何,函数是JavaScript中的第一类公民,因此它们可以具有任何其他对象的属性。 我们需要这些属性来表示原始目标函数的命名形式参数。 编写调用代码,开发人员可以选择定义一个或者多个命名参数默认值,或者在。

这是完整的实现 argumentify :

"use strict";
Function.prototype.argumentify = function argumentify() {
 var self = this,
 argumentValues = Array.prototype.slice.call(arguments),
 argumentNames = (self + "").match(/function[^(]*(([^)]*))/)[1].split(/,s*/);
 if (!(!argumentNames ||!argumentNames[0])) {
 for (var i = 0, l = self.length; i <argumentNames.length; ++i) {
 if (!self.hasOwnProperty(argumentNames[i]) || (self.$$arguments &&
 self.$$arguments.names.indexOf(argumentNames[i])> -1)) {
 Object.defineProperty(self, argumentNames[i], {
 get: function(index, length) {
 returnfunction() {
 return argumentValues[index + length];
 };
 }.call(null, i, l),
 set: function(index, length) {
 returnfunction(value) {
 argumentValues[index + length] = value;
 }
 }.call(null, i, l),
 configurable: true,
 enumerable: false });
 self[argumentNames[i]] = argumentValues[i];
 }
 }
 }
 Object.defineProperty(self, "$$arguments", {
 configurable: true,
 enumerable: false,
 value: {
 names: argumentNames,
 values: argumentValues
 }
 });
 return self;
};

在实例化并增加了 argumentify 之后,开发人员可以在该实例上任意次执行函数逻辑调用 invoke 方法。 invoke 方法行为模仿 apply 本机方法,接受两个参数 分别是 this 上下文对象和包含命名函数参数的对象。 因此,如果正确提供,invoke 方法会预期 第二个参数键入为 文字 对象,承载一些表示函数参数值的属性,该属性在调用时可以承载所有参数或者它的部分。 而且,如果错误的属性名称,则错误不会保持捕获状态,因为该方法在提升 TypeError 异常时。

下面是 invoke 源代码 :

Function.prototype.invoke = function invoke(context) {
 var i = 0, args, invokeArgs, $$arguments = this.$$arguments;
 for (; $$arguments && i <$$arguments.names.length; i++)
 (args || (args = [])).push(this[$$arguments.names[i]]);
 invokeArgs = Array.prototype.slice.call(arguments, 1);
 if (invokeArgs.length === 1 && invokeArgs[0].constructor === Object) {
 var $args = invokeArgs[0];
 for (var prop in $args) {
 if ($args.hasOwnProperty(prop)) {
 if ((i = $$arguments.names.indexOf(prop)) === -1) {
 thrownew TypeError(""" + prop + "" argument name is invalid");
 } else {
 args[i] = $args[prop];
 }
 }
 }
 }
 returnthis.apply(context, args || invokeArgs);
};

当然所有的参数( 属性) 都可以设置为未定义调用 cleanUpNamedArguments :

Function.prototype.cleanUpNamedArguments = function cleanUpNamedArguments(undefined) {
 var $$arguments = this.$$arguments;
 for (var i = 0; $$arguments && i <$$arguments.names.length; i++)
 this[$$arguments.names[i]] = undefined;
};

其实 为了添加辅助功能,例如那些以观察一个或者多个参数的变化,watchNamedArgumentsunWatchNamedArguments 被实现为。

监视/unwatch方法和依赖项如下所示:

if (!Function.prototype.watch) {
 Object.defineProperty(Function.prototype, "watch", {
 enumerable: false,
 configurable: true,
 writable: false,
 value: function(prop, handler) {
 var oldval = this[prop],
 newval = oldval,
 getter = function() {
 return newval;
 },
 setter = function(val) {
 oldval = newval;
 return newval = handler.call(this, prop, oldval, val);
 };
 if (deletethis[prop]) {
 Object.defineProperty(this, prop, {
 get: getter,
 set: setter,
 enumerable: true,
 configurable: true });
 }
 }
 });
}if (!Function.prototype.unwatch) {
 Object.defineProperty(Function.prototype, "unwatch", {
 enumerable: false,
 configurable: true,
 writable: false,
 value: function(prop) {
 var val = this[prop];
 deletethis[prop];
 this[prop] = val;
 }
 });
}
Function.prototype.watchNamedArguments = function watchNamedArguments(callback) {
 var $$arguments = this.$$arguments;
 for (var i = 0; $$arguments && i <$$arguments.names.length; i++)
 this.watch([$$arguments.names[i]], function (id, oldval, newval) {
 callback.call(null, id, oldval, newval);
 return newval;
 });
};
Function.prototype.unwatchNamedArguments = function watchNamedArguments() {
 var $$arguments = this.$$arguments;
 for (var i = 0; $$arguments && i <$$arguments.names.length; i++)
 this.unwatch([$$arguments.names[i]]);
};

如所述 argumentify 方法被认为是一种更好的Javascript函数友好方法,这意味着它总是可以被调用的。

要查看完整的示例,让我们使用sergey的文章中显示的3个参数来考虑"普通"函数:

var fn, fn0;function f(first, medium, last) {
 return first + medium + last;
}try {
 fn = f.argumentify(5, 10);
 console.log("fn:" + (fn.last = 1, fn.invoke(null)));
 console.log(fn.first);
 console.log(fn.medium);
 fn0 = f.argumentify(15, 20);
 console.log("fn0:" + fn0.invoke(null, {last: 1}));
 console.log(fn.first);
 console.log(fn.medium);
} catch (ex) {
 console.log(ex);
} finally {
 fn.cleanUpNamedArguments();
}

就像你看到的,调用代码没有显式检查,尽管参数名称在两个示例中有拼写错误,但是要注意后面的invoke 调用可能会引发 TypeError 异常以通知代码错误。 在示例中,可以更改参数的顺序,也感谢你在 argumentify 方法调用中为省略的参数定义默认参数值。 在每个函数调用中,我们可以使用不同参数值的invoke 方法作为 this 上下文,或者取消它的值。 这种方法增加了对调用代码的额外操作,但是尽管如此,Javascript程序员还是可以选择使用逗号运算符语法或者更常见的mispelling语法列表,比如的文本对象语法。 在这种情况下,两个示例都首选将最后一个参数设置为 undefined 作为默认值。 总之,我们关心的真正问题是可以读性,当你有很多职责时,更好地维护代码库。

fnfn0 变量引用的原始函数与附加到函数对象的属性定义相同。 这是主要原因,因为在 finally block cleanUpNamedArguments 中只调用一次。

在同一时刻 所提供的拼错机制,与监视/unwatching机制配对使开发人员无法完成工作。 实际上,本文所展示的编程风格在以下监视回调时变得更加有趣。

fn.watchNamedArguments(function(id, oldval, newval) {
 console.log('o.' + id + ' changed from ' + oldval + ' to ' + newval);
});

这里我使用了一个回调函数来记录命名参数活动的一般思想。 请注意,只有第一个监视回调被触发为当前所建议的实现。

ECMAScript 6到救援

ECMAScript 5是目前所有主要浏览器中采用ECMAScript标准的mosted,但是 ECMAScript 6引入了这种可能性来定义缺省参数值: 如果你是begging的开发者,那么在Javascript中请求命名参数可能会很不错。

Points of Interest

有可能,有两个关于所提出的方法的。 这两个参数之一是参数初始化,因为我建议使用逗号运算符,而另一个则认为实际参数与常规函数调用语句中传递的参数没有任何关联。

我们开始说,逗号运算符并不特定于 Javascript,而且也可以用于C 和 C++ 等它的他语言。 作为从C 语言继承的二进制操作符,当左操作数通常需要第二个操作数所需要的副作用时,它很有用。 无论如何,在var语句中使用逗号有一个特殊的含义。 实际上,变量声明是指在代码中初始化和使用的两个或者多个变量,表达式在最后一个变量或者由逗号运算符分隔的指令中不存在。 一个模糊的例子可能是这样的:

var b = (1,2)> (4,8)? 'bar' : (4,2)> (1,0)? '' : 'baz'; // b == ''

smelling示例 smelling variabile起始 intialization '' intialization起始convulted表达式,然后开始解析 (1,2) 表达式,然后将解析表达式转换为 2 表达式,然后返回 (1,0) 表达式到 1 表达式。 然后,分别将这些瞬时值分别作为左手和右手两个 ?: 三元算子进行计算,得到了上述结果的结果。 部分deobfuscation效果可以如下所示:

var b = 2> 8? 'bar' : 2> 0? '' : 'baz';

因这里,移动到另一个潜在的复杂点,从调用语句的圆括号中忽略参数看起来很奇怪。 考虑到编写函数调用需要具有一个具体的思想,一个重要的方法是:将参数集作为一组属性。

让我们看几个等价的函数调用语句:

console.log(fn(1, -2, 3)); // => 2console.log((fn.first = 1, fn.medium = -2, fn.last = 3, fn.invoke(null))); // => 2

尽管我们 main fn.invoke() 方法声明了 f.first 方法,但它仍然是引用第1 个参数值的重要方法,因为我们在主示例中声明了函数名,这是一种很重要的方法。 这是一种合理的方法。 如果要重用函数对象以避免贪婪GC操作,则在创建一个对象的方式下,更新它的命名参数值。 argumentify 通话。

另一个问题可能是 参数化参数,我希望ECMAScript规范能够帮助清理特别是关于 configurable 设置的事情,以便允许命名参数的缺省值进一步改变

8.6.1摘录:" [[Configurable] ]: 在属性为 accessor delete的情况下,将该属性更改为存取器属性,或者将属性更改为属性( 除了 [[Value] ] ) 或者更改它的属性。 "

在代码设计决策的同一个区域,我决定将 enumerable 设置为 false ;这样一个 for-in 循环就可以跳过。

结语

在我的文章中,我已经花了一些时间来得出结论,我在本文中描述的简单技术甚至可以部分用于其他开发项目项目。 它不仅提供了控制函数传递函数的方法,而且也可以成为一种可以维护的Javascript解决方案。


相关文章