Apakah ada cara untuk memberikan parameter bernama dalam pemanggilan fungsi dalam JavaScript?

207

Saya menemukan fitur parameter bernama di C # cukup berguna dalam beberapa kasus.

calculateBMI(70, height: 175);

Apa yang dapat saya gunakan jika saya menginginkan ini dalam JavaScript?


Yang tidak saya inginkan adalah ini:

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
}

Pendekatan itu sudah saya gunakan. Apakah ada cara lain?

Saya baik-baik saja menggunakan perpustakaan apa pun untuk melakukan ini.

Robin Maben
sumber
Saya tidak berpikir ini mungkin, tetapi Anda dapat mencoba untuk meletakkan beberapa undefined di tempat kosong. Itu sangat buruk. Gunakan objeknya, itu bagus.
Vladislav Qulin
14
Tidak, JavaScript / EcmaScript tidak mendukung parameter bernama. Maaf.
smilly92
1
Saya sudah tahu itu. Terima kasih. Saya sedang mencari cara yang melibatkan tweaker apa yang Functionbisa dilakukan di javascript.
Robin Maben
1
Yang ada Functiondi Javascript tidak dapat mengubah sintaksis inti Javascript
Gareth
2
Saya tidak berpikir Javascript mendukung fitur ini. Saya pikir yang paling dekat dengan parameter bernama adalah (1) tambahkan komentar calculateBMI(70, /*height:*/ 175);, (2) berikan objek calculateBMI(70, {height: 175}), atau (3) gunakan konstanta const height = 175; calculateBMI(70, height);.
tfmontague

Jawaban:

212

ES2015 dan yang lebih baru

Dalam ES2015, perusakan parameter dapat digunakan untuk mensimulasikan parameter bernama. Ini akan meminta penelepon untuk melewati objek, tetapi Anda dapat menghindari semua pemeriksaan di dalam fungsi jika Anda juga menggunakan parameter default:

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

Ada cara untuk mendekati apa yang Anda inginkan, tetapi didasarkan pada output Function.prototype.toString [ES5] , yang implementasinya tergantung pada tingkat tertentu, sehingga mungkin tidak kompatibel dengan browser.

Idenya adalah untuk mem-parsing nama parameter dari representasi string dari fungsi sehingga Anda dapat mengaitkan properti suatu objek dengan parameter yang sesuai.

Panggilan fungsi kemudian bisa terlihat seperti

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

di mana adan badalah argumen posisi dan argumen terakhir adalah objek dengan argumen bernama.

Sebagai contoh:

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);
        };
    };
}());

Yang akan Anda gunakan sebagai:

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 

DEMO

Ada beberapa kelemahan dari pendekatan ini (Anda telah diperingatkan!):

  • Jika argumen terakhir adalah objek, itu diperlakukan sebagai "objek argumen bernama"
  • Anda akan selalu mendapatkan argumen sebanyak yang Anda tetapkan dalam fungsi, tetapi beberapa dari mereka mungkin memiliki nilai undefined(yang berbeda dari tidak memiliki nilai sama sekali). Itu berarti Anda tidak dapat menggunakan arguments.lengthuntuk menguji berapa banyak argumen yang telah diajukan.

Alih-alih memiliki fungsi membuat wrapper, Anda juga bisa memiliki fungsi yang menerima fungsi dan berbagai nilai sebagai argumen, seperti

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

atau bahkan meluas Function.prototypesehingga Anda bisa melakukan:

foo.execute(a, b, {posArg: ...});
Felix Kling
sumber
Ya ... di sini adalah contoh untuk itu: jsfiddle.net/9U328/1 (meskipun Anda sebaiknya menggunakan Object.definePropertydan mengatur enumerableke false). Seseorang harus selalu berhati-hati ketika memperluas benda-benda asli. Seluruh pendekatan terasa agak berantakan, jadi saya tidak akan berharap itu berfungsi sekarang dan selamanya;)
Felix Kling
Dicatat. Saya akan mulai menggunakan ini. Menandai sebagai jawaban! ... Untuk saat ini;)
Robin Maben
1
Nitpick sangat kecil: Saya tidak berpikir pendekatan ini akan menangkap EcmaScript 6 Arrow Functions . Bukan masalah besar saat ini, tetapi mungkin layak disebutkan di bagian peringatan dari jawaban Anda.
NobodyMan
3
@NobodyMan: Benar. Saya menulis jawaban ini sebelum fungsi panah adalah suatu hal. Dalam ES6, saya benar-benar akan menggunakan destructive parameter.
Felix Kling
1
Pada masalah undefinedvs "tidak ada nilai", dapat ditambahkan bahwa ini adalah cara kerja nilai default fungsi JS - memperlakukan undefinedsebagai nilai yang hilang.
Dmitri Zaitsev
71

