Как определить, вызывается ли функция как конструктор? - PullRequest
101 голосов
/ 15 декабря 2008

С учетом функции:

function x(arg) { return 30; }

Вы можете назвать это двумя способами:

result = x(4);
result = new x(4);

Первое возвращает 30, второе возвращает объект.

Как определить способ вызова функции внутри самой функции ?

Каким бы ни было ваше решение, оно также должно работать со следующим вызовом:

var Z = new x(); 
Z.lolol = x; 
Z.lolol();

Все решения в настоящее время думают, что Z.lolol() вызывает его как конструктор.

Ответы [ 21 ]

2 голосов
/ 16 сентября 2010

От Джона Резига:

function makecls() {

   return function(args) {

        if( this instanceof arguments.callee) {
            if ( typeof this.init == "function")
                this.init.apply(this, args.callee ? args : arguments)
        }else{
            return new arguments.callee(args);
        }
    };
}

var User = makecls();

User.prototype.init = function(first, last){

    this.name = first + last;
};

var user = User("John", "Resig");

user.name
2 голосов
/ 28 июля 2016

Если вы собираетесь взломать, то instanceof - это минимальное решение после new.target, как и в других ответах. Но при использовании решения instanceof в этом примере произойдет сбой:

let inst = new x;
x.call(inst);

В сочетании с решением @TimDown вы можете использовать ES6 WeakSet, если вам нужна совместимость со старыми версиями ECMAScript, чтобы предотвратить помещение свойств внутри экземпляров. Хорошо, WeakSet будет использоваться для того, чтобы разрешить сбор неиспользуемых объектов. new.target не будет совместимым в том же исходном коде, так как это синтаксическая функция ES6. ECMAScript указывает, что идентификаторы не могут быть одним из зарезервированных слов, и, в любом случае, new не является объектом.

(function factory()
{
    'use strict';
    var log = console.log;

    function x()
    {
        log(isConstructing(this) ?
            'Constructing' :
            'Not constructing'
        );
    }

    var isConstructing, tracks;
    var hasOwnProperty = {}.hasOwnProperty;

    if (typeof WeakMap === 'function')
    {
        tracks = new WeakSet;
        isConstructing = function(inst)
        {
            if (inst instanceof x)
            {
                return tracks.has(inst) ?
                    false : !!tracks.add(inst);
            }
            return false;
        }
    } else {
        isConstructing = function(inst)
        {
            return inst._constructed ?
                false : inst._constructed = true;
        };
    }
    var z = new x; // Constructing
    x.call(z)      // Not constructing
})();

Оператор ECMAScript 3 instanceof имеет значение , указанное как:

11.8.6 Оператор instanceof
--- Производится выражение RelationalExpression: выражение RelationalExpression instanceof ShiftExpression следующим образом:
--- 1. Оцените RelationalExpression.
--- 2. Вызвать GetValue (Результат (1)).
--- 3. Оценить ShiftExpression.
--- 4. Вызвать GetValue (Результат (3)).
--- 5. Если Result (4) не является объектом, выведите исключение TypeError .
--- 6. Если в Результате (4) нет метода [[HasInstance]], выведите исключение TypeError .
--- 7. Вызвать метод [[HasInstance]] Результата (4) с параметром Результат (2).
--- 8. Вернуть результат (7).
15.3.5.3 [[HasInstance]] (V)
--- Предположим, что F является объектом Function.
--- Когда метод [[HasInstance]] для F вызывается со значением V, предпринимаются следующие шаги:
--- 1. Если V не является объектом, вернуть false .
--- 2. Вызвать метод [[Get]] для F с именем свойства "prototype" .
--- 3. Пусть O будет результатом (2).
--- 4. Если O не является объектом, выдать исключение TypeError .
--- 5. Пусть V будет значением свойства [[Prototype]] для V.
--- 6. Если V ** ноль **, вернуть false .
--- 7. Если O и V ссылаются на один и тот же объект или если они ссылаются на объекты, соединенные друг с другом (13.1.2), верните true .
--- 8. Перейти к шагу 5.

И это означает, что оно будет возвращать значение левой стороны после перехода к его прототипу, пока оно не станет объектом или пока оно не станет равным прототипу объекта правой стороны с указанным методом [[HasInstance]]. Что означает, что он проверит, является ли левая сторона экземпляром правой стороны, хотя и потребляет все внутренние прототипы левой стороны.

function x() {
    if (this instanceof x) {
        /* Probably invoked as constructor */
    } else return 30;
}
2 голосов
/ 06 августа 2009

В моем тесте на http://packagesinjavascript.wordpress.com/ я обнаружил, что тест (если это окно ==) работает во всех случаях кросс-браузерно, так что это тот, который я в итоге использовал.

-Stijn

1 голос
/ 15 ноября 2013

возможно я ошибаюсь, но (за счет паразита) следующий код кажется решением:

function x(arg) {
    //console.debug('_' in this ? 'function' : 'constructor'); //WRONG!!!
    //
    // RIGHT(as accepted)
    console.debug((this instanceof x && !('_' in this)) ? 'function' : 'constructor');
    this._ = 1;
    return 30;
}
var result1 = x(4),     // function
    result2 = new x(4), // constructor
    Z = new x();        // constructor
Z.lolol = x; 
Z.lolol();              // function
0 голосов
/ 15 февраля 2019

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

function Car() {

    if (!(this instanceof Car)) return new Car();

    this.a = 1;
    console.log("Called as Constructor");

}
let c1 = new Car();
console.log(c1);
0 голосов
/ 13 марта 2018

Хотя этот поток является древним, я удивлен, что никто не упомянул, что в строгом режиме ('use strict') значение функции по умолчанию this не определено, вместо того, чтобы установить значение global / window как раньше, чтобы проверить, не используется ли new, просто проверьте значение falsey, равное !this - EG:

function ctor() { 'use strict';
  if (typeof this === 'undefined') 
    console.log('Function called under strict mode (this == undefined)');
  else if (this == (window || global))
    console.log('Function called normally (this == window)');
  else if (this instanceof ctor)
    console.log('Function called with new (this == instance)');
  return this; 
}

Если вы протестируете эту функцию как есть, вы получите неопределенное значение this из-за директивы 'use strict' в начале функции. Конечно, если уже включен строгий режим, он не изменится, если вы удалите директиву 'use strict', но в противном случае, если вы удалите ее, значение this будет установлено на window или global. Если вы используете new для вызова функции, тогда значение this будет соответствовать проверке instanceof (хотя, если вы проверили другие вещи, то instance является последним вариантом, поэтому эта проверка не нужна, и ее следует избегать, если вы хотите Все равно наследовать экземпляры)

function ctor() { 'use strict';
  if (!this) return ctor.apply(Object.create(ctor.prototype), arguments);
  console.log([this].concat([].slice.call(arguments)));
  return this;
}

Это записывает значение this и все аргументы, которые вы передаете функции в консоль, и возвращает значение this. Если значение this равно falsey, то он создает новый экземпляр с использованием Object.create(ctor.prototype) и использует Function.apply() для повторного вызова конструктора с теми же параметрами, но с правильным экземпляром, как this. Если значение this отличается от falsey, то предполагается, что оно является допустимым экземпляром и возвращается.

0 голосов
/ 17 июля 2015
function createConstructor(func) {
    return func.bind(Object.create(null));
}

var myClass = createConstructor(function myClass() {
    if (this instanceof myClass) {
        console.log('You used the "new" keyword');
    } else {
        console.log('You did NOT use the "new" keyword');
        return;
    }
    // constructor logic here
    // ...
});
0 голосов
/ 09 декабря 2009

Используйте this instanceof arguments.callee (возможно, заменив arguments.callee на функцию, в которой он находится, что повышает производительность), чтобы проверить, вызывается ли что-то как конструктор. Не используйте this.constructor, так как это можно легко изменить.

0 голосов
/ 31 августа 2013

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

Кажется, достаточно проверить наличие «this» в начале вашей функции:

function RGB(red, green, blue) {
    if (this) {
        throw new Error("RGB can't be instantiated");
    }

    var result = "#";
    result += toHex(red);
    result += toHex(green);
    result += toHex(blue);

    function toHex(dec) {
        var result = dec.toString(16);

        if (result.length < 2) {
            result = "0" + result;
        }

        return result;
    }

    return result;
}

В любом случае, в конце концов я просто решил превратить свой псевдокласс RGB () в функцию rgb (), так что я просто не буду пытаться создавать его экземпляр, поэтому вообще не нужно проверять безопасность. Но это будет зависеть от того, что вы пытаетесь сделать.

0 голосов
/ 11 сентября 2012

Если вы не хотите помещать свойство __previouslyConstructedByX в объект - поскольку оно загрязняет открытый интерфейс объекта и может быть легко перезаписано - просто не возвращайте экземпляр x:

function x() {

    if(this instanceof x) {
        console.log("You invoked the new keyword!");
        return that;
    }
    else {
        console.log("No new keyword");
        return undefined;
    }

}

x();
var Z = new x(); 
Z.lolol = x; 
Z.lolol();
new Z.lolol();

Теперь функция x никогда не возвращает объект типа x, поэтому (я думаю) this instanceof x принимает значение true только тогда, когда функция вызывается с ключевым словом new.

Недостатком является то, что это эффективно портит поведение instanceof - но в зависимости от того, насколько вы его используете (я не склонен), это не может быть проблемой.


Если ваша цель состоит в том, чтобы в обоих случаях возвратить 30, вы можете вернуть экземпляр Number вместо экземпляра x:

function x() {

    if(this instanceof x) {
        console.log("You invoked the new keyword!");
        var that = {};
        return new Number(30);
    }
    else {
        console.log("No new");
        return 30;
    }

}

console.log(x());
var Z = new x();
console.log(Z);
Z.lolol = x;
console.log(Z.lolol());
console.log(new Z.lolol());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...