Phát hiện các thay đổi trong DOM


242

Tôi muốn thực thi một chức năng khi một số div hoặc đầu vào được thêm vào html. Điều này có thể không?

Ví dụ, một đầu vào văn bản được thêm vào, sau đó chức năng sẽ được gọi.


1
Trừ khi một số tập lệnh của bên thứ ba đang thêm các nút vào DOM, điều này là không cần thiết.
Justin Johnson


Bản sao có thể có của Trình nghe thay đổi JavaScript / jQuery DOM không?
Makyen

4
@JustinJohnson nếu bạn đang tạo một tiện ích mở rộng chrome có mã JS, điều này rất hữu ích.
Huỳnh quangGreen5

Câu trả lời:


206

Bản cập nhật 2015, mới MutationObserverđược hỗ trợ bởi các trình duyệt hiện đại:

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

Nếu bạn cần hỗ trợ những người lớn tuổi hơn, bạn có thể cố gắng quay trở lại các phương pháp khác như phương pháp được đề cập trong câu trả lời 5 tuổi (!) Dưới đây. Có rồng. Thưởng thức :)


Một người khác đang thay đổi tài liệu? Bởi vì nếu bạn có toàn quyền kiểm soát các thay đổi, bạn chỉ cần tạo domChangedAPI của riêng mình - với một chức năng hoặc sự kiện tùy chỉnh - và kích hoạt / gọi nó ở mọi nơi bạn sửa đổi mọi thứ.

Các DOM Level-2 có loại sự kiện đột biến , nhưng phiên bản cũ của trình duyệt IE không hỗ trợ nó. Lưu ý rằng các sự kiện đột biến không được chấp nhận trong thông số Sự kiện DOM3 và có hình phạt về hiệu suất .

Bạn có thể thử mô phỏng sự kiện đột biến bằng onpropertychangeIE (và quay lại cách tiếp cận vũ phu nếu không có sẵn chúng).

Đối với một domChange đầy đủ, một khoảng có thể là một giết quá mức. Hãy tưởng tượng rằng bạn cần lưu trữ trạng thái hiện tại của toàn bộ tài liệu và kiểm tra mọi thuộc tính của mọi thành phần đều giống nhau.

Có lẽ nếu bạn chỉ quan tâm đến các yếu tố và thứ tự của chúng (như bạn đã đề cập trong câu hỏi của bạn), thì getElementsByTagName("*")có thể hoạt động. Điều này sẽ tự động kích hoạt nếu bạn thêm một phần tử, loại bỏ một phần tử, thay thế các phần tử hoặc thay đổi cấu trúc của tài liệu.

Tôi đã viết một bằng chứng về khái niệm:

(function (window) {
    var last = +new Date();
    var delay = 100; // default delay

    // Manage event queue
    var stack = [];

    function callback() {
        var now = +new Date();
        if (now - last > delay) {
            for (var i = 0; i < stack.length; i++) {
                stack[i]();
            }
            last = now;
        }
    }

    // Public interface
    var onDomChange = function (fn, newdelay) {
        if (newdelay) delay = newdelay;
        stack.push(fn);
    };

    // Naive approach for compatibility
    function naive() {

        var last = document.getElementsByTagName('*');
        var lastlen = last.length;
        var timer = setTimeout(function check() {

            // get current state of the document
            var current = document.getElementsByTagName('*');
            var len = current.length;

            // if the length is different
            // it's fairly obvious
            if (len != lastlen) {
                // just make sure the loop finishes early
                last = [];
            }

            // go check every element in order
            for (var i = 0; i < len; i++) {
                if (current[i] !== last[i]) {
                    callback();
                    last = current;
                    lastlen = len;
                    break;
                }
            }

            // over, and over, and over again
            setTimeout(check, delay);

        }, delay);
    }

    //
    //  Check for mutation events support
    //

    var support = {};

    var el = document.documentElement;
    var remain = 3;

    // callback for the tests
    function decide() {
        if (support.DOMNodeInserted) {
            window.addEventListener("DOMContentLoaded", function () {
                if (support.DOMSubtreeModified) { // for FF 3+, Chrome
                    el.addEventListener('DOMSubtreeModified', callback, false);
                } else { // for FF 2, Safari, Opera 9.6+
                    el.addEventListener('DOMNodeInserted', callback, false);
                    el.addEventListener('DOMNodeRemoved', callback, false);
                }
            }, false);
        } else if (document.onpropertychange) { // for IE 5.5+
            document.onpropertychange = callback;
        } else { // fallback
            naive();
        }
    }

    // checks a particular event
    function test(event) {
        el.addEventListener(event, function fn() {
            support[event] = true;
            el.removeEventListener(event, fn, false);
            if (--remain === 0) decide();
        }, false);
    }

    // attach test events
    if (window.addEventListener) {
        test('DOMSubtreeModified');
        test('DOMNodeInserted');
        test('DOMNodeRemoved');
    } else {
        decide();
    }

    // do the dummy test
    var dummy = document.createElement("div");
    el.appendChild(dummy);
    el.removeChild(dummy);

    // expose
    window.onDomChange = onDomChange;
})(window);

