开发您自己的框架和JS程序员的专业成长

您是否想过框架如何工作? 该材料的作者(我们今天翻译的译文)说,很多年前,当他学习jQuery之后 ,偶然发现Angular.js时 ,在他看来,他所看到的东西非常复杂且难以理解。 然后出现了Vue.js,并处理了这个框架,他被启发编写了自己的双向数据绑定系统 。 这样的实验有助于程序员的专业发展。 本文面向那些希望在现代JS框架所基于的技术领域扩展自己的知识的人。 特别是,它将着重于如何编写自己的框架的核心,以支持HTML元素的自定义属性,反应性和双向数据绑定。

图片

关于反应系统


如果一开始就弄清楚现代框架的反应系统是如何工作的,那将是很好的。 实际上,这里的一切都很简单。 例如,Vue.js在声明新组件时使用“代理 ”设计模式代理每个属性 (获取器和设置器)。

结果,该框架将能够检测从代码和用户界面执行的值更改的每个事实。

代理模式


代理模式基于对对象的重载访问机制。 这类似于人们使用银行帐户的方式。

例如,没有人可以直接使用他们的帐户,根据他们的需要更改其余额。 为了对帐户执行任何操作,您需要联系有权与他合作的人,即与银行联系。 银行充当帐户持有人和帐户本身之间的代理人。

var account = {    balance: 5000 } var bank = new Proxy(account, {    get: function (target, prop) {        return 9000000;    } }); console.log(account.balance); // 5,000 ( ) console.log(bank.balance);    // 9,000,000 (   ) console.log(bank.currency);   // 9,000,000 (  ) 

在此示例中,当使用bank对象访问由account对象表示的帐户余额时,getter函数将被重载,这导致以下事实:即使发出这样的请求,即使返回了9,000,000的值也总是返回,而不是不动产的值。该属性不存在。

这是重载setter函数的示例。

 var bank = new Proxy(account, {   set: function (target, prop, value) {       //      0       return Reflect.set(target, prop, 0);   } }); account.balance = 5800; console.log(account.balance); // 5,800 bank.balance = 5400; console.log(account.balance); // 0 (  ) 

在这里,通过重载set函数,可以控制其行为。 例如,您可以更改要写入属性的值,将数据写入其他属性,甚至什么都不做。

反应系统示例


现在我们已经弄清了代理模式,我们将开始开发自己的JS框架。

为了不使其复杂化,我们将使用与Angular.js中使用的语法非常相似的语法。 结果,控制器的声明以及模板元素与控制器属性的绑定将变得简单明了。

这是模板代码。

 <div ng-controller="InputController">   <!-- "Hello World!" -->   <input ng-bind="message"/>     <input ng-bind="message"/> </div> <script type="javascript"> function InputController () {     this.message = 'Hello World!'; } angular.controller('InputController', InputController); </script> 

首先,您需要声明一个带有属性的控制器。 接下来,在模板中使用此控制器,最后使用ng-bind属性,以便为元素值建立双向数据绑定。

解析模板并实例化控制器


为了使我们具有可以附加数据的某些属性,我们需要一个可以声明这些属性的位置(控制器)。 因此,有必要描述控制器并将其包含在框架中。

在使用控制器的过程中,框架将查找具有ng-controller属性的元素。 如果找到的内容与声明的控制器之一匹配,则框架将创建此控制器的新实例。 该控制器实例仅负责模板的此特定片段。

 var controllers = {}; var addController = function (name, constructor) {   //     controllers[name] = {       factory: constructor,       instances: []   };     // ,     var element = document.querySelector('[ng-controller=' + name + ']');   if (!element){      return; //  ,      }     //         var ctrl = new controllers[name].factory;   controllers[name].instances.push(ctrl);     //   ..... }; addController('InputController', InputController); 

以下是“自制”变量controllers的声明。 请注意, controllers对象包含通过调用addController在框架中声明的所有控制器。

 var controllers = {   InputController: {       factory: function InputController(){           this.message = "Hello World!";       },       instances: [           {message: "Hello World"}       ]   } }; 

每个控制器都有一个factory功能,这样做是为了使您可以在必要时创建新控制器的实例。 此外,框架在instances属性中存储模板中使用的相同类型的控制器的所有实例。

搜索数据绑定中涉及的项目


目前,我们有一个控制器实例和使用该控制器的模板片段。 我们的下一步将是搜索包含需要绑定到控制器属性的数据的元素。

 var bindings = {}; //  : element   DOM,   Array.prototype.slice.call(element.querySelectorAll('[ng-bind]'))   .map(function (element) {       var boundValue = element.getAttribute('ng-bind');       if(!bindings[boundValue]) {           bindings[boundValue] = {               boundValue: boundValue,               elements: []           }       }       bindings[boundValue].elements.push(element);   }); 

此处显示了使用哈希表存储所有对象绑定的组织。 所考虑的变量包含用于绑定的所有属性及其当前值,以及绑定到特定属性的所有DOM元素。

这是我们的bindings变量版本:

     var bindings = {       message: {           //   :           // controllers.InputController.instances[0].message           boundValue: 'Hello World',           // HTML- (   ng-bind="message")           elements: [               Object { ... },               Object { ... }           ]       }   }; 

控制器属性的双向绑定


在框架完成初步准备后,需要进行一件有趣的事情:双向数据绑定。 其含义如下。 首先,控制器的属性需要绑定到DOM元素,这将允许它们在代码中的属性值发生更改时进行更新,其次,DOM元素也必须绑定到控制器的属性。 因此,当用户对此类元素进行操作时,这会导致控制器属性的变化。 而且,如果将几个HTML元素附加到该属性,则还会导致其状态也被更新的事实。

使用代理检测对代码所做的更改


如上所述,Vue.js将组件包装在代理中以检测属性更改。 我们将通过仅代理设置器来完成数据绑定中涉及的控制器属性,从而完成此操作:

 //  : ctrl -    var proxy = new Proxy(ctrl, {   set: function (target, prop, value) {       var bind = bindings[prop];       if(bind) {           //    DOM,               bind.elements.forEach(function (element) {               element.value = value;               element.setAttribute('value', value);           });       }       return Reflect.set(target, prop, value);   } }); 

结果,事实证明,当将值写入绑定属性时,代理将检测到绑定到该属性的所有元素,并将它们传递给新值。

在此示例中,我们仅支持input元素的绑定,因为此处仅设置了value属性。

对元素事件的响应


现在,我们只需要提供对用户操作的系统响应即可。 为此,请考虑以下事实:DOM元素在检测到其值的更改时会引发事件。

我们组织监听这些事件,并在与元素相关的属性中记录从事件获得的新数据。 借助代理,绑定到同一属性的所有其他元素将自动更新。

 Object.keys(bindings).forEach(function (boundValue) {   var bind = bindings[boundValue];     //          bind.elements.forEach(function (element) {     element.addEventListener('input', function (event) {       proxy[bind.boundValue] = event.target.value; //  ,          });   }) }); 

总结


结果,将我们在这里讨论的所有内容放到一起,您将获得实现现代JS框架基本机制的自己的系统。 这是这种系统实施的工作示例。 我们希望本文能帮助所有人更好地了解现代Web开发工具的工作方式。

亲爱的读者们! 如果您专业使用现代JS框架,请​​告诉我们您如何开始他们的研究以及如何帮助您理解它们。

Source: https://habr.com/ru/post/zh-CN416021/


All Articles