Bagaimana cara mengikat argumen fungsi tanpa mengikat ini?

115

Dalam Javascript, bagaimana cara mengikat argumen ke suatu fungsi tanpa mengikat thisparameter?

Sebagai contoh:

//Example function.
var c = function(a, b, c, callback) {};

//Bind values 1, 2, and 3 to a, b, and c, leave callback unbound.
var b = c.bind(null, 1, 2, 3); //How can I do this without binding scope?

Bagaimana saya bisa menghindari efek samping karena harus mengikat ruang lingkup fungsi (misalnya, pengaturan this= null) juga?

Edit:

Maaf bila membingungkan. Saya ingin mengikat argumen, kemudian dapat memanggil fungsi terikat nanti dan membuatnya berperilaku persis seperti jika saya memanggil fungsi asli dan meneruskannya ke argumen terikat:

var x = 'outside object';

var obj = {
  x: 'inside object',
  c: function(a, b, c, callback) {
    console.log(this.x);
  }
};

var b = obj.c.bind(null, 1, 2, 3);

//These should both have exact same output.
obj.c(1, 2, 3, function(){});
b(function(){});

//The following works, but I was hoping there was a better way:
var b = obj.c.bind(obj, 1, 2, 3); //Anyway to make it work without typing obj twice?

Saya masih baru dalam hal ini, maaf atas kebingungannya.

Terima kasih!

Mengilhami
sumber
1
Apakah tidak cukup hanya mengikat kembali this? var b = c.bind(this, 1,2,3);
kojiro
Mengapa sulit untuk memiliki nilai pertama dari bind()be null? Sepertinya bekerja dengan baik di FF.
Russ
7
Jika fungsi sudah memiliki batasan ini, menggunakan null sebagai argumen pertama dari bind akan mengacaukan segalanya (yaitu mengikat metode objek ke lingkup global).
Imbue
1
Saya benar-benar tidak jelas apa yang Anda coba lakukan. Anda tidak boleh thissepenuhnya melepaskan JavaScript. Itu selalu berarti sesuatu . Jadi mengikat thiske thisfungsi penutup sangat masuk akal.
kojiro
Saya cukup baru di JS. Saya mengedit pertanyaan untuk membuatnya lebih jelas. Mungkin apa yang saya inginkan tidak mungkin. Terima kasih.
Mulai

Jawaban:

32

Anda dapat melakukan ini, tetapi sebaiknya hindari menganggapnya sebagai "mengikat" karena itulah istilah yang digunakan untuk menetapkan nilai "ini". Mungkin menganggapnya sebagai "membungkus" argumen menjadi suatu fungsi?

Apa yang Anda lakukan adalah membuat fungsi yang memiliki argumen yang diinginkan dibangun di dalamnya melalui closures:

var withWrappedArguments = function(arg1, arg2)
    {
        return function() { ... do your stuff with arg1 and arg2 ... };
    }(actualArg1Value, actualArg2Value);

Semoga saya mendapatkan sintaks di sana. Apa yang dilakukannya adalah membuat fungsi yang disebut withWrappedArguments () (menjadi pedantic, ini adalah fungsi anonim yang ditetapkan ke variabel) yang dapat Anda panggil kapan saja di mana saja dan akan selalu bertindak dengan actualArg1Value dan actualArg2Value, dan apa pun yang ingin Anda masukkan sana. Anda juga dapat membuatnya menerima argumen lebih lanjut pada saat menelepon jika Anda mau. Rahasianya adalah tanda kurung setelah kurung kurawal tutup. Ini menyebabkan fungsi luar segera dieksekusi, dengan nilai yang diteruskan, dan menghasilkan fungsi dalam yang bisa dipanggil nanti. Nilai yang diteruskan kemudian dibekukan pada saat fungsi tersebut dibuat.

Inilah yang secara efektif dilakukan bind, tetapi dengan cara ini secara eksplisit bahwa argumen yang dibungkus hanyalah penutupan pada variabel lokal, dan tidak perlu mengubah perilaku ini.

