> 웹 프론트엔드 > JS 튜토리얼 > AngularJS 프레임워크에서 데이터의 양방향 바인딩 적용 사례 분석

AngularJS 프레임워크에서 데이터의 양방향 바인딩 적용 사례 분석

高洛峰
풀어 주다: 2016-12-24 10:14:48
원래의
989명이 탐색했습니다.

데이터 바인딩

텍스트 입력 상자를 person.name 속성에 바인딩하면 애플리케이션을 좀 더 흥미롭게 만들 수 있습니다. 이 단계에서는 텍스트 입력 상자와 페이지 사이에 양방향 바인딩을 설정합니다.

AngularJS 프레임워크에서 데이터의 양방향 바인딩 적용 사례 분석

이 맥락에서 "양방향"은 뷰가 속성 값을 변경하면 모델이 변경 사항을 "인식"하고, 모델이 속성 값을 변경하면 view는 이 변경사항도 "확인"합니다. Angular.js는 자동으로 이 메커니즘을 설정합니다. 이것이 어떻게 수행되는지 궁금하다면, Digest_loop의 작동을 심도 있게 논의하는 곧 나올 기사를 확인하세요.

이 바인딩을 생성하기 위해 다음과 같이 텍스트 입력 필드에 ng-model 지시어 속성을 사용합니다.

<p ng-controller="MyController">
 <input type="text" ng-model="person.name" placeholder="Enter your name" />
 <h5>Hello {{ person.name }}</h5>
</p>
로그인 후 복사

이제 데이터 바인딩이 설정되었습니다(예, 그만큼 쉽습니다. ), 보기가 모델을 어떻게 변경하는지 살펴보겠습니다.

시도해 보세요:

AngularJS 프레임워크에서 데이터의 양방향 바인딩 적용 사례 분석

텍스트 상자에 입력하면 다음 이름도 자동으로 변경됩니다. , 이는 데이터 바인딩의 한 방향(뷰에서 모델로)을 보여줍니다.

또한 (클라이언트) 백엔드에서 모델을 변경할 수 있으며 이 변경 사항이 프런트 엔드에 자동으로 반영되는 것을 확인할 수 있습니다. 이 프로세스를 보여주기 위해 MyController 모델에 $scope의 데이터를 업데이트하는 타이머 함수를 작성해 보겠습니다. 다음 코드에서는 매초(시계처럼) 시간을 측정하고 $scope에서 시계 변수 데이터를 업데이트하는 타이머 함수를 만듭니다.

app.controller(&#39;MyController&#39;, function($scope) {
 $scope.person = { name: "Ari Lerner" };
 var updateClock = function() {
  $scope.clock = new Date();
 };
 var timer = setInterval(function() {
  $scope.$apply(updateClock);
 }, 1000);
 updateClock();
});
로그인 후 복사


보시다시피 모델에서 시계 변수의 데이터를 변경하면 뷰가 자동으로 업데이트되어 이 변경 사항을 반영합니다. 중괄호를 사용하면 뷰에 시계 변수의 값을 쉽게 표시할 수 있습니다.

<p ng-controller="MyController">
 <h5>{{ clock }}</h5>
</p>
로그인 후 복사



Interactive

앞서 The 데이터는 텍스트 입력 상자에 바인딩됩니다. 데이터 바인딩은 데이터에만 국한되지 않으며 바인딩을 사용하여 $scope에서 함수를 호출할 수도 있습니다(이것은 이전에 언급했습니다).

버튼, 링크 또는 기타 DOM 요소의 경우 바인딩을 달성하기 위해 다른 지시어 속성인 ng-click을 사용할 수 있습니다. 이 ng-click 지시문은 DOM 요소의 마우스 클릭 이벤트(예: mousedown 브라우저 이벤트)를 메서드에 바인딩합니다. 브라우저가 DOM 요소에서 마우스로 클릭 이벤트를 트리거하면 바인딩된 메서드가 호출됩니다. 이전 예와 유사하게 바인딩 코드는 다음과 같습니다.

<p ng-controller="DemoController">
 <h4>The simplest adding machine ever</h4>
 <button ng-click="add(1)" class="button">Add</button>
 <button ng-click="subtract(1)" class="button">Subtract</button>
 <h4>Current count: {{ counter }}</h4>
