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


471

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

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

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

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


13
Нет лучшего пути.
Триптих

Разве не selfзарезервированное слово? Если нет, то должно быть; поскольку selfпредопределенная переменная ссылается на текущее окно. self === window
Шаз

2
@Shaz: это не зарезервированное слово, как и другие свойства windowобъектной модели браузера, такие как documentor frames; Вы, безусловно, можете повторно использовать идентификатор в качестве имени переменной. Хотя, да, стилистически я предпочитаю var that= thisизбегать любой возможной путаницы. Несмотря на то, что window.selfв конечном итоге это бессмысленно, редко есть какая-либо причина, чтобы прикоснуться к нему.
bobince

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

Classjs новая ссылка: github.com/divio/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в приведенном выше фрагменте бесплатно, поэтому вам все равно понадобится другое закрытие или a, 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.
Crescent Fresh

8
Кажется, что JavaScript не ваш любимый язык, потому что вы хотите использовать его, как если бы у него были классы.
Джонатан Фейнберг

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

4
Боб, я думаю, что это потрясающий ответ - я какое-то время боролся с этими двумя шаблонами, и я думаю, что вы написали что-то более лаконично, чем Resig, и объяснили с большей проницательностью, чем Crockford. Никаких высших похвал я не могу вспомнить ...
Джеймс Вестгейт

4
Мне всегда казалось, что отображение классических парадигм наследования на прототипных языках, таких как javascript, - это квадратный колышек и круглое отверстие. Бывают ли времена, когда это действительно необходимо, или это просто способ заставить людей придумать язык так, как они хотят, а не просто использовать язык таким, какой он есть?
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переменной.

По сути, этот подход сочетает в себе крокфордский подход со стандартными объектами Javascript для создания более мощного класса.

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

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

4
Это выглядит интересно, потому что это довольно близко к моему "home-turf", который является C #. Я также думаю, что начинаю понимать, почему privateStaticVariable действительно закрытая (так как она определена в рамках функции и сохраняется, пока есть ссылки на нее?)
Майкл Стум

Поскольку он не используется this, нужно ли создавать его экземпляр new?
Джордан Пармер

На самом деле, this действительно привыкают в constructorфункции, которая становится Fooв примере.
ШЗ

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

2
@virtualnobi: Эта модель не мешает вам писать методы protytpe: constructor.prototype.myMethod = function () { ... }.
Николя Ле Тьерри д'Аннекин

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

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, и стоит послушать, что он скажет.
Бобинц

2
@bobince: Согласен. Его письма о замыканиях открыли мне глаза на многие вещи около 5 лет назад, и он поощряет вдумчивый подход.
Тим Даун

20
Я согласен с Крокфордом. Проблема с оператором new заключается в том, что JavaScript сделает контекст «this» совершенно другим, чем при вызове функции в противном случае. Несмотря на правильное соглашение о случаях, есть проблемы, которые возникают в более крупных базах кода, так как разработчики забывают использовать новое, забывают использовать заглавные буквы и т. Д. Чтобы быть прагматичным, вы можете делать все, что вам нужно, без нового ключевого слова - так зачем его использовать ввести больше точек сбоя в коде? 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

Когда кто-то использует трюк закрытия «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. Джон Резиг также великолепен. Удачи!


1
Ох, закрытие вокруг thisимеет отношение к тому, чтобы "сделать прицел правильным".
Роатин Март

3
Джонатан прав. Область действия функции js - это то, что вы планируете. Self = этот трюк - один из способов привязать его к конкретному экземпляру, чтобы он не изменился при вызове в другом контексте. Но иногда это то, что вы на самом деле хотите. Зависит от контекста.
Марко

Я думаю, что вы все говорите одно и то же. self=thisхотя не заставляет thisупорствовать, легко позволяет «исправить» область видимости через замыкание.
Crescent Fresh

2
Причина, по которой вы делаете это =, заключается в том, чтобы предоставить вложенным функциям доступ к области действия, существующей в функции конструктора. Когда вложенные функции находятся внутри функций конструктора, их область «this» возвращается к глобальной области.
Джошуа Рамирес

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 о закрытии. Вот что бросается в глаза: «Закрытие позволяет связать некоторые данные (среду) с функцией, которая работает с этими данными. Это имеет очевидные параллели с объектно-ориентированным программированием, где объекты позволяют нам связывать некоторые данные (свойства объекта). ) одним или несколькими методами ". Это был первый раз, когда я прочитал параллелизм между замыканием и классическим ООП без ссылки на прототип.

Как?

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

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

Это позволяет вам создавать несколько объектов, которые разделяют один и тот же «план», подобно тому, как вы используете классы, например, в. Ява.

Однако это можно сделать более эффективно, используя прототип.

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

В нашем случае имеет смысл перенести метод 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использовать их и поддерживать браузеры, которые их не реализуют.

Вы можете найти полифилл 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 () определяет новое свойство непосредственно для объекта или изменяет существующее свойство объекта и возвращает объект. Источник: Мозилла

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.


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объекта . Я почти всегда объявляю методы внутри конструктора, потому что я часто использую Constructor Hijacking для целей наследования и декораторов.

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

  • Никогда не объявляйте метод непосредственно в объекте контекста ( 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ключевое слово над {скобками}:

Preferred
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Не предпочитаемый
var klass = Super.apply({
    override: x
});

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

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

Preferred
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

Ссылка: Профессиональный JS для веб-разработчиков - Nik Z


Принято понижение: с уважительной причиной было бы более информативным и дало бы возможность улучшить.
Airwind711

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