Ian Nartowicz
sumber
16
Perhatikan bahwa ini biasanya disebut "Currying".
M3D
@ M3D Menggunakan huruf besar yang membuatnya terlihat sangat aneh.
Andrew
Ini akan jauh lebih membantu jika diberikan contoh tentang cara menggunakannya.
Andrew
en.wikipedia.org/wiki/Mengirimkan tautan untuk siapa saja yang tertarik, meskipun versi TLDRnya adalah "Daripada f(x,y)kami ingin f(x)(y)atau g=f(x); g(y). Jadi kami akan mengubahnya f=(x,y)=>x+ymenjadi f=(x)=>(y)=>x+y."
M3D
26

Di ES6, ini mudah dilakukan dengan menggunakan parameter istirahat sehubungan dengan operator sebaran .

Jadi kita bisa mendefinisikan fungsi bindArgsyang berfungsi seperti itu bind, kecuali hanya argumen yang terikat, tetapi tidak konteksnya ( this).

Function.prototype.bindArgs =
    function (...boundArgs)
    {
        const targetFunction = this;
        return function (...args) { return targetFunction.call(this, ...boundArgs, ...args); };
    };

Kemudian, untuk fungsi foodan objek tertentu obj, pernyataan

return foo.call(obj, 1, 2, 3, 4);

setara dengan

let bar = foo.bindArgs(1, 2);
return bar.call(obj, 3, 4);

di mana hanya argumen pertama dan kedua yang terikat bar, sementara konteks yang objditentukan dalam pemanggilan digunakan dan argumen tambahan ditambahkan setelah argumen terikat. Nilai pengembalian hanya diteruskan.