Tidak - pendekatan objek adalah jawaban JavaScript untuk ini. Tidak ada masalah dengan ini asalkan fungsi Anda mengharapkan objek daripada params terpisah.

Mitya
sumber
35
@RobertMaben - Jawaban untuk pertanyaan spesifik yang diajukan adalah bahwa tidak ada cara asli untuk mengumpulkan vars atau fungsi yang dideklarasikan tanpa mengetahui mereka tinggal di namespace tertentu. Hanya karena jawabannya pendek, itu tidak meniadakan kesesuaiannya sebagai jawaban - tidakkah Anda setuju? Ada jawaban yang jauh lebih pendek di luar sana, di sepanjang baris "tidak, tidak mungkin". Pendek mungkin, tetapi mereka juga jawaban untuk pertanyaan itu.
Mitya
3
Saat ini ada es6: 2ality.com/2011/11/keyword-parameters.html
slacktracer
1
Ini jelas merupakan jawaban yang adil - 'parameter bernama' adalah fitur bahasa. Pendekatan objek adalah hal terbaik berikutnya tanpa adanya fitur tersebut.
fatuhoku
32

Masalah ini telah membuat saya kesal hewan peliharaan selama beberapa waktu. Saya seorang programmer berpengalaman dengan banyak bahasa di bawah ikat pinggang saya. Salah satu bahasa favorit saya yang senang saya gunakan adalah Python. Python mendukung parameter bernama tanpa tipuan .... Karena saya mulai menggunakan Python (beberapa waktu lalu) semuanya menjadi lebih mudah. Saya percaya bahwa setiap bahasa harus mendukung parameter bernama, tetapi itu tidak terjadi.

Banyak orang mengatakan untuk hanya menggunakan trik "Lulus objek" sehingga Anda telah menetapkan parameter.

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

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

Dan itu bekerja dengan baik, kecuali ..... ketika datang ke sebagian besar IDE di luar sana, banyak dari kita pengembang bergantung pada tipe / petunjuk petunjuk dalam IDE kami. Saya pribadi menggunakan PHP Storm (Bersama dengan JetBrains IDE lainnya seperti PyCharm untuk python dan AppCode untuk Objective C)

Dan masalah terbesar dengan menggunakan trik "Lulus objek" adalah bahwa ketika Anda memanggil fungsi, IDE memberi Anda petunjuk jenis tunggal dan hanya itu ... Bagaimana kita bisa tahu parameter dan tipe apa yang harus dimasukkan ke dalam objek arg1?

Saya tidak tahu parameter apa yang harus masuk dalam arg1

Jadi ... trik "Lewati sebuah objek" tidak berfungsi untuk saya ... Itu sebenarnya menyebabkan lebih banyak sakit kepala karena harus melihat setiap fungsi docblock sebelum saya tahu parameter apa yang diharapkan fungsi tersebut .... Tentu, itu bagus untuk ketika Anda mempertahankan kode yang ada, tetapi mengerikan untuk menulis kode baru.

Nah, ini adalah teknik yang saya gunakan .... Sekarang, mungkin ada beberapa masalah dengan itu, dan beberapa pengembang mungkin mengatakan saya salah, dan saya memiliki pikiran terbuka mengenai hal-hal ini ... Saya selalu ingin melihat cara yang lebih baik untuk menyelesaikan tugas ... Jadi, jika ada masalah dengan teknik ini, maka komentar dipersilahkan.

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

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

Dengan cara ini, saya memiliki yang terbaik dari kedua dunia ... kode baru mudah untuk ditulis karena IDE saya memberi saya semua argumen argumen yang tepat ... Dan, sambil mempertahankan kode nanti, saya bisa melihat sekilas, tidak hanya nilai diteruskan ke fungsi, tetapi juga nama argumen. Satu-satunya overhead yang saya lihat adalah mendeklarasikan nama argumen Anda sebagai variabel lokal agar tidak mencemari namespace global. Tentu, ini sedikit mengetik ekstra, tetapi sepele dibandingkan dengan waktu yang diperlukan untuk mencari dokumen saat menulis kode baru atau mempertahankan kode yang ada.

Sekarang, saya memiliki semua parameter dan tipe saat membuat kode baru

Ray Perea
sumber
2
Satu-satunya hal dengan teknik ini adalah kenyataan bahwa Anda tidak dapat mengubah urutan parameter ... Saya pribadi baik-baik saja dengan itu.
Ray Perea
14
Sepertinya ini hanya meminta masalah ketika beberapa pengelola masa depan datang dan berpikir mereka dapat mengubah urutan argumen (tapi jelas tidak bisa).
tidak ada yang
1
@AndrewMedico Saya setuju ... sepertinya Anda bisa mengubah urutan argumen seperti di Python. Satu-satunya hal yang bisa saya katakan tentang itu adalah mereka akan mengetahui dengan cepat ketika mengubah urutan argumen merusak program.
Ray Perea
7
Saya berpendapat bahwa myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');itu lebih baik daripada myFunc(arg1='Param1', arg2='Param2');, karena itu tidak memiliki kesempatan untuk menipu pembaca daripada yang terjadi argumen bernama
Eric
3
Pola yang diajukan tidak ada hubungannya dengan argumen bernama, urutan masih penting, nama tidak perlu tetap sinkron dengan parameter aktual, namespace dicemari dengan variabel yang tidak perlu dan hubungan tersirat yang tidak ada.
Dmitri Zaitsev
25

