Sự khác biệt giữa chức năng biên dịch và liên kết trong angularjs là gì


208

Ai đó có thể giải thích bằng thuật ngữ đơn giản?

Các tài liệu có vẻ hơi khó hiểu. Tôi không nhận được bản chất và bức tranh lớn khi nào nên sử dụng cái này hơn cái kia. Một ví dụ tương phản hai sẽ là tuyệt vời.


2
Có lẽ một tổng quan toàn diện hơn về các chức năng chỉ thị: Chỉ thị góc - khi nào nên sử dụng biên dịch, bộ điều khiển, liên kết trước và sau liên kết .
Izhaki

Câu trả lời:


217
  • hàm biên dịch - sử dụng cho thao tác DOM mẫu (nghĩa là thao tác của phần tử tEuity = mẫu), do đó các thao tác áp dụng cho tất cả các bản sao DOM của mẫu được liên kết với lệnh.

  • chức năng liên kết - sử dụng để đăng ký trình nghe DOM (nghĩa là biểu thức $ watch trên phạm vi cá thể) cũng như thao tác DOM thể hiện (nghĩa là thao tác của phần tử cá thể iEuity = cá thể).
    Nó được thực hiện sau khi mẫu đã được nhân bản. Ví dụ: bên trong <li ng-repeat ...>, chức năng liên kết được thực thi sau khi mẫu <li> (tEuity) đã được sao chép (thành một iEuity) cho phần tử <li> cụ thể đó.
    $ Watch () cho phép chỉ thị được thông báo về các thay đổi thuộc tính phạm vi cá thể (phạm vi thể hiện được liên kết với từng phiên bản), cho phép lệnh hiển thị giá trị cá thể được cập nhật vào DOM - bằng cách sao chép nội dung từ phạm vi cá thể vào DOM.

Lưu ý rằng các phép biến đổi DOM có thể được thực hiện trong hàm biên dịch và / hoặc hàm liên kết.

Hầu hết các lệnh chỉ cần một hàm liên kết, vì hầu hết các lệnh chỉ xử lý một thể hiện phần tử DOM cụ thể (và phạm vi thể hiện của nó).

Một cách để giúp xác định nên sử dụng: xem xét rằng hàm biên dịch không nhận được một scopeđối số. (Tôi cố tình bỏ qua đối số hàm liên kết transclude, nhận được phạm vi được nhúng - điều này hiếm khi được sử dụng.) Vì vậy, hàm biên dịch không thể làm bất cứ điều gì bạn muốn làm mà yêu cầu phạm vi (ví dụ) - bạn có thể 't $ xem bất kỳ thuộc tính phạm vi mô hình / thể hiện nào, bạn không thể thao tác DOM bằng thông tin phạm vi thể hiện, bạn không thể gọi các hàm được xác định trên phạm vi thể hiện, v.v.

Tuy nhiên, hàm biên dịch (như hàm liên kết) không có quyền truy cập vào các thuộc tính. Vì vậy, nếu các thao tác DOM của bạn không yêu cầu phạm vi thể hiện, bạn có thể sử dụng hàm biên dịch. Đây là một ví dụ về một lệnh chỉ sử dụng hàm biên dịch, vì những lý do đó. Nó kiểm tra các thuộc tính, nhưng nó không cần một phạm vi thể hiện để thực hiện công việc của nó.

Đây là một ví dụ về một lệnh cũng chỉ sử dụng hàm biên dịch. Lệnh chỉ cần chuyển đổi DOM mẫu, do đó, một hàm biên dịch có thể được sử dụng.

Một cách khác để giúp xác định nên sử dụng: nếu bạn không sử dụng tham số "phần tử" trong chức năng liên kết, thì có lẽ bạn không cần chức năng liên kết.

Vì hầu hết các chỉ thị đều có chức năng liên kết, tôi sẽ không cung cấp bất kỳ ví dụ nào - chúng sẽ rất dễ tìm.

Lưu ý rằng nếu bạn cần một hàm biên dịch và một hàm liên kết (hoặc các hàm liên kết trước và sau), hàm biên dịch phải trả về (các) hàm liên kết vì thuộc tính 'link' bị bỏ qua nếu thuộc tính 'compile' được xác định.

Xem thêm


5
Giải thích tốt nhất về biên dịch vs liên kết.
Nexus23

1
Khi bạn nói if you don't use the "element" parameter in the link function, then you probably don't need a link function.bạn có nghĩa là "phạm vi" thay vì "yếu tố"?
Jason Larke

69

