-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathnewless.js
207 lines (186 loc) · 8.69 KB
/
newless.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
(function(global) {
"use strict";
// Used to track the original wrapped constructor on a newless instance
var TRUE_CONSTRUCTOR = global.Symbol
? Symbol("trueConstructor")
: "__newlessTrueConstructor__";
// Test whether a given new syntax is supported
function isSyntaxSupported(example) {
try { return !!Function("", "'use strict';" + example); }
catch (error) { return false; }
}
// Polyfills for get/set prototype
var getPrototype = Object.getPrototypeOf || function getPrototype(object) {
return object.__proto__ ||
(object.constructor && object.constructor.prototype) ||
Object.prototype;
};
var setPrototype = Object.setPrototypeOf ||
function setPrototypeOf(object, newPrototype) {
object.__proto__ = newPrototype;
};
// Polyfill for Reflect.construct
var construct = global.Reflect && global.Reflect.construct || (function() {
if (isSyntaxSupported("class Test {}")) {
// The spread operator is *dramatically faster, so use it if we can:
// http://jsperf.com/new-via-spread-vs-dynamic-function/4
var supportsSpread = isSyntaxSupported("Object(...[{}])");
return Function("constructor, args, target",
"'use strict';" +
"target = target || constructor;" +
// extend target so the right prototype is constructed (or nearly the
// right one; ideally we'd do instantiator.prototype = target.prototype,
// but a class's prototype property is not writable)
"class instantiator extends target {};" +
// but ensure the *logic* is `constructor` for ES2015-compliant engines
"Object.setPrototypeOf(instantiator, constructor);" +
// ...and for Safari 9
"instantiator.prototype.constructor = constructor;" +
(supportsSpread ?
"var value = new instantiator(...([].slice.call(args)));" :
// otherwise, create a dynamic function in order to use `new`
// Note using `function.bind` would be simpler, but is much slower:
// http://jsperf.com/new-operator-with-dynamic-function-vs-bind
"var argList = '';" +
"for (var i = 0, len = args.length; i < len; i++) {" +
"if (i > 0) argList += ',';" +
"argList += 'args[' + i + ']';" +
"}" +
"var constructCall = Function('constructor, args'," +
"'return new constructor(' + argList + ');'" +
");" +
"var value = constructCall(constructor, args);"
) +
// fix up the prototype so it matches the intended one, not one who's
// prototype is the intended one :P
"Object.setPrototypeOf(value, target.prototype);" +
"return value;"
);
}
else {
var instantiator = function() {};
return function construct(constructor, args, target) {
instantiator.prototype = (target || constructor).prototype;
var instance = new instantiator();
var value = constructor.apply(instance, args);
return (typeof value === "object" && value) || instance;
}
}
})();
// ES2015 class methods are non-enumerable; we need a helper for copying them.
var SKIP_PROPERTIES = ["arguments", "caller", "length", "name", "prototype"];
function copyProperties(source, destination) {
if (Object.getOwnPropertyNames && Object.defineProperty) {
var properties = Object.getOwnPropertyNames(source);
if (Object.getOwnPropertySymbols) {
properties = properties.concat(Object.getOwnPropertySymbols(source));
}
for (var i = properties.length - 1; i >= 0; i--) {
if (SKIP_PROPERTIES.indexOf(properties[i]) === -1) {
Object.defineProperty(
destination,
properties[i],
Object.getOwnPropertyDescriptor(source, properties[i]));
}
}
}
else {
for (var property in source) {
destination[property] = source[property];
}
}
}
var newless = function(constructor) {
var name = constructor.name || "";
// create a list of arguments so that the newless constructor's `length`
// is the same as the original constructor's
var argumentList = [];
for (var i = constructor.length; i > 0; i--) {
argumentList.unshift("a" + i);
}
// V8 and newer versions of JSCore return the full class declaration from
// `toString()`, which lets us be a little smarter and more performant
// about what to do, since we know we are dealing with a "class". Note,
// however, not all engines do this. This could be false and the constructor
// might still use class syntax.
var usesClassSyntax = constructor.toString().substr(0, 5) === "class";
// code to call a *function* constructor with the current `this` context
var applyThisAndReturn =
"var returnValue = constructor.apply(this, arguments);" +
"return (typeof returnValue === 'object' && returnValue) || this;";
// Create and call a function that returns a wrapped constructor that can
// be called without new. This is needed in order to give the wrapper some
// otherwise unalterable properties like name and arguments.
var newlessConstructor = Function("constructor, construct, setPrototype",
"var requiresNew = " + (usesClassSyntax ? true : null) + ";" +
"var newlessConstructor = function " + name +
"(" + argumentList.join(",") + ") {" +
// If called with an already valid `this`, preserve that `this` value
// in the super-type's constructor whenever possible. With function
// constructors (as opposed to class constructors), it's possible to
// alter the instance before calling the super constructor--so it's
// important to preserve that instance if at all possible.
"if (!requiresNew && this instanceof newlessConstructor) {" +
// requiresNew = `false` indicates we know the `new` operator isn't
// necessary for this constructor, but `null` indicates uncertainty,
// so the call needs to handle potential errors the first time in
// order to determine whether `new` is definitely required.
"if (requiresNew === false) {" +
applyThisAndReturn +
"}" +
"try {" +
"requiresNew = false;" +
applyThisAndReturn +
"}" +
"catch (error) {" +
// Do our best to only capture errors triggred by class syntax.
// Unfortunately, there's no special error type for this and the
// message is non-standard, so this is the best check we can do.
"if (!(error instanceof TypeError &&" +
" /class constructor/i.test(error.message))) {" +
"throw error;" +
"}" +
// mark this constructor as requiring `new` for next time
"requiresNew = true;" +
"}" +
"}" +
// make a reasonably good replacement for `new.target` which is a
// syntax error in older engines
"var newTarget = (this instanceof newlessConstructor) ? " +
"this.constructor : constructor;" +
"var returnValue = construct(constructor, arguments, newTarget);" +
// best effort to make things easy for functions inheriting from classes
"if (this instanceof newlessConstructor) {" +
"setPrototype(this, returnValue);" +
"}" +
"return returnValue;" +
"};" +
"return newlessConstructor;")(constructor, construct, setPrototype);
copyProperties(constructor, newlessConstructor);
newlessConstructor.prototype = constructor.prototype;
// NOTE: *usually* the below will already be true, but we ensure it here.
// Safari 9 requires this for the `super` keyword to work. Newer versions
// of WebKit and other engines do not. Instead, they use the constructor's
// prototype chain (which is correct by ES2015 spec) (see below).
newlessConstructor.prototype.constructor = constructor;
// for ES2015 classes, we need to make sure the constructor's prototype
// is the super class's constructor. Further, optimize performance by
// pointing at the actual constructor implementation instead of the
// newless wrapper (in the case that it is wrapped by newless).
newlessConstructor[TRUE_CONSTRUCTOR] = constructor;
var superConstructor = getPrototype(constructor);
var realSuperConstructor = superConstructor[TRUE_CONSTRUCTOR];
setPrototype(newlessConstructor, realSuperConstructor || superConstructor);
if (realSuperConstructor) {
setPrototype(constructor, realSuperConstructor);
}
return newlessConstructor;
};
// support Node and browser
if (typeof module !== "undefined") {
module.exports = newless;
}
else {
global.newless = newless;
}
}(this));