</p>
로그인 후 복사


두 버튼과 링크 모두 이를 포함하는 버튼과 링크에 바인딩됩니다. DOM 요소 컨트롤러의 $scope 객체를 마우스로 클릭하면 Angular는 해당 메소드를 호출합니다. Angular에게 어떤 메서드를 호출할지 알려줄 때 메서드 이름을 따옴표로 묶은 문자열에 씁니다.

app.controller(&#39;DemoController&#39;, function($scope) {
 $scope.counter = 0;
 $scope.add = function(amount) { $scope.counter += amount; };
 $scope.subtract = function(amount) { $scope.counter -= amount; };
});
로그인 후 복사

$scope.$watch

$scope.$watch( watchExp, listener, objectEquality );
로그인 후 복사


변수 변경 사항을 보려면 $scope를 사용할 수 있습니다. .$watch 기능. 이 함수에는 "감시할 내용"(watchExp), "변경 시 발생하는 내용"(리스너) 및 변수 또는 객체를 감시할지 여부를 지정하는 세 가지 매개 변수가 있습니다. 하나의 매개변수를 확인할 때 세 번째 매개변수는 무시할 수 있습니다. 예를 들어 다음 예에서는

$scope.name = &#39;Ryan&#39;;
 
$scope.$watch( function( ) {
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log(&#39;$scope.name was updated!&#39;);
} );
로그인 후 복사


AngularJS가 $scope에 모니터링 기능을 등록합니다. $scope에 등록된 항목을 보려면 콘솔에 $scope를 출력하면 됩니다.

$scope.name이 변경된 것을 콘솔에서 볼 수 있습니다. 이는 $scope.name의 이전 값이 정의되지 않은 것처럼 보였고 이제 이를 Ryan에게 할당하기 때문입니다!

For $wach의 첫 번째 매개변수로 문자열을 사용할 수도 있습니다. 이는 기능을 제공하는 것과 똑같습니다. AngularJS 소스 코드에서 볼 수 있듯이 문자열을 사용하면 다음 코드가 실행됩니다.

if (typeof watchExp == &#39;string&#39; && get.constant) {
 var originalFn = watcher.fn;
 watcher.fn = function(newVal, oldVal, scope) {
  originalFn.call(this, newVal, oldVal, scope);
  arrayRemove(array, watcher);
 };
}
로그인 후 복사


This This is will watchExp를 함수로 설정하면 이름을 지정한 범위의 변수도 자동으로 반환됩니다.

$$watchers
$scope의 $$watchers 변수는 우리가 정의한 모든 모니터를 저장합니다. 콘솔에서 $$watchers를 보면 객체의 배열임을 알 수 있습니다.

$$watchers = [
  {
    eq: false, // 表明我们是否需要检查对象级别的相等
    fn: function( newValue, oldValue ) {}, // 这是我们提供的监听器函数
    last: &#39;Ryan&#39;, // 变量的最新值
    exp: function(){}, // 我们提供的watchExp函数
    get: function(){} // Angular&#39;s编译后的watchExp函数
  }
];
로그인 후 복사


$watch 함수는 deregisterWatch 함수를 반환합니다. 이는 $scope.$watch를 사용하여 변수를 관찰하는 경우 나중에 함수를 호출하여 관찰을 중지할 수도 있음을 의미합니다.

$scope.$apply
AngularJS에서 컨트롤러/지시문/등이 실행되면 AngularJS는 내부적으로 $scope.$apply라는 함수를 실행합니다. $apply 함수는 rootScope에서 $digest 함수를 실행하기 전에 함수를 매개변수로 받아 실행합니다.

AngularJS의 $apply 함수 코드는 다음과 같습니다.

   
$apply: function(expr) {
  try {
   beginPhase(&#39;$apply&#39;);
   return this.$eval(expr);
  } catch (e) {
   $exceptionHandler(e);
  } finally {
   clearPhase();
   try {
    $rootScope.$digest();
   } catch (e) {
    $exceptionHandler(e);
    throw e;
   }
  }
}
로그인 후 복사


上面代码中的expr参数就是你在调用$scope.$apply()时传递的参数 – 但是大多数时候你可能都不会去使用$apply这个函数,要用的时候记得给它传递一个参数。

下面我们来看看ng-keydown是怎么来使用$scope.$apply的。为了注册这个指令,AngularJS会使用下面的代码。

var ngEventDirectives = {};
forEach(
 &#39;click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste&#39;.split(&#39; &#39;),
 function(name) {
  var directiveName = directiveNormalize(&#39;ng-&#39; + name);
  ngEventDirectives[directiveName] = [&#39;$parse&#39;, function($parse) {
   return {
    compile: function($element, attr) {
     var fn = $parse(attr[directiveName]);
     return function ngEventHandler(scope, element) {
      element.on(lowercase(name), function(event) {
       scope.$apply(function() {
        fn(scope, {$event:event});
       });
      });
     };
    }
   };
  }];
 }
);
로그인 후 복사


上面的代码做的事情是循环了不同的类型的事件,这些事件在之后可能会被触发并创建一个叫做ng-[某个事件]的新指令。在指令的compile函数中,它在元素上注册了一个事件处理器,它和指令的名字一一对应。当事件被出发时,AngularJS就会运行scope.$apply函数,并让它运行一个函数。

只是单向数据绑定吗?
上面所说的ng-keydown只能够改变和元素值相关联的$scope中的值 – 这只是单项数据绑定。这也是这个指令叫做ng-keydown的原因,只有在keydown事件被触发时,能够给与我们一个新值。

但是我们想要的是双向数据绑定!
我们现在来看一看ng-model。当你在使用ng-model时,你可以使用双向数据绑定 – 这正是我们想要的。AngularJS使用$scope.$watch(视图到模型)以及$scope.$apply(模型到视图)来实现这个功能。

ng-model会把事件处理指令(例如keydown)绑定到我们运用的输入元素上 – 这就是$scope.$apply被调用的地方!而$scope.$watch是在指令的控制器中被调用的。你可以在下面代码中看到这一点:

$scope.$watch(function ngModelWatch() {
  var value = ngModelGet($scope);
 
  //如果作用域模型值和ngModel值没有同步
  if (ctrl.$modelValue !== value) {
 
    var formatters = ctrl.$formatters,
      idx = formatters.length;
 
    ctrl.$modelValue = value;
    while(idx--) {
      value = formatters[idx](value);
    }
 
    if (ctrl.$viewValue !== value) {
      ctrl.$viewValue = value;
      ctrl.$render();
    }
  }
 
  return value;
});
로그인 후 복사


如果你在调用$scope.$watch时只为它传递了一个参数,无论作用域中的什么东西发生了变化,这个函数都会被调用。在ng-model中,这个函数被用来检查模型和视图有没有同步,如果没有同步,它将会使用新值来更新模型数据。这个函数会返回一个新值,当它在$digest函数中运行时,我们就会知道这个值是什么!

为什么我们的监听器没有被触发?
如果我们在$scope.$watch的监听器函数中停止这个监听,即使我们更新了$scope.name,该监听器也不会被触发。

正如前面所提到的,AngularJS将会在每一个指令的控制器函数中运行$scope.$apply。如果我们查看$scope.$apply函数的代码,我们会发现它只会在控制器函数已经开始被调用之后才会运行$digest函数 – 这意味着如果我们马上停止监听,$scope.$watch函数甚至都不会被调用!但是它究竟是怎样运行的呢?

$digest函数将会在$rootScope中被$scope.$apply所调用。它将会在$rootScope中运行digest循环,然后向下遍历每一个作用域并在每个作用域上运行循环。在简单的情形中,digest循环将会触发所有位于$$watchers变量中的所有watchExp函数,将它们和最新的值进行对比,如果值不相同,就会触发监听器。

当digest循环运行时,它将会遍历所有的监听器然后再次循环,只要这次循环发现了”脏值”,循环就会继续下去。如果watchExp的值和最新的值不相同,那么这次循环就会被认为发现了脏值。理想情况下它会运行一次,如果它运行超10次,你会看到一个错误。

因此当$scope.$apply运行的时候,$digest也会运行,它将会循环遍历$$watchers,只要发现watchExp和最新的值不相等,变化触发事件监听器。在AngularJS中,只要一个模型的值可能发生变化,$scope.$apply就会运行。这就是为什么当你在AngularJS之外更新$scope时,例如在一个setTimeout函数中,你需要手动去运行$scope.$apply():这能够让AngularJS意识到它的作用域发生了变化。

创建自己的脏值检查
到此为止,我们已经可以来创建一个小巧的,简化版本的脏值检查了。当然,相比较之下,AngularJS中实现的脏值检查要更加先进一些,它提供疯了异步队列以及其他一些高级功能。

设置Scope
Scope仅仅只是一个函数,它其中包含任何我们想要存储的对象。我们可以扩展这个函数的原型对象来复制$digest和$watch。我们不需要$apply方法,因为我们不需要在作用域的上下文中执行任何函数 – 我们只需要简单的使用$digest。我们的Scope的代码如下所示:

var Scope = function( ) {
  this.$$watchers = []; 
};
 
Scope.prototype.$watch = function( ) {
 
};
 
Scope.prototype.$digest = function( ) {
 
};
로그인 후 복사


我们的$watch函数需要接受两个参数,watchExp和listener。当$watch被调用时,我们需要将它们push进入到Scope的$$watcher数组中。

var Scope = function( ) {
  this.$$watchers = []; 
};
 
Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};
 
Scope.prototype.$digest = function( ) {
 
};
로그인 후 복사


你可能已经注意到了,如果没有提供listener,我们会将listener设置为一个空函数 – 这样一来我们可以$watch所有的变量。

接下来我们将会创建$digest。我们需要来检查旧值是否等于新的值,如果二者不相等,监听器就会被触发。我们会一直循环这个过程,直到二者相等。这就是”脏值”的来源 – 脏值意味着新的值和旧的值不相等!

var Scope = function( ) {
  this.$$watchers = []; 
};
 
Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};
 
