Làm cách nào để ràng buộc các đối số của hàm mà không ràng buộc điều này?


115

Trong Javascript, làm cách nào để tôi có thể liên kết các đối số với một hàm mà không ràng buộc thistham số?

Ví dụ:

//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?

Làm cách nào để tránh tác dụng phụ của việc phải ràng buộc cả phạm vi của hàm (ví dụ: setting this= null)?

Biên tập:

Xin lỗi vì sự nhầm lẫn. Tôi muốn ràng buộc các đối số, sau đó có thể gọi hàm bị ràng buộc sau đó và nó hoạt động chính xác như thể tôi đã gọi hàm ban đầu và chuyển cho nó các đối số bị ràng buộc:

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?

Tôi vẫn còn mới về điều này, xin lỗi vì sự nhầm lẫn.

Cảm ơn!


1
Sẽ là không đủ nếu chỉ ràng buộc lại this? var b = c.bind(this, 1,2,3);
kojiro 13/12/12

Tại sao lại có vấn đề khi có giá trị đầu tiên bind()là be null? Có vẻ hoạt động tốt trong FF.
Russ

7
Nếu hàm đã có ràng buộc này, việc sử dụng null làm đối số đầu tiên của bind sẽ làm rối tung mọi thứ (tức là ràng buộc một phương thức đối tượng với phạm vi toàn cục).
Imbue

1
Tôi thực sự không rõ bạn đang cố gắng làm gì. Bạn không thể thishoàn toàn không bị ràng buộc trong JavaScript. Nó luôn luôn có nghĩa là một cái gì đó . Vì vậy, liên kết thisvới thishàm bao quanh có rất nhiều ý nghĩa.
kojiro 13/12/12

Tôi khá mới ở JS. Tôi đã chỉnh sửa câu hỏi để làm cho nó rõ ràng hơn. Có thể điều tôi muốn là không thể. Cảm ơn.
Imbue

Câu trả lời:


32

Bạn có thể làm điều này, nhưng tốt nhất nên tránh nghĩ nó là "ràng buộc" vì đó là thuật ngữ được sử dụng để đặt giá trị "này". Có lẽ nghĩ về nó như là "gói" các đối số vào một hàm?

Những gì bạn làm là tạo một hàm có các đối số mong muốn được tích hợp vào nó thông qua các bao đóng:

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

Hy vọng tôi có cú pháp ngay tại đó. Những gì nó làm là tạo một hàm có tên là withWrappedArguments () (nói theo kiểu pedantic thì nó là một hàm ẩn danh được gán cho biến) mà bạn có thể gọi bất kỳ lúc nào ở bất kỳ đâu và sẽ luôn hoạt động với thực tếArg1Value và thực tếArg2Value và bất kỳ thứ gì khác bạn muốn đưa vào ở đó. Bạn cũng có thể yêu cầu nó chấp nhận các đối số khác tại thời điểm cuộc gọi nếu bạn muốn. Bí mật là dấu ngoặc đơn sau dấu ngoặc nhọn cuối cùng. Những điều này làm cho hàm bên ngoài được thực thi ngay lập tức, với các giá trị được truyền vào và tạo ra hàm bên trong có thể được gọi sau này. Các giá trị được truyền sau đó được đóng băng tại thời điểm hàm được tạo.

Đây là cách thực hiện hiệu quả của ràng buộc, nhưng theo cách này rõ ràng là các đối số được bao bọc chỉ đơn giản là các đóng trên các biến cục bộ và không cần phải thay đổi hành vi của điều này.


16
Lưu ý rằng điều này thường được gọi là "Currying".
M3D

@ M3D Viết hoa làm cho nó trông rất lạ.
Andrew

Điều này sẽ hữu ích hơn rất nhiều nếu một ví dụ được cung cấp về cách sử dụng nó.
Andrew

