如其他答案中提到的那样,mutation events已被弃用,因此您应该使用MutationObserver。由于还没有详细解释,所以在这里我来简单介绍一下...
基本JavaScript API
MutationObserver的API相当简单。虽然不像变异事件那么简单,但仍然可以接受。
function callback(records) {
records.forEach(function (record) {
var list = record.addedNodes;
var i = list.length - 1;
for ( ; i > -1; i-- ) {
if (list[i].nodeName === 'SELECT') {
console.log(list[i]);
}
}
});
}
var observer = new MutationObserver(callback);
var targetNode = document.body;
observer.observe(targetNode, { childList: true, subtree: true });
<script>
setTimeout(function() {
var $el = document.createElement('select');
document.body.appendChild($el);
}, 500);
</script>
让我们分解一下。
var observer = new MutationObserver(callback);
这将创建观察者。观察者目前尚未观察任何内容;这只是事件监听器附加的位置。
observer.observe(targetNode, { childList: true, subtree: true })
这会启动观察器。第一个参数是观察器将在其上观察更改的节点。第二个参数是要观察什么内容的选项。
childList
表示我需要观察子元素的添加或删除。
subtree
是一个修饰符,将childList
扩展到该元素子树中的任何位置(否则,它只会查看直接在targetNode
中进行的更改)。
除了childList
之外的另外两个主要选项是attributes
和characterData
,它们的意义与它们的名称相似。您必须使用其中之一。
function callback(records) {
records.forEach(function (record) {
在回调函数中有一些棘手的问题。 回调函数接收一个MutationRecord数组。 每个MutationRecord可以描述一个类型的多个更改(childList
,attributes
或characterData
)。 由于我只告诉观察者监视childList
,所以不需要检查类型。
var list = record.addedNodes;
在这里,我获取了一个包含所有添加的子节点的NodeList。对于没有添加节点的记录,这将为空(可能有许多这样的记录)。
从那里开始,我循环遍历所添加的节点,并查找其中任何一个是 <select>
元素。
没有真正复杂的内容。
jQuery
...但是您要求使用jQuery。好吧。
(function($) {
var observers = [];
$.event.special.domNodeInserted = {
setup: function setup(data, namespaces) {
var observer = new MutationObserver(checkObservers);
observers.push([this, observer, []]);
},
teardown: function teardown(namespaces) {
var obs = getObserverData(this);
obs[1].disconnect();
observers = $.grep(observers, function(item) {
return item !== obs;
});
},
remove: function remove(handleObj) {
var obs = getObserverData(this);
obs[2] = obs[2].filter(function(event) {
return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
});
},
add: function add(handleObj) {
var obs = getObserverData(this);
var opts = $.extend({}, {
childList: true,
subtree: true
}, handleObj.data);
obs[1].observe(this, opts);
obs[2].push([handleObj.selector, handleObj.handler]);
}
};
function getObserverData(element) {
var $el = $(element);
return $.grep(observers, function(item) {
return $el.is(item[0]);
})[0];
}
function checkObservers(records, observer) {
var obs = $.grep(observers, function(item) {
return item[1] === observer;
})[0];
var triggers = obs[2];
var changes = [];
records.forEach(function(record) {
if (record.type === 'attributes') {
if (changes.indexOf(record.target) === -1) {
changes.push(record.target);
}
return;
}
$(record.addedNodes).toArray().forEach(function(el) {
if (changes.indexOf(el) === -1) {
changes.push(el);
}
})
});
triggers.forEach(function checkTrigger(item) {
changes.forEach(function(el) {
var $el = $(el);
if ($el.is(item[0])) {
$el.trigger('domNodeInserted');
}
});
});
}
})(jQuery);
使用jQuery特殊事件API,创建名为domNodeInserted
的新事件。可以像这样使用它:
$(document).on("domNodeInserted", "select", function () {
$(this).combobox();
});
我个人建议寻找一个类,因为有些库会创建 select
元素用于测试目的。
当然,你也可以使用 .off("domNodeInserted", ...)
或通过传递数据来微调观察:
$(document.body).on("domNodeInserted", "select.test", {
attributes: true,
subtree: false
}, function () {
$(this).combobox();
});
每当body内的元素属性更改时,都会触发检查是否出现了select.test
元素。
您可以在下面或jsFiddle上实时查看。
\
(function($) {
$(document).on("domNodeInserted", "select", function() {
console.log(this);
});
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
setTimeout(function() {
var $el = document.createElement('select');
document.body.appendChild($el);
}, 500);
</script>
<script>
(function($) {
var observers = [];
$.event.special.domNodeInserted = {
setup: function setup(data, namespaces) {
var observer = new MutationObserver(checkObservers);
observers.push([this, observer, []]);
},
teardown: function teardown(namespaces) {
var obs = getObserverData(this);
obs[1].disconnect();
observers = $.grep(observers, function(item) {
return item !== obs;
});
},
remove: function remove(handleObj) {
var obs = getObserverData(this);
obs[2] = obs[2].filter(function(event) {
return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
});
},
add: function add(handleObj) {
var obs = getObserverData(this);
var opts = $.extend({}, {
childList: true,
subtree: true
}, handleObj.data);
obs[1].observe(this, opts);
obs[2].push([handleObj.selector, handleObj.handler]);
}
};
function getObserverData(element) {
var $el = $(element);
return $.grep(observers, function(item) {
return $el.is(item[0]);
})[0];
}
function checkObservers(records, observer) {
var obs = $.grep(observers, function(item) {
return item[1] === observer;
})[0];
var triggers = obs[2];
var changes = [];
records.forEach(function(record) {
if (record.type === 'attributes') {
if (changes.indexOf(record.target) === -1) {
changes.push(record.target);
}
return;
}
$(record.addedNodes).toArray().forEach(function(el) {
if (changes.indexOf(el) === -1) {
changes.push(el);
}
})
});
triggers.forEach(function checkTrigger(item) {
changes.forEach(function(el) {
var $el = $(el);
if ($el.is(item[0])) {
$el.trigger('domNodeInserted');
}
});
});
}
})(jQuery);
</script>
注意事项
这段 jQuery 代码实现相当基础,当文档中其他地方的修改使您的选择器有效时,它不会触发。
例如,假设您的选择器是.test select
,并且文档已经有一个<select>
。将类test
添加到<body>
将使选择器有效,但由于我只检查record.target
和record.addedNodes
,因此事件不会触发。更改必须发生在您希望选择的元素本身。
这可以通过在发生变异时查询选择器来避免。我选择不这样做,以避免为已处理的元素产生重复事件。正确处理相邻或通用兄弟组合器会使事情变得更加棘手。
有关更全面的解决方案,请参见https://github.com/pie6k/jquery.initialize,如Damien Ó Ceallaigh的回答中所述。然而,该库的作者已经宣布该库是过时的,并建议您不要使用jQuery来实现此功能。
$(select).ready(function() { });
- Pablo Banderasready
用于: "指定一个函数,在 DOM 完全加载后执行。"(来自 jquery.com) - Caballerocombobox()
方法,而是之后再应用呢? - David Thomas$(document).on('onComplete', 'select', function(){ // bind here? });
吗?(显然应该使用比document
更接近的父元素。) - David Thomas