Як зв’язати аргументи функції, не зв’язуючи це?


115

Як у Javascript можна прив’язати аргументи до функції без прив'язки thisпараметра?

Наприклад:

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

Як я можу уникнути побічного ефекту від необхідності також прив’язувати область функції (наприклад, встановлення this= null)?

Редагувати:

Вибачте за непорозуміння. Я хочу прив’язати аргументи, а потім зможу викликати зв'язану функцію пізніше і змусити її поводитись так, як ніби я викликав початкову функцію і передав їй зв'язані аргументи:

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?

Я все ще нова в цьому, вибачте за плутанину.

Дякую!


1
Чи було б недостатньо просто повторно зв’язати this? var b = c.bind(this, 1,2,3);
kojiro

Чому першим значенням bind()бути проблематично null? Здається, добре працює у ФФ.
Русс

7
Якщо функція вже має цю межу, використання null в якості першого аргументу прив'язки зіпсує речі (тобто прив’яже метод об'єкта до глобальної області).
Імбуе

1
Мені дійсно незрозуміло, що ти намагаєшся зробити. Ви не можете thisповністю розв'язати JavaScript. Це завжди щось означає . Тому прив'язка thisдо функції, thisщо вкладається, має багато сенсу.
Кідро

Я досить новий в JS. Я відредагував це питання, щоб зробити його більш зрозумілим. Можливо, те, що я хочу, неможливо. Дякую.
Імбуе

Відповіді:


32

Ви можете це зробити, але найкраще уникати думки про це як "обов'язкове", оскільки це термін, який використовується для встановлення значення "це". Можливо, ви думаєте про це як "вкручування" аргументів у функцію?

Що ви робите, це створити функцію, в яку вбудовані потрібні аргументи через закриття:

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

Сподіваюся, що я отримав синтаксис саме там. Для цього потрібно створити функцію, яку називають withWrappedArguments () (щоб бути педантичною, це анонімна функція, присвоєна змінній), яку ви можете викликати будь-коли в будь-якому місці і завжди буде діяти з фактичнимArg1Value та фактичнимArg2Value, і будь-чим іншим, що ви хочете ввести в там. Ви також можете прийняти додаткові аргументи під час дзвінка, якщо хочете. Секрет - це круглі дужки після фінальної дужки закриття. Вони спричиняють негайне виконання зовнішньої функції із переданими значеннями та генерування внутрішньої функції, яку можна викликати пізніше. Потім передані значення заморожуються в момент створення функції.

Це ефективно те, що пов'язує, але таким чином явно, що обкладені аргументи - це просто закриття локальних змінних, і не потрібно змінювати поведінку цього.


16
Зауважте, що це зазвичай називається "просівання".
M3D

@ M3D Капіталізація, що робить це так дивно.
Андрій

Це було б набагато корисніше, якби навести приклад того, як його використовувати.
Андрій

en.wikipedia.org/wiki/Присутність посилання для всіх, хто зацікавлений, хоча версія TLDR - "Замість того, щоб f(x,y)ми хотіли f(x)(y)або g=f(x); g(y). Тож ми б змінилися f=(x,y)=>x+yна f=(x)=>(y)=>x+y".
M3D

26

У ES6 це легко зробити за допомогою параметрів спокою спільно з оператором спред .

Таким чином, ми можемо визначити функцію, bindArgsяка працює як bind, за винятком того, що пов'язані лише аргументи, але не контекст ( this).

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

Потім для визначеної функції fooта об'єкта objвисловлювання

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

еквівалентно

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

де пов'язані лише перший та другий аргументи bar, тоді як використовується контекст, objзазначений у виклику, та додаткові аргументи додаються після пов'язаних аргументів. Повернене значення просто пересилається.