Tôi đập đầu vào tường trong vài ngày và tôi cảm thấy có thêm một chút giải thích.

Về cơ bản, các tài liệu đề cập rằng sự tách biệt phần lớn là nâng cao hiệu suất. Tôi xin nhắc lại rằng giai đoạn biên dịch chủ yếu được sử dụng khi bạn cần sửa đổi DOM TRƯỚC KHI các phần tử con được biên dịch.

Đối với mục đích của chúng tôi, tôi sẽ nhấn mạnh thuật ngữ, điều này gây nhầm lẫn:

Trình biên dịch DỊCH VỤ ($ compile) là cơ chế góc xử lý DOM và chạy các bit mã khác nhau trong các lệnh.

FUNCTION biên dịch là một bit mã trong một lệnh, được chạy tại một thời điểm cụ thể B BYNG trình biên dịch DỊCH VỤ ($ compile).

Một số lưu ý về CHỨC NĂNG biên dịch:

  1. Bạn không thể sửa đổi phần tử ROOT (chỉ thị của bạn ảnh hưởng), vì phần tử này đã được biên dịch từ cấp độ bên ngoài của DOM (DỊCH VỤ biên dịch đã được quét các chỉ thị trên phần tử đó).

  2. Nếu bạn muốn thêm các chỉ thị khác vào các phần tử (lồng nhau), bạn cũng:

    1. Phải thêm chúng trong giai đoạn biên dịch.

    2. Phải tiêm dịch vụ biên dịch vào giai đoạn liên kết và biên dịch các phần tử theo cách thủ công. NHƯNG, hãy cẩn thận với việc biên dịch một cái gì đó hai lần!

Cũng rất hữu ích khi xem cách các lệnh gọi lồng nhau và rõ ràng đến $ compile hoạt động, vì vậy tôi đã tạo một sân chơi để xem tại http://jsbin.com/imUPAMoV/1/edit . Về cơ bản, nó chỉ ghi nhật ký các bước vào console.log.

Tôi sẽ nêu kết quả của những gì bạn thấy trong thùng đó ở đây. Đối với một DOM của các chỉ thị tùy chỉnh tp và sp lồng nhau như sau:

<tp>
   <sp>
   </sp>
</tp>

DỊCH VỤ biên dịch góc sẽ gọi:

tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link

Mã jsbin cũng có CHỨC NĂNG hậu liên kết tp gọi rõ ràng DỊCH VỤ biên dịch theo lệnh thứ ba (lên), thực hiện cả ba bước ở cuối.

Bây giờ, tôi muốn xem qua một vài tình huống để cho thấy cách người ta có thể sử dụng trình biên dịch và liên kết để làm nhiều việc khác nhau:

SCENARIO 1: Chỉ thị là MACRO

Bạn muốn thêm một lệnh (nói ng-show) một cách linh hoạt vào một cái gì đó trong mẫu của bạn mà bạn có thể rút ra từ một thuộc tính.

Giả sử bạn có một templateUrl trỏ đến:

<div><span><input type="text"></span><div>

và bạn muốn có một chỉ thị tùy chỉnh:

<my-field model="state" name="address"></my-field>

biến DOM thành thế này:

<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>

về cơ bản, bạn muốn giảm nồi hơi bằng cách có một số cấu trúc mô hình nhất quán mà chỉ thị của bạn có thể diễn giải. Nói cách khác: bạn muốn một macro.

Đây là một cách sử dụng tuyệt vời cho giai đoạn biên dịch, vì bạn có thể dựa trên tất cả các thao tác DOM trên những thứ bạn biết chỉ từ các thuộc tính. Chỉ cần sử dụng jQuery để thêm các thuộc tính:

compile: function(tele, tattr) {
   var span = jQuery(tele).find('span').first();
   span.attr('ng-show', tattr.model + ".visible." + tattr.name);
   ...
   return { 
     pre: function() { },
     post: function() {}
   };
}

Trình tự các hoạt động sẽ là (bạn có thể thấy điều này thông qua jsbin đã đề cập trước đó):

  1. DỊCH VỤ biên dịch tìm trường của tôi
  2. Nó gọi FUNCTION biên dịch trên lệnh, cập nhật DOM.
  3. Sau đó, dịch vụ biên dịch đi vào DOM kết quả và TÍNH TOÁN (đệ quy)
  4. DỊCH VỤ biên dịch sau đó gọi từ trên xuống liên kết từ trên xuống
  5. DỊCH VỤ biên dịch sau đó gọi BOTTOM LÊN sau liên kết, do đó chức năng liên kết của trường của tôi được gọi là các nút bên trong SAU đã được liên kết.

