Помогите процедурщику переписать маленький скриптик на javascript в ООП стиле


Есть код

function popup(){}
popup.hello = function(){
  $("#container").html('<div onclick="popup.bye()">Hello</div>')
}
popup.bye = function(){
  alert("Bye");
}

popup.hello()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container"></div>

Как ни пробовал его сделать через классы, модули или вообще в ООП стиле, ничего не получается.


Так, чтобы не было лишних вопросов с лишним кодом и минусов, решил сделать пример более наглядным

var module =(function (){
var instanceCount = 0;
var CONSTANT_GlOBAL = Math.random();
function render(localRandom){
  	$("#container").append('\
		<div onclick="module.create('+ instanceCount++ +')">\
			Module №: '+ instanceCount +'\
			<br>Instance random variable: '+localRandom+'\
			<br>Module random constant: '+ CONSTANT_GlOBAL +'\
		</div><br>');
}
return {
	run: function(number){
		var localRandom = Math.random();
		render(localRandom,number);
	},
  	create: function(number){
 			this.run(number);
	}
}
}());

module.run(1);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container"></div>
Author: PashaPash, 2016-11-28

3 answers

Вообще реализация чистого ООП подхода на js, до стандарта es6, это одна из самых нетривиальных задач и поэтому у нее есть множество вариантов решения:

Вариант 1 - найти подходящую js библиотеку

Таких библиотек в интернете множество, вот пара примеров:

Вариант 2 - использовать js compiler

Тоже множество вариантов, например:

  • Babel - компилирует(транслирует) ванильный es6 код.
  • TypeScript - отдельное надмножество js, позиционируемый как js c типами.

Вариант 3 - написать свою реализацию

В ответах уже есть простейшая реализация класса с использованием module-pattern, но ООП на этом не заканчивается, а какже наследование? В js уже реализован механизм наследования через prototype вот так:

MyClass.prototype = Object.create(Base.prototype);

Но это скопирует только методы прототипа, а у нас есть еще свойства самого объекта - в ООП это статические методы и свойства, поэтому я использую следующий паттерн:

function extend(current, base) {
    // копируем статические проперти
    for (var key in base) {
        if (base.hasOwnProperty(key)) {
            current[key] = base[key];
        }
    }

    // копируем прототип
    current.prototype = Object.create(base.prototype);

};

Теперь можно это использовать:

var Dialogs;
(function (Dialogs) {

    // класс Popup
    Dialogs.Popup = (function () {
        // статическая функция
        Popup.show = function () {
            alert("show");
        };
        // Конструктор класса
        function Popup(message) {
            this._message = message;
        }
        Popup.prototype.hello = function () {
            var self = this;
            var message = $(this._message).click(function () {
                    self.sayBye();
                });
            $("#container").append(message);
        };
        Popup.prototype.sayBye = function () {
            alert("Bye");
        };
        Popup.prototype.say = function () {
            alert(this._message);
        };
        return Popup;
    }());

    // класс Popup2 extend Popup
    Dialogs.Popup2 = (function (superClass) {
        // наследуем прототип
        extend(Popup2, superClass);
        function Popup2() {
            // вызываем конструктор базового класса
            superClass.apply(this, arguments);
        }
        // перегружаем базовую функцию sayBye 
        Popup2.prototype.sayBye = function () {
            alert("Bye2");
        };
        return Popup2;
    }(Dialogs.Popup)); // передаем базовый класс

})(Dialogs || (Dialogs = {}));

В коде ваше создается область видимости(namespace) Dialogs внутри которой происходит вся магия, так же в ней можно объявлять глобальные переменные:

var Dialogs;
    (function (Dialogs) {
        var a = 100; // переменная видна внутри Dialogs
        Dialogs.b = 100; // можно обратиться как к Dialogs.b снаружи
        ...

Можно конечно и без нее тогда паттерн класса будет выглядеть так:

var Popup2 = (function (superClass) {
    extend(Popup2, superClass);
    function Popup2() {
        superClass.apply(this, arguments);
    }
    return Popup2;
}(Popup));

Посмотреть как это работает можно тут: jsfiddle

 6
Author: Codd Wrench, 2017-04-13 12:53:25

А React использовать можно?

class Popup extends React.Component {
  constructor(props) {
    super(props);
    this.bye = this.bye.bind(this);
  }
  
  render() {
    return <div onClick={this.bye}>{this.props.msg}</div>;
  }
  
  bye() {
    alert("Bye");
  }
}

class App extends React.Component {
  render() {
    return <Popup msg="Hello"/>;
  }
}

ReactDOM.render(<App />, document.getElementById('container'));
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.4.0/react.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.4.0/react-dom.js"></script>
<div id="container"></div>
 2
Author: Qwertiy, 2016-11-28 18:50:22
var Popup = function () {
    this.hello = function() {
      $("#container").html('<div onclick="popup.bye()">Hello</div>')
    },
    this.bye = function(){
      alert("Bye");
    }
}

var popup = new Popup;

popup.bye()
 0
Author: pwnz22, 2016-11-28 20:54:52