GOTO 0
sumber
11
membawa es6 ke meja, masih menggunakan var :(
Daniel Kobe
7
@DanielKobe Terima kasih! Operator penyebaran dan parameter lainnya telah diterapkan di Firefox jauh sebelumnya letdan const, yang belum tersedia pada saat saya pertama kali memposting ini. Ini diperbarui sekarang.
GOTO 0
Ini bagus dan berfungsi untuk fungsi panggilan balik! Dapat juga diubah untuk menambahkan argumen daripada menambahkannya.
DenisM
1
Perlu dicatat bahwa ini membuat penutupan setiap kali bindArgs () dipanggil, yang berarti kinerjanya tidak sebaik menggunakan fungsionalitas bind () asli.
Joshua Walsh
17

Dalam bindmetode asli , thisnilai dalam fungsi hasil hilang. Namun, Anda dapat dengan mudah mengode ulang shim umum untuk tidak menggunakan argumen untuk konteksnya:

Function.prototype.arg = function() {
    if (typeof this !== "function")
        throw new TypeError("Function.prototype.arg needs to be called on a function");
    var slice = Array.prototype.slice,
        args = slice.call(arguments), 
        fn = this, 
        partial = function() {
            return fn.apply(this, args.concat(slice.call(arguments)));
//                          ^^^^
        };
    partial.prototype = Object.create(this.prototype);
    return partial;
};
Bergi
sumber
4
Tidak suka fakta bahwa Anda harus bermain-main dengan prototipe Functiontetapi itu menghemat banyak waktu dan beberapa solusi buruk. Terima kasih
jackdbernier
3
@jackdbernier: Anda tidak harus melakukannya, tetapi menurut saya metode ini lebih intuitif bind. Anda dapat dengan mudah mengubahnya menjadifunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
Bergi
@ Qantas94Heavy: Itulah yang selalu harus Anda lakukan saat Anda thisingin melakukannya a. Anda bisa menggunakan bindsebagai gantinya, tentu saja. Namun, OP secara eksplisit tidak peduli dengan thisnilai dan menginginkan fungsi parsial yang tidak terikat.
Bergi
@Bergi: Saya berasumsi bahwa tujuan dari tidak mengatur thisadalah fungsi yang digunakan this, tetapi mereka tidak ingin mengikatnya pada saat itu. Juga, bagian dalam pertanyaan OP itu //These should both have exact same output.kontradiktif dengan bagian lain darinya.
Qantas 94 Heavy
1
@KubaWyrostek: Ya, itu untuk membuat fungsi konstruktor parsial bekerja, agak seperti subclassing (meskipun saya tidak merekomendasikan ini). Aktual bindtidak berfungsi sama sekali dengan konstruktor, fungsi terikat tidak memiliki .prototype. Tidak,Function.prototype !== mymethod.prototype
Bergi
7

Satu lagi penerapan kecil hanya untuk kesenangan:

function bindWithoutThis(cb) {
    var bindArgs = Array.prototype.slice.call(arguments, 1);

    return function () {
        var internalArgs = Array.prototype.slice.call(arguments, 0);
        var args = Array.prototype.concat(bindArgs, internalArgs);
        return cb.apply(this, args);
    };
}

Cara Penggunaan:

function onWriteEnd(evt) {}
var myPersonalWriteEnd = bindWithoutThis(onWriteEnd, "some", "data");
Dmitrii Sorin
sumber
Sepertinya akan lebih tepat untuk memberikan "null" untuk ini saat menjalankan apply ()
eddiewould
6
var b = function() {
    return c(1,2,3);
};
mike
sumber
3
Dalam semua jawaban yang berisik dan ambigu ini, ini adalah jawaban yang tepat dan bermanfaat. Untuk itu +1. Mungkin Anda bisa memperhalus gaya jawaban untuk membuatnya lebih menarik bagi mata telanjang di sisa hutan yang bising dan mewah itu. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Joehannes
2

Agak sulit untuk mengatakan dengan tepat apa yang akhirnya ingin Anda lakukan karena contohnya agak sewenang-wenang, tetapi Anda mungkin ingin melihat sebagian (atau kari): http://jsbin.com/ifoqoj/1/edit

Function.prototype.partial = function(){
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function(){
    var arg = 0;
    for ( var i = 0; i < args.length && arg < arguments.length; i++ )
      if ( args[i] === undefined )
        args[i] = arguments[arg++];
    return fn.apply(this, args);
  };
};

var c = function(a, b, c, callback) {
  console.log( a, b, c, callback )
};

var b = c.partial(1, 2, 3, undefined);

b(function(){})

Tautan ke artikel John Resig: http://ejohn.org/blog/p Partial-functions-in-javascript/

Kevin Ennis
sumber
Fungsi ini tidak berfungsi, Anda tidak dapat memanggil fungsi yang diterapkan sebagian lebih dari sekali.
Bergi
Tidak yakin saya ikuti. Bisakah Anda menjelaskannya sedikit atau memposting contoh jsbin?
Kevin Ennis
Lihat contoh ini . Selain itu, partialkebutuhan Anda untuk dipanggil dengan undefinedargumen agar berfungsi - bukan yang diharapkan, dan rusak pada fungsi yang mengambil sejumlah parameter sewenang-wenang.
Bergi
Kevin, terima kasih atas jawaban Anda. Ini mendekati apa yang saya inginkan, tapi saya pikir thisobjeknya masih kacau balau. Lihat di: jsbin.com/ifoqoj/4/edit
Imbue
Ohhh, Gotchya. Contoh tersebut memperjelas apa yang Anda cari.
Kevin Ennis
2

Mungkin Anda ingin mengikat referensi ini di bagian terakhir tetapi kode Anda: -

var c = function(a, b, c, callback) {};
var b = c.bind(null, 1, 2, 3); 

Sudah diterapkan pengikatan misalnya ini dan nanti Anda tidak dapat mengubahnya. Apa yang akan saya sarankan agar gunakan referensi juga sebagai parameter seperti ini: -

var c = function(a, b, c, callback, ref) {  
    var self = this ? this : ref; 
    // Now you can use self just like this in your code 
};
var b = c.bind(null, 1, 2, 3),
    newRef = this, // or ref whatever you want to apply inside function c()
    d = c.bind(callback, newRef);
Deepak Dixit
sumber
1

Gunakan protagonist!

var geoOpts = {...};

function geoSuccess(user){  // protagonizes for 'user'
  return function Success(pos){
    if(!pos || !pos.coords || !pos.coords.latitude || !pos.coords.longitude){ throw new Error('Geolocation Error: insufficient data.'); }

    var data = {pos.coords: pos.coords, ...};

    // now we have a callback we can turn into an object. implementation can use 'this' inside callback
    if(user){
      user.prototype = data;
      user.prototype.watch = watchUser;
      thus.User = (new user(data));
      console.log('thus.User', thus, thus.User);
    }
  }
}

function geoError(errorCallback){  // protagonizes for 'errorCallback'
  return function(err){
    console.log('@DECLINED', err);
    errorCallback && errorCallback(err);
  }
}

function getUserPos(user, error, opts){
  nav.geo.getPos(geoSuccess(user), geoError(error), opts || geoOpts);
}

Pada dasarnya, fungsi yang ingin Anda sampaikan params menjadi proxy yang dapat Anda panggil untuk meneruskan variabel, dan mengembalikan fungsi yang sebenarnya ingin Anda lakukan.

Semoga ini membantu!

Cody
sumber
1

Seorang pengguna anonim memposting info tambahan ini:

Membangun dari apa yang telah disediakan dalam posting ini - solusi paling elegan yang pernah saya lihat adalah Curry argumen dan konteks Anda:

function Class(a, b, c, d){
    console.log('@Class #this', this, a, b, c, d);
}

function Context(name){
    console.log('@Context', this, name);
    this.name = name;
}

var context1 = new Context('One');
var context2 = new Context('Two');

function curryArguments(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function bindContext() {
      var additional = Array.prototype.slice.call(arguments, 0);
      return fn.apply(this, args.concat(additional));
    };
}

var bindContext = curryArguments(Class, 'A', 'B');

bindContext.apply(context1, ['C', 'D']);
bindContext.apply(context2, ['Y', 'Z']);
Barett
sumber
1

Nah untuk contoh yang Anda berikan, ini akan berhasil

var b= function(callback){
        return obj.c(1,2,3, callback);
};

Jika Anda ingin menjamin parameter penutup:

var b= (function(p1,p2,p3, obj){
        var c=obj.c;
        return function(callback){
                return c.call(obj,p1,p2,p3, callback);
        }
})(1,2,3,obj)

Tetapi jika demikian, Anda harus tetap berpegang pada solusi Anda:

var b = obj.c.bind(obj, 1, 2, 3);

Itu cara yang lebih baik.

Joseph Garrone
sumber
1

Sederhana seperti itu?

var b = (cb) => obj.c(1,2,3, cb)
b(function(){}) // insidde object

Solusi yang lebih umum:

function original(a, b, c) { console.log(a, b, c) }
let tied = (...args) => original(1, 2, ...args)

original(1,2,3) // 1 2 3
tied(5,6,7) // 1 2 5

Qwerty
sumber
1

Menggunakan LoDash Anda dapat menggunakan _.partialfungsi tersebut.

const f  = function (a, b, c, callback) {}

const pf = _.partial(f, 1, 2, 3)  // f has first 3 arguments bound.

pf(function () {})                // callback.
Daniele Orlando
sumber
0

Mengapa tidak menggunakan pembungkus di sekitar fungsi untuk menyimpan ini sebagai mitos?

function mythis() {
  this.name = "mythis";
  mythis = this;
  function c(a, b) {
    this.name = "original";
    alert('a=' + a + ' b =' + b + 'this = ' + this.name + ' mythis = ' + mythis.name);
    return "ok";
  }    
  return {
    c: c
  }
};

var retval = mythis().c(0, 1);
Olivier Archer
sumber
-1

jQuery 1.9 membawa fitur itu dengan fungsi proxy.

Pada jQuery 1.9, ketika konteksnya null atau tidak ditentukan, fungsi yang diproksikan akan dipanggil dengan objek yang sama dengan yang dipanggil dengan proxy. Ini memungkinkan $ .proxy () digunakan untuk menerapkan sebagian argumen dari suatu fungsi tanpa mengubah konteksnya.

Contoh:

$.proxy(this.myFunction, 
        undefined /* leaving the context empty */, 
        [precededArg1, precededArg2]);
Gosua
sumber
-5

Kasus penggunaan Jquery:

sebagai gantinya:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(i){
        $(this).val(i); // wont work, because 'this' becomes 'i'
    }.bind(i));
}

Gunakan ini:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}

sumber