簡體   English   中英

有沒有辦法在 JavaScript 的 function 調用中提供命名參數?

[英]Is there a way to provide named parameters in a function call in JavaScript?

我發現 C# 中的命名參數功能在某些情況下非常有用。

calculateBMI(70, height: 175);

如果我想在 JavaScript 中使用這個,我可以使用什么?


我不想要的是:

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

我已經使用過這種方法。 還有其他方法嗎?

我可以使用任何庫來執行此操作。

ES2015 及更高版本

在 ES2015 中,可以使用參數解構來模擬命名參數。 它需要調用者傳遞一個對象,但如果您還使用默認參數,則可以避免函數內部的所有檢查:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

有一種方法可以接近您想要的,但它基於Function.prototype.toString [ES5]的輸出,這在某種程度上依賴於實現,因此它可能不兼容跨瀏覽器。

這個想法是從函數的字符串表示中解析參數名稱,以便您可以將對象的屬性與相應的參數相關聯。

函數調用可能看起來像

func(a, b, {someArg: ..., someOtherArg: ...});

其中ab是位置參數,最后一個參數是具有命名參數的對象。

例如:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

您將用作:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

演示

這種方法有一些缺點(您已被警告!):

  • 如果最后一個參數是對象,則將其視為“命名參數對象”
  • 您將始終獲得與函數中定義的參數一樣多的參數,但其中一些可能具有undefined的值(這與根本沒有值不同)。 這意味着您不能使用arguments.length來測試傳遞了多少個參數。

除了讓函數創建包裝器之外,您還可以擁有一個接受函數和各種值作為參數的函數,例如

call(func, a, b, {posArg: ... });

甚至擴展Function.prototype以便您可以執行以下操作:

foo.execute(a, b, {posArg: ...});

——對象方法是 JavaScript 對此的回答。 如果您的函數需要一個對象而不是單獨的參數,這沒有問題。

很多人說只使用“傳遞對象”技巧,以便您命名參數。

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

這很好用,除了......當涉及到大多數 IDE 時,我們中的許多開發人員都依賴於我們 IDE 中的類型/參數提示。 我個人使用 PHP Storm(以及其他 JetBrains IDE,例如用於 python 的 PyCharm 和用於 Objective C 的 AppCode)

使用“傳遞對象”技巧的最大問題是,當您調用函數時,IDE 會為您提供單一類型提示,僅此而已......我們應該如何知道應該進入的參數和類型arg1 對象?

我不知道 arg1 中應該包含哪些參數

所以......“傳遞對象”技巧對我不起作用......它實際上會導致更多的頭痛,因為在我知道函數期望的參數之前必須查看每個函數的文檔塊......當然,它非常適合當您維護現有代碼時,編寫新代碼卻很糟糕。

嗯,這就是我使用的技術......現在,它可能存在一些問題,一些開發人員可能會告訴我我做錯了,而我對這些事情持開放態度......我總是願意尋找更好的方法來完成任務......所以,如果這種技術有問題,那么歡迎發表評論。

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

這樣,我就擁有了兩全其美...新代碼很容易編寫,因為我的 IDE 為我提供了所有正確的參數提示...而且,在以后維護代碼時,我可以一目了然,不僅傳遞給函數的值,也是參數的名稱。 我看到的唯一開銷是將您的參數名稱聲明為局部變量,以防止污染全局命名空間。 當然,這有點額外的輸入,但與在編寫新代碼或維護現有代碼時查找文檔塊所需的時間相比,這是微不足道的。

現在,我在創建新代碼時擁有了所有參數和類型

如果你想清楚每個參數是什么,而不僅僅是調用

someFunction(70, 115);

為什么不做以下

var width = 70, height = 115;
someFunction(width, height);

當然,這是一行額外的代碼,但它在可讀性方面勝出。

另一種方法是使用合適對象的屬性,例如:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

當然,對於這個虛構的例子來說,這有點毫無意義。 但是在函數看起來像的情況下

do_something(some_connection_handle, some_context_parameter, some_value)

它可能更有用。 它還可以與“parameterfy”的想法相結合,以通用方式從現有函數中創建這樣的對象。 也就是說,對於每個參數,它將創建一個可以評估為函數的部分評估版本的成員。

這個想法當然與 Schönfinkeling aka Currying 有關。

使用作為對象傳遞的命名參數調用函數f

o = {height: 1, width: 5, ...}

基本上將其組合稱為f(...g(o)) ,其中我使用的是擴展語法,而g是一個“綁定”映射,將對象值與其參數位置連接起來。