Trong ví dụ trên, không cần liên kết, vì tất cả các công việc của chỉ thị đã được thực hiện trong việc biên dịch FUNCTION.

Tại bất kỳ thời điểm nào, mã trong một lệnh có thể yêu cầu trình biên dịch DỊCH VỤ chạy trên các phần tử bổ sung.

Điều này có nghĩa là chúng ta có thể thực hiện chính xác điều tương tự trong hàm liên kết nếu bạn tiêm dịch vụ biên dịch:

directive('d', function($compile) {
  return {
    // REMEMBER, link is called AFTER nested elements have been compiled and linked!
    link: function(scope, iele, iattr) {
      var span = jQuery(iele).find('span').first();
      span.attr('ng-show', iattr.model + ".visible." + iattr.name);
      // CAREFUL! If span had directives on it before
      // you will cause them to be processed again:
      $compile(span)(scope);
    }
});

Nếu bạn chắc chắn rằng các phần tử bạn chuyển đến $ compile DỊCH VỤ ban đầu không có chỉ thị (ví dụ: chúng đến từ một mẫu bạn đã xác định hoặc bạn chỉ tạo chúng với angular.element ()), thì kết quả cuối cùng là khá nhiều giống như trước đây (mặc dù bạn có thể lặp lại một số công việc). Tuy nhiên, nếu phần tử có các chỉ thị khác trên đó, bạn chỉ khiến chúng bị xử lý lại, điều này có thể gây ra tất cả các loại hành vi thất thường (ví dụ: đăng ký kép các sự kiện và đồng hồ).

Do đó, giai đoạn biên dịch là một lựa chọn tốt hơn nhiều cho công việc theo phong cách vĩ mô.

SCENARIO 2: Cấu hình DOM thông qua dữ liệu phạm vi

Điều này sau từ ví dụ trên. Giả sử bạn cần truy cập vào phạm vi trong khi thao tác DOM. Chà, trong trường hợp đó, phần biên dịch là vô dụng đối với bạn, vì nó xảy ra trước khi một phạm vi có sẵn.

Vì vậy, giả sử bạn muốn tìm ra đầu vào có xác nhận, nhưng bạn muốn xuất xác thực của mình từ lớp ORM phía máy chủ (DRY) và để chúng tự động áp dụng và tạo UI phía máy khách phù hợp cho các xác nhận hợp lệ đó.

Mô hình của bạn có thể đẩy:

scope.metadata = {
  validations: {
     address: [ {
       pattern: '^[0-9]',
       message: "Address must begin with a number"
     },
     { maxlength: 100,
       message: "Address too long"
     } ]
  }
};
scope.state = {
  address: '123 Fern Dr'
};

và bạn có thể muốn một chỉ thị:

<form name="theForm">
  <my-field model="state" metadata="metadata" name="address">
</form>

để tự động bao gồm các chỉ thị và div phù hợp để hiển thị các lỗi xác thực khác nhau:

<form name="theForm">
  <div>
    <input ng-model="state.address" type="text">
    <div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...

Trong trường hợp này, bạn chắc chắn cần truy cập vào phạm vi (vì đó là nơi lưu trữ xác thực của bạn) và sẽ phải biên dịch các bổ sung theo cách thủ công, một lần nữa cẩn thận không biên dịch hai thứ. (như một ghi chú bên cạnh, bạn sẽ cần đặt tên trên thẻ biểu mẫu có chứa (Tôi giả sử theForm ở đây) và có thể truy cập nó trong liên kết với iEuity.parent (). control ('form'). $ name) .

Trong trường hợp này, không có điểm nào trong việc viết một hàm biên dịch. Liên kết thực sự là những gì bạn muốn. Các bước sẽ là:

  1. Xác định một mẫu hoàn toàn không có các chỉ thị góc.
  2. Xác định hàm liên kết có thêm các thuộc tính khác nhau
  3. XÓA bất kỳ chỉ thị góc nào mà bạn có thể cho phép trên phần tử cấp cao nhất của mình (chỉ thị trường của tôi). Chúng đã được xử lý và đây là một cách để giữ cho chúng không bị xử lý kép.
  4. Kết thúc bằng cách gọi DỊCH VỤ biên dịch trên phần tử cấp cao nhất của bạn

Thích như vậy:

angular.module('app', []).
directive('my-field', function($compile) {
  return {
    link: function(scope, iele, iattr) {
      // jquery additions via attr()
      // remove ng attr from top-level iele (to avoid duplicate processing)
      $compile(iele)(scope); // will pick up additions
    }
  };
});

Tất nhiên, bạn có thể biên dịch từng phần tử lồng nhau để tránh phải lo lắng về việc xử lý trùng lặp các lệnh ng khi bạn biên dịch lại phần tử cấp cao nhất.

Một lưu ý cuối cùng về kịch bản này: Tôi ngụ ý rằng bạn sẽ đẩy định nghĩa về các xác nhận từ máy chủ và trong ví dụ của tôi, tôi đã chỉ ra chúng là dữ liệu đã có trong phạm vi. Tôi để nó như một bài tập cho người đọc để tìm ra cách người ta có thể đối phó với việc cần phải lấy dữ liệu đó từ API REST (gợi ý: biên dịch hoãn lại).

SCENARIO 3: liên kết dữ liệu hai chiều thông qua liên kết

Tất nhiên, việc sử dụng liên kết phổ biến nhất là chỉ cần kết nối dữ liệu hai chiều thông qua đồng hồ / áp dụng. Hầu hết các chỉ thị thuộc loại này, vì vậy nó được bảo hiểm đầy đủ ở nơi khác.


2
Awsome & Cool Trả lời!
Nexus23

Làm thế nào để thêm các phần tử lồng nhau mà không cần biên dịch chúng?
Art713

50

Từ các tài liệu:

Trình biên dịch

Trình biên dịch là một dịch vụ góc cạnh đi qua DOM tìm kiếm các thuộc tính. Quá trình biên dịch xảy ra thành hai giai đoạn.

  1. Biên dịch: duyệt qua DOM và thu thập tất cả các lệnh. Kết quả là một chức năng liên kết.

  2. Liên kết: kết hợp các chỉ thị với một phạm vi và tạo ra một chế độ xem trực tiếp. Mọi thay đổi trong mô hình phạm vi được phản ánh trong chế độ xem và mọi tương tác của người dùng với chế độ xem được phản ánh trong mô hình phạm vi. Làm cho mô hình phạm vi là một nguồn của sự thật.

Một số chỉ thị các ng-repeatphần tử DOM sao chép một lần cho mỗi mục trong bộ sưu tập. Có một giai đoạn biên dịch và liên kết sẽ cải thiện hiệu suất do mẫu nhân bản chỉ cần được biên dịch một lần, và sau đó được liên kết một lần cho mỗi phiên bản nhân bản.

Vì vậy, ít nhất trong một số trường hợp, hai giai đoạn tồn tại riêng biệt như một sự tối ưu hóa.


Từ @ UmurKontacı :

Nếu bạn định thực hiện chuyển đổi DOM, thì nó nên như vậy compile. Nếu bạn muốn thêm một số tính năng thay đổi hành vi, thì nó nên có link.


46
Nếu bạn định thực hiện DOMchuyển đổi, thì compilenếu bạn muốn thêm một số tính năng là thay đổi hành vi thì nên có link.
Umur Kontacı

4
+1 để nhận xét trên; đây là mô tả ngắn gọn nhất mà tôi tìm thấy cho đến nay. Nó phù hợp với hướng dẫn tôi tìm thấy ở đây .
Benny Bottema

18

Đây là từ bài nói chuyện của Misko về các chỉ thị. http://youtu.be/WqmeI5fZcho?t=16m23s

Hãy nghĩ về hàm biên dịch như là thứ hoạt động trên một mẫu và thứ được phép thay đổi chính mẫu đó, ví dụ, thêm một lớp vào nó hoặc bất cứ thứ gì tương tự. Nhưng đó là chức năng liên kết thực sự làm việc liên kết cả hai với nhau vì chức năng liên kết có quyền truy cập vào phạm vi và đó là chức năng liên kết thực thi một lần cho mỗi lần khởi tạo của mẫu cụ thể. Vì vậy, loại điều duy nhất bạn có thể đặt bên trong các hàm biên dịch là những thứ phổ biến trong tất cả các trường hợp.


10

Ít muộn để chủ đề. Nhưng, vì lợi ích của độc giả tương lai:

Tôi đã xem qua video sau đây giải thích về Biên dịch và Liên kết trong Angular JS theo cách rất tuyệt vời:

https://www.youtube.com/watch?v=bjFqSyddCeA

Nó sẽ không vui lòng sao chép / gõ vào tất cả các nội dung ở đây. Tôi đã chụp một vài ảnh chụp màn hình từ video, giải thích mọi giai đoạn của giai đoạn Biên dịch và Liên kết:

Biên dịch và liên kết trong JS góc

Biên dịch và liên kết trong Angular JS - Chỉ thị lồng nhau

Ảnh chụp màn hình thứ hai hơi khó hiểu. Nhưng, nếu chúng ta làm theo cách đánh số bước, nó khá đơn giản.

Chu kỳ đầu tiên: "Biên dịch" được thực hiện trên tất cả các chỉ thị trước.
Chu kỳ thứ hai: "Trình điều khiển" và "Liên kết trước" được thực hiện (chỉ lần lượt từng bước) Chu kỳ thứ ba: "Liên kết sau" được thực hiện theo thứ tự ngược lại (bắt đầu từ trong cùng)

Sau đây là mã, thể hiện ở trên:

var app = angular.module ('ứng dụng', []);

app.controll ('dir', ['$ scope', function ($ scope) {

}]);

app.directive ('tin nhắn', hàm ($ nội suy) {
    trở về{

        biên dịch: hàm (tEuity, tAttribut) { 
            console.log (tAttribut.text + "-Trong biên dịch ..");
            trở về {

                pre: function (scope, iEuity, iAttribut, controller) {
                    console.log (iAttribut.text + "-Trong pre ..");
                },

                post: function (scope, iEuity, iAttribut, controller) {
                    console.log (iAttribut.text + "-In Post ..");
                }

            }
        },

        bộ điều khiển: hàm ($ scope, $ phần tử, $ attrs) {
            console.log ($ attrs.text + "-Trong bộ điều khiển ..");
        },

    }
});
<body ng-app="app">
<div ng-controller="msg">
    <div message text="first">
        <div message text="..second">
            <div message text="....third">

            </div>              
        </div>  
    </div>
</div>

CẬP NHẬT:

Phần 2 của cùng một video có sẵn tại đây: https://www.youtube.com/watch?v=1M3LZ1cu7rw Video giải thích thêm về cách sửa đổi DOM và xử lý các sự kiện trong quá trình Biên dịch và Liên kết của Angular JS, trong một ví dụ đơn giản .


Được sử dụng compilepostsửa đổi DOM trước khi nó được sửa đổi templatemột phần từ chỉ thị của nhà cung cấp.
jedi

6

Hai giai đoạn: Biên dịch và liên kết

Biên dịch:

Đi qua cây DOM tìm kiếm các lệnh (phần tử / thuộc tính / lớp / bình luận). Mỗi phần biên dịch của một lệnh có thể sửa đổi mẫu của nó hoặc sửa đổi nội dung của nó chưa được biên dịch. Khi một lệnh được khớp, nó trả về một hàm liên kết, được sử dụng trong pha sau để liên kết các phần tử lại với nhau. Vào cuối giai đoạn biên dịch, chúng tôi có một danh sách các lệnh được biên dịch và các hàm liên kết tương ứng của chúng.

Liên kết:

Khi một phần tử được liên kết, cây DOM bị phá vỡ tại điểm nhánh của nó trong cây DOM và nội dung được thay thế bằng thể hiện được biên dịch (và được liên kết) của mẫu. Nội dung thay thế ban đầu hoặc bị loại bỏ hoặc trong trường hợp loại trừ, được liên kết lại vào mẫu. Với việc loại trừ, hai mảnh được liên kết lại với nhau (giống như một chuỗi, với mảnh mẫu nằm ở giữa). Khi hàm liên kết được gọi, mẫu đã được liên kết với một phạm vi và được thêm vào như là một phần tử con của phần tử. Chức năng liên kết là cơ hội để bạn thao túng DOM hơn nữa và thiết lập trình nghe thay đổi.


3

Câu hỏi này đã cũ bởi tôi muốn tóm tắt ngắn gọn có thể giúp:

  • Biên dịch được gọi một lần cho tất cả các thể hiện chỉ thị
  • Biên dịch mục đích chính là trả về / tạo liên kết (và có thể trước / bài) hàm / đối tượng. Bạn cũng có thể khởi tạo nội dung được chia sẻ giữa các phiên bản của lệnh.
  • Theo tôi, "link" là một cái tên khó hiểu cho tính năng này. Tôi thích "kết xuất trước".
  • liên kết được gọi cho mỗi phiên bản lệnh và mục đích của nó là chuẩn bị kết xuất lệnh trong DOM.

1
một điểm cộng cho tên gợi ý: "pre-render"
Hailong Cao
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.