简体   繁体   English

使用IIFE方法并将变量从一个文件传递到另一个文件

[英]Use an IIFE approach and pass a variable from one file to another

One of my previous questions was how to organize the code between multiple .js files. 我之前的一个问题是如何在多个.js文件之间组织代码。 Now I have a problem. 现在我有一个问题。

I have a map in d3.js divided by countries. 我在d3.js中有一张按国家划分的地图。 When the user double-clicks on a country, I would like to pass a variable to another js file. 当用户双击某个国家/地区时,我想将变量传递给另一个js文件。

This is my html file, index.hbs : 这是我的html文件index.hbs

<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
        <script src='https://d3js.org/topojson.v2.min.js'></script>
        <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>

        <link href='/css/all.css' rel='stylesheet'/>
    </head>

    <body>
        <div id='map'></div>

        <script> 
            var viewData = {};  
            viewData.nuts0 = JSON.parse('{{json nuts0}}'.replace(/&quot;/g, '"').replace(/&lt;/, ''));
            viewData.CONFIG = JSON.parse('{{json CONFIG}}'.replace(/&quot;/g, '"').replace(/&lt;/, '')); 
        </script>

        <script src='/script/map.js' rel='script'/><{{!}}/script>
        <script src='/script/other.js' rel='script'/><{{!}}/script>
    </body>
</html>

map.js : map.js

var NAME=(function map() {

    var my = {};

    var CONFIG = viewData.CONFIG;
    var nuts0 = viewData.nuts0;

    // paths
    var countries;

    // width and height of svg map container
    var width = CONFIG.bubbleMap.width;
    var height = CONFIG.bubbleMap.height;

    // to check if user clicks or double click
    var dblclick_timer = false;

    // create Hammer projection
    var projectionCurrent = d3.geoHammer() 
        .scale(1) 
        .translate([width/2, height/2]); 

    var projectionBase = d3.geoHammer()
        .scale(1) 
        .translate([width/2, height/2]);

    // creates a new geographic path generator with the default settings. If projection is specified, sets the current projection
    var path = d3.geoPath().projection(projectionCurrent);

    // creates the svg element that contains the map
    var map = d3.select('#map');

    var mapSvg = map.append('svg')
        .attr('id', 'map-svg')
        .attr('width', width)
        .attr('height', height);

    var mapSvgGCountry = mapSvg.append('g').attr('id', 'nuts0');

    countries = topojson.feature(nuts0, nuts0.objects.nuts0);
    projectionCurrent.fitSize([width, height], countries);

    var mapSvgGCountryPath = mapSvgGCountry.selectAll('path')
        .data(countries.features)
        .enter()
        .append('path');

    mapSvgGCountryPath.attr('class', 'country')
        .attr('fill', 'tomato')
        .style('stroke', 'white')
        .style('stroke-width', 1) 
        .attr('d', path)
        .attr('id', function(c) {
            return 'country' + c.properties.nuts_id;
        })
        .on('click', clickOrDoubleCountry);

    function clickOrDoubleCountry(d, i) {
        if(dblclick_timer) { // double click
            clearTimeout(dblclick_timer);
            dblclick_timer = false;
            my.countryDoubleClicked = d.country; // <-- variable to pass
        }
        else { // single click
            dblclick_timer = setTimeout(function() {
                dblclick_timer = false;
            }, 250)
        } 
    } 

    return my;

}());

other.js : other.js

(function other(NAME) {
    console.log('my:', NAME.my); // undefined
    console.log('my:', NAME.countryDoubleClicked); // undefined
})(NAME);

I would like to be able to read the my object created in map.js in the other.js file and then be able to access the my.countryDoubleClicked variable from other.js . 我希望能够在other.js文件中读取我在map.js中创建的对象,然后能够从other.js访问my.countryDoubleClicked变量。

This code doesn't work, I get TypeError: NAME.my is undefined . 这段代码不起作用,我得到TypeError: NAME.my is undefined

There are a few things going on: 有几件事情在发生:

Revealing Variables 揭示变量