綁定映射正是缺少的成分,可以通過其鍵的數組來表示:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

請注意三個解耦的成分:函數、具有命名參數的對象和綁定。 這種解耦為使用此構造提供了很大的靈活性,其中綁定可以在函數定義中任意定制,並在函數調用時任意擴展。

例如,您可能希望在函數定義中將heightwidth縮寫為hw ,以使其更短、更清晰,但為了清楚起見,您仍希望使用全名來調用它:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

這種靈活性對於函數式編程也更有用,在函數式編程中,可以任意轉換函數,而原始參數名稱會丟失。

還有另一種方法。 如果您通過引用傳遞對象,則該對象的屬性將出現在函數的本地范圍內。 我知道這適用於 Safari(尚未檢查其他瀏覽器)並且我不知道此功能是否有名稱,但下面的示例說明了它的用法。

盡管在實踐中我認為這不會提供超出您已經使用的技術的任何功能價值,但它在語義上更清晰一些。 它仍然需要傳遞對象引用或對象字面量。

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));

注意。 如評論中所述,我對 2016 年的回答不正確且具有誤導性。

嘗試 Node-6.4.0 (process.versions.v8 = '5.0.71.60') 和 Node Chakracore-v7.0.0-pre8 然后 Chrome-52 (V8=5.2.361.49),我注意到命名參數幾乎已實施,但該命令仍具有優先權。 我找不到 ECMA 標准所說的內容。

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

但是需要下單!! 這是標准行為嗎?

> f(b=10)
a=10 + b=2 = 12

來自 Python 這讓我很煩。 我為節點編寫了一個簡單的包裝器/代理,它將接受位置對象和關鍵字對象。

https://github.com/vinces1979/node-def/blob/master/README.md

這無疑是偽代碼,但我相信它會起作用(我知道它在 Typescript 中起作用;我正在將它用於 JavaScript)

// Target Function
const myFunc = (a=1,b=2,c=3) => {a+b+c}

// Goal usage:
myFunc(a=5, b=6) // 14
myFunc(c=0) // 3

// set your defaults
const myFuncDefaults = {a:1, b:2, c:3};

// override them with passed params
const myFuncParams = (params) => { return Object.assign(myFuncDefaults, params)}


// use the overloaded dict as the input
const myFunc2 = (params) => {
  let {a, b, c} = myFuncParams(params);
  return myFunc(a, b, c)
}

// Usage:
myFunc({a:5, b:6}) // 14
myFunc({c:0}) // 3

// Written more succinctly:
const myFunc = (params) => {
  let {a,b,c} = Object.assign({a:1, b:2, c:3}, params)
  return a + b + c
}

FWIW Typescript 通過提示使這種方式變得很好:

interface IParams {
  a: number;
  b: number;
  c: number;
}

const myFunc = (params: Partial<IParams>): number => {
  const default: IParams = {a:1, b:2, c:3};
  let {a, b, c} = Object.assign(default, params)
  return a + b + c
}

的,嗯,有點。 我找到了 2 個解決方案。 我只解釋一個。

在這個解決方案中,我們放棄了位置參數。

我們可以使用一個對象(幾乎與python 中的 dict相同)來傳遞參數。

在此示例中,我使用該函數生成圖像文件的名稱

//first we define our function with just ONE argument
function name_of_img(img_desc){

  // with this step, any undefined value will be assigned a value
  if(img_desc.size == undefined) {img_desc.size = "400x500"}
  if(img_desc.format == undefined) {img_desc.format = ".png"}

  console.log(img_desc.size + img_desc.format)
}

//notice inside our function we're passing a dict/object
    
name_of_img({size: "200x250", format=".jpg"})
// returns "200x250.jpg"

name_of_img({size: "1200x950"})
// returns "1200x950.png"

我們可以修改這個例子,所以我們也可以使用位置參數,我們也可以修改它,所以可以傳遞無效的參數,我想我會為此做一個 github 存儲庫

與通常認為的相反,命名參數可以通過簡單、整潔的編碼約定在標准的老式 JavaScript(僅用於布爾參數)中實現,如下所示。

function f(p1=true, p2=false) {
    ...
}

f(!!"p1"==false, !!"p2"==true); // call f(p1=false, p2=true)

注意事項:

  • 必須保留參數的順序 - 但該模式仍然有用,因為它使哪個實際參數適用於哪個形式參數很明顯,而無需 grep 函數簽名或使用 IDE。

  • 這僅適用於布爾值。 但是,我確信可以使用 JavaScript 獨特的類型強制語義為其他類型開發類似的模式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM