Як «правильно» створити спеціальний об’єкт у JavaScript?


471

Цікаво, що найкращий спосіб створити об’єкт JavaScript, який має властивості та методи.

Я бачив приклади, коли людина використовував, var self = thisа потім використовує self.всі функції, щоб переконатися, що сфера дії завжди правильна.

Тоді я бачив приклади використання .prototypeдля додавання властивостей, а інші роблять це в рядку.

Чи може хтось надати мені належний приклад об’єкта JavaScript з деякими властивостями та методами?


13
Не існує "найкращого" способу.
Триптих

Це не selfзастережене слово? Якщо ні, то має бути; оскільки selfце заздалегідь визначена змінна, що посилається на поточне вікно. self === window
Шаз

2
@Shaz: це не зарезервоване слово більше, ніж інші властивості windowмоделі об’єкта браузера, як documentабо frames; ви, звичайно, можете повторно використовувати ідентифікатор як ім'я змінної. Хоча, так, стилістично я вважаю var that= thisза краще уникати будь-якої можливої ​​плутанини. Незважаючи на те, що window.selfв кінцевому рахунку безглуздо, тому рідко є якась причина торкнутися цього.
bobince

7
Коли JS мінімізується, присвоєння thisлокальної змінної (наприклад self) зменшує розміри файлів.
Патрік Фішер

Нове посилання Classjs
Микола

Відповіді:


889

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

Результат полягає в тому, що в змішаній компанії у вас буде мішаний метаклас, і всі поводяться трохи інакше. Що ще гірше, більшість матеріалів підручника 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's 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.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 не є моєю улюбленою мовою програмування.]


13
Дуже приємний поступовий перехід від «класу» до об'єкта екземплярів. І приємний дотик до обходу new.
Півмісяць Свіжий

8
Здається, що JavaScript не є вашою улюбленою мовою, тому що ви хочете використовувати її так, ніби в ній були класи.
Джонатан Файнберг

59
Звичайно, це так, як і всі: модель класу та примірника є більш природною для більшості поширених проблем, з якими стикаються сьогодні програмісти. Я згоден, що на теоретичній основі наслідування на основі прототипу потенційно може запропонувати більш гнучкий спосіб роботи, але JavaScript цілком не виконує цієї обіцянки. Його незграбна функціональна система конструктора дає нам найгірше з обох світів, що робить наслідування схожим на клас, не даючи жодного з прототипів гнучкості та простоти. Словом, це пу.
bobince

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

4
Мені завжди здавалося, що графічне класичне парадигма успадкування на прототипних мовах, таких як javas, - це квадратний кілочок і кругла дірка. Чи бувають випадки, коли це справді необхідно чи це просто спосіб, коли люди стискають кулачок мовою так, як хочуть, а не просто використовують мову такою, якою вона є?
slf

90

Я використовую цей шаблон досить часто - я виявив, що він дає мені величезну гнучкість, коли мені це потрібно. У використанні він досить схожий на класи в стилі 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

4
Це виглядає цікаво, адже він досить близький до мого "домашнього дерну", який є C #. Я також думаю, що я починаю розуміти, чому privateStaticVariable насправді приватний (як це визначено в межах функції і зберігається в живих, поки на нього є посилання?)
Michael Stum

Оскільки він не використовує, thisчи все-таки його потрібно придумувати new?
Йордан Пармер

Насправді, this він звикає до constructorфункції, яка стає Fooв прикладі.
ШЗ

4
Проблема тут полягає в тому, що кожен об’єкт отримує власну копію всіх приватних та публічних функцій.
virtualnobi

2
@virtualnobi: Ця модель не заважає вам писати методи protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin

25

Дуглас Крокфорд широко обговорює цю тему у «Гарних частинах» . Він рекомендує уникати нового оператора для створення нових об'єктів. Натомість він пропонує створити індивідуальні конструктори. Наприклад:

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 функція є об'єктом, і її можна використовувати для побудови об'єктів разом із новим оператором. За умовою, функції, призначені для використання в якості конструкторів, починаються з великої літери. Ви часто бачите такі речі, як:

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

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

У разі , якщо ви забули використовувати новий оператор , а інстанцірованія нового об'єкта, то , що ви отримуєте звичайний виклик функції, і це пов'язано з глобальним об'єктом , а не на новий об'єкт.


5
Це я чи я вважаю, що Крокфорд не має абсолютно ніякого сенсу з його ударом нового оператора?
meder omuraliev

3
@meder: Не тільки ти. Принаймні, я думаю, що з новим оператором немає нічого поганого. І є неявне newв var that = {};будь-якому випадку.
Тім Даун

17
Крокфорд - хитрий старий, і я з ним багато чого не погоджуюся, але він, принаймні, сприяє критичному погляду на JavaScript, і варто слухати, що він має сказати.
bobince

2
@bobince: Погоджено. Його написання про закриття відкрило мені очі на багато речей близько 5 років тому, і він заохочує продуманий підхід.
Тім Даун

20
Я згоден з Крокфордом. Проблема з новим оператором полягає в тому, що JavaScript зробить контекст "цього" дуже іншим, ніж при іншому виклику функції. Незважаючи на правильну конвенцію випадку, виникають проблеми, що виникають на більшій базі коду, оскільки розробники забувають використовувати нове, забувають використовувати великі літери тощо. Щоб бути прагматичним, ви можете зробити все, що вам потрібно зробити, без нового ключового слова - так навіщо використовувати його і ввести більше кодів відмови в код? JS є прототиповою мовою, що не заснована на класах. То чому ми хочемо, щоб вона діяла як статично набрана мова? Я, звичайно, ні.
Джошуа Рамірес

13

Щоб продовжити відповідь бобінця

Тепер у програмі 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 і трохи легше читати.


Ось хороший приклад цього в дії:


6

Ви також можете зробити це так, використовуючи структури:

function createCounter () {
    var count = 0;

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

Тоді :

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

6
Мені це не подобається, тому що пробіл важливий. Кучеряве після повернення повинно знаходитися на одній лінії для сумісності між браузерами.
geowa4

5

Іншим способом може бути 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"

4

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

Ось ванільний 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. Джон Ресіг також геніальний. Удачі!


1
Ага, закриття навколо thisмає все відношення до "правильної області".
Roatin Marth

3
Джонатан має рацію. Обсяг функції js - це те, яким ви її проектуєте. Self = цей трюк є одним із способів прив’язати його до певного екземпляра, щоб він не змінювався, коли викликався в іншому контексті. Але іноді це те, що ти насправді хочеш. Залежить від контексту.
Марко

Я думаю, ви всі говорите одне й те саме. self=thisХоча не змушує thisзберігатись, легко дозволяє "правильне" вимірювання за допомогою закриття.
Півмісяць свіжий

2
Причина цього = це надати вкладеним функціям доступ до сфери цього, як це існує у функції конструктора. Коли вкладені функції знаходяться всередині функцій конструктора, їх "це" область повертається до глобальної сфери.
Джошуа Рамірес

4

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 про закриття. Ось що стрибає мені погляд: "Закриття дозволяє пов'язувати деякі дані (оточення) з функцією, яка працює на цих даних. Це має очевидні паралелі з об'єктно-орієнтованим програмуванням, де об'єкти дозволяють нам пов'язувати деякі дані (властивості об'єкта ) одним або декількома методами ". Це я вперше прочитав паралелізм між закриттям та класичним OOP, не маючи посилання на прототип.

Як?

Припустимо, ви хочете розрахувати ПДВ деяких предметів. ПДВ, ймовірно, залишатиметься стабільним протягом життя заявки. Один із способів зробити це в OOP (псевдокод):

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


3

Створення об’єкта

Найпростіший спосіб створити об’єкт в JavaScript - це використовувати наступний синтаксис:

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

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

Це чудово підходить для структурованого зберігання даних.

Однак для складніших випадків використання часто краще створювати екземпляри функцій:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

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

Однак це все ще можна зробити більш ефективно, використовуючи прототип.

Щоразу, коли різні екземпляри функції мають однакові методи чи властивості, ви можете перемістити їх до прототипу цього об'єкта. Таким чином, кожен екземпляр функції має доступ до цього методу чи властивості, але його не потрібно дублювати для кожного екземпляра.

У нашому випадку має сенс перемістити метод fдо прототипу:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Спадщина

Простий, але ефективний спосіб зробити спадщину в JavaScript - це використовувати наступний дволанковий:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Це схоже на це:

B.prototype = new A();

Основна відмінність обох полягає в тому, що конструктор Aне запускається при використанні Object.create, що більш інтуїтивно зрозуміло і схоже на спадкування на основі класу.

Ви завжди можете необов'язково запустити конструктор програми Aпри створенні нового примірника B, додавши його до конструктора B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Якщо ви хочете передати всі аргументи , Bщоб A, ви також можете використовувати Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Якщо ви хочете змішати інший об'єкт у ланцюжку конструктора B, ви можете комбінувати Object.createз Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Демо

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


Примітка

Object.createможе бути безпечно використаний у кожному сучасному браузері, включаючи IE9 +. Object.assignне працює ні в одній версії IE, ні в деяких мобільних браузерах. Рекомендується виконувати поліфаування Object.create та / або Object.assignякщо ви хочете їх використовувати та підтримувати браузери, які не реалізують їх.

Ви можете знайти polyfill для Object.create тут і один для Object.assign тут .


0
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);