Jika Anda ingin memperjelas masing-masing parameter, bukan hanya menelepon

someFunction(70, 115);

mengapa tidak melakukan yang berikut ini

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

tentu saja, ini merupakan baris kode tambahan, tetapi menang pada keterbacaan.

dav_i
sumber
5
+1 untuk mengikuti prinsip KISS, plus itu juga membantu dengan debugging. Namun, saya pikir masing-masing var harus pada jalurnya sendiri, walaupun dengan sedikit kinerja hit ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
clairestreb
21
Ini bukan hanya tentang baris kode tambahan, tetapi juga tentang urutan argumen dan menjadikannya opsional. Jadi Anda bisa menulis ini dengan parameter bernama:, someFunction(height: 115);tetapi jika Anda menulis someFunction(height);Anda sebenarnya sedang mengatur lebar.
Francisco Presencia
Dalam hal ini, CoffeeScript mendukung argumen bernama. Ini akan membiarkan Anda menulis adil someFunction(width = 70, height = 115);. Variabel dideklarasikan di bagian atas ruang lingkup saat ini dalam kode JavaScript yang dihasilkan.
Audun Olsen
6

Cara lain adalah dengan menggunakan atribut dari objek yang sesuai, misalnya seperti:

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);

Tentu saja untuk contoh yang dibuat ini agak tidak berarti. Tetapi dalam kasus-kasus di mana fungsinya terlihat

do_something(some_connection_handle, some_context_parameter, some_value)

mungkin lebih bermanfaat. Itu juga dapat dikombinasikan dengan ide "parameterfy" untuk membuat objek seperti itu dari fungsi yang ada dengan cara yang umum. Itu untuk setiap parameter itu akan membuat anggota yang dapat mengevaluasi ke versi fungsi yang dievaluasi parsial.

Ide ini tentu saja terkait dengan Schonfinkeling alias Currying.

Udo Klein
sumber
Ini adalah ide yang rapi, dan menjadi lebih baik jika Anda memadukannya dengan trik introspeksi argumen. Sayangnya, itu gagal bekerja dengan argumen opsional
Eric
2

Ada cara lain. Jika Anda melewati objek dengan referensi, properti objek itu akan muncul di lingkup lokal fungsi. Saya tahu ini berfungsi untuk Safari (belum memeriksa peramban lain) dan saya tidak tahu apakah fitur ini memiliki nama, tetapi contoh di bawah ini menggambarkan penggunaannya.

Meskipun dalam praktiknya saya tidak berpikir bahwa ini menawarkan nilai fungsional di luar teknik yang sudah Anda gunakan, ini sedikit lebih bersih secara semantik. Dan itu masih membutuhkan melewati referensi objek atau objek literal.

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));
Allen Ellison
sumber
2

Mencoba Node-6.4.0 (process.versions.v8 = '5.0.71.60') dan Node Chakracore-v7.0.0-pre8 dan kemudian Chrome-52 (V8 = 5.2.361.49), saya perhatikan bahwa parameter bernama hampir diimplementasikan, tetapi urutan itu masih diutamakan. Saya tidak dapat menemukan apa yang dikatakan standar 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

Tapi pesanan diperlukan !! Apakah itu perilaku standar?

> f(b=10)
a=10 + b=2 = 12
jgran
sumber
10
Itu tidak melakukan apa yang Anda pikirkan. Hasil dari b=10ekspresi adalah 10, dan itulah yang diteruskan ke fungsi. f(bla=10)juga akan bekerja (ini memberikan 10 ke variabel bladan meneruskan nilai ke fungsi setelahnya).
Matthias Kestenholz
1
Ini juga menciptakan adan bvariabel dalam lingkup global sebagai efek samping.
Gershom
2

Fungsi panggilan fdengan parameter bernama dilewatkan sebagai objek

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

pada dasarnya memanggil komposisinya di f(...g(o))mana saya menggunakan sintaks spread dang merupakan "mengikat" peta yang menghubungkan nilai objek dengan posisi parameter mereka.

Peta pengikat justru merupakan bahan yang hilang, yang bisa direpresentasikan dengan susunan kuncinya:

// 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}))

