Object.assign()
сохранит метод getWeekdays()
, если привязать метод к объекту вместо его прототипа с помощью одного из следующих подходов:
⚠️ Связывание методов непосредственно собъект вместо своего прототипа обычно считается антипаттерном, особенно в тех случаях, когда производительность имеет более высокий приоритет, поскольку N Schedule
s будет ссылаться на N отдельных getWeekend()
функций вместо ссылки на одну функцию getWeekend()
в противном случае он будет использоваться прототипом.
Методы функций со стрелками
Первый подход заключается в объявлении вашего метода в определении class
с помощью стрелки.функция, например, так:
class Schedule {
public days: Array<number> = [];
public getWeekdays = (): Array<number> => {
return this.days;
}
}
const clone = Object.assign({}, new Schedule());
... но почему?
Причина, по которой это работает, двоякая:
- , поскольку синтаксис функции стрелки связывает методв результирующий объект вместо его прототипа.
- , поскольку
Object.assign()
копирует собственные свойства объекта, но не его унаследованные свойства.
Если вы запуститеconsole.log(new Schedule());
вы можете увидеть первую точку в действии:
// with arrow function:
▼ Schedule {days: Array(0), getWeekdays: } ⓘ
▷ days: Array(0) []
▷ getWeekdays: () => { … }
▷ __proto__: Object { constructor: … }
// without arrow function:
▼ Schedule { days: Array(0) } ⓘ
▷ days: Array(0) []
▼ __proto__: Object { constructor: , getWeekdays: }
▷ constructor: class Schedule { … }
▷ getWeekdays: getWeekdays() { … }
▷ __proto__: Object { constructor: , __defineGetter__: , __defineSetter__: , … }
Чем это отличается от метода static
?
Метод static
связан не с прототипом объекта, но для самого class
, который является конструктором прототипа:
class Schedule {
public static days: Array<number> = [];
public static getWeekdays(): Array<number> {
return this.days;
}
}
const clone = Object.assign({}, new Schedule());
console.log(new Schedule());
// console
▼ Schedule {} ⓘ
▼ __proto__: Object { constructor: … }
▼ constructor: class Schedule { … }
[[FunctionLocation]]: internal#location
▷ [[Scopes]]: Scopes[1]
arguments: …
caller: …
▷ days: Array(0) []
▷ getWeekdays: getWeekdays() { … }
length: 0
name: "Schedule"
▷ prototype: Object { constructor: … }
▷ __proto__: function () { … }
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
Это означает, что метод static
не может быть напрямую связан с объектом.Если вы попытаетесь, вы получите этот TSError:
~/dev/tmp/node_modules/ts-node/src/index.ts:261
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: ⨯ Unable to compile TypeScript:
index.ts(14,14): error TS2334: 'this' cannot be referenced in a static property initializer.
at createTSError (~/dev/tmp/node_modules/ts-node/src/index.ts:261:12)
at getOutput (~/dev/tmp/node_modules/ts-node/src/index.ts:367:40)
at Object.compile (~/dev/tmp/node_modules/ts-node/src/index.ts:558:11)
at Module._compile (~/dev/tmp/node_modules/ts-node/src/index.ts:439:43)
at internal/modules/cjs/loader.js:733:10
at Object..ts (~/dev/tmp/node_modules/ts-node/src/index.ts:442:12)
at Module.load (internal/modules/cjs/loader.js:620:32)
at tryModuleLoad (internal/modules/cjs/loader.js:560:12)
at Function._load (internal/modules/cjs/loader.js:552:3)
at Function.runMain (internal/modules/cjs/loader.js:775:12)
.bind()
в конструкторе
Функции стрелок (включая те, которые используются в определениях class
методов) являютсяФункция ES6, обеспечивающая более краткий синтаксис выражений объявлений функций в отношении поведения ключевого слова this
.В отличие от обычных функций, функции-стрелки используют значение this
своей лексической области видимости, а не устанавливают свое собственное значение this
на основе контекста их вызова.Они также не получают свой собственный объект arguments
(или super
, или new.target
).
До ES6, если вам нужно было использовать this
в методе, используемом в качестве обратного вызова,вам придется привязать значение хост-объекта this
к значению метода this
с помощью .bind()
, который возвращает обновленную функцию со значением this
, установленным в предоставленное значение, например:
var clone;
function Schedule() {
this.days = [];
this.setWeekdays = function(days) {
this.days = days;
}
this.setWeekdays = this.setWeekdays.bind(this);
}
clone = Object.assign({}, new Schedule());
console.log(clone);
// console
▼ Object {days: Array(0), setWeekdays: }
▷ days:Array(0) []
▷ setWeekdays:function () { … }
▷ __proto__:Object {constructor: , __defineGetter__: , __defineSetter__: , …}
В ES6 class
вы можете достичь тех же результатов, вызвав .bind()
для метода в конструкторе:
class Schedule {
public days: Array<number> = [];
constructor() {
this.getWeekdays = this.getWeekdays.bind(this);
}
public getWeekdays(): Array<number> {
return this.days;
}
}
const clone = Object.assign({}, new Schedule());
console.log(clone);
// console
▼ Object {days: Array(0), setWeekdays: … } ⓘ
▷ days: Array(0) []
▷ setWeekdays: function () { … }
▷ __proto__: Object { constructor: , __defineGetter__: , __defineSetter__: , … }
Будущий бонус: декораторы автосвязки
⚠️ Также не обязательно рекомендуется, поскольку в конечном итоге вы выделяете функции, которые обычно никогда не вызываются, как описано ниже.
Декораторы считаются экспериментальной функцией вНапечатайте и потребуйте, чтобы вы явно установили experimentalDecorators
на true
в своем tsconfig.json
.
Использование декоратора автосвязки позволит вам повторно связать метод getWeekdays()
"по требованию" - так же, как и использование .bind()
ключ в конструкторе, нопривязка происходит, когда getWeekdays()
вызывается вместо того, когда new Schedule()
вызывается только в более компактном виде:
class Schedule {
public days: Array<number> = [];
@bound
public getWeekdays(): Array<number> {
return this.days;
}
}
Однако, поскольку декораторы все еще находятся на стадии 2, включение декораторов в TypeScript предоставляет толькоинтерфейсы для 4 типов функций декоратора (то есть ClassDecorator
, PropertyDecorator
, MethodDecorator
, ParameterDecorator
.) Встроенные декораторы, предложенные на этапе 2, включая @bound
, не являются
Для того, чтобы использовать @bound
, вам нужно будет разрешить Babel обрабатывать ваш TypeScript с помощью @babel/preset-typescript
вместе с @babel/preset-stage-2
.
В качестве альтернативы эта функциональность может быть (несколько) реализована с помощью этого пакета NPM:
Этот пакет @boundMethod
будет привязывать метод getWeekdays()
к результирующему объекту new Schedule()
в дополнение к его прототипу, но не будет скопирован Object.assign()
:
// console.log(new Schedule());
▼ Schedule { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ getWeekdays: function () { … }
▼ __proto__: Object { constructor: , getWeekdays: <accessor> }
▷ constructor: class Schedule { … }
▷ getWeekdays: getWeekdays() { … }
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
// console.log(clone);
▼ Object { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
чеэто потому, что декоратор @boundMethod
переопределяет методы доступа get
и set
метода для вызова .bind()
(поскольку значение this
в этих методах доступа установлено для объекта, которому назначено свойство), присоедините егок объекту с Object.defineProperty()
, затем верните PropertyDescriptor
для связанного метода, который имеет несколько интересных эффектов:
const instance = new Schedule();
console.log('instance:', instance);
console.log('\ninstance.hasOwnProperty(\'getWeekdays\'):', instance.hasOwnProperty('getWeekdays'));
console.log('\ninstance.getWeekdays():', instance.getWeekdays());
console.log('\ninstance.hasOwnProperty(\'getWeekdays\'):', instance.hasOwnProperty('getWeekdays'));
// console
instance:
▼ Schedule { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ getWeekdays: function () { … }
▷ __proto__: Object { constructor: , getWeekdays: <accessor> }
instance.hasOwnProperty('getWeekdays'): false
instance.getWeekdays():
▷ Array(0) []
instance.hasOwnProperty('getWeekdays'): true
Причина, по которой Object.assign()
не будет работать, на самом деле двойная:
- на самом деле вызывает
[[Get]]
на исходном объекте (т.е. new Schedule()
) и [[Set]]
на целевом объекте (т.е. {}
). - the
PropertyDescriptor
s, которые @boundMethod
использует для капитального ремонта * Средства доступа 1158 * не перечисляются.
Если бы мы изменили эту последнюю точку и использовали перечислимые средства доступа, мы могли бы заставить Object.assign()
работать, но только после getWeekdays()
уже был вызван хотя бы один раз:
const instance = new Schedule();
const clone1 = Object.assign({}, instance);
void instance.getWeekdays();
const clone2 = Object.assign({}, instance);
console.log('clone1:', clone1);
console.log('clone2:', clone2);
// console
clone1:
▼ Object { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
clone2:
▼ Object { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ getWeekdays: function () { … }
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }