How do I translate the following code to an ES6 class:
var Mergesort = (function() {
function Mergesort(){}
Object.define(Mergesort.prototype,'readOnlyMethod',{
value:function(){/*DO SOMETHING, RETURN SOME VAL*/},
configurable:false,
writable:false,
enumerable:false
});
return Mergesort;
})()
The literal ES6 translation would be
const Mergesort = (() => {
class Mergesort {}
Object.defineProperty(Mergesort.prototype, 'readOnlyMethod', {
value() { /*DO SOMETHING, RETURN SOME VAL*/ },
configurable: false,
writable: false,
enumerable: false
});
return Mergesort;
})();
but assuming you want to define the method as part of the class expression, just change the property descriptor afterwards:
class Mergesort {
readOnlyMethod() { /*DO SOMETHING, RETURN SOME VAL*/ }
}
Object.defineProperty(Mergesort.prototype, 'readOnlyMethod', {configurable:false, writable:false});
Related
I use babel with preset-env, when I use a class with properties it transform it with _defineProperty
example:
class A {
foo = 'bar'
}
is transformed to:
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class A {
constructor() {
_defineProperty(this, "foo", 'bar');
}
}
this transformation seems to be caused by #babel/plugin-proposal-class-properties but I do not understand what this transformation provides nor to which new feature of ECMAScript it corresponds.
In our application we compress our JavaScript classes using UglifyJS which doesn't support being able to compress syntax like onBlur = (event) => {} as it returns Unexpected token: operator (=).
To solve this problem we have used the following function to define them:
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
And then we use it inside our constructor like so:
class OurClass {
constructor(...args) {
super(...args);
_defineProperty(this, "onBlur", event => {
});
}
}
Which works great! however it doesn't work for defining getters:
static get values() {
return { index: Number }
}
Like so:
_defineProperty(this, 'values', () => {
return { index: Number };
});
The getter is never defined and isn't accessible in the rest of the class in the same way that we were able to define the other methods...
How can we define the getter using this same function with defineProperty?
Object.defineProperty accepts two different formats for its third argument:
data descriptor, which you currently use
accessor descriptor, which allows for defining a getter/setter
So, for instance, you could extend your own _defineProperty function with an optional argument to indicate that a getter/setter is intended:
function _defineProperty(obj, key, value, accessor) {
if (accessor == "getter") {
Object.defineProperty(obj, key, {
get: value,
enumerable: true,
configurable: true,
});
} else if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// simple demo
class MyClass {
constructor() {
_defineProperty(this, "num", () => 3, "getter");
}
}
console.log(new MyClass().num);
If you want it as a static method, then define it on the class (i.e. on the constructor):
function _defineProperty(obj, key, value, accessor) {
if (accessor == "getter") {
Object.defineProperty(obj, key, {
get: value,
enumerable: true,
configurable: true,
});
} else if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// simple demo
class MyClass {}
_defineProperty(MyClass, "num", () => 3, "getter");
console.log(MyClass.num);
This is the source code autobind.js.
I am confused of the getter method.The getter method return a boundFn, but also define a boundFn on "this",why? For the efficiency?
get() {
// ...omit
const boundFn = bind(fn, this);
defineProperty(this, key, {
configurable: true,
writable: true,
// NOT enumerable when it's a bound method
enumerable: false,
value: boundFn
});
return boundFn;
},
I test like this.
function Foo(){}
Object.defineProperty(Foo.prototype, 'test', {
get() {
console.log('123');
Object.defineProperty(this, 'test', {
get(){
console.log('456');
return '456';
}
})
return '123'
}
});
var a = new Foo();
a.test; //FirstTime: 123,
a.test; //and then always: 456,
For the efficiency.It will not always lookup the prototype chain.I think so.
Here is my way I usually use to prevent to override name property.
let _name = Symbol('name');
class Cat {
constructor(name) {
this[_name] = name;
}
set name(newName) {
return this[_name];
}
get name() {
return this[_name];
}
}
// default name
let cat = new Cat('Hermione');
// new name
cat.name = 'Voldermort';
// testing
console.log(cat.name);
My idea: Saving the value of name property to another variable.
But if I have multiple class properties, like this:
that would be wasting to create a lot of variables for saving.
let _Min = Symbol('Min'), _Max = Symbol('Max'); // and so on
Is there another way to achieve this goal? Thank you!
In order to be read-only, the property should have no set accessor:
class Foo {
get bar() {
return 'bar';
}
}
If the property is supposed to be defined on construction, the descriptor can be defined too:
class Cat {
constructor(name) {
Object.defineProperty(this, name, {
get: () => name,
configurable: true
});
}
}
Or
class Cat {
constructor(name) {
Object.defineProperty(this, name, {
value: name,
writable: false,
configurable: true
});
}
}
Try following examples, which may solve your problem
Example 1:
class A {
a = () => {
console.log('A#a');
}
}
class B extends A {
// this will throw error as
// Class 'A' defines instance member property 'a', but extended class 'B'
// defines it as instance member function.
a() {
console.log('B#a')
}
}
new B().a()
/**
Error: Class 'A' defines instance member property 'a', but extended class 'B'
defines it as instance member function.
*/
Example 2:
class Renderer {
constructor(args) {
Object.defineProperty(this, 'render', {
writable: false,
configurable: true,
value: () {
return this.childRender();
}
});
}
childRender() {
throw Error('render() is already instance of Renderer. Did you mean? childRender()');
}
// do not define methodName() here
}
class Draw extends Renderer {
render() {
console.log('DrawB#render');
}
}
new Draw().render()
/**
VM1597:13 Uncaught Error: render() is already instance of Renderer. Did you mean? childRender()
at Drawee.childRender (<anonymous>:13:11)
at Drawee.value (<anonymous>:7:22)
at <anonymous>:25:14
*/
I hope this helps 🙂
Some ES6 features are really easy to polyfill:
if(!Array.prototype.find){
Array.prototype.find=...
}
How would you polyfill new.target? It triggers a syntax error when it's used in an unsupported browser. try/catch doesn't work because it's a syntax error. I don't have to use new.target, I'm mostly just curious.
As Jaromanda commented, you cannot polyfill new syntax, but you can easily work around some new.target use cases for now
Taking a look at the new.target docs you'll see some examples that can easily be written with es5
with new.target
function Foo() {
if (!new.target) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
without
function Foo() {
if (!(this instanceof Foo)) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
with new.target
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A { constructor() { super(); } }
var a = new A(); // logs "A"
var b = new B(); // logs "B"
without
class A {
constructor() {
// class forces constructor to be called with `new`, so
// `this` will always be set
console.log(this.constructor.name);
}
}
class B extends A { constructor() { super(); } }
var a = new A(); // logs "A"
var b = new B(); // logs "B"
Hope this helps a little
Here's a way using Function::bind:
const requireNew = (() => {
const kFake = {};
const CtorMap = new WeakMap();
const FuncToString = function toString () {
const info = CtorMap.get(this);
return Function.prototype.toString.apply(
info ? info.ctor : this, arguments);
};
const GetProto = function prototype() {
const info = CtorMap.get(this);
return info ? info.ctor.prototype : undefined;
};
const SetProto = function prototype(prototype) {
const info = CtorMap.get(this);
return !info ? prototype
: info.wrapper.prototype = info.ctor.prototype = prototype;
}
return (Ctor) => {
const wrapper = function () {
if (this === kFake) {
throw new TypeError("Please use 'new' to call this");
}
return Ctor.apply(this, arguments);
}
wrapper.prototype = Ctor.prototype;
const bound = wrapper.bind(kFake);
CtorMap.set(bound, { ctor: Ctor, wrapper });
Object.defineProperties(bound, {
prototype: { get: GetProto, set: SetProto,
enumerable: false, configurable: true },
name: { value: Ctor.name, writable: false,
enumerable: false, configurable: true },
length: { value: Ctor.length, writable: false,
enumerable: false, configurable: true },
toString: { value: FuncToString, writable: true,
enumerable: false, configurable: true }
});
return bound;
}
})();
And here's a simple demo:
function Abc (a) {
this.a = a;
console.log("this =", this, "; .a =", this.a, "; new.target:", new.target);
}
const A = requireNew(Abc);
const a = new A(1);