Подтвердить что ты не робот

Как "правильно" создать пользовательский объект в JavaScript?

Интересно, как лучше всего создать объект JavaScript, который имеет свойства и методы.

Я видел примеры, в которых человек использовал var self = this, а затем использует self. во всех функциях, чтобы убедиться, что область всегда правильная.

Затем я видел примеры использования .prototype для добавления свойств, в то время как другие делают это inline.

Может ли кто-нибудь дать мне правильный пример объекта JavaScript с некоторыми свойствами и методами?

4b9b3361

Ответ 1

Существуют две модели для реализации классов и экземпляров в JavaScript: путь прототипирования и способ закрытия. У обоих есть преимущества и недостатки, и существует множество расширенных вариантов. Многие программисты и библиотеки имеют разные подходы и функции полезности для обработки текста для обработки некоторых из более уродливых частей языка.

В результате в смешанной компании у вас будет mishmash из метаклассов, все ведут себя немного по-другому. Что еще хуже, большинство материалов для учебников по JavaScript ужасны и служат для своего рода промежуточным компромиссом, чтобы охватить все базы, оставив вас очень смущенными. (Вероятно, автор тоже смущен. Объектная модель JavaScript очень отличается от большинства языков программирования, а во многих местах плохо спроектирована.)

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

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

Мы можем добавлять методы к экземпляру, созданному new Shape, путем записи их в поиск prototype этой функции конструктора:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Теперь, чтобы подклассировать его, вы можете назвать то, что JavaScript делает подклассом. Мы делаем это, полностью заменив это странное магическое свойство prototype:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

перед добавлением к нему методов:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Этот пример будет работать, и во многих учебниках вы увидите такой код. Но человек, что new Shape() является уродливым: мы создаем базовый класс, хотя никакой реальной формы не должно быть создано. Это происходит в этом простом случае, потому что JavaScript настолько неряшлив: он позволяет передавать нулевые аргументы, в этом случае x и y становятся undefined и назначаются прототипу this.x и this.y, Если функция конструктора делала что-то более сложное, она бы упала на его лице.

Итак, нам нужно найти способ создания объекта-прототипа, который содержит методы и другие члены, которые мы хотим на уровне класса, без вызова функции конструктора базового класса. Для этого нам нужно будет начать писать вспомогательный код. Это самый простой способ, о котором я знаю:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

Это переносит члены базового класса в его прототипе на новую конструкторную функцию, которая ничего не делает, а затем использует этот конструктор. Теперь мы можем просто написать:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

вместо ошибки new Shape(). Теперь у нас есть приемлемый набор примитивов для построенных классов.

Есть несколько уточнений и расширений, которые мы можем рассмотреть в рамках этой модели. Например, здесь представлена ​​версия синтаксического сахара:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

Любая версия имеет недостаток, что функция-конструктор не может быть унаследована, как это происходит на многих языках. Поэтому, даже если ваш подкласс ничего не добавляет к процессу построения, он должен помнить, чтобы вызвать базовый конструктор с любыми аргументами, которые захотели в базе. Это можно слегка автоматизировать с помощью apply, но все же вам нужно записать:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Таким образом, общее расширение состоит в том, чтобы разбить материал инициализации на свою собственную функцию, а не на сам конструктор. Эта функция может затем наследовать от базы просто отлично:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

Теперь у нас есть тот же шаблон конструктора для каждого класса. Возможно, мы можем переместить это в свою собственную вспомогательную функцию, поэтому нам не нужно набирать ее, например, вместо Function.prototype.subclass, поворачивая ее и позволяя базовому классу Function выплевывать подклассы:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

..., который начинает немного напоминать другие языки, хотя и с немного более неуклюжим синтаксисом. Если хотите, вы можете посыпать несколькими дополнительными функциями. Возможно, вы хотите, чтобы makeSubclass взял и запомнил имя класса и предоставил по умолчанию toString его использование. Возможно, вы хотите, чтобы конструктор обнаружил, когда он случайно был вызван без оператора new (что в противном случае часто приводило бы к очень раздражающей отладке):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Возможно, вы хотите передать всем новым членам и makeSubclass добавить их в прототип, чтобы сохранить вам достаточно писать Class.prototype.... Многие системы классов делают это, например:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

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


способ закрытия. Это позволяет избежать проблем наследования на основе прототипов JavaScript, вообще не используя наследование. Вместо этого:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

Теперь каждый отдельный экземпляр Shape будет иметь свою собственную копию метода toString (и любые другие методы или другие члены класса, которые мы добавляем).

Плохая вещь о каждом экземпляре, имеющем собственную копию каждого члена класса, заключается в том, что он менее эффективен. Если вы имеете дело с большим количеством подклассовых экземпляров, прототипное наследование может служить вам лучше. Также вызов метода базового класса немного раздражает, как вы можете видеть: мы должны помнить, что метод был до того, как конструктор подкласса перезаписал его, или он потерялся.

[Также, поскольку здесь нет наследования, оператор instanceof не работает; вы должны будете предоставить свой собственный механизм для обмана, если вам это нужно. Хотя вы могли бы скриптировать объекты прототипа так же, как с наследованием прототипов, это немного сложно, а не стоит просто работать instanceof.]

Хорошо, что каждый экземпляр, имеющий свой собственный метод, заключается в том, что метод может быть привязан к конкретному экземпляру, который его владеет. Это полезно из-за JavaScript странного способа привязки this в вызовах методов, что приводит к тому, что если вы отсоедините метод от своего владельца:

var ts= mycircle.toString;
alert(ts());

тогда this внутри метода не будет экземпляром Circle, как ожидалось (это будет фактически глобальный объект window, что приведет к большому голоду отладки). В действительности это обычно происходит, когда метод принимается и назначается setTimeout, onclick или EventListener вообще.

С прототипом вы должны включить замыкание для каждого такого назначения:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

или, в будущем (или теперь, если вы взломаете Function.prototype), вы также можете сделать это с помощью function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

если ваши экземпляры сделаны способом закрытия, привязка выполняется бесплатно путем замыкания по переменной экземпляра (обычно называемой that или self), хотя лично я бы посоветовал последнему, поскольку self уже имеет другое, другое значение в JavaScript). Вы не получите аргументы 1, 1 в приведенном выше фрагменте бесплатно, поэтому вам потребуется еще одно закрытие или bind(), если вам нужно это сделать.

Существует множество вариантов метода закрытия. Вы можете полностью отказаться от this, создав новый that и вернув его вместо использования оператора new:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

Какой способ "правильный"? И то и другое. Что является "лучшим"? Это зависит от вашей ситуации. FWIW Я склонен к прототипированию для реального наследования JavaScript, когда я делаю сильно OO-материал и закрывает для простых эффектов страницы сбрасывания.

Но оба способа довольно противоречивы большинству программистов. У обоих есть много потенциальных беспорядочных вариаций. Вы будете встречаться с обоими (а также с множеством промежуточных и вообще сломанных схем), если вы используете код/​​библиотеки других людей. Нет общепринятого ответа. Добро пожаловать в прекрасный мир объектов JavaScript.

[Это было частью 94 того, почему JavaScript не является моим любимым языком программирования.]

Ответ 2

Я использую этот шаблон довольно часто - я обнаружил, что он приносит мне огромную гибкость, когда мне это нужно. При использовании он скорее похож на классы Java-стиля.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Здесь используется анонимная функция, вызываемая при создании, возвращающая новую конструкторскую функцию. Поскольку анонимная функция вызывается только один раз, вы можете создавать в ней частные статические переменные (они находятся внутри закрытия, видимые для других членов класса). Функция-конструктор - это в основном стандартный объект Javascript - вы определяете его внутренние атрибуты, а публичные атрибуты привязаны к переменной this.

В принципе, этот подход сочетает подход Crockfordian со стандартными объектами Javascript для создания более мощного класса.

Вы можете использовать его так же, как и любой другой объект Javascript:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

Ответ 3

Дуглас Крокфорд подробно обсуждает эту тему в "Хороших частях". Он рекомендует избегать оператора new для создания новых объектов. Вместо этого он предлагает создавать персонализированные конструкторы. Например:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

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

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

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

Ответ 4

Для продолжения bobince answer

В es6 теперь вы можете фактически создать class

Итак, теперь вы можете сделать:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Итак, расширяйтесь до круга (как и в другом ответе):

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

Заканчивается бит очиститель в es6 и немного легче читать.


Вот хороший пример этого в действии:

class Shape {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return `Shape at ${this.x}, ${this.y}`;
  }
}

class Circle extends Shape {
  constructor(x, y, r) {
    super(x, y);
    this.r = r;
  }

  toString() {
    let shapeString = super.toString();
    return `Circular ${shapeString} with radius ${this.r}`;
  }
}

let c = new Circle(1, 2, 4);

console.log('' + c, c);

Ответ 5

Вы также можете сделать это, используя структуры:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Тогда:

var counter1 = createCounter();
counter1.increaseBy(4);

Ответ 6

Когда вы используете трюк закрытия на "this" во время вызова конструктора, он должен написать функцию, которая может использоваться как обратный вызов каким-либо другим объектом, который не хочет вызывать метод для объекта. Это не связано с "правильной настройкой области".

Здесь ванильный объект JavaScript:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Вы можете получить много чего из того, что Дуглас Крокфорд должен сказать о JavaScript. John Resig также блестящий. Удачи!

Ответ 7

Другой способ: http://jsfiddle.net/nnUY4/ (я не знаю, подходит ли этот вид создания и раскрытия функций обработки для любого конкретного шаблона)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

Ответ 8

Closure универсален. bobince хорошо обобщил прототипы против приближений закрытия при создании объектов. Однако вы можете имитировать некоторые аспекты OOP, используя закрытие в функциональном программировании. Помните, что функции - объекты в JavaScript; поэтому используйте функцию как объект по-другому.

Вот пример закрытия:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Некоторое время назад я наткнулся на статью Mozilla о Closure. Вот что скачок в моих глазах: "Закрытие позволяет связать некоторые данные (среду) с функцией, которая работает с этими данными. Это имеет очевидные параллели с объектно-ориентированным программированием, где объекты позволяют нам связывать некоторые данные ( свойства объекта) с одним или несколькими методами. Это был первый раз, когда я прочитал parallelism между закрытием и классическим ООП без ссылки на прототип.

Как?

Предположим, вы хотите рассчитать НДС по некоторым товарам. НДС, вероятно, останется стабильным в течение срока действия заявки. Один из способов сделать это в ООП (псевдокод):

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

В основном вы передаете значение НДС в свой конструктор, и ваш метод расчета может работать на нем через закрытие. Теперь вместо использования класса/конструктора передайте свой НДС в качестве аргумента в функцию. Поскольку единственным интересующим вас материалом является сам вычисление, возвращается новая функция, которая является методом расчета:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

В вашем проекте определите значения верхнего уровня, которые являются хорошим кандидатом для расчета НДС. Как правило, всякий раз, когда вы передаете одни и те же аргументы, есть способ улучшить его, используя закрытие. Нет необходимости создавать традиционные объекты.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

Ответ 9

Создание объекта

Самый простой способ создать объект в JavaScript - использовать следующий синтаксис:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Ответ 10

В дополнение к принятому ответу с 2009 года. Если вы можете настроить таргетинг на современные браузеры, можно использовать Object.defineProperty.

Метод Object.defineProperty() определяет новое свойство непосредственно на объект или изменяет существующее свойство объекта и возвращает объект. Источник: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Чтобы просмотреть список совместимости с рабочим столом и мобильными устройствами, см. Список совместимости браузера Mozilla. Да, IE9 + поддерживает его, а также Safari mobile.

Ответ 11

Вы также можете попробовать это

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

Ответ 12

Шаблон, который служит мне хорошо
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

Во-первых, вы можете изменить предпочтение добавления методов к экземпляру вместо объекта- prototype конструктора. Я почти всегда объявляю методы внутри конструктора, потому что я часто использую Constructor Hijacking для целей, связанных с Inheritance & Decorators.

Вот как я решаю, какие декларации написаны:

  • Никогда не объявляйте метод непосредственно на объект контекста (this)
  • Пусть объявления var имеют приоритет над объявлениями function
  • Пусть примитивы имеют приоритет над объектами ({} и [])
  • Пусть public объявления имеют приоритет над private декларациями
  • Предпочитают Function.prototype.bind thus, self, vm и etc
  • Избегайте объявления класса в другом классе, если:
    • Очевидно, что они неразделимы
    • Класс Inner реализует Command Pattern
    • Внутренний класс реализует шаблон Singleton
    • Внутренний класс реализует шаблон состояния
    • Внутренний класс реализует другой шаблон проектирования, который гарантирует это
  • Всегда возвращайте this из лексической области пространства закрытия.

Вот почему они помогают:

Устранение конструктора
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property 'inherited'
    ...
};
Дизайн модели
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Шаблоны проектирования
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Как вы можете видеть, мне действительно не нужно, thus как я предпочитаю Function.prototype.bind (или .call или .apply) thus. В нашем классе Singleton мы даже не INSTANCE его thus потому что INSTANCE передает больше информации. Для Model мы возвращаем this так, чтобы мы могли вызвать конструктор, используя .call чтобы вернуть экземпляр, который мы передали в него. Избыточно, мы назначили его updated переменной, хотя она полезна в других сценариях.

Наряду с этим я предпочитаю создавать объектные литералы с использованием new ключевого слова по {brackets}:

Предпочтительный
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
не является предпочтительным
var klass = Super.apply({
    override: x
});

Как вы можете видеть, последний не имеет возможности переопределить свойство переопределения суперкласса.

Если я добавляю методы объекту prototype класса, я предпочитаю литерал объекта - с использованием или без использования new ключевого слова:

Предпочтительный
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
не является предпочтительным
Klass.prototype.method = function m() {...};

Ответ 13

Я хотел бы упомянуть, что мы можем использовать заголовок или строку для объявления объекта.
Существуют разные способы вызова каждого из них. Смотри ниже:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));

Ответ 14

В принципе нет понятия класса в JS, поэтому мы используем функцию как конструктор класса, которая имеет отношение к существующим шаблонам проектирования.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

До сих пор JS не имеет понятия, что вы хотите создать объект, поэтому здесь появляется новое ключевое слово.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ссылка: Professional JS для веб-разработчиков - Nik Z

Ответ 15

var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);