Есть три класса: A, B
и C
. Как классу C
унаследовать параметры и методы классов A
и B
?
/**
* @class A
*/
function A (name) {
this.name = name;
}
A.prototype.methodA = function(){
console.log("name:" + this.name)
};
/**
* @class B
*/
function B (age) {
this.age = age;
}
B.prototype.methodB = function(){
console.log("age:" + this.age)
};
/**
* @class C exstend A and B
*/
function C () {}
При условии, что A
и B
не связанны друг с другом.
при создании экземпляра pointof
.
pointof = new C ('alex', 35)
В консоль должно быть следующие:
console.log(pointof.name) //'alex'
console.log(pointof.age) //35
pointof.methodA() //"name:alex"
pointof.methodB() //"age:35"
pointof instanceof A //true
pointof instanceof B //true
pointof instanceof C //true
В версиях языка раньше ES 2015 (он же ES6, "Harmony") желаемого вами поведения добиться невозможно: если конструкторы A и B никак не связаны друг с другом, то одна из проверок x instanceof A
или x instanceof B
обязана вернуть false.
Тем не менее, начиная с Harmony появилась возможность переопределять оператор instanceof при помощи Symbol.hasInstance
.
Поэтому множественное наследование можно реализовать следующим образом.
Для начала, функция-конструктор должна вызывать оба базовых конструктора:
function C(name, age) {
A.call(this, name);
B.call(this, age);
}
Далее, от одного из классов следует отнаследоваться обычным способом:
C.prototype = Object.create(A.prototype);
C.prototype.constructor = C;
От второго класса нужно отнаследоваться вручную:
Object.defineProperties(C.prototype, Object.getOwnPropertyDescriptors(B.prototype));
Однако, эта строчка работает только когда B ни от кого сам не унаследован. Иначе надо честно идти по цепочке прототипов:
var chain = [];
for (let base = B.prototype; base && !base.isPrototypeOf(C.prototype); base = Object.getPrototypeOf(base))
chain.push(base);
chain.reverse(); // Обход должен идти от предка к потомку, а мы прошли наоборот
for (const base of chain)
Object.defineProperties(C.prototype, Object.getOwnPropertyDescriptors(base));
Теперь в C.prototype
есть все свойства из обоих прототипов предков, осталось сделать так чтобы проверка pointof instanceof B
работала:
Object.defineProperty(B, Symbol.hasInstance, {
value: function (obj) {
return B.prototype.isPrototypeOf(obj) || C.prototype.isPrototypeOf(obj);
}
});
Вот весь код вместе:
/**
* @class A
*/
function A (name) {
this.name = name;
}
A.prototype.methodA = function(){
console.log("name:" + this.name)
};
/**
* @class B
*/
function B (age) {
this.age = age;
}
B.prototype.methodB = function(){
console.log("age:" + this.age)
};
/**
* @class C exstend A and B
*/
function C(name, age) {
A.call(this, name);
B.call(this, age);
}
C.prototype = Object.create(A.prototype);
C.prototype.constructor = C;
{ // Доп. блок чтобы не загрязнять пространство имен переменных временной переменной
let chain = [];
for (let base = B.prototype; base && !base.isPrototypeOf(C.prototype); base = Object.getPrototypeOf(base))
chain.push(base);
chain.reverse(); // Обход должен идти от предка к потомку, а мы прошли наоборот
for (const base of chain)
Object.defineProperties(C.prototype, Object.getOwnPropertyDescriptors(base));
}
Object.defineProperty(B, Symbol.hasInstance, {
value: function (obj) {
return B.prototype.isPrototypeOf(obj) || C.prototype.isPrototypeOf(obj);
}
});
var pointof = new C ('alex', 35);
console.log(pointof.name) //'alex'
console.log(pointof.age) //35
pointof.methodA() //"name:alex"
pointof.methodB() //"age:35"
console.log(pointof instanceof A) //true
console.log(pointof instanceof B) //true
console.log(pointof instanceof C) //true
Предупреждение. Решение приведенное выше - не более чем костыль, у него нет нескольких важных свойств. К примеру, там предполагается что все прототипы объекта достижимы по цепочке прототипов - но для класса C это свойство нарушается! То есть такой класс с несколькими предками нарушает принцип композиции.
Ну и манки-патчинг B[Symbol.hasInstance]
в таком виде - тоже не самая совместимая вещь. Второе наследование от B затрет результаты первого.
Если по какой-то причине захочется множественного наследования в реальном коде - нужно будет внимательнее отнестись к таким случаям. Например, хранить список "дополнительных" наследников для каждого класса чтобы иметь возможность дополнять проверку...
var s_children = Symbol("children");
B[s_children] = [B, C];
Object.defineProperty(B, Symbol.hasInstance, {
value: function (obj) {
return this[s_children].some(c => c.prototype.IsPrototypeOf(obj));
}
});
Правда, вариант выше подвержен утечкам памяти, поэтому по-хорошему проверку надо "развернуть"...
var s_extra_prototypes = Symbol("extra_prototypes");
C.prototype[s_extra_prototypes] = [B.prototype];
Object.defineProperty(B, Symbol.hasInstance, {
value: function (obj) {
return this.prototype.IsPrototypeOf(obj)
|| obj[s_extra_prototypes].some(c => this.prototype.IsPrototypeOf(c));
}
});
И таких символов понадобится много.
Возможно, намного проще вместо множественного наследования использовать обертки:
function A (base) {
return class A extends base {
constructor(name) {
this.name = name;
}
methodA() {
console.log("name:" + this.name);
}
}
}
function B (base) {
return class B extends base {
constructor(age) {
this.age = age;
}
methodB() {
console.log("age:" + this.age);
}
}
}
class C extends A(B(Object)) { }
Правда, instanceof для оберток работать не будет.
по мне так лучше выработать другой подход в написании конструкторов и определению их протоптанных свойств.
/**
* @class A
*/
function A (name) {
/*
* инициализация методов для конструктора типа A
*/
SCOPE_mtd0001.call(this.constructor.prototype)
this.name = name;
}
/**
* конструктор набора методов типа _mtd0001
*/
function SCOPE_mtd0001(){
this['methodA'] = function(){
console.log("name:" + this.name)
};
}
/**
* @class B
*/
function B(age){
/*
* инициализация методов для конструктора типа B
*/
SCOPE_mtd0002.call(this.constructor.prototype)
this.age = age;
}
/**
* конструктор набора методов типа _mtd0002
*/
function SCOPE_mtd0002(){
this['methodB'] = function(){
console.log("age:" + this.age)
};
}
/**
* @class C exstend A and B
*/
function C(_name, _age) {
/*
* наследование свойств конструкторов A и B
*/
A.call(this, _name)
B.call(this, _age)
/*
* инициализация метода для конструктора типа С
*/
this.constructor.prototype["message"] = function(){
this.methodA()
this.methodB()
}
}
var c_obj = new C("федя", 25)
c_obj.message()
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Всем приветСобственно, проблема в том, что не получается отловить клик, как и написано в заголовке
Дано: на странице есть карта с несколькими маркерамиЕсть список ссылок с координатами этих маркеров, по клику отрабатывает onclick=changeCenter(x,y)