Sử dụng:

onDomChange(function(){ 
    alert("The Times They Are a-Changin'");
});

Điều này hoạt động trên IE 5.5+, FF 2+, Chrome, Safari 3+ và Opera 9.6+


4
Tự hỏi: làm thế nào để jQuery sống () giải quyết vấn đề này nếu họ không thể phát hiện ra một thay đổi DOM?
Kees C. Bakker

162
Tôi không thể tin rằng năm 2010 bạn đã có IE5.5 để kiểm tra điều này.
Oscar Godson

1
@JoshStodola Sự táo bạo cũng làm tôi khó chịu. Tôi quyết định sửa nó.
Bojangles

1
Các sự kiện đột biến bị phản đối. Bạn nên sử dụng M mutObserver. Tôi đã viết plugin của mình cho các vấn đề như thế này - github.com/AdamPietrasiak/jquery.initialize
pie6k 6/2/2015

1
Làm cách nào tôi có thể khiến jquery onClick kích hoạt trước một người quan sát đột biến, kích hoạt khi một nút được nhấp bằng hành động than hồng? stackoverflow.com/questions/29216434/
Mạnh

219

Đây là cách tiếp cận cuối cùng cho đến nay, với mã nhỏ nhất:

IE9 +, FF, Webkit:

Sử dụng MutingObserver và quay lại các sự kiện Đột biến không dùng nữa nếu cần:
(Ví dụ bên dưới nếu chỉ thay đổi DOM liên quan đến các nút được nối hoặc xóa)

var observeDOM = (function(){
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

  return function( obj, callback ){
    if( !obj || !obj.nodeType === 1 ) return; // validation

    if( MutationObserver ){
      // define a new observer
      var obs = new MutationObserver(function(mutations, observer){
          callback(mutations);
      })
      // have the observer observe foo for changes in children
      obs.observe( obj, { childList:true, subtree:true });
    }
    
    else if( window.addEventListener ){
      obj.addEventListener('DOMNodeInserted', callback, false);
      obj.addEventListener('DOMNodeRemoved', callback, false);
    }
  }
})();

//------------< DEMO BELOW >----------------
// add item
var itemHTML = "<li><button>list item (click to delete)</button></li>",
    listElm = document.querySelector('ol');

document.querySelector('body > button').onclick = function(e){
  listElm.insertAdjacentHTML("beforeend", itemHTML);
}