Perhatikan tiga bahan yang dipisahkan: fungsi, objek dengan argumen bernama dan pengikatan. Decoupling ini memungkinkan banyak fleksibilitas untuk menggunakan konstruksi ini, di mana pengikatan dapat disesuaikan secara sewenang-wenang dalam definisi fungsi dan diperpanjang secara sewenang-wenang pada waktu pemanggilan fungsi.

Misalnya, Anda mungkin ingin menyingkat heightdan widthsebagai hdan wdi dalam definisi fungsi Anda, untuk membuatnya lebih pendek dan lebih bersih, sementara Anda masih ingin menyebutnya dengan nama lengkap untuk kejelasan:

// 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})

Fleksibilitas ini juga lebih berguna untuk pemrograman fungsional, di mana fungsi dapat diubah secara sewenang-wenang dengan nama param aslinya hilang.

Dmitri Zaitsev
sumber
0

Berasal dari Python, ini menyadap saya. Saya menulis pembungkus sederhana / Proxy untuk node yang akan menerima objek posisi dan kata kunci.

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

Vince Spicer
sumber
Jika saya mengerti dengan benar, solusi Anda perlu membedakan posisi dan nama parameter dalam definisi fungsi, yang berarti saya tidak memiliki kebebasan untuk membuat pilihan ini pada saat panggilan.
Dmitri Zaitsev
@DmitriZaitsev: Di komunitas Python (dengan argumen kata kunci menjadi fitur sehari-hari), kami menganggapnya sebagai ide yang sangat baik untuk dapat memaksa pengguna menentukan argumen opsional dengan kata kunci; ini membantu menghindari kesalahan.
Tobias
@Tobias Memaksa pengguna melakukannya di JS adalah masalah terpecahkan:, di f(x, opt)mana optobjek. Apakah itu membantu untuk menghindari atau membuat kesalahan (seperti yang disebabkan oleh kesalahan ejaan dan rasa sakit untuk mengingat nama kata kunci) tetap menjadi pertanyaan.
Dmitri Zaitsev
@DmitriZaitsev: Dalam Python ini secara ketat menghindari kesalahan, karena (tentu saja) argumen kata kunci adalah fitur bahasa inti di sana. Untuk argumen hanya kata kunci , Python 3 memiliki sintaks khusus (sedangkan dalam Python 2 Anda akan memunculkan kunci dari kwargs diksi satu per satu dan akhirnya menaikkan TypeErrorjika ada kunci yang tidak diketahui yang tersisa). Anda f(x, opt)solusi memungkinkan para ffungsi untuk melakukan sesuatu seperti di Python 2, tetapi Anda akan perlu untuk menangani semua nilai default sendiri.
Tobias
@Tobias Apakah proposal ini relevan untuk JS? Tampaknya menggambarkan bagaimana f(x, opt)sudah berfungsi, sementara saya tidak melihat bagaimana ini membantu menjawab pertanyaan, di mana misalnya Anda ingin melakukan keduanya request(url)dan request({url: url})itu tidak akan mungkin dilakukan dengan membuat urlparameter hanya kata kunci.
Dmitri Zaitsev
-1

Berlawanan dengan apa yang umumnya diyakini, parameter bernama dapat diimplementasikan dalam JavaScript standar, old-school (hanya untuk parameter boolean) melalui konvensi pengkodean yang rapi dan sederhana, seperti yang ditunjukkan di bawah ini.

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

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

Peringatan:

  • Urutan argumen harus dipertahankan - tetapi polanya masih berguna, karena memperjelas argumen aktual mana yang dimaksudkan untuk parameter formal mana tanpa harus memahami fungsi tanda tangan atau menggunakan IDE.

  • Ini hanya berfungsi untuk boolean. Namun, saya yakin pola yang sama dapat dikembangkan untuk jenis lain menggunakan semantik jenis paksaan JavaScript.

pengguna234461
sumber
Anda masih mengacu pada posisi, bukan dengan nama di sini.
Dmitri Zaitsev
@ DmitriZaitsev ya, saya bahkan mengatakan demikian di atas. Namun, tujuan dari argumen yang disebutkan adalah untuk memperjelas bagi pembaca apa arti setiap argumen; ini adalah bentuk dokumentasi. Solusi saya memungkinkan dokumentasi untuk tertanam dalam panggilan fungsi tanpa menggunakan komentar, yang terlihat tidak rapi.
user234461
Ini memecahkan masalah yang berbeda. Pertanyaannya adalah tentang heightmenyebutkan nama terlepas dari urutan param.
Dmitri Zaitsev
@ DmitriZaitsev sebenarnya, pertanyaannya tidak mengatakan apa-apa tentang urutan parameter, atau mengapa OP ingin menggunakan params bernama.
user234461
Itu dengan merujuk ke C # di mana melewati params dengan nama dalam urutan apa pun adalah kuncinya.
Dmitri Zaitsev