这是一个完全更新的帖子,以更好的方式解释问题,改进概念和代码(基于目前为止给出的答案)
我试图实现一个完整的ajaxed网站。但是我遇到了多个绑定事件的问题。
这是基本的HTML:
<header id="navigation">
<ul>
<li class="red" data-type="cars">Get Cars</li>
<li class="red" data-type="vegetables">Get Vegetables</li>
</ul>
</header>
<div id="anything">
<section id="dymanic-content"></section>
</div>
导航是动态创建的(因为#navigation
的内容可以替换为其他导航),因此nav元素的绑定将如下所示:
$('#navigation').off('click', '.red').on('click', '.red', function() {
var type = $(this).attr('data-type');
var data = { 'param': 'content', 'type': type };
ajaxSend(data);
});
该网站的内容也是动态加载的。例如,有两种不同的内容:
1:
<div id="vegetables">Here are some informations about vegetables: <button>Anything</button></div>
2
<div id="cars"><img src="car.jpg"></div>
在加载内容时,我还将为这种类型的内容加载一个特定的JS文件,该文件具有所需的所有绑定。所以加载脚本看起来像这样:
var ajaxSend = function(data) {
$.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
.done(function( json ) {
if (json.logged === false) { login(ajaxSend, data); }
else {
$.getScript( 'js/' + json.file + '.js' )
.done(function( script, textStatus ) {
$('#result').html(json.antwort);
});
}
});
}
当您传递所需结果类型的参数(即蔬菜或汽车)时,结果将显示在#result
中。还将加载文件cars.js或vegetables.js。
所以我的问题是避免多个事件绑定。这就是我的做法:
cars.js:
$('#result').off('mouseover', 'img').on('mouseover', 'img', function () {
// do anything
});
vegetables.js:
$('#result').off('click', 'button').on('click', 'button', function () {
// do anything
});
这是正确的方法吗?我认为这只是一种使用off()
的解决方法。所以我很感激任何改进!
此外,如果用户多次点击导航,我不知道是否存在问题:在这种情况下,js文件被多次加载,不是吗?那么这个概念有多个绑定吗?
答案 0 :(得分:2)
当您引用完全ajaxed网站时,我认为 SPA - S ingle P 年龄 A 应用。
区别可能是语义,但 AJAX 意味着DOM操作,而 SPA 意味着模板化和导航。
加载页面时会加载HTML模板。每个模板都映射到特定的导航路线。主要更改不是事件映射,而是显示模板,以及是否已加载新数据。
AngularJS路由如下所示:
scotchApp.config(function($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl: 'pages/home.html',
controller: 'mainController'
})
// route for the cars page
.when('/cars', {
templateUrl: 'pages/Cars.html',
controller: 'CarsController'
})
// route for the vegetables page
.when('/vegetables', {
templateUrl: 'pages/Vegetables.html',
controller: 'VegetablesController'
});
});
因此每个路由都有一个相应的HTML模板和控制器(其中定义了回调函数)。
出于CDN目的,模板可以作为JSON传回
// route for the vegetables page
.when('/vegetables', {
template: '<div class="jumbotron text-center"><div class="row"><h3>Cars Page</h3>Available Cars: <a class="btn btn-primary" ng-click='LoadCars()'>LoadCars</a></div><div class="col-sm-4"><a class="btn btn-default" ng-click="sort='name'"> Make/Model</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='year'"> Year</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='price'"> Price</a></div><div class="row" ng-repeat="car in cars | orderBy:sort"><div class="row"></div><div class="col-sm-4">{{ car.name }}</div><div class="col-sm-2">{{ car.year }}</div><div class="col-sm-2">${{ car.price }}</div></div></div>',
controller: 'VegetablesController'
});
在“模板化”应用程序中,每种类型的HTML都会加载一次。
事件和控件绑定一次。
增量更改 JSON 来回传递。您的终点不负责呈现HTML。他们可以保持安宁,并且有一个明确的关注点。
您可以使用 AngularJS , Ember , Backbone , JQuery 等等。
答案 1 :(得分:1)
首先,我建议你选择像AngularJS这样的框架,正如其他人提出的那样。
但是,除此之外,您还可以考虑使用namespaces:
<强> cars.js:强>
$('#result').off('mouseover.cars', 'img').on('mouseover.cars', 'img', function () {
// do anything
});
<强> vegetables.js:强>
$('#result').off('click.vegetables', 'button').on('click.vegetables', 'button', function () {
// do anything
});
这将是一项改进(以及更少的解决方法),因为:
(它可以完成工作)而不会打扰其他
点击事件处理程序 附在元素上。
答案 2 :(得分:1)
<强> cars.js:强>
$('#result').off('mouseover', 'img').on('mouseover', 'img', function () { // do anything });
<强> vegetabels.js:强>
$('#result').off('click', 'button').on('click', 'button', function () { // do anything });
我不确定,但是,如果用户首先点击导航上的汽车类型,那么('mouseover', 'img')
听众取消注册,然后再次注册,对吧?
然后,当用户点击导航('click', 'button')
上的蔬菜类型 - 取消注册(但!!! 'mouseover', 'img'
- 保留!!!),然后用户点击某个类型导航,哪个脚本没有('mouseover', 'img')
监听器,但内容有img - 然后有非法的内容监听器(从之前的行动开始)。
因此,在开始加载新内容和脚本之前,您需要清除所有已注册的#result
个侦听器,可能:
var ajaxSend = function(data) {
$.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
.done(function( json ) {
if (json.logged === false) { login(ajaxSend, data); }
else {
$('#result').off();
$.getScript( 'js/' + json.file + '.js' )
.done(function( script, textStatus ) {
$('#result').html(json.antwort);
});
}
});
}
或
<强> cars.js:强>
$('#result').off().on('mouseover', 'img', function () {
// do anything
});
<强> vegetabels.js:强>
$('#result').off().on('click', 'button', function () {
// do anything
});
编辑:关于多次加载脚本。我没有找到明确的答案,我认为这是浏览器依赖和jquery实现,并且每次创建新脚本时都可能会创建新脚本,即使它是之前创建的,因此可能存在两个缺点:
但是,取决于JQuery文档。
缓存响应
默认情况下,
全局设置缓存属性来覆盖此功能$.getScript()
会将缓存设置设置为false
。这会将带时间戳的查询参数附加到请求URL,以确保浏览器在每次请求时都下载脚本。您可以使用$.ajaxSetup()
$.ajaxSetup({ cache: true });
您可以依赖JQuery cache (there is an option to cache only scripts),或实施自己的,例如:
var scriptCache = [];
function loadScript(scriptName, cb){
if (scriptCache[scriptName]) {
jQuery.globalEval(scriptCache[scriptName]);
cb();
} else {
$.getScript( scriptName )
.done(function( script, textStatus ) {
scriptCache[scriptName] = script;
//$('#result').html(json.antwort);
cb();
});
}
}
答案 3 :(得分:0)
您可以创建一个函数,该函数使用要加载的页面名称并使用单个函数来加载页面。然后让回调函数加载一个同名的javascript文件(具有公共init
函数)。像:
function loadPage( pageName ) {
$('#dymanic-content').load( pageName +'.php', function() {
$.getScript( pageName +'.js', function() {
init();
});
});
}
或者您可以将回调函数名称传递给函数。
function loadPage( pageName, cb ) {
$('#dymanic-content').load( pageName +'.php', function() {
$.getScript( pageName +'.js', function() {
cb();
});
});
}
你可以用promises而不是回调来做到这一点。
答案 4 :(得分:0)
如果你采用网络的AJAX方式,请考虑使用PJAX。它是一个用于创建AJAX网站的战斗测试库,并且正在github上使用。
以下PJAX的完整示例:
<强> HTML:强>
一旦脚本加载完成,data-js 属性将用于运行我们的函数。每个页面需要有所不同。
data-url-js 属性包含要加载的JS脚本列表。
<div id="content" data-js="vegetablesAndCars" data-urljs="['/js/library1.js','/js/vegetablesAndCars.js']">
<ul class="navigation">
<li><a href="to/link/1">Link 1</a></li>
<li><a href="to/link/2">Link 2</a></li>
</ul>
<div id="vegetables">
</div>
<div id="cars">
</div>
</div>
模板:您的所有网页都必须包含#content
div作为容器,并带有上述两个属性。
<强> JS:强>
App.js - 每个页面都需要包含此文件。
/*
* PJAX Global Defaults
*/
$.pjax.defaults.timeout = 30000;
$.pjax.defaults.container = "#content";
/*
* Loads JS scripts for each page
*/
function loadScript(scriptName, callback) {
var body = document.getElementsByTagName('body')[0];
$.each(scriptArray,function(key,scripturl){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scripturl;
// fire the loading
body.appendChild(script);
});
}
/*
* Execute JS script for current Page
*/
function executePageScript()
{
//Check if we have a script to execute
if(typeof document.getElementById('content').getAttribute('data-js') !== null)
{
var functionName = document.getElementById('content').getAttribute('data-js').toString();
if(typeof window[functionName] === "undefined")
{
var jsUrl = document.getElementById('content').getAttribute('data-url-js').toString();
if(typeof jsUrl !== "undefined")
{
jsLoader(JSON.parse(jsUrl));
}
else
{
console.log('Js Url not set');
}
}
else
{
//If script is already loaded, just execute the script
window[functionName]();
}
}
}
$(function(){
/*
* PJAX events
*/
$(document).on('pjax:success, pjax:end',function(){
//After Successful loading
//Execute Javascript
executePageScript();
}).on('pjax:beforeSend',function(){
//Before HTML replace. You might want to show a little spinning loader to your users here.
console.log('We are loading our stuff through pjax');
});
});
vegetableAndCars.js - 这是您的页面特定的js文件。所有特定于页面的js脚本都必须遵循此模板。
/*
* Name: vegetablesAndCars Script
* Description: Self-executing function on document load.
*/
(window.vegetablesAndCars = function() {
$('#cars').on('click',function(){
console.log('Vegetables and cars dont mix');
});
$('.navigation a').on('click',function() {
//Loads our page through pjax, i mean, ajax.
$.pjax({url:$(this).attr('href')});
});
})();
更多解释:
函数名已附加到窗口global js名称空间,因此可以在不重新加载脚本的情况下重新执行该函数。如您所知,此函数名称必须是唯一的。
该函数是自执行的,因此如果用户在不使用AJAX的情况下到达页面(即直接进入页面URL),它将自行执行。
你可能会问,我有on
我的HTML元素的所有绑定怎么样?好吧,一旦元素被销毁/替换,对它们的代码绑定将被浏览器垃圾收集。所以你的记忆力不会超过屋顶。
以上用于实施基于ajax的网站的模式目前正在我的一个客户端进行制作。因此,它已针对所有场景进行了大量测试。
答案 5 :(得分:0)
当你做$('#navigation').on('some-event', '.red',function(){});
时
您将事件绑定到#navigation
元素(您可以使用$('#navigation').data('events')
看到这一点),但不能绑定到.red
- 元素,这就是为什么当您使用新的js-logic加载新元素时你正在获得新的和旧的绑定。
如果在您的情况下可以这样做,只需对{(1}}这样的直接绑定使用应该与元素一起删除/重新加载的所有事件。
答案 6 :(得分:0)
在大多数情况下,您可以想象在Web开发中做的所有事情都已经完成。您只需要找到它并使其与您的环境一起工作。您的代码存在许多问题,但还有其他一些问题让我感到困扰 - 为什么没有人提到angularJS或requireJS?使用这样的框架和库有很多好处,包括
此处还有使用您自己的代码的好处
我的观点是,您应该使用其他人已经建立的内容,其中99%的内容完全免费。
另外,使用像angular这样的框架,最终会得到更清晰,可维护的代码。
答案 7 :(得分:-1)
使用.off(...).on(...)
方法,您可以保证在新绑定之前事件将被清除,以防您有多个.js文件绑定到同一事件(即:汽车和蔬菜都有不同逻辑的按钮点击)
但是,如果不是这种情况,您可以使用类过滤器来检测已经包含事件的元素:
$('#result:not(.click-bound)').addClass('click-bound').on('click', 'button', function() {
// your stuff in here
});
这样,选择器只会将事件绑定到尚未使用类click-bound
修饰的元素。