Сделать объект или свойство класса только доступным - PullRequest
6 голосов
/ 21 сентября 2019

TL: DR;Можно ли сделать свойство объекта только для вызова (как функции)?

Что я подразумеваю под этим

class Foo{
  bar(value){
    return value
  }
}

let newFoo = new Foo()

console.log(newFoo.bar(123))  // should work fine as function is invoked
console.log(newFoo.bar)  // here i need to throw or display an error instead of returning value

Я пытался сделать это с Proxy и handler.get ловушкой, но я понятия не имею, как захватитьбудь то вызов функции или просто доступ к свойству,

class Foo {
  bar(value) {
    return value
  }
}


const proxied = new Proxy(new Foo(), {
  get: function(target, prop, reciver) {
    if (prop === 'bar') {
      throw new Error('Bar is method need to be invoced')
    }
    return target[prop]
  }
})

console.log(proxied.bar(true))
console.log(proxied.bar)

Я также проверил handler.apply, но это также, кажется, бесполезно, так как это ловушка для функции, а не длясвойство

class Foo {
  bar(value) {
    return value
  }
}


const proxied = new Proxy(new Foo(), {
  apply: function(target, thisArg, argumentsList) {
    return target(argumentsList[0])
  },
  get: function(target, prop, reciver) {
    if (prop === 'bar') {
      throw new Error('Bar is method need to be invoced')
    }
    return target[prop]
  }
})

console.log(proxied.bar(true))
console.log(proxied.bar)

1 Ответ

2 голосов
/ 21 сентября 2019

Нет, это невозможно.Не существует различия между

const newFoo = new Foo()
newFoo.bar(123);

и

const newFoo = new Foo()
const bar = newFoo.bar;
Function.prototype.call.call(bar, newFoo, 123); // like `bar.call(newFoo, 123)`
// or Reflect.apply(bar, newFoo, [123]);

, т.е. ни newFoo, ни bar не могут отличить их "изнутри".Теперь между доступом к свойству и вызовом метода могут произойти произвольные вещи, и во время доступа к свойству вы не можете знать, что произойдет дальше, поэтому вы не можете вызвать исключение преждевременно.Вызов метода может произойти никогда (в newFoo.bar;), и нет способа распознать это только из newFoo.

Единственный подход - перехватить все другие обращения к newFoo и его свойствам,и throw после вы обнаружили вредную последовательность;возможно, ваш «линтер» проверит последовательность извне после запуска всей программы:

const lint = {
  access: 0,
  call: 0,
  check() {
    console.log(this.access == this.call
      ? "It's ok"
      : this.access > this.call
        ? "method was not called"
        : "property was reused");
  },
  run(fn) {
    this.call = this.access = 0;
    try {
      fn();
    } finally {
      this.check();
    }
  }
}

function bar(value) {
  lint.call++; lint.check();
  return value;
}
class Foo {
  get bar() {
    lint.check(); lint.access++;
    return bar;
  }
}
lint.run(() => {
  const newFoo = new Foo;
  newFoo.bar(123);
});
lint.run(() => {
  const newFoo = new Foo;
  newFoo.bar;
});
lint.run(() => {
  const newFoo = new Foo;
  const bar = newFoo.bar;
  bar(123);
  bar(456);
});

Возможно, лучшим решением будет написать собственный интерпретатор для простых выражений,который позволял бы только вызовы методов.

...