my previous个问题之一是如何在多个.js文件之间组织代码。 现在我有一个问题。
我在d3.js中有一张按国家划分的地图。当用户双击某个国家/地区时,我想将变量传递给另一个js文件。
这是我的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(/"/g, '"').replace(/</, ''));
viewData.CONFIG = JSON.parse('{{json CONFIG}}'.replace(/"/g, '"').replace(/</, ''));
</script>
<script src='/script/map.js' rel='script'/><{{!}}/script>
<script src='/script/other.js' rel='script'/><{{!}}/script>
</body>
</html>
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 :
(function other(NAME) {
console.log('my:', NAME.my); // undefined
console.log('my:', NAME.countryDoubleClicked); // undefined
})(NAME);
我希望能够在map.js
文件中阅读other.js
中创建的对象,然后才能从my.countryDoubleClicked
访问other.js
变量。< / p>
此代码不起作用,我得到TypeError: NAME.my is undefined
。
答案 0 :(得分:2)
有一些事情正在发生:
首先,您没有透露my
变量在 map.js 中显示为NAME.my
:
var NAME = (function map() {
var my = {};
//...
return my;
}());
这会将NAME
设置为my
,而不是将NAME.my
设置为my
。如果你想这样做,你可以这样做:
var NAME = (function map() {
var my = {};
//...
return {
my: my
};
}());
您可以从以下文章中了解有关此技术的更多信息,称为“揭示模块模式”:http://jargon.js.org/_glossary/REVEALING_MODULE_PATTERN.md
其次,正如其他人提到的那样,正如您所意识到的那样,由于other.js
中的代码会立即运行,因此在用户有机会点击某个国家/地区之前,它会运行该代码。相反,您需要可以按需运行的代码(在这种情况下,当用户双击某些内容时)。在JavaScript中,传统上通过分配或传递函数来完成。为简单起见,我们可以为my.doubleClickHandler
分配一些内容,然后在clickOrDoubleCountry
中调用该函数。为此,我将国家作为传递给处理程序的参数,除了将其分配给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);
}
}
// ...
}
然后在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);
因此,除了上面修改过的 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
};
}());
如果您不希望NAME.my
用于所有内容,并希望直接从NAME
(例如NAME.countryDoubleClicked
而不是NAME.my.countryDoubleClicked
)访问方法和变量,则可以使用原始的返回语句return my;
,请记住,不会有名为NAME.my
的变量。
答案 1 :(得分:1)
您需要设置显式字段...例如:
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
Javascript函数的局部变量或局部函数不会在任何地方隐式发布。如果要访问它们,则需要设置对象字段。
答案 2 :(得分:0)
我更喜欢Revealing Module模式上方的原始模块模式,主要是因为松散扩充的好处;简而言之,它允许将模块分解为可以异步加载的部分,阅读更多here。
通过以下代码中的window.NAME = window.NAME || {}
,如果尚未存在,则会声明名称为NAME的自定义命名空间。模块1向其声明变量my
,模块2向变量other
声明变量// 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 || {});
;模块1在模块2之前或之后运行是否无关紧要。
当执行主模块时(在模块1和2之后),它可以访问两者中定义的变量。请注意,可以通过3种不同的方式访问它们。
{
"status": 4
}
&#13;