Ответ 1
Вариант 1 - Mixin
function SomeType() {
var priv = "I'm private";
this.publ = "I'm public";
this.action = function() {
return priv + this.publ;
};
}
var obj = new SomeType();
С помощью этого метода вы создаете новый объект каждый раз, когда вы вызываете new SomeType()
, создавая все его методы и добавляя весь этот метод к новому объекту. Каждый раз, когда вы создаете объект.
Pros
- Это похоже на классическое наследование, поэтому легко понять Java-С# -С++ - и т.д. люди.
- Он может иметь частные переменные на один экземпляр, поскольку у вас есть одна функция closure для каждого создаваемого вами объекта
- Он допускает множественное наследование, также известное как Twitter-mixins или функциональные микшины
-
obj instanceof SomeType
вернет true
Против
- Он потребляет больше памяти в качестве большего количества объектов, созданных вами, потому что с каждым объектом вы создаете новое замыкание и снова создаете каждый из его методов.
- Частные свойства
private
, а неprotected
, подтипы не могут получить к ним доступ. - Нет простого способа узнать, имеет ли объект некоторый тип в качестве суперкласса.
Наследование
function SubType() {
SomeType.call(this);
this.newMethod = function() {
// can't access priv
return this.publ;
};
}
var child = new SubType();
child instanceof SomeType
вернет false, нет другого способа узнать, есть ли у ребенка методы SomeType, чем посмотреть, есть ли он один за другим.
Вариант 2 - Объектный литерал с прототипом
var obj = {
publ: "I'm public",
_convention: "I'm public too, but please don't touch me!",
someMethod: function() {
return this.publ + this._convention;
}
};
В этом случае вы создаете один объект. Если вам понадобится только один экземпляр этого типа, это может быть лучшим решением.
Pros
- Это быстро и легко понять.
- производительным
Против
- Отсутствие конфиденциальности, каждое свойство является общедоступным.
Наследование
Вы можете наследовать объект, прототипирующий его.
var child = Object.create(obj);
child.otherMethod = function() {
return this._convention + this.publ;
};
Если вы находитесь в старом браузере, вам нужно будет гарантировать Object.create
works:
if (!Object.create) {
Object.create = function(obj) {
function tmp() { }
tmp.prototype = obj;
return new tmp;
};
}
Чтобы узнать, является ли объект прототипом другого, вы можете использовать
obj.isPrototypeOf(child); // true
Вариант 3 - Конструкторский шаблон
ОБНОВЛЕНИЕ: Это шаблон ES6 - это синтаксис сахара. Если вы используете классы ES6, вы следуете этому шаблону под капотом.
class SomeType {
constructor() {
// REALLY important to declare every non-function property here
this.publ = "I'm public";
this._convention = "I'm public too, but please don't touch me!";
}
someMethod() {
return this.publ + this._convention;
}
}
class SubType extends SomeType {
constructor() {
super(/* parent constructor parameters here */);
this.otherValue = 'Hi';
}
otherMethod() {
return this._convention + this.publ + this.otherValue;
}
}
function SomeType() {
// REALLY important to declare every non-function property here
this.publ = "I'm public";
this._convention = "I'm public too, but please don't touch me!";
}
SomeType.prototype.someMethod = function() {
return this.publ + this._convention;
};
var obj = new SomeType();
Вы можете повторно назначить прототип вместо добавления каждого метода, если вы не наследуете, и не забудьте повторно назначить свойство конструктора:
SomeType.prototype = {
constructor: SomeType,
someMethod = function() {
return this.publ + this._convention;
}
};
Или используйте _.extend или $.extend, если на вашей странице есть символ подчеркивания или jquery
_.extend(SomeType.prototype, {
someMethod = function() {
return this.publ + this._convention;
}
};
Ключевое слово new
под капотом просто делает это:
function doNew(Constructor) {
var instance = Object.create(Constructor.prototype);
instance.constructor();
return instance;
}
var obj = doNew(SomeType);
У вас есть функция, которая не имеет методов; он просто имеет свойство prototype
со списком функций, оператор new
означает создание нового объекта и использование этого свойства прототипа функции (Object.create
) и constructor
как инициализатор.
Pros
- производительным
- Цепочка прототипа позволит вам узнать, наследует ли объект от какого-либо типа
Против
- Двушаговое наследование
Наследование
function SubType() {
// Step 1, exactly as Variation 1
// This inherits the non-function properties
SomeType.call(this);
this.otherValue = 'Hi';
}
// Step 2, this inherits the methods
SubType.prototype = Object.create(SomeType.prototype);
SubType.prototype.otherMethod = function() {
return this._convention + this.publ + this.otherValue;
};
var child = new SubType();
Вы можете подумать, что это похоже на супер-набор вариации 2... и вы будете правы. Это похоже на вариант 2, но с функцией инициализации (конструктор);
child instanceof SubType
и child instanceof SomeType
вернут оба true
Любопытство: под капотом instanceof
оператор имеет
function isInstanceOf(obj, Type) {
return Type.prototype.isPrototypeOf(obj);
}
Вариант 4 - Перезаписать __proto__
Когда вы делаете Object.create(obj)
под капотом, он делает
function fakeCreate(obj) {
var child = {};
child.__proto__ = obj;
return child;
}
var child = fakeCreate(obj);
Свойство __proto__
изменяет непосредственно свойство скрытого объекта [Prototype]
. Поскольку это может нарушить поведение JavaScript, оно не является стандартным. И стандартный способ является предпочтительным (Object.create
).
Pros
- Быстрый и эффективный
Против
- Нестандартные
- Опасные; вы не можете иметь hashmap, поскольку ключ
__proto__
может изменить прототип объекта
Наследование
var child = { __proto__: obj };
obj.isPrototypeOf(child); // true
Комментировать вопросы
1. var1: что происходит в SomeType.call(это)? Специальная функция "вызов"?
О, да, функции - это объекты, поэтому у них есть методы, я упомянул три: . call(), . apply() и . bind()
Когда вы используете .call() для функции, вы можете передать один дополнительный аргумент, контекст, значение this
внутри функции, например:
var obj = {
test: function(arg1, arg2) {
console.log(this);
console.log(arg1);
console.log(arg2);
}
};
// These two ways to invoke the function are equivalent
obj.test('hi', 'lol');
// If we call fn('hi', 'lol') it will receive "window" as "this" so we have to use call.
var fn = obj.test;
fn.call(obj, 'hi', 'lol');
Итак, когда мы делаем SomeType.call(this)
, мы передаем объект this
в функцию SomeCall
, поскольку вы помните, что эта функция будет добавлять методы к объекту this
.
2. var3: С вашими "ДЕЙСТВИТЕЛЬНО определите свойства" вы имеете в виду, если я использую их в функциях? Это конвенция? Поскольку получение this.newProperty без его определения на одном уровне с другими функциями-членами не является проблемой.
Я имею в виду, что любое свойство, которое будет иметь ваш объект, а не функция, должно быть определено конструктором, а не прототипом, иначе вы столкнетесь с одной из наиболее запутанных проблем JS. Вы можете увидеть его здесь, но это вне фокуса этого вопроса.
3. Var3: что произойдет, если я не назначу конструктор?
На самом деле вы можете не видеть разницу, и это то, что делает ее опасной ошибкой. Каждый объект прототипа функции имеет свойство constructor
, поэтому вы можете получить доступ к конструктору из экземпляра.
function A() { }
// When you create a function automatically, JS does this:
// A.prototype = { constructor: A };
A.prototype.someMethod = function() {
console.log(this.constructor === A); // true
this.constructor.staticMethod();
return new this.constructor();
};
A.staticMethod = function() { };
Это не лучшая практика, потому что не все об этом знают, но иногда это помогает. Но если вы переназначите прототип...
A.prototype = {
someMethod = function() {
console.log(this.constructor === A); // false
console.log(this.constructor === Object); // true
this.constructor.staticMethod();
return new this.constructor();
}
};
A.prototype
- это новый объект, экземпляр Object
, чем прототипы Object.prototype
и Object.prototype.constructor
- Object
. Смущает, не так ли?: P
Итак, если вы перезапишите прототип и не reset свойство "конструктор", оно будет ссылаться на Object
вместо A
, и если вы попытаетесь использовать свойство "конструктор" для доступа к некоторым статическим вы можете сходить с ума.