Как мне наследовать функции JavaScript? - PullRequest
47 голосов
/ 24 сентября 2011
// Don't break the function prototype.
// pd - https://github.com/Raynos/pd
var proto = Object.create(Function.prototype, pd({
  "prop": 42
}));

var f = function() { return "is a function"; };
f.__proto__ = proto;

console.log(f.hasOwnProperty("prop")); // false
console.log(f.prop); // 42
console.log(f()); // "is a function"

.__proto__ нестандартно и устарело.

Как я должен наследовать прототип, создавая объект, но имея этот объект как функцию.

Object.create возвращает объект, а не функцию.

new Constructor возвращает объект, а не функцию.

Мотивация: - Кросс-браузер finherit

var finherit = function (parent, child) {
    var f = function() { 
        parent.apply(this, arguments);
        child.apply(this, arguments);
    };
    f.__proto__ = parent;
    Object.keys(child).forEach(function _copy(key) {
        f[key] = child[key];
    });
    return f;
};

Я не думаю, что это возможно, поэтому мы, вероятно, должны предложить Function.create списку рассылки es-обсудить

/*
  Creates a new function whose prototype is proto.
  The function body is the same as the function fbody.
  The hash of propertydescriptors props is passed to defineproperties just like
  Object.create does.
*/
Function.create = (function() {
  var functionBody = function _getFunctionBody(f) {
    return f.toString().replace(/.+\{/, "").replace(/\}$/, "");
  };
  var letters = "abcdefghijklmnopqrstuvwxyz".split("");

  return function _create(proto, fbody, props) {
    var parameters = letters.slice(0, fbody.length);
    parameters.push(functionBody(fbody));
    var f = Function.apply(this, parameters);
    f.__proto__ = proto;
    Object.defineProperties(f, props);
    return f;
  };
})();

Связанные с нами сообщения для обсуждения

Как уже упоминалось в обсуждении es, существует ES: strawman <| оператор-прототип, который позволил бы это.

Посмотрим, как это будет выглядеть, используя <|

var f1 = function () {
  console.log("do things");
};

f1.method = function() { return 42; };

var f2 = f1 <| function () {
  super();
  console.log("do more things");
}
console.log(f1.isPrototypeOf(f2)); // true
console.log(f2()); // do things do more things
console.log(f2.hasOwnProperty("method")); // false
console.log(f2.method()); // 42

Ответы [ 2 ]

9 голосов
/ 14 октября 2011

Я надеюсь, что я правильно понимаю.

Я полагаю, вам нужен функтор, который одновременно является экземпляром предопределенного прототипа (да, класса, но не классического класса), а также может вызываться напрямую? Правильно? Если это так, то это имеет смысл и является очень мощным и гибким (особенно в сильно асинхронной среде, такой как JavaScript). К сожалению, нет способа сделать это элегантно в JavaScript без манипулирования __proto__. Вы можете сделать это, выделив анонимную функцию и скопировав все ссылки на все методы (что, по-видимому, является направлением, которым вы руководили), чтобы действовать как прокси-класс. Недостатками этого являются ...

  1. Это очень дорого с точки зрения времени выполнения.
  2. (functorObj instanceof MyClass) никогда не будет true.
  3. Свойства не будут доступны напрямую (если бы они были все назначены по ссылке, это была бы другая история, но примитивы назначались по значению). Это может быть решено с помощью методов доступа через defineProperty или просто с помощью именованных методов доступа, если необходимо (кажется, это то, что вы ищете, просто добавьте все свойства к функтору с помощью defineProperty через методы получения / установки вместо просто функций, если вам не нужна поддержка кросс-движка / обратная совместимость).
  4. Скорее всего, вы столкнетесь с крайними случаями, когда конечные собственные прототипы (такие как Object.prototype или Array.prototype [если вы наследуете это]) могут работать не так, как ожидалось.
  5. вызов functorObj(someArg) будет всегда сделает контекст this объектом, независимо от того, вызывается ли он functorObj.call(someOtherObj, someArg) (хотя это не относится к вызовам методов)
  6. Поскольку объект functor создается во время запроса, он будет заблокирован во времени, и манипулирование исходным прототипом не повлияет на выделенные объекты functor, как это будет затронуто для обычного объекта (изменение MyClass.prototype не повлияет на объекты functor и верно и обратное).

Если вы используете его осторожно, ничего из этого не должно иметь большого значения.

В вашем прототипе вашего класса определите что-то вроде ...

// This is you're emulated "overloaded" call() operator.
MyClass.prototype.execute = function() {
   alert('I have been called like a function but have (semi-)proper access to this!');
};

MyClass.prototype.asFunctor = function(/* templateFunction */) {
   if ((typeof arguments[0] !== 'function') && (typeof this.execute !== 'function'))
      throw new TypeError('You really should define the calling operator for a functor shouldn\'t you?');
   // This is both the resulting functor proxy object as well as the proxy call function
   var res = function() {
      var ret;
      if (res.templateFunction !== null)
         // the this context here could be res.asObject, or res, or whatever your goal is here
         ret = res.templateFunction.call(this, arguments);
      if (typeof res.asObject.execute === 'function')
         ret = res.asObject.execute.apply(res.asObject, arguments);
      return ret;
   };
   res.asObject = this;
   res.templateFunction = (typeof arguments[0] === 'function') ? arguments[0] : null;
   for (var k in this) {
      if (typeof this[k] === 'function') {
         res[k] = (function(reference) {
            var m = function() {
               return m.proxyReference.apply((this === res) ? res.asObject : this, arguments);
            };
            m.proxyReference = reference;
            return m;
         })(this.asObject[k]);
      }
   }
   return res;
};

Результирующее использование будет выглядеть примерно так ...

var aobj = new MyClass();
var afunctor = aobj.asFunctor();
aobj.someMethodOfMine(); // << works
afunctor.someMethodOfMine(); // << works exactly like the previous call (including the this context).
afunctor('hello'); // << works by calling aobj.execute('hello');

(aobj instanceof MyClass) // << true
(afunctor instanceof MyClass) // << false
(afunctor.asObject === aobj) // << true

// to bind with a previous function...
var afunctor = (new MyClass()).asFunctor(function() { alert('I am the original call'); });
afunctor() // << first calls the original, then execute();
// To simply wrap a previous function, don't define execute() in the prototype.

Вы могли бы даже связать цепь бесчисленными другими объектами / функциями / и т.д., пока коровы не вернутся домой. Просто немного измени рефакторинг вызова прокси.

Надеюсь, это поможет. Да, и, конечно, вы можете изменить фабричный поток так, чтобы конструктор, вызываемый без оператора new, затем создавал новый объект и возвращал объект функтор. Однако вы предпочитаете (вы могли бы сделать это и другими способами).

Наконец, чтобы любая функция стала оператором выполнения для функтора более элегантным способом, просто сделайте прокси-функцию методом Function.prototype и передайте ей объект для переноса, если вы хотите сделать что-то вроде (вам нужно поменять местами templateFunction с this и this с аргументом, конечно) ...

var functor = (function() { /* something */ }).asFunctor(aobj);
0 голосов
/ 23 февраля 2018

С ES6 можно наследовать от Function, см. (Дублировать) вопрос

класс javascript наследуется от класса Function

default export Attribute extends Function {
...
}
...