First you're not revealing the my variable to show up as NAME.my in map.js : 首先你不透露出my变量显示为NAME.mymap.js:

var NAME = (function map() {
    var my = {};
    //...
    return my;
}());

This sets NAME to my , instead of setting NAME.my to my . NAME.my NAME设置为my ,而不是将NAME.my设置为my If you do want to do this, you can do something like this: 如果你想这样做,你可以这样做:

var NAME = (function map() {
    var my = {};
    //...
    return {
      my: my
    };
}());

You can read more about this technique, called the "Revealing Module Pattern", from articles like this one: http://jargon.js.org/_glossary/REVEALING_MODULE_PATTERN.md 您可以从以下文章中了解有关此技术的更多信息,称为“显示模块模式”: http//jargon.js.org/_glossary/REVEALING_MODULE_PATTERN.md

Using functions to run code later 使用函数稍后运行代码

Second, as others have mentioned and as you've realized, since the code in other.js runs immediately, it'll run that code before the user has any chance to click on a country. 其次,正如其他人所提到的那样,正如您other.js ,由于other.js的代码会立即运行,因此在用户有机会点击某个国家/地区之前,它会运行该代码。 Instead, you want code that can run on demand, (in this case when the user double clicks on something). 相反,您需要可以按需运行的代码(在这种情况下,当用户双击某些内容时)。 In JavaScript, this is traditionally done by assigning or passing a function. 在JavaScript中,传统上通过分配或传递函数来完成。 For simplicity, we can assign something to my.doubleClickHandler and then invoke that function in clickOrDoubleCountry . 为简单起见,我们可以为my.doubleClickHandler分配一些my.doubleClickHandler ,然后在clickOrDoubleCountry调用该函数。 For this I've made the country an argument passed to the handler, in addition to assigning it to NAME.my.countryDoubleClicked , but you'll probably only need to use one of them. 为此,除了将它分配给NAME.my.countryDoubleClicked之外,我已经将该国家作为传递给处理程序的参数,但您可能只需要使用其中一个。

function clickOrDoubleCountry(d, i) {
    if(dblclick_timer) { // double click
        clearTimeout(dblclick_timer);
        dblclick_timer = false;
        my.countryDoubleClicked = d.country; // <-- variable to pass
        if (my.doubleClickHandler) {
            my.doubleClickHandler(d.country);
        }
    }
    // ...
} 

Then in other.js , you'd assign the function you want to run to NAME.my.doubleClickHandler : 然后在other.js ,您将要运行的函数分配给NAME.my.doubleClickHandler

(function other(NAME) {
    NAME.my.doubleClickHandler = function (country) {
        // now this code runs whenever the user double clicks on something
        console.log('exposed variable', NAME.my.countryDoubleClicked); // should be the country
        console.log('argument', country); // should be the same country
    });
})(NAME);

So in addition to the modified other.js above, this is complete modified map.js : 所以除了上面修改的other.js之外,这是完整修改的map.js

var NAME=(function map() {

    var my = {};

    var CONFIG = viewData.CONFIG;
    var nuts0 = viewData.nuts0;

    // paths
    var countries;

    // width and height of svg map container
    var width = CONFIG.bubbleMap.width;
    var height = CONFIG.bubbleMap.height;

    // to check if user clicks or double click
    var dblclick_timer = false;

    // create Hammer projection
    var projectionCurrent = d3.geoHammer() 
        .scale(1) 
        .translate([width/2, height/2]); 

    var projectionBase = d3.geoHammer()
        .scale(1) 
        .translate([width/2, height/2]);

    // creates a new geographic path generator with the default settings. If projection is specified, sets the current projection
    var path = d3.geoPath().projection(projectionCurrent);

    // creates the svg element that contains the map
    var map = d3.select('#map');

    var mapSvg = map.append('svg')
        .attr('id', 'map-svg')
        .attr('width', width)
        .attr('height', height);

    var mapSvgGCountry = mapSvg.append('g').attr('id', 'nuts0');

    countries = topojson.feature(nuts0, nuts0.objects.nuts0);
    projectionCurrent.fitSize([width, height], countries);

    var mapSvgGCountryPath = mapSvgGCountry.selectAll('path')
        .data(countries.features)
        .enter()
        .append('path');

    mapSvgGCountryPath.attr('class', 'country')
        .attr('fill', 'tomato')
        .style('stroke', 'white')
        .style('stroke-width', 1) 
        .attr('d', path)
        .attr('id', function(c) {
            return 'country' + c.properties.nuts_id;
        })
        .on('click', clickOrDoubleCountry);

    function clickOrDoubleCountry(d, i) {
        if(dblclick_timer) { // double click
            clearTimeout(dblclick_timer);
            dblclick_timer = false;
            my.countryDoubleClicked = d.country; // <-- variable to pass
            if (my.doubleClickHandler) {
                my.doubleClickHandler(d.country);
            }
        }
        else { // single click
            dblclick_timer = setTimeout(function() {
                dblclick_timer = false;
            }, 250)
        } 
    } 

    return {
        my: my
    };

}());

