Які методи можна використовувати для визначення класу в JavaScript та які їх компроміси?


686

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

Я хотів би уникати використання сторонніх бібліотек - принаймні спочатку.
Шукаючи інші відповіді, я знайшов статтю Об'єктно-орієнтоване програмування з JavaScript, частина I: Спадщина - Doc JavaScript, що обговорює об’єктно-орієнтоване програмування в JavaScript. Чи є кращий спосіб зробити спадщину?


Примітка: це дублікат stackoverflow.com/questions/355848
Jason S

3
Особисто мені подобається заявляти членів класу всередині функції функції. Я використовую техніку "виправлення цього", щоб створити закриття, щоб змусити себе вести себе як клас. У мене є докладний приклад у моєму блозі: ncombo.wordpress.com/2012/12/30/…
Jon

Я переніс більшість функцій C ++ OOP в JavaScript з простим і природним синтаксисом. Дивіться моя відповідь тут: stackoverflow.com/a/18239463/1115652

У JavaScript немає класів. Але якщо ви хочете імітувати поведінку, схожу на клас у JS, можете. Детальніше дивіться у: symfony-world.blogspot.com/2013/10/…
ducin

Відповіді:


743

Ось як це зробити без використання зовнішніх бібліотек:

// Define a class like this
function Person(name, gender){

   // Add object properties like this
   this.name = name;
   this.gender = gender;
}

// Add methods like this.  All Person objects will be able to invoke this
Person.prototype.speak = function(){
    alert("Howdy, my name is" + this.name);
};

// Instantiate new objects with 'new'
var person = new Person("Bob", "M");

// Invoke methods like this
person.speak(); // alerts "Howdy, my name is Bob"

Тепер справжня відповідь - це набагато складніше за це. Наприклад, немає такого поняття, як класи в JavaScript. JavaScript використовує prototypeсхему успадкування на основі.

Крім того, існує чимало популярних бібліотек JavaScript, які мають свій стиль наближення до класової функціональності в JavaScript. Ви хочете перевірити принаймні прототип та jQuery .

Вирішити, хто з них є "найкращим", - це прекрасний спосіб розпочати святу війну на Stack Overflow. Якщо ви починаєте більш масштабний JavaScript-проект, то, безумовно, варто вивчити популярну бібліотеку і зробити це по-своєму. Я хлопець-прототип, але, схоже, переповнення стека схиляється до jQuery.

Наскільки існує лише "один спосіб зробити це", без будь-яких залежностей від зовнішніх бібліотек, так, як я написав, це майже все.


48
Але це не працює як мова X, де я дізнався єдиний справжній спосіб, що річ, яка використовується для створення об'єктів, повинна працювати :(
Erik Reppen

2
Відповідно до developer.mozilla.org/en-US/docs/Web/JavaScript/… властивості також слід додати до прототипу ("Person.prototype.name = '';")
DaveD

1
@DaveD - можливо, так і було, але, здається, не більше ..?
Кірен Джонстон

6
jQuery навіть не забезпечує жодного способу створення функціоналу, подібного до класу ??? (Усі його класи - це CSS-класи). Ви повинні видалити його з тієї частини відповіді.
Бергі

