this-value.js 13 KB

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