11
приносить es6 до столу, все ще використовує var :(
Даніель Кобе

7
@DanielKobe Дякую! Оператори Поширення і відпочинок PARAMS були реалізовані в Firefox задовго до того, letі const, які не були доступні ще до того часу , я перший відправив це. Зараз оновлено.
GOTO 0

Це чудово і працює для функцій зворотного дзвінка! Також можна змінити навколо, щоб додати аргументи, а не додати їх.
DenisM

1
Варто зауважити, що це створює закриття щоразу, коли викликається bindArgs (), що означає, що продуктивність не настільки хороша, як використання нативного bind () функціональності.
Джошуа Уолш

17

У рідному bindметодіthis значення у функції результату втрачається. Однак ви можете легко перекодувати загальну шим, щоб не використовувати аргумент для контексту:

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
Не подобається той факт, що вам доведеться грати з прототипом, Functionале це врятувало мені багато часу та деякі потворні обходи. Спасибі
jackdbernier

3
@jackdbernier: Вам не доведеться, але я вважаю це більш інтуїтивно зрозумілим як такий метод bind. Ви можете легко перетворити його наfunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
Бергі

@ Qantas94Heavy: Це те, що ти завжди повинен робити, коли ти хочеш thisбути a. Ви можете використовувати bindзамість цього, звичайно. Тим не менш, ОП явно не піклується про thisзначення і хотіла би незв'язану часткову функцію.
Бергі

@Bergi: Я припускав, що ціль не встановлення thisполягає в тому, що функція використовує this, але в той момент вони не хотіли прив'язувати її. Також біт у питанні ОП //These should both have exact same output.суперечить іншим його частинам.
Qantas 94 Важкий

1
@KubaWyrostek: Так, це змусити часткові функції конструктора працювати, начебто як підкласифікація (не те, що я рекомендую). Фактичний bindвзагалі не працює з конструкторами, пов'язаних функцій немає .prototype. Ні,Function.prototype !== mymethod.prototype
Бергі

7

Ще одна крихітна реалізація просто для розваги:

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

Як використовувати:

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

Здається, було б правильніше пропустити "null" для цього при виклику apply ()
eddiewould

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

3
У всіх цих галасливих і неоднозначних відповідях це точна і корисна відповідь. Для цього +1. Можливо, ви могли б вдосконалити стиль відповіді, щоб зробити його привабливішим неозброєним оком у тому вигадливому галасливому решті джунглів. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Джоханнес

2

Трохи важко точно сказати, що ви в кінцевому підсумку хочете зробити, тому що приклад є довільним, але ви, можливо, захочете розібратися у сторонах (або домовитись): 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(){})

Посилання на статтю Джона Ресіга: http://ejohn.org/blog/partial-functions-in-javascript/


Ця функція не працює, частково застосовану функцію ви не можете викликати більше одного разу.
Бергі

Не впевнений, що я слідую. Чи можете ви трохи розібратися в цьому чи опублікувати приклад jsbin?
Кевін Енніс

Дивіться цей приклад . Крім того, вам partialпотрібно викликати undefinedаргументи для роботи - не те, що можна було б очікувати, а розбитий на функції, які приймають довільну кількість параметрів.
Бергі

Кевін, дякую за вашу відповідь. Це близько до того, що я хочу, але я думаю, що thisоб’єкт все-таки псується. Погляньте на: jsbin.com/ifoqoj/4/edit
Посадити

Ох, готчя. Цей приклад робить набагато зрозумілішим те, що ви шукаєте.
Кевін Енніс

2

Можливо, ви хочете зв'язати посилання на це в останньому, але ваш код: -

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

Наприклад, уже застосовано прив'язку, наприклад, це та пізніше ви не можете змінити. Що я запропонував би використовувати посилання також як параметр на зразок цього: -

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

Використовуйте 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);
}

В основному, функція, якій ви хочете передати парами, стає проксі-сервером, який ви можете викликати для передачі змінної, і вона повертає функцію, якою ви хочете виконати завдання.

Сподіваюся, це допомагає!


1

Анонімний користувач опублікував цю додаткову інформацію:

Спираючись на те, що вже було запропоновано в цій публікації - найелегантніше рішення, яке я бачив, - це перекрити свої аргументи та контекст:

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

Ну для прикладу, який ви дали, це зробить

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

Якщо ви хочете гарантувати охоплення параметрів:

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)

Але якщо так, то просто слід дотримуватися свого рішення:

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

Це кращий спосіб.


1

Простий такий?

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

Більш загальне рішення:

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

За допомогою LoDash ви можете використовувати _.partialфункцію.

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

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

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

0

Чому б не використовувати обгортку навколо функції, щоб зберегти це як міф?

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 приніс саме цю функцію з функцією проксі.

Як і в jQuery 1.9, коли контекст є нульовим або невизначеним, функція proxied буде викликана тим самим об'єктом, з яким був викликаний проксі. Це дозволяє використовувати $ .proxy () для часткового застосування аргументів функції без зміни контексту.

Приклад:

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

-5

Випадок використання Jquery:

натомість:

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

використовуй це:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.