2

I just discovered that current iOS doesn't support ES2015 and Proxy yet. I have a very small Proxy object in my code, which I'm using to allow a function to be called upon access to myobject.non_formally_defined_property, where I have no explicit property or property getter defined for the property in question.

How I can implement ES2015-compatible Proxy-like behavior? I want to continue to keep things the way they are. (A refactor would probably be ~20 lines of code, but the result would be a mess - I'm using this to implement initialization-time values for running configuration/state.)

After learning about defineProperty I tried wiring that up to use the same function for all property accesses, but I'm very perplexed by the specific way it didn't work.

Specifically, I did this:

'use strict';

var arr = {
  one: 1,
  two: 2,
  three: 3
}

var obj = {};

for (var item in arr) {
  Object.defineProperty(obj, item, {
    get: function (value) {
      var local1 = item;
      var local2 = (' ' + item).slice(1);
      console.log('get: item=' + item + '  local1=' + local1 + "  local2=" + local2);
    },
  });
}

obj.one;
<pre id=log></pre>

If you click Run Code, you'll see that console.log() prints item=three local1=three local2=three.

This is very confusing to me because

  • I don't think I'm overwriting the setter - I'm running defineProperty on the same object, yes, but on a different item each time - so the closures should be different, right? - but I'm seeing three.

    The only way I can reason about that is that

    • That was the final loop value when the last closure (which overwrite the others?) was created, or

    • item, local1 and local2 are somehow being fished out of the parent lexical scope, and of course they're all three from the final loop iteration. This doesn't make any sense though, because...

  • ...var creates locally-scoped variables which are independent. To ensure the local* variables were unique, I local1 is a simple = association, and local2 is a "deep copy" hack I grabbed from here just in case the copied string was linked by reference to the original.

So by every metric I can think of, the closures should be different, and I should be seeing the right variable show up. I'm very curious to understand why my approach doesn't work, but I'm especially interested to know how I can get getters/setters working!

Note that I want both get and set functionality, but I've only discussed getting here for simplicity.

I do not want to use any libraries. In my case this is because the JavaScript code is going into an HTML file I am emailing someone, I only want to email one file, and some pregenerated tables elsewhere in the HTML are already using 300KB+.

i336_
  • 1,813
  • 1
  • 20
  • 41
  • Is your question why your `defineProperty` code doesn't work? – T.J. Crowder Sep 03 '17 at 10:28
  • 1
    ...And now I get what you mean, thanks for the persistent clarification :) I updated the title (it previously said "ES2015/Safari"). – i336_ Sep 03 '17 at 10:29
  • The TL;DR of my question is "how do I get Proxy-like behavior? I tried this approach and it didn't work. Is my approach on the right track, or do I need to try something else?" – i336_ Sep 03 '17 at 10:30
  • Ack, marking the duplicate deleted my previous comment which had a lot more than just the duplicate link in it. *sigh* Anyway, marked as duplicate because if you just need the property to behave correctly, yes, you're on the right track with `defineProperty` and it's just a closures-in-loops error. If Safari handles `let` correctly, just change the `var` in `for (var item in arr) {` to `let`. If it doesn't, the linked question's answers provide solutions that work even in ES5. – T.J. Crowder Sep 03 '17 at 10:36
  • The answer to the general case "How do I get Proxy-like behavior" is: Other than getters/setters, you can't; Proxy isn't polyfillable. But what you've done is not only a solid way to work around not having Proxy in Safari, it's probably a better solution than Proxy was anyway. :-) Proxy is very expensive. – T.J. Crowder Sep 03 '17 at 10:37
  • @T.J.Crowder: I did actually see the other comment before it got chewed, FWIW. iOS [doesn't support `let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Browser_compatibility), so I went with [this approach](https://stackoverflow.com/a/19324832/3229684) instead. – i336_ Sep 03 '17 at 11:12
  • Looks like MDN's out of date, I got curious a while ago and tested it in iOS Safari on my iPad (iOS 10.3.3): http://jsfiddle.net/Lt4L2kr4/ That shows 012, whereas it would show 333 if `let` weren't handled correctly in that loop. (For example, IE11 shows 333 because although it supports a version of `let`, it doesn't support ES2015's semantics for it.) – T.J. Crowder Sep 03 '17 at 11:17

0 Answers0