7
З другої половини 2015 року було випущено новий стандарт EcmaScript 6, тому я пропоную зробити це по-новому (набагато чистіше і легше
DevWL

213

Найкращий спосіб визначення класу в JavaScript - це не визначити клас.

Серйозно.

Існує кілька різних ароматів об'єктно-орієнтованої, деякі з них:

  • клас на основі OO (вперше представлений Smalltalk)
  • заснований на прототипі ОО (вперше представлений Self)
  • мультиметодний OO (вперше представлений CommonLoops, я думаю)
  • OO на основі предикатів (немає ідеї)

І, мабуть, про інших, про яких я не знаю.

JavaScript реалізує прототипи, засновані на прототипі. У прототипі, заснованому на прототипі, нові об'єкти створюються шляхом копіювання інших об'єктів (замість того, щоб інстанціюватися з шаблону класу), а методи живуть безпосередньо в об'єктах, а не в класах. Спадкування здійснюється за допомогою делегування: якщо об'єкт не має методу чи властивості, він шукається на його прототипі (тобто об'єкт, з якого він був клонований), то прототипи прототипу тощо.

Іншими словами: занять немає.

У JavaScript насправді є чітка настройка цієї моделі: конструктори. Ви не тільки можете створювати об’єкти, копіюючи існуючі, ви також можете їх сконструювати "з повітря", так би мовити. Якщо ви викликаєте функцію з newключовим словом, ця функція стає конструктором, і thisключове слово не буде вказувати на поточний об'єкт, а на новостворений "порожній". Отже, ви можете налаштувати об’єкт будь-яким способом. Таким чином, конструктори JavaScript можуть взяти на себе одну з ролей класів у традиційному OO на основі класу: слугуючи шаблоном або кресленням для нових об’єктів.

Тепер JavaScript - це дуже потужна мова, тому впровадити систему OO на основі класу в JavaScript досить легко, якщо ви хочете. Однак ви повинні робити це лише в тому випадку, якщо у вас є справді потреба в цьому, а не тільки тому, що так це робить Java.


"Якщо ви викликаєте функцію з новим ключовим словом, ця функція стає конструктором, і це ключове слово не буде вказувати на поточний об'єкт, а на новостворений" порожній ". Якщо ви викликаєте функцію без нового ключового слова, це буде посилатися на контекст виклику, за замовчуванням глобальний об'єкт (вікно). У суворому режимі за замовчуванням визначено невизначене. виклик, застосування та прив'язка приймає контекст виклику як перший параметр. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Elias Hasle

83

ES2015 Заняття

У специфікації ES2015 ви можете використовувати синтаксис класу, який є просто цукром для прототипу системи.

class Person {
  constructor(name) {
    this.name = name;
  }
  toString() {
    return `My name is ${ this.name }.`;
  }
}

class Employee extends Person {
  constructor(name, hours) {
    super(name);
    this.hours = hours;
  }
  toString() {
    return `${ super.toString() } I work ${ this.hours } hours.`;
  }
}

Переваги

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

Коваджі

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

Підтримка

Підтримка веб-переглядачів наразі не дуже хороша (підтримується майже всіма, крім IE), але ви можете використовувати ці функції зараз із транспілером, як Babel .

Ресурси


56

Я вважаю за краще використовувати Даніеля X. Мура {SUPER: SYSTEM}. Це дисципліна, яка забезпечує такі переваги, як справжні змінні екземпляра, успадкування на основі ознак, ієрархії класів та параметри конфігурації. Наведений нижче приклад ілюструє використання справжніх змінних екземплярів, які, на мою думку, є найбільшою перевагою. Якщо вам не потрібні змінні екземпляра і задоволені лише загальнодоступними або приватними змінними, то, ймовірно, є більш прості системи.

function Person(I) {
  I = I || {};

  Object.reverseMerge(I, {
    name: "McLovin",
    age: 25,
    homeState: "Hawaii"
  });

  return {
    introduce: function() {
      return "Hi I'm " + I.name + " and I'm " + I.age;
    }
  };
}

var fogel = Person({
  age: "old enough"
});
fogel.introduce(); // "Hi I'm McLovin and I'm old enough"

Нічого собі, це не дуже корисно самостійно, але погляньте на додавання підкласу:

function Ninja(I) {
  I = I || {};

  Object.reverseMerge(I, {
    belt: "black"
  });

  // Ninja is a subclass of person
  return Object.extend(Person(I), {
    greetChallenger: function() {
      return "In all my " + I.age + " years as a ninja, I've never met a challenger as worthy as you...";
    }
  });
}

var resig = Ninja({name: "John Resig"});

resig.introduce(); // "Hi I'm John Resig and I'm 25"

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

// The Bindable module
function Bindable() {

  var eventCallbacks = {};

  return {
    bind: function(event, callback) {
      eventCallbacks[event] = eventCallbacks[event] || [];

      eventCallbacks[event].push(callback);
    },

    trigger: function(event) {
      var callbacks = eventCallbacks[event];

      if(callbacks && callbacks.length) {
        var self = this;
        callbacks.forEach(function(callback) {
          callback(self);
        });
      }
    },
  };
}

Приклад наявності класу "person" включає модуль, що поєднує файли.

function Person(I) {
  I = I || {};

  Object.reverseMerge(I, {
    name: "McLovin",
    age: 25,
    homeState: "Hawaii"
  });

  var self = {
    introduce: function() {
      return "Hi I'm " + I.name + " and I'm " + I.age;
    }
  };

  // Including the Bindable module
  Object.extend(self, Bindable());

  return self;
}

var person = Person();
person.bind("eat", function() {
  alert(person.introduce() + " and I'm eating!");
});

person.trigger("eat"); // Blasts the alert!

Розкриття: Я - Даніель X. Мур, і це моє {SUPER: SYSTEM}. Це найкращий спосіб визначити клас у JavaScript.


@DanielXMoore "Змінні екземплярів поділяються між окремими екземплярами класу" Це не змінні екземпляри, це статичні / змінні класу.
JAB

2
@JAB Це неправильно, статичні / класові змінні поділяються між усіма примірниками класу. У кожного екземпляра є свої змінні екземпляри.
Даніель Х Мур

(По-іншому, використовуючи звичайне значення терміна "змінна інстанція", незалежно від того, чи є змінна ортогональною рівню доступності змінної.)
JAB

2
Ви майже звучали як супергерой за те, що претендували на кращий xD
Дадан

Простий підхід до визначення класу Javascript за допомогою об’єктів javascript: wapgee.com/story/i/203
Ilyas karim

41
var Animal = function(options) {
    var name = options.name;
    var animal = {};

    animal.getName = function() {
        return name;
    };

    var somePrivateMethod = function() {

    };

    return animal;
};

// usage
var cat = Animal({name: 'tiger'});

Це дуже елегантний спосіб побудувати зручну структуру об'єкта, не потребуючи нічого імпорту. Я використовував класову систему Резіга, але мені це може подобатися краще. Дякую.
Тім Сколлік

29
Проблема такого підходу полягає в тому, що кожного разу, коли ви створюєте новий екземпляр Animal, потрібно буде переглядати функції, а не лише визначати їх один раз прототипом.
Джастін

33

Нижче наведено способи створення об’єктів у javascript, якими я користувався досі

Приклад 1:

obj = new Object();
obj.name = 'test';
obj.sayHello = function() {
    console.log('Hello '+ this.name);
}

Приклад 2:

obj = {};
obj.name = 'test';
obj.sayHello = function() {
    console.log('Hello '+ this.name);
}
obj.sayHello();

Приклад 3:

var obj = function(nameParam) {
    this.name = nameParam;
}
obj.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}

Приклад 4: Фактичні переваги Object.create (). будь ласка, зверніться до цього посилання

var Obj = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var usrObj = Object.create(Obj);  // <== one level of inheritance

usrObj.init('Bob');
usrObj.sayHello();

Приклад 5 (настроєний Crockford's Object.create):

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}
MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();


Щоб відповісти оновленим за допомогою ES6 / ES2015

Клас визначається так:

class Person {
    constructor(strName, numAge) {
        this.name = strName;
        this.age = numAge;
    }

    toString() {
        return '((Class::Person) named ' + this.name + ' & of age ' + this.age + ')';
    }
}

let objPerson = new Person("Bob",33);
console.log(objPerson.toString());

1
@Justin: Будь ласка, дайте мені знати, що не вірно?
Amol M Kulkarni

Під час вивчення цих позначень я також натрапив на this.set (). Наприклад: this.set ('порт', 3000). Моя здогадка, це використовується для встановлення властивості порту для об'єкта. Якщо так, то чому ми не використовуємо безпосередньо: {port: 3000}. Чи є якась документація, де я можу отримати більше деталей.
adityah

24

Я думаю, вам слід прочитати прототипічне успадкування Дугласа Крокфорда на JavaScript та класичне спадкування в JavaScript .

Приклади з його сторінки:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

Ефект? Це дозволить додати методи більш елегантним способом:

function Parenizor(value) {
    this.setValue(value);
}

Parenizor.method('setValue', function (value) {
    this.value = value;
    return this;
});

Я також рекомендую його відео: Розширений JavaScript .

Ви можете знайти більше відео на його сторінці: http://javascript.crockford.com/ У книзі Джона Рейсіга ви можете знайти багато прикладів з веб-сайту Дугласа Крокфора.


25
Це тільки я? Як чорт такий елегантніший? Я б назвав визначення функцій із фактичними, 'strings'як називає багато речей, але елегантна не одна з них ...
fgysin відновила Моніку

4
@JAB, але рефлексія - це виняток, а не правило. З описаним вище методом ви повинні оголосити всі свої методи рядками.
Кірк Волл

16

Тому що я не визнаю фабричного плану YUI / Crockford і тому, що мені подобається зберігати речі самостійно та розширювані, це моя зміна:

function Person(params)
{
  this.name = params.name || defaultnamevalue;
  this.role = params.role || defaultrolevalue;

  if(typeof(this.speak)=='undefined') //guarantees one time prototyping
  {
    Person.prototype.speak = function() {/* do whatever */};
  }
}

var Robert = new Person({name:'Bob'});

де в ідеалі тест typeof проводиться на щось подібне до першого способу, прототипованого


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

1
Не впевнений, але я зрозумів, що визначення функції прототипу всередині області (дещо як закриття) функції призводить до витоку пам'яті, оскільки збирач сміття не може потрапити туди в екземпляр цих класів.
Sanne

15

Якщо ви збираєтеся просто, ви можете повністю уникнути "нового" ключового слова і просто використовувати заводські методи. Я вважаю за краще інколи, тому що мені подобається використовувати JSON для створення об'єктів.

function getSomeObj(var1, var2){
  var obj = {
     instancevar1: var1,
     instancevar2: var2,
     someMethod: function(param)
     {  
          //stuff; 
     }
  };
  return obj;
}

var myobj = getSomeObj("var1", "var2");
myobj.someMethod("bla");

Я не впевнений, який саме показник для великих об'єктів.


Рядок obj.instanvar1 = var1 не потрібний, оскільки внутрішній об'єкт матиме доступ до параметрів getSomeObj ().
Триптих

Ого. Це болить мій мозок, але в цьому є певна елегантність. Отже, частина "obj.instanvar1 = var1" - це початок свого роду конструктора, я гадаю?
Карим

Щойно побачив коментар Триптиха. Розумію. Отже, ви можете просто зробити щось на кшталт "instancevar1: var1", де внутрішній об'єкт інстанціюється.
Карим

Саме ... коли ви використовуєте {} для визначення об'єкта, він має доступ до змінних, які зараз є в області застосування.
Сем

10
При такому підході ви втрачаєте можливість успадкування, і оскільки ви не використовуєте obj.prototype.something, ви визначаєте функції щоразу, коли використовуєте об'єкт = більше пам'яті і повільніше.
десь

12
var Student = (function () {
    function Student(firstname, lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.fullname = firstname + " " + lastname;
    }

    Student.prototype.sayMyName = function () {
        return this.fullname;
    };

    return Student;
}());

var user = new Student("Jane", "User");
var user_fullname = user.sayMyName();

Ось так TypeScript компілює клас з конструктором в JavaScript.


10

Простий спосіб:

function Foo(a) {
  var that=this;

  function privateMethod() { .. }

  // public methods
  that.add = function(b) {
    return a + b;
  };
  that.avg = function(b) {
    return that.add(b) / 2; // calling another public method
  };
}

var x = new Foo(10);
alert(x.add(2)); // 12
alert(x.avg(20)); // 15

Причина в thatтому, що ви thisможете прив’язати до чогось іншого, якщо ви надаєте метод як обробник подій, тож ви зберігаєте значення під час інстанції та використовуєте його пізніше.

Редагувати: це, безумовно, не найкращий спосіб, просто простий спосіб. Я чекаю і гарних відповідей!


1
Тут = ця конструкція не потрібна. Також методи add () та avg () будуть скопійовані для кожного "екземпляра" класу Foo, а не розділяються між ними.
Триптих

1
Чи потрібно в цьому випадку (сорти), але не простий випадок, який ви вказали.
Триптих

9

Можливо, ви хочете створити тип, використовуючи шаблон складання:

    // Here is the constructor section.
    var myType = function () {
        var N = {}, // Enclosed (private) members are here.
            X = this; // Exposed (public) members are here.

        (function ENCLOSED_FIELDS() {
            N.toggle = false;
            N.text = '';
        }());

        (function EXPOSED_FIELDS() {
            X.count = 0;
            X.numbers = [1, 2, 3];
        }());

        // The properties below have access to the enclosed fields.
        // Careful with functions exposed within the closure of the
        // constructor, each new instance will have it's own copy.
        (function EXPOSED_PROPERTIES_WITHIN_CONSTRUCTOR() {
            Object.defineProperty(X, 'toggle', {
                get: function () {
                    var before = N.toggle;
                    N.toggle = !N.toggle;
                    return before;
                }
            });

            Object.defineProperty(X, 'text', {
                get: function () {
                    return N.text;
                },
                set: function (value) {
                    N.text = value;
                }
            });
        }());
    };

    // Here is the prototype section.
    (function PROTOTYPE() {
        var P = myType.prototype;

        (function EXPOSED_PROPERTIES_WITHIN_PROTOTYPE() {
            Object.defineProperty(P, 'numberLength', {
                get: function () {
                    return this.numbers.length;
                }
            });
        }());

        (function EXPOSED_METHODS() {
            P.incrementNumbersByCount = function () {
                var i;
                for (i = 0; i < this.numbers.length; i++) {
                    this.numbers[i] += this.count;
                }
            };
            P.tweak = function () {
                if (this.toggle) {
                    this.count++;
                }
                this.text = 'tweaked';
            };
        }());
    }());

Цей код надасть вам тип, який називається myType . У ньому будуть внутрішні приватні поля під назвою перемикання та текст . Він також матиме ці відкриті елементи: поля підраховують і номер ; перемикання властивостей , текст та числоLength ; методи збільшення NumbersByCount і налаштування .

Шаблон складання детально описаний тут: Шаблон складання Javascript


3

Код для гольфу @ liammclennan - х відповіді .

var Animal = function (args) {
  return {
    name: args.name,

    getName: function () {
      return this.name; // member access
    },

    callGetName: function () {
      return this.getName(); // method call
    }
  };
};

var cat = Animal({ name: 'tiger' });
console.log(cat.callGetName());


2

MooTools (My Object Oriented Tools) зосереджена на ідеї занять . Можна навіть продовжити та реалізувати із спадщиною.

При освоєнні це робить смішно багаторазовий, потужний javascript.


2

Класи, що ґрунтуються на об'єктах з спадковою ознакою

var baseObject = 
{
     // Replication / Constructor function
     new : function(){
         return Object.create(this);   
     },

    aProperty : null,
    aMethod : function(param){
      alert("Heres your " + param + "!");
    },
}


newObject = baseObject.new();
newObject.aProperty = "Hello";

anotherObject = Object.create(baseObject); 
anotherObject.aProperty = "There";

console.log(newObject.aProperty) // "Hello"
console.log(anotherObject.aProperty) // "There"
console.log(baseObject.aProperty) // null

Просте, миле, і все робиться.


1

База

function Base(kind) {
    this.kind = kind;
}

Клас

// Shared var
var _greeting;

(function _init() {
    Class.prototype = new Base();
    Class.prototype.constructor = Class;
    Class.prototype.log = function() { _log.apply(this, arguments); }
    _greeting = "Good afternoon!";
})();

function Class(name, kind) {
    Base.call(this, kind);
    this.name = name;
}

// Shared function
function _log() {
    console.log(_greeting + " Me name is " + this.name + " and I'm a " + this.kind);
}

Дія

var c = new Class("Joe", "Object");
c.log(); // "Good afternoon! Me name is Joe and I'm a Object"

1

На основі прикладу Триптиха це може бути навіть простіше:

    // Define a class and instantiate it
    var ThePerson = new function Person(name, gender) {
        // Add class data members
        this.name = name;
        this.gender = gender;
        // Add class methods
        this.hello = function () { alert('Hello, this is ' + this.name); }
    }("Bob", "M"); // this instantiates the 'new' object

    // Use the object
    ThePerson.hello(); // alerts "Hello, this is Bob"

Це створює лише один екземпляр об'єкта, але все ще корисно, якщо ви хочете інкапсулювати купу імен змінної та методів у класі. Зазвичай в конструкторі не було б аргументів "Bob, M", наприклад, якщо методи будуть викликами до системи з власними даними, такими як база даних або мережа.

Я все ще занадто новий в JS, щоб зрозуміти, чому це не використовує prototypeріч.


0

JavaScript об'єктно-орієнтований , але він докорінно відрізняється від інших мов OOP, таких як Java, C # або C ++. Не намагайтеся зрозуміти це так. Викиньте ці старі знання і почніть заново. JavaScript потребує іншого мислення.

Я б запропонував отримати хороший посібник чи щось на цю тему. Я сам знайшов підручники ExtJS найкращими для мене, хоча я не використовував рамки до чи після її читання. Але це дає гарне пояснення щодо того, що є у світі JavaScript. На жаль, схоже, що цей вміст видалено. Ось тут посилання на copy.org copy copy . Працює сьогодні. : P


2
Об'єктно-орієнтований? Я думав, що це функціонально .
Пітер Мортенсен

Посилання "Підручники ExtJS" розірвано.
Пітер Мортенсен

Я думаю, було б більш пояснювати, щоб пояснити, що функції в javascript - це об'єкти, а правила, що містять дужки в області javascript, роблять кожен функціональний блок інкапсульованим.
міббіт

-1

//new way using this and new
function Persons(name) {
  this.name = name;
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

var gee=new Persons("gee");
gee.greeting();

var gray=new Persons("gray");
gray.greeting();

//old way
function createPerson(name){
 var obj={};
 obj.name=name;
 obj.greeting = function(){
 console.log("hello I am"+obj.name);
 }; 
  return obj;
}

var gita=createPerson('Gita');
gita.greeting();

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.