this-value-strict.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. "use strict";
  2. // Note the globalThisValue and globalObject do not need to be the same.
  3. const globalThisValue = this;
  4. const globalObject = (0, eval)("this");
  5. // These tests are done in global state to ensure that is possible
  6. const globalArrow = () => {
  7. expect(this).toBe(globalThisValue);
  8. return this;
  9. };
  10. function globalFunction() {
  11. expect(this).toBe(undefined);
  12. expect(globalArrow()).toBe(globalThisValue);
  13. const arrowInGlobalFunction = () => this;
  14. expect(arrowInGlobalFunction()).toBe(undefined);
  15. return arrowInGlobalFunction;
  16. }
  17. expect(globalArrow()).toBe(globalThisValue);
  18. expect(globalFunction()()).toBe(undefined);
  19. const arrowFromGlobalFunction = globalFunction();
  20. const customThisValue = {
  21. isCustomThis: true,
  22. variant: 0,
  23. };
  24. const otherCustomThisValue = {
  25. isCustomThis: true,
  26. variant: 1,
  27. };
  28. describe("describe with arrow function", () => {
  29. expect(this).toBe(globalThisValue);
  30. test("nested test with normal function should get global object", function () {
  31. expect(this).toBe(undefined);
  32. });
  33. test("nested test with arrow function should get same this value as enclosing function", () => {
  34. expect(this).toBe(globalThisValue);
  35. });
  36. });
  37. describe("describe with normal function", function () {
  38. expect(this).toBe(undefined);
  39. test("nested test with normal function should get global object", function () {
  40. expect(this).toBe(undefined);
  41. });
  42. test("nested test with arrow function should get same this value as enclosing function", () => {
  43. expect(this).toBe(undefined);
  44. });
  45. });
  46. describe("basic behavior", () => {
  47. expect(this).toBe(globalThisValue);
  48. expect(arrowFromGlobalFunction()).toBeUndefined();
  49. expect(customThisValue).not.toBe(otherCustomThisValue);
  50. test("binding arrow function does not influence this value", () => {
  51. const boundGlobalArrow = globalArrow.bind({ shouldNotBeHere: true });
  52. expect(boundGlobalArrow()).toBe(globalThisValue);
  53. });
  54. function functionInArrow() {
  55. expect(arrowFromGlobalFunction()).toBeUndefined();
  56. return this;
  57. }
  58. function functionWithArrow() {
  59. expect(arrowFromGlobalFunction()).toBeUndefined();
  60. return () => {
  61. expect(arrowFromGlobalFunction()).toBeUndefined();
  62. return this;
  63. };
  64. }
  65. function strictFunction() {
  66. "use strict";
  67. return this;
  68. }
  69. test("functions get globalObject as this value", () => {
  70. expect(functionInArrow()).toBeUndefined();
  71. expect(functionWithArrow()()).toBeUndefined();
  72. });
  73. test("strict functions get undefined as this value", () => {
  74. expect(strictFunction()).toBeUndefined();
  75. });
  76. test("bound function gets overwritten this value", () => {
  77. const boundFunction = functionInArrow.bind(customThisValue);
  78. expect(boundFunction()).toBe(customThisValue);
  79. const boundFunctionWithArrow = functionWithArrow.bind(customThisValue);
  80. expect(boundFunctionWithArrow()()).toBe(customThisValue);
  81. // However we cannot bind the arrow function itself
  82. const failingArrowBound = boundFunctionWithArrow().bind(otherCustomThisValue);
  83. expect(failingArrowBound()).toBe(customThisValue);
  84. const boundStrictFunction = strictFunction.bind(customThisValue);
  85. expect(boundStrictFunction()).toBe(customThisValue);
  86. });
  87. });
  88. describe("functions on created objects", () => {
  89. const obj = {
  90. func: function () {
  91. expect(arrowFromGlobalFunction()).toBeUndefined();
  92. return this;
  93. },
  94. funcWithArrow: function () {
  95. expect(arrowFromGlobalFunction()).toBeUndefined();
  96. return () => this;
  97. },
  98. arrow: () => {
  99. expect(arrowFromGlobalFunction()).toBeUndefined();
  100. return this;
  101. },
  102. otherProperty: "yes",
  103. };
  104. test("function get this value of associated object", () => {
  105. expect(obj.func()).toBe(obj);
  106. });
  107. test("arrow function on object get above this value", () => {
  108. expect(obj.arrow()).toBe(globalThisValue);
  109. });
  110. test("arrow function from normal function from object has object as this value", () => {
  111. expect(obj.funcWithArrow()()).toBe(obj);
  112. });
  113. test("bound overwrites value of normal object function", () => {
  114. const boundFunction = obj.func.bind(customThisValue);
  115. expect(boundFunction()).toBe(customThisValue);
  116. const boundFunctionWithArrow = obj.funcWithArrow.bind(customThisValue);
  117. expect(boundFunctionWithArrow()()).toBe(customThisValue);
  118. const boundArrowFunction = obj.arrow.bind(customThisValue);
  119. expect(boundArrowFunction()).toBe(globalThisValue);
  120. });
  121. test("also works for object defined in function", () => {
  122. (function () {
  123. expect(arrowFromGlobalFunction()).toBeUndefined();
  124. // It is bound below
  125. expect(this).toBe(customThisValue);
  126. const obj2 = {
  127. func: function () {
  128. expect(arrowFromGlobalFunction()).toBeUndefined();
  129. return this;
  130. },
  131. arrow: () => {
  132. expect(arrowFromGlobalFunction()).toBeUndefined();
  133. return this;
  134. },
  135. otherProperty: "also",
  136. };
  137. expect(obj2.func()).toBe(obj2);
  138. expect(obj2.arrow()).toBe(customThisValue);
  139. }.bind(customThisValue)());
  140. });
  141. });
  142. describe("behavior with classes", () => {
  143. class Basic {
  144. constructor(value) {
  145. expect(this).toBeInstanceOf(Basic);
  146. this.arrowFunctionInClass = () => {
  147. return this;
  148. };
  149. this.value = value;
  150. expect(arrowFromGlobalFunction()).toBeUndefined();
  151. }
  152. func() {
  153. expect(arrowFromGlobalFunction()).toBeUndefined();
  154. return this;
  155. }
  156. }
  157. const basic = new Basic(14);
  158. const basic2 = new Basic(457);
  159. expect(basic).not.toBe(basic2);
  160. test("calling functions on class should give instance as this value", () => {
  161. expect(basic.func()).toBe(basic);
  162. expect(basic2.func()).toBe(basic2);
  163. });
  164. test("calling arrow function created in constructor should give instance as this value", () => {
  165. expect(basic.arrowFunctionInClass()).toBe(basic);
  166. expect(basic2.arrowFunctionInClass()).toBe(basic2);
  167. });
  168. test("can bind function in class", () => {
  169. const boundFunction = basic.func.bind(customThisValue);
  170. expect(boundFunction()).toBe(customThisValue);
  171. const boundFunction2 = basic2.func.bind(otherCustomThisValue);
  172. expect(boundFunction2()).toBe(otherCustomThisValue);
  173. });
  174. });
  175. describe("derived classes behavior", () => {
  176. class Base {
  177. baseFunction() {
  178. expect(arrowFromGlobalFunction()).toBeUndefined();
  179. return this;
  180. }
  181. }
  182. class Derived extends Base {
  183. constructor(value) {
  184. expect(arrowFromGlobalFunction()).toBeUndefined();
  185. const arrowMadeBeforeSuper = () => {
  186. expect(this).toBeInstanceOf(Derived);
  187. return this;
  188. };
  189. super();
  190. expect(arrowMadeBeforeSuper()).toBe(this);
  191. this.arrowMadeBeforeSuper = arrowMadeBeforeSuper;
  192. this.arrowMadeAfterSuper = () => {
  193. expect(this).toBeInstanceOf(Derived);
  194. return this;
  195. };
  196. this.value = value;
  197. }
  198. derivedFunction() {
  199. expect(arrowFromGlobalFunction()).toBeUndefined();
  200. return this;
  201. }
  202. }
  203. test("can create derived with arrow functions using this before super", () => {
  204. const testDerived = new Derived(-89);
  205. expect(testDerived.arrowMadeBeforeSuper()).toBe(testDerived);
  206. expect(testDerived.arrowMadeAfterSuper()).toBe(testDerived);
  207. });
  208. test("base and derived functions get correct this values", () => {
  209. const derived = new Derived(12);
  210. expect(derived.derivedFunction()).toBe(derived);
  211. expect(derived.baseFunction()).toBe(derived);
  212. });
  213. test("can bind derived and base functions", () => {
  214. const derived = new Derived(846);
  215. const boundDerivedFunction = derived.derivedFunction.bind(customThisValue);
  216. expect(boundDerivedFunction()).toBe(customThisValue);
  217. const boundBaseFunction = derived.baseFunction.bind(otherCustomThisValue);
  218. expect(boundBaseFunction()).toBe(otherCustomThisValue);
  219. });
  220. });
  221. describe("proxy behavior", () => {
  222. test("with no handler it makes no difference", () => {
  223. const globalArrowProxyNoHandler = new Proxy(globalArrow, {});
  224. expect(globalArrowProxyNoHandler()).toBe(globalThisValue);
  225. });
  226. test("proxy around global arrow still gives correct this value", () => {
  227. let lastThisArg = null;
  228. const handler = {
  229. apply(target, thisArg, argArray) {
  230. expect(target).toBe(globalArrow);
  231. lastThisArg = thisArg;
  232. expect(this).toBe(handler);
  233. return target(...argArray);
  234. },
  235. };
  236. const globalArrowProxy = new Proxy(globalArrow, handler);
  237. expect(globalArrowProxy()).toBe(globalThisValue);
  238. expect(lastThisArg).toBeUndefined();
  239. const boundProxy = globalArrowProxy.bind(customThisValue);
  240. expect(boundProxy()).toBe(globalThisValue);
  241. expect(lastThisArg).toBe(customThisValue);
  242. expect(globalArrowProxy.call(15)).toBe(globalThisValue);
  243. expect(lastThisArg).toBe(15);
  244. });
  245. });
  246. describe("derived classes which access this before super should fail", () => {
  247. class Base {}
  248. test("direct access of this should throw reference error", () => {
  249. class IncorrectConstructor extends Base {
  250. constructor() {
  251. this.something = "this will fail";
  252. super();
  253. }
  254. }
  255. expect(() => {
  256. new IncorrectConstructor();
  257. }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
  258. });
  259. test("access of this via a arrow function", () => {
  260. class IncorrectConstructor extends Base {
  261. constructor() {
  262. const arrow = () => this;
  263. arrow();
  264. super();
  265. }
  266. }
  267. expect(() => {
  268. new IncorrectConstructor();
  269. }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
  270. });
  271. test("access of this via a eval", () => {
  272. class IncorrectConstructor extends Base {
  273. constructor() {
  274. eval("this.foo = 'bar'");
  275. super();
  276. }
  277. }
  278. expect(() => {
  279. new IncorrectConstructor();
  280. }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
  281. });
  282. test.skip("access of this via a eval in arrow function", () => {
  283. class IncorrectConstructor extends Base {
  284. constructor() {
  285. const arrow = () => eval("() => this")();
  286. arrow();
  287. super();
  288. }
  289. }
  290. expect(() => {
  291. new IncorrectConstructor();
  292. }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
  293. });
  294. test("access of this via arrow function even if bound with something else", () => {
  295. class IncorrectConstructor extends Base {
  296. constructor() {
  297. const arrow = () => this;
  298. const boundArrow = arrow.bind(customThisValue);
  299. boundArrow();
  300. super();
  301. }
  302. }
  303. expect(() => {
  304. new IncorrectConstructor();
  305. }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
  306. });
  307. });
  308. describe("in strict mode primitive this values are not converted to objects", () => {
  309. const array = [true, false];
  310. // Technically the comma is implementation defined here. (Also for tests below.)
  311. expect(array.toLocaleString()).toBe("true,false");
  312. test("directly overwriting toString", () => {
  313. let count = 0;
  314. Boolean.prototype.toString = function () {
  315. count++;
  316. return typeof this;
  317. };
  318. expect(array.toLocaleString()).toBe("boolean,boolean");
  319. expect(count).toBe(2);
  320. });
  321. test("overwriting toString with a getter", () => {
  322. let count = 0;
  323. Object.defineProperty(Boolean.prototype, "toString", {
  324. get() {
  325. count++;
  326. const that = typeof this;
  327. return function () {
  328. return that;
  329. };
  330. },
  331. });
  332. expect(array.toLocaleString()).toBe("boolean,boolean");
  333. expect(count).toBe(2);
  334. });
  335. });