en.wikipedia.org/wiki/ Liên kết đang thử cho bất kỳ ai quan tâm, mặc dù phiên bản TLDR là "Thay vì f(x,y)chúng tôi muốn f(x)(y)hoặc g=f(x); g(y). Vì vậy, chúng tôi sẽ thay đổi f=(x,y)=>x+ythành f=(x)=>(y)=>x+y."
M3D

26

Trong ES6, điều này được thực hiện dễ dàng bằng cách sử dụng các tham số nghỉ kết hợp với toán tử trải rộng .

Vì vậy, chúng ta có thể định nghĩa một hàm bindArgshoạt động như thế nào bind, ngoại trừ việc chỉ có các đối số bị ràng buộc, chứ không phải là ngữ cảnh ( this).

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

Sau đó, đối với một hàm foovà một đối tượng được chỉ định obj, câu lệnh

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

tương đương với

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

trong đó chỉ các đối số thứ nhất và thứ hai được liên kết bar, trong khi ngữ cảnh objđược chỉ định trong lời gọi được sử dụng và các đối số phụ được nối sau các đối số ràng buộc. Giá trị trả về chỉ được chuyển tiếp.


11
mang es6 để bàn, vẫn sử dụng var :(
Daniel Kobe

7
@DanielKobe Cảm ơn! Các toán tử Spread và các tham số còn lại đã được triển khai trong Firefox từ rất lâu trước đó letconstvẫn chưa có sẵn vào thời điểm tôi đăng bài này lần đầu. Nó được cập nhật ngay bây giờ.
GOTO 0

Điều này là tuyệt vời và hoạt động cho các chức năng gọi lại! Cũng có thể được thay đổi xung quanh để nối các args hơn là thêm chúng trước.
DenisM

1
Cần lưu ý rằng điều này tạo ra một bao đóng mỗi khi bindArgs () được gọi, có nghĩa là hiệu suất không tốt bằng việc sử dụng chức năng bind () gốc.
Joshua Walsh

17

Ở bản địa bind phương thức , thisgiá trị trong hàm kết quả bị mất. Tuy nhiên, bạn có thể dễ dàng mã hóa lại miếng đệm thông thường để không sử dụng đối số cho ngữ cảnh:

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

4
Không thích thực tế là bạn phải chơi với nguyên mẫu của Functionnhưng nó đã giúp tôi tiết kiệm rất nhiều thời gian và một số cách giải quyết xấu xí. Cảm ơn
jackdbernier

3
@jackdbernier: Bạn không cần phải làm như vậy, nhưng tôi thấy nó trực quan hơn như một phương pháp bind. Bạn có thể dễ dàng chuyển đổi nó thành mộtfunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
Bergi 19/05

@ Qantas94Heavy: Đó là những gì bạn luôn phải làm khi bạn thismuốn trở thành a. Tất nhiên, bạn có thể sử dụng bindthay thế. Tuy nhiên, OP rõ ràng không quan tâm đến thisgiá trị và muốn một hàm một phần không bị ràng buộc.
Bergi

@Bergi: Tôi đã cho rằng mục đích của việc không thiết lập thislà hàm sử dụng this, nhưng họ không muốn ràng buộc nó vào lúc đó. Ngoài ra, phần trong câu hỏi của OP //These should both have exact same output.mâu thuẫn với các phần khác của nó.
Qantas 94 Heavy

1
@KubaWyrostek: Vâng, nó để làm cho các hàm khởi tạo một phần hoạt động, giống như phân lớp con (mặc dù vậy, tôi không khuyến khích điều này). Một thực tế bindhoàn toàn không hoạt động với các hàm tạo, các hàm ràng buộc không có .prototype. Không,Function.prototype !== mymethod.prototype
Bergi

7

Thêm một triển khai nhỏ nữa chỉ cho vui:

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

Cách sử dụng:

function onWriteEnd(evt) {}
var myPersonalWriteEnd = bindWithoutThis(onWriteEnd, "some", "data");

Có vẻ như sẽ đúng hơn nếu vượt qua "null" cho điều này khi gọi apply ()
eddiewould

6
var b = function() {
    return c(1,2,3);
};

3
Trong tất cả những câu trả lời ồn ào và mơ hồ này, đây là một câu trả lời chính xác và hữu ích. Cho +1. Có thể bạn có thể cải tiến kiểu câu trả lời để làm cho nó hấp dẫn hơn đối với mắt thường trong phần còn lại ồn ào lạ mắt của khu rừng. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Joehannes

2

Hơi khó để nói chính xác cuối cùng bạn muốn làm gì vì ví dụ này hơi tùy ý, nhưng bạn có thể muốn xem xét các thành phần (hoặc currying): 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(){})