// delete item
listElm.onclick = function(e){
  if( e.target.nodeName == "BUTTON" )
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
    
// Observe a specific DOM element:
observeDOM( listElm, function(m){ 
   var addedNodes = [], removedNodes = [];

   m.forEach(record => record.addedNodes.length & addedNodes.push(...record.addedNodes))
   
   m.forEach(record => record.removedNodes.length & removedNodes.push(...record.removedNodes))

  console.clear();
  console.log('Added:', addedNodes, 'Removed:', removedNodes);
});


// Insert 3 DOM nodes at once after 3 seconds
setTimeout(function(){
   listElm.removeChild(listElm.lastElementChild);
   listElm.insertAdjacentHTML("beforeend", Array(4).join(itemHTML));
}, 3000);
<button>Add Item</button>
<ol>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><em>&hellip;More will be added after 3 seconds&hellip;</em></li>
</ol>


1
nó dường như hoạt động khá độc đáo cho các nút DOM mới. Chúng ta có thể điều chỉnh nó để xử lý các thay đổi nút dom (ít nhất là các giá trị / văn bản nút DOM không?)
Sebastien Lorber

8
@SebastienLorber - "chúng ta" là ai? bạn, là một lập trình viên, có thể lấy mã này và sử dụng nó theo cách bạn muốn. chỉ cần đọc trên MDN những thứ bạn có thể quan sát DOM và những thứ bạn không thể.
vsync

2
Truyền các mutations, observertham số cho chức năng gọi lại để kiểm soát nhiều hơn.
A1rPun

2
Điều này đã giúp tôi rất nhiều, nhưng làm thế nào để tôi "cởi trói" điều này? Nói rằng tôi muốn xem thay đổi chỉ một lần, nhưng làm điều này nhiều lần? oberserveDOM = null rõ ràng sẽ không hoạt động ...
stiller_leser

Tại sao điều này chỉ làm việc cho bổ sung / loại bỏ? Có vẻ như các sự kiện đột biến bao trùm nhiều hơn thế .. developer.mozilla.org/en-US/docs/Web/Guide/Events/
mẹo

15

Gần đây tôi đã viết một plugin thực hiện chính xác điều đó - jquery.initialize

Bạn sử dụng nó giống như .eachchức năng

$(".some-element").initialize( function(){
    $(this).css("color", "blue"); 
});

Sự khác biệt .eachlà - nó sẽ chọn bộ chọn của bạn, trong trường hợp này .some-elementvà chờ đợi các phần tử mới với bộ chọn này trong tương lai, nếu phần tử đó sẽ được thêm vào, nó cũng sẽ được khởi tạo.

Trong trường hợp của chúng tôi, chức năng khởi tạo chỉ cần thay đổi màu phần tử thành màu xanh. Vì vậy, nếu chúng tôi sẽ thêm phần tử mới (bất kể là với ajax hay thậm chí là thanh tra F12 hay bất cứ thứ gì) như:

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

Plugin sẽ khởi tạo nó ngay lập tức. Ngoài ra plugin đảm bảo một yếu tố được khởi tạo chỉ một lần. Vì vậy, nếu bạn thêm phần tử, sau đó .detach()nó từ phần thân và sau đó thêm phần tử đó , nó sẽ không được khởi tạo lại.

$("<div/>").addClass('some-element').appendTo("body").detach()
    .appendTo(".some-container");
//initialized only once

Plugin dựa trên MutationObserver- nó sẽ hoạt động trên IE9 và 10 với các phụ thuộc như chi tiết trên trang readme .


Xin vui lòng, thêm vào npm.
thexpand

14

hoặc bạn có thể chỉ cần tạo sự kiện của riêng bạn , diễn ra ở mọi nơi

 $("body").on("domChanged", function () {
                //dom is changed 
            });


 $(".button").click(function () {

          //do some change
          $("button").append("<span>i am the new change</span>");

          //fire event
          $("body").trigger("domChanged");

        });

Ví dụ đầy đủ http://jsfiddle.net/hbmaam/Mq7NX/


điều này không giống nhau ... phương pháp được mô tả ở trên vẫn còn hiệu lực api.jquery.com/trigger
lefoy

10

Đây là một ví dụ sử dụng MutingObserver từ Mozilla được điều chỉnh từ bài đăng trên blog này

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');

// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };

// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

// Later, you can stop observing
observer.disconnect();

4

Sử dụng các MutationObserver giao diện như trong Gabriele Romanato của diễn đàn

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// The node to be monitored
var target = $( "#content" )[0];

// Create an observer instance
var observer = new MutationObserver(function( mutations ) {
  mutations.forEach(function( mutation ) {
    var newNodes = mutation.addedNodes; // DOM NodeList
    if( newNodes !== null ) { // If there are new nodes added
        var $nodes = $( newNodes ); // jQuery set
        $nodes.each(function() {
            var $node = $( this );
            if( $node.hasClass( "message" ) ) {
                // do something
            }
        });
    }
  });    
});

// Configuration of the observer:
var config = { 
    attributes: true, 
    childList: true, 
    characterData: true 
};

// Pass in the target node, as well as the observer options
observer.observe(target, config);

// Later, you can stop observing
observer.disconnect();

3
M mutObserver là JavaScript nguyên gốc, không phải jQuery.
Benjamin

2

Làm thế nào về việc mở rộng một jquery cho điều này?

   (function () {
        var ev = new $.Event('remove'),
            orig = $.fn.remove;
        var evap = new $.Event('append'),
           origap = $.fn.append;
        $.fn.remove = function () {
            $(this).trigger(ev);
            return orig.apply(this, arguments);
        }
        $.fn.append = function () {
            $(this).trigger(evap);
            return origap.apply(this, arguments);
        }
    })();
    $(document).on('append', function (e) { /*write your logic here*/ });
    $(document).on('remove', function (e) { /*write your logic here*/ ) });

Jquery 1.9+ đã xây dựng hỗ trợ cho việc này (Tôi đã nghe nói chưa được thử nghiệm).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.