Шаблон для применения нового в javascript
Я читал книгу шаблонов JavaScript Stoyan Stefanov, и один из шаблонов для принудительного применения нового оператора для функций конструктора выглядит следующим образом
function Waffle() {
if (!(this instanceof Waffle)) {
return new Waffle();
}
this.tastes = "yummy";
}
Waffle.prototype.wantAnother = true;
при написании этого способа вы можете вызвать Waffle либо один из этих способов.
var first = new Waffle(),
second = Waffle();
Я думаю, что это полезная функция, не уверенная в том, что она реализована в будущих версиях ecma/javascript
Я придумал что-то само по себе, которое, как я думал, может просто копировать и вставлять каждый раз при создании функции-конструктора
что-то вроде этого
function checkInstance (name) {
if (name.constructor.name === undefined) {
return "construct it"
} else {
return false;
}
}
function Waffle() {
var _self = checkInstance.call(this, this);
if (_self === "construct it") {
return new Waffle()
}
this.tastes = "yummy"
}
var waffle = Waffle()
waffle
Поэтому я могу вызвать Waffle в любом случае с помощью новой Waffle или Waffle() и все равно вернуть объект
Моя проблема, что у меня есть, здесь
if (_self === "construct it") {
return new Waffle()
}
В любом случае, я могу ссылаться на new Waffle()
, не ссылаясь на фактическое имя функции конструктора, поэтому я мог бы копировать и вставлять это каждый раз и ничего не менять. Значение Я мог бы сохранить Waffle() как переменную и сделать что-то вроде
return new var
Я бы хотел использовать this.name, но это не работает либо до его вызова.
У меня есть чувство, что я не могу не хотеть, по крайней мере, спросить некоторых людей здесь о переполнении стека, если это была возможность
Снова ваши комментарии и отзывы приветствуются
Ответы
Ответ 1
У меня лучшее решение. Это то, что вы сейчас делаете:
function Waffle() {
if (!(this instanceof Waffle))
return new Waffle;
this.tastes = "yummy";
}
Waffle.prototype.wantAnother = true;
Этот шаблон не очень приятен, потому что вы смешиваете код для создания нового объекта с кодом, чтобы проверить, используется ли ключевое слово new
.
Я уже упоминал, что вы не следует использовать ключевое слово new
в JavaScript, поскольку он нарушает функциональные возможности. Вместо этого создайте другую функцию, которая делает то же самое:
Function.prototype.new = (function () {
return function () {
functor.prototype = this.prototype;
return new functor(this, arguments);
};
function functor(constructor, args) {
return constructor.apply(this, args);
}
}());
Эта функция позволяет вам создать экземпляр функции следующим образом:
var waffle = Waffle.new();
Однако мы вообще не хотим использовать new
. Поэтому, чтобы покончить с этим, мы создадим функцию, которая обертывает конструктор следующим образом:
function constructible(constructor) {
function functor() { return Function.new.apply(constructor, arguments); }
functor.prototype = constructor.prototype;
return functor;
}
Теперь мы можем определить функцию Waffle
следующим образом:
var Waffle = constructible(function () {
this.tastes = "yummy";
});
Waffle.prototype.wantAnother = true;
Теперь вы можете создавать объекты с использованием или без использования new
:
var first = new Waffle;
var second = Waffle();
Примечание. Функция constructible
выполняется довольно медленно. Вместо этого используйте следующую версию constructible
- это немного быстрее:
function constructible(constructor) {
constructor = Function.bind.bind(constructor, null);
function functor() { return new (constructor.apply(null, arguments)); }
functor.prototype = constructor.prototype;
return functor;
}
Лично я бы не использовал ни один из этих двух методов. Я бы просто забыл написать new
, или (более вероятно), я бы реструктурировал свой код следующим образом:
var waffle = {
create: function () {
var waffle = Object.create(this);
waffle.tastes = "yummy";
return waffle;
},
wantAnother: true
};
var first = waffle.create();
var second = waffle.create();
Если вы хотите узнать больше об этом шаблоне, прочитайте следующий ответ: fooobar.com/questions/17945/...
Ответ 2
Вы можете использовать что-то вроде этого:
var Waffle = (function() {
function Waffle() {
this.tastes = "yummy"
}
return exportCtor( Waffle );
})();
var waffle = Waffle();
alert(waffle.tastes);
console.log(Waffle);
/*
function ConstructorProxy() {
"use strict";
return new Constructor();
}
*/
http://jsfiddle.net/ywQJF/
Он также обрабатывает переменные аргументы
Ответ 3
arguments.callee
, который относится к текущей функции, является самым простым решением. Однако он не рекомендуется, поэтому используйте его на свой страх и риск.
function Waffle() {
if (!(this instanceof arguments.callee))
return new arguments.callee();
this.tastes = 'yummy';
}
Это тяжелая проблема, потому что вы, вероятно, хотите сохранить аргументы, которые вы передаете, как упоминал Винотбабу. Но если вы действительно намерены использовать new
, вы можете просто выбросить ошибку, которая представляет собой простую две строки кода:
if (!(this instanceof Waffle))
throw new Error('Constructor called without new');
Вы можете даже обернуть его в функцию:
function cons(C) {
var c = function () {
if (!(this instanceof c))
throw new Error('Constructor called without new');
C.apply(this, arguments);
};
c.prototype = C.prototype;
return c;
}
var Waffle = cons(function () {
this.tastes = 'yummy';
});
Waffle.prototype.wantAnother = function () {
return true;
};
new Waffle(); // { tastes: 'yummy', 'wantAnother': true }
Waffle(); // throws error
Теперь Waffle
должен быть вызван с помощью new
- в противном случае он выдает ошибку.
Ответ 4
Существует более простой способ принудительного создания нового объекта даже без new
:
function Waffle() {
return {tastes:"yummy"};
}
var a = Waffle();
var b = new Waffle();
alert(a.tastes); // yummy
alert(b.tastes); // yummy
Объяснение
Используя new
с функцией, есть две возможности:
- функция возвращает объект: объект является результатом выражения
new function()
- функция не возвращает объект: возвращается сама функция с новым контекстом
См. документацию ECMA script
Обходной путь: прототип и аргументы
function Waffle(taste,how) {
return {
tastes: taste+" "+how,
__proto__: Waffle.prototype
}
}
Waffle.prototype.wantmore = "yes";
var a = Waffle("yummy","much");
var b = new Waffle("gummy","little");
console.log(a.tastes,b.tastes); // yummy much, gummy little
console.log(a.wantmore,b.wantmore); // yes, yes
Это заслуживает скрипку.
Примечание. constructor.name
(который вы использовали в своем шаблоне) нестандартно
Примечание 2: __proto__
также не является стандартным, но поддерживается современными браузерами и будет стандартизоваться на ES6.
Ответ 5
Лучший подход, на мой взгляд, заключается не в том, чтобы позволить себе неправильно ссылаться на вещи:
function Waffle() {
if (!(this instanceof Waffle)) {
throw "Waffles need to be fresh or they're gross. Use 'new'.";
}
}
Но если вы просто должны позволить себе писать непоследовательный код, сделайте инициализацию отдельным шагом.
function Waffle(options) {
var o = options || {};
if (this instanceof Waffle) {
this.init = function() {
/* this is really your constructor */
console.log("initializing ... ");
}
if (!o.__do_not_initialize) {
this.init(arguments);
}
} else {
var rv = new Waffle( { __do_not_initialize: true } );
rv.init(arguments);
return rv;
}
}
Если вы хотите форсировать согласованность другим способом - никогда не используйте ключевое слово new
, создайте функцию построения:
function BuildWaffle(options) {
var o = options || {};
if (this instanceof WaffleBuilder) {
throw "BuildWaffle cannot be instantiated.";
}
var Waffle = function Waffle() { /* whatever */ }
Waffle.prototype.doStuff = function() { /* whatever else */ }
var rv = new Waffle(options);
return rv;
}
Ответ 6
if (!(this instanceof Waffle)) {
return new Waffle();
}
У этого есть две проблемы...
- что он не будет работать в анонимной функции, которая не имеет имени
- он теряет все аргументы, отправленные конструктору.
Использование более общего подхода может выглядеть примерно так:
if (!instanceExists(this, arguments)) {
return requireInstance(this, arguments);
}
Этот подход гарантирует, что constructor
вызывается с помощью new
, без необходимости state the function'
s name
и adds all arguments sent to the constuctor so they aren 't lost during the process
.
Здесь полный код для вышеперечисленного:
Function.prototype.callNew = function (args) {
var a = [];
for (var i = 0; i < args.length; i++) a.push("a[" + i + "]");
var fn = new Function("var a=arguments;return new this(" + a.join(",") + ");");
return fn.apply(this, args);
}
function instanceExists(t, args) {
if (t instanceof args.callee) {
return true;
} else {
return false;
}
}
function requireInstance(t, args) {
var fn = args.callee;
if (!instanceExists(t, args)) {
return fn.callNew(args);
}
}
function Waffle(one, two, three) {
if (!instanceExists(this, arguments)) {
return requireInstance(this, arguments);
}
this.one = one;
this.two = two;
this.three = three;
}
Waffle.prototype.serve = function () {
var out = [];
for (var j in this) {
if (!this.hasOwnProperty(j)) continue;
out.push(j + ': ' + this[j]);
}
return ' {
' + out.join(",\n") + '
}
';
}
Сцена для игры.
http://jsfiddle.net/RkPpH/
var waffle = Waffle(1, 2, 3);
alert(waffle.serve());
Ответ 7
Я не понял, что это клиент или сервер, но шаблон, который я использую, иногда выглядит следующим образом. Я использую это в Node, но попытался сделать его возможным клиентским решением, а Node -специальный материал закомментирован, но там для ссылки в зависимости от вашей среды.
Во-первых, я создаю что-то, что можно использовать по строкам традиционной базы OO или суперкласса:
//// Node:
//module.exports.Base = Base;
function Base(opts) {
var self = this;
if (!(self instanceof Base)) return new Base(opts);
self.opts = opts || {};
}
На котором вы можете определить свои методы, в обычном порядке. Вы можете даже вручную выбросить, если метод должен предоставляться подклассами, реализующими нечто вроде абстрактного:
// commonMethod is available to subclasses:
Base.prototype.commonMethod = function () {
var self = this;
//access self.opts to get the constructor arguments.
//makes self always point to the right object.
}
// Provide abstractMethod, but subclass is responsible for implementation:
Base.prototype.abstractMethod = function () {
//or throw an error if this should be implemented by subclasses:
throw new Error('implement me');
}
Теперь вы можете сделать это:
//// If using Node:
//var inherits = require('util').inherits;
//var Parent = require('./Base').Base;
function Sub (opts) {
var self = this;
//// If using node and you want super_ to be called prior to creating a new Sub:
//if(Sub.super_) Sub.super_.call(this, opts);
// Always do this:
if (!(self instanceof Sub)) return new Sub(opts);
//// If using node and you are ok with super_ called after creating new Sub:
//if(Sub.super_) Sub.super_.call(this, opts);
//// otherwise:
parent(opts);
}
//// If using Node:
//inherits(Sub, Base);
//// Otherwise:
Sub.prototype.constructor = Base;
Sub.prototype.parent = Base.prototype;
//and provide the implementation of abstractMethod:
Sub.prototype.abstractMethod() {
//...
}
И чтобы официально ответить на конкретный вопрос, все
if (!(self instanceof Sub)) return new Sub(opts);
где вы получаете гарантированную новую ситуацию.