4
Що це додає до вже запропонованих численних обширних відповідей?
blm

Мені подобається, що ця відповідь є стислою і показує три частини реалізації: 1) Визначте об'єкт, 2) Негайне екземпляр об'єкта, 3) Використовуйте екземпляр - він показує все це з першого погляду, замість того, щоб мати аналіз через всі багатослівні відповіді вище (що, звичайно, всі надзвичайно хороші відповіді з усіма відповідними деталями, які хотіли б хотіти) - тут такий простий підсумок
G-Man

0

На додаток до прийнятої відповіді з 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.


0

Ви також можете спробувати це

    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();

0
Шаблон, який мені добре допомагає
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об'єкта . Я майже завжди декларую методи всередині конструктора, оскільки я дуже часто використовую викрадення конструктора для цілей щодо спадкування та декораторів.

Ось як я вирішую, куди писати декларації:

  • Ніколи не оголошуйте метод безпосередньо на контекстному об'єкті ( this)
  • Нехай varдекларації мають перевагу над functionдеклараціями
  • Нехай примітиви мають перевагу над об'єктами ( {}і [])
  • Нехай publicдекларації мають перевагу над privateдеклараціями
  • Віддаю перевагу Function.prototype.bindбільш thus, self, vm,etc
  • Уникайте декларування класу в іншому класі, якщо:
    • Має бути очевидним, що ці два нероздільні
    • Внутрішній клас реалізує шаблон команд
    • Внутрішній клас реалізує шаблон 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класі ми навіть не називаємо його, 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() {...};

0

Я хотів би зазначити, що ми можемо використовувати або Заголовок, або Рядок для оголошення об’єкта.
Існують різні способи виклику кожного з них. Дивись нижче:

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'));


-1

В основному немає поняття класу в 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

Ref: Професійний JS для веб-розробників - Nik Z


Downvote прийняв: з поважною причиною було б більш інформативним і послужило б можливістю вдосконалитись.
Airwind711

Там є поняття classв JS, як ви згадали в своєму заголовку , використовуючи functionключове слово. Це не модель дизайну, а навмисна особливість мови. Я не відповідав тобі на це, але, схоже, хтось зробив це через нестриманість і майже невідповідність питання. Сподіваюся, цей відгук допомагає.
Коді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.