Scope.prototype.$digest = function( ) {
  var dirty;
 
  do {
      dirty = false;
 
      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;
 
        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);
 
          dirty = true;
 
          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};
로그인 후 복사


接下来,我们将创建一个作用域的实例。我们将这个实例赋值给$scope。我们接着会注册一个监听函数,在更新$scope之后运行$digest!

var Scope = function( ) {
  this.$$watchers = []; 
};
 
Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};
 
Scope.prototype.$digest = function( ) {
  var dirty;
 
  do {
      dirty = false;
 
      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;
 
        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);
 
          dirty = true;
 
          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};
 
 
var $scope = new Scope();
 
$scope.name = 'Ryan';
 
$scope.$watch(function(){
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log(newValue, oldValue);
} );
 
  
$scope.$digest();
로그인 후 복사


成功了!我们现在已经实现了脏值检查(虽然这是最简单的形式)!上述代码将会在控制台中输出下面的内容:

Ryan undefined
로그인 후 복사


这正是我们想要的结果 – $scope.name之前的值是undefined,而现在的值是Ryan。

现在我们把$digest函数绑定到一个input元素的keyup事件上。这就意味着我们不需要自己去调用$digest。这也意味着我们现在可以实现双向数据绑定!

var Scope = function( ) {
  this.$$watchers = []; 
};
 
Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};
 
Scope.prototype.$digest = function( ) {
  var dirty;
 
  do {
      dirty = false;
 
      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;
 
        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);
 
          dirty = true;
 
          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};
 
 
var $scope = new Scope();
 
$scope.name = 'Ryan';
 
var element = document.querySelectorAll('input');
 
element[0].onkeyup = function() {
  $scope.name = element[0].value;
 
  $scope.$digest();
};
 
$scope.$watch(function(){
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log('Input value updated - it is now ' + newValue);
 
  element[0].value = $scope.name;
} );
 
var updateScopeValue = function updateScopeValue( ) {
  $scope.name = 'Bob';
  $scope.$digest();
};
로그인 후 복사

   


使用上面的代码,无论何时我们改变了input的值,$scope中的name属性都会相应的发生变化。这就是隐藏在AngularJS神秘外衣之下数据双向绑定的秘密!

更多AngularJS 프레임워크에서 데이터의 양방향 바인딩 적용 사례 분석相关文章请关注PHP中文网!


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