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.
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.
- 5 you can't polyfill new syntax – Jaromanda X Commented Oct 27, 2015 at 5:02
- 3 And looks like babel doesn't transpile this yet: github./babel/babel/issues/1088 – S McCrohan Commented Oct 27, 2015 at 5:20
3 Answers
Reset to default 7As Jaromanda mented, 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);
For me it depends what I'm trying to do. The following works in ES3 to get the constructor function that was used with the new
operator:
function getTarget(thisObj) {
for(i in thisObj) {// test thisObj for enumerable properties
if ({}.hasOwnProperty.call(thisObj, i)) {
return;// not called with new
}
}
try {
var i
, C = thisObj.constructor//throws if thisObj is undefined
if (thisObj instanceof C) return C// throws if not a constructor
}
catch(e) {
}
}
function MyClass() {
if (getTarget(this)) {
// `this` is an instance of MyClass or another class if MyClass.call()
// was used to pass a different subclassed instance as the `this` value
}
}
But if all I want to know is if MyClass was called with new
, then Function.prototype.bind() is a clever way of handling it because bound functions have a bound this
arg and therefore when a bound function's this
value isn't what it's expected to be, that means it was called with the new
operator. this.constructor
then contains the new.target
value:
function MyClass() {
if (this===window) {
console.log('definitely not called with new');
}
else {
console.log('new.target value is ', this.constructor);
}
}
MyClass = MyClass.bind(window);
var myclass = new MyClass; // new.target value is function MyClass() {...
MyClass.call(myclass);// definitely not called with new
But personally I prefer the first way of doing it so that MyClass.call() can pass a subclassed this
value. Technically that would mean that it wasn't necessarily called with new
. But before ES6, inheritance was super tricky and you had to use hacks like that to make it work.