If you don't want to use NAME.my for everything and want methods and variables accessible directly from NAME (eg NAME.countryDoubleClicked instead of NAME.my.countryDoubleClicked ), you can use the original return statement return my; 如果您不想将NAME.my用于所有内容并希望直接从NAME访问方法和变量(例如NAME.countryDoubleClicked而不是NAME.my.countryDoubleClicked ),则可以使用原始的return语句return my; , just bear in mind that there will be no variable named NAME.my . ,请记住,没有名为NAME.my变量。

You need to set explicit fields... for example: 您需要设置显式字段...例如:

let x = (function(){
    let obj = {}; // the "namespace"
    let private_var = 0;
    function foo() {
        return private_var++; // Access private vars freely
    };
    obj.foo = foo; // "publish" the function
    console.log(foo()); // you can use unqualified foo here
    return obj;
})();

// outputs 0 from the console log call inside the "constructor"

console.log(x.private_var); // undefined, it's not published
console.log(x.foo()); // outputs 1, the function was published
console.log(x.foo()); // 2
console.log(x.foo()); // 3

Local variables or local functions of a Javascript function are not implicitly published anywhere. Javascript函数的局部变量或局部函数不会在任何地方隐式发布。 If you want to access them then you need to set up an object field. 如果要访问它们,则需要设置对象字段。

I prefer the Original Module pattern above the Revealing Module pattern, mainly because of the benefits of loose augmentation; 我更喜欢Revealing Module模式上方的Original Module模式 ,主要是因为松散增强的好处; In short, it allows to break up a module into parts which can be loaded asynchronously, read more here . 简而言之,它允许将模块分解为可以异步加载的部分, 在此处阅读更多内容。

Via window.NAME = window.NAME || {} 通过window.NAME = window.NAME || {} window.NAME = window.NAME || {} in the code below a custom namespace with name NAME gets declared if it doesn't exist yet. 如果名称为NAME的自定义命名空间尚不存在,则会在下面的代码中声明window.NAME = window.NAME || {} Module 1 declares a variable my into it and module 2 a variable other ; 模块1向其声明变量my ,模块2变量为变量other ; whether module 1 runs before or after module 2 doesn't matter. 模块1在模块2之前或之后运行无关紧要。

When the main module gets executed (after module 1 and 2) it can access the variables defined in both. 当主模块执行时(在模块1和2之后),它可以访问两者中定义的变量。 Notice that these can be accessed in 3 different ways. 请注意,可以通过3种不同方式访问它们。

 // Module1.js. (function(NAME) { NAME.my = "foo" // Replace with your map instance. })(window.NAME = window.NAME || {}); // Module2.js. (function(namespace) { namespace.other = "bar" })(window.NAME = window.NAME || {}); // Main module using what is defined in the 2 modules above. (function(namespace) { console.log(NAME.my); console.log(namespace.my) console.log(window.NAME.my); console.log(NAME.other); console.log(namespace.other) console.log(window.NAME.other) })(window.NAME = window.NAME || {}); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM