Javascript const coming up as undefined and not un

2019-07-10 04:27发布

问题:

I have a const newProducts that is returning undefined and yet says it isn't undefined in the chrome dev console:

It says it is defined in the yellow highlighted text in the console, and undefined when hovering over it. It shouldn't be undefined. I have stepped through the code and the returns inside the map are returning values. Why is this const coming up undefined and also not undefined?

searchResults.reducer.js

// @flow
import initialState from '../../config/config'
import {
  FETCH_PRODUCTS_REJECTED,
  UPDATE_SEARCH_RESULTS
} from '../search-page/searchPage.action'
import { SET_SELECTED_SHOP } from '../search-results/searchResults.action'

const searchResults = (
  initialResultsState: [] = initialState.get('searchResults'),
  action: Object): string => {
  switch (action.type) {
    case UPDATE_SEARCH_RESULTS: {
      const newProducts = action.payload.products.map(product => {
        return {
          shop: {
            id: product.shop[0].f1,
            name: product.shop[0].f2,
            coordinate: {
              latitude: product.shop[0].f4,
              longitude: product.shop[0].f3
            },
            selected: false
          },
          products: product.products
        }
      })
      return initialResultsState.set('products', newProducts)
    }
    case SET_SELECTED_SHOP: {
      const newProducts = initialResultsState.get('products').map(product => {
        if (product.shop.id === action.payload) {
          return {
            shop: {
              ...product.shop,
              selected: true
            },
            products: product.products
          }
        } else {
          return {
            shop: {
              ...product.shop,
              selected: false
            },
            products: product.products
          }
        }
      })
      return initialResultsState.set('products', newProducts)
    }
    case FETCH_PRODUCTS_REJECTED: {
      console.log('there was an issue getting products: ',
        action.payload)
      return initialResultsState
    }
    default:
      return initialResultsState
  }
}

export default searchResults

回答1:

I think it's a side effect of what transpilation does with const, as after transpilation the JS runtime actually has to track 2 different variables.

React Native uses Babel to transpile (source, pointing at line in 0.45.1 where it enables block scoping).

You define a constant with same name several times in different blocks, it's OK in ES2015 as constants are block-scoped, but this concept does not exist in ES5, so such constants are transpiled to variables with different names.

For example, consider this ES2015 snippet:

const i = Date.now() % 2;
switch(i) {
    case 0: {
        const x = "zero";
        console.log("x", x);
    }
    case 1: {
        const x = "one";
        console.log("x", x);
    }
    default: {
        const x = "wat";
        console.log("x", x);
    }
}

With Babel, it gets transpiled (← see it in action) to:

"use strict";

var i = Date.now() % 2;
switch (i) {
    case 0:
        {
            var x = "zero";
            console.log("x", x);
        }
    case 1:
        {
            var _x = "one";
            console.log("x", _x);
        }
    default:
        {
            var _x2 = "wat";
            console.log("x", _x2);
        }
}

So in this example the JS runtime actually has 3 different variables to represent x.

So you might be looking at a line with foo, which your browser shows via a source map, but in "reality" what the browser is looking at might be _foo2, so depending on a lot of things (transpilation settings, source map generation, Chrome version, rest of the code, where exactly you are in the call stack...), Chrome dev tools might have trouble to track this and decide which of foo or _foo or _foo2 it should pick when you look at foo.

Example with ES2015 transpiled to ES5 and source map:

(behavior is slightly different, as it depends on a lot of parameters, but it shows the issue with transpiling of constants with identical names -- in another test, with different transpiling + sourcemapping parameters, I managed to get something similar to your situation)


Suggestion

(I expect the following to be very obvious to O.P., but could be useful to leave in the answer)

With const semantics, you have 2 different constants with the same name, in 2 different blocks. In my opinion it's useful to define a constant (or variable using let instead of var) with a block scope, in order to avoid surprises with var scoping (scoped to the nearest parent function), but defining variables or constants with the same name in 2 neighboring blocks is calling for confusion (transpiled or not).

It would make better sense to decide whether or not those 2 symbols represent the same thing or not (up to you), and then:

  • if so, use let to define them as a single variable for the parent scope.

  • if not, keep them as const but with 2 different names. Code will be clearer, and after transpilation, debugging information shown by dev tools should be more useful.