Liên kết đến bài viết của John Resig: http://ejohn.org/blog/partial-functions-in-javascript/


Hàm này không hoạt động, bạn không thể gọi một hàm được áp dụng một phần nhiều hơn một lần.
Bergi

Không chắc tôi làm theo. Bạn có thể nói rõ hơn về điều đó một chút hoặc đăng một ví dụ jsbin không?
Kevin Ennis

Hãy xem ví dụ này . Ngoài ra, partialnhu cầu của bạn phải được gọi với các undefinedđối số để hoạt động - không phải những gì người ta mong đợi và bị hỏng trên các hàm có một số lượng tham số tùy ý.
Bergi 13/12/12

Kevin, cảm ơn câu trả lời của bạn. Nó gần với những gì tôi muốn, nhưng tôi nghĩ rằng thisđối tượng vẫn còn lộn xộn. Hãy xem tại: jsbin.com/ifoqoj/4/edit
Imbue

Ohhh, gotchya. Ví dụ đó giúp bạn hiểu rõ hơn những gì bạn đang tìm kiếm.
Kevin Ennis

2

Có thể bạn muốn liên kết tham chiếu cuối cùng của điều này nhưng mã của bạn: -

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

Đã áp dụng ràng buộc cho ví dụ này và sau này bạn không thể thay đổi nó. Những gì tôi sẽ đề xuất rằng sử dụng tham chiếu cũng như một tham số như thế này: -

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

1

Sử dụng a 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);
}

Về cơ bản, hàm bạn muốn truyền các tham số để trở thành một proxy mà bạn có thể gọi để truyền một biến và nó trả về hàm mà bạn thực sự muốn thực hiện.

Hi vọng điêu nay co ich!


1

Một người dùng ẩn danh đã đăng thông tin bổ sung này:

Xây dựng dựa trên những gì đã được cung cấp trong bài đăng này - giải pháp thanh lịch nhất mà tôi đã thấy là Curry lập luận và ngữ cảnh của bạn:

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

1

Đối với ví dụ bạn đã đưa ra, điều này sẽ làm được

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

Nếu bạn muốn bao vây rõ ràng các thông số:

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)

Nhưng nếu vậy bạn chỉ nên kiên trì giải pháp của mình:

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

Đó là cách tốt hơn.


1

Đơn giản như thế?

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

Giải pháp chung hơn:

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


1

Sử dụng LoDash bạn có thể sử dụng _.partialchức năng.

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

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

pf(function () {})                // callback.

0

Tại sao không sử dụng một trình bao bọc xung quanh hàm để lưu nó dưới dạng mythis?

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

-1

jQuery 1.9 mang lại chính xác tính năng đó với chức năng proxy.

Kể từ jQuery 1.9, khi ngữ cảnh là null hoặc không được xác định, hàm proxied sẽ được gọi với đối tượng này giống như đối tượng được gọi với proxy. Điều này cho phép $ .proxy () được sử dụng để áp dụng một phần các đối số của một hàm mà không thay đổi ngữ cảnh.

Thí dụ:

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

-5

Trường hợp sử dụng jquery:

thay thế:

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

dùng cái này:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.