Two-way binding articles

Interviewer: How to achieve two-way binding Proxy than defineproperty pros and cons?

Interviewer Series (4): How to achieve two-way binding Proxy than defineproperty pros and cons?


Past


Foreword

Two-way binding is already an old-fashioned problem, as long as it comes to MVVM framework would have to talk about knowledge, but it is after all one of the three elements of Vue.

Vue three elements

  • Responsive: such as how to monitor data changes, the implementation of which is the two-way binding we mentioned
  • Template engine: how to parse template
  • Rendering: How Vue will monitor data changes and parsed HTML rendering

There are ways to do a lot of two-way binding, KnockoutJS based on observer mode of two-way binding, Ember-based two-way data binding model, Angular dirty check on two-way binding, this article we highlight some common interview-based data hijacking of two-way binding.

Common data based on the hijacking of two-way binding, there are two, one in the Vue is currently in use Object.defineProperty, and the other is the new ES2015 Proxy, the author claims that the Vue will be added after Vue3.0 version Proxyso instead of Object.definePropertythrough your article Vue also know why the future will choose Proxy.

Proxy strictly speaking, should be called "proxy" instead of "hijacking", but due to there are many similarities, we do not distinguish below, unified called "hijacking."

We can clearly see that these two methods in the following figure by two-way binding relationship system.

<Figure style = "display: block; margin: 22px auto; text-align: center;"> [Image Uploading ... (image-6f9b58-1526012269856-2)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

Of course there have been hijacked cool thoroughly data based Object.observemethod has to be discarded.

Advance statement: We did not make timely judgments passed parameters and avoid errors, only the core methods of implementation.


Article Directory

  1. Based on two-way data binding implementation of hijacking features
  2. Based on the characteristics of two-way binding Object.defineProperty
  3. Based on the characteristics of two-way binding Proxy

1. Based on data two-way binding characteristics to achieve hijacking

1.1 What is a data hijacking

Data hijacking is better understood, we usually use Object.definePropertyto access hijacking object when a property value changes we can get change, thereby further operations.

// 这是将要被劫持的对象
const data = {
  name: '',
};

function say(name) { if (name === '古天乐') { console.log('给大家推荐一款超好玩的游戏'); } else if (name === '渣渣辉') { console.log('戏我演过很多,可游戏我只玩贪玩懒月'); } else { console.log('来做我的兄弟'); } } // 遍历对象,对其属性值进行劫持 Object.keys(data).forEach(function(key) { Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { console.log('get'); }, set: function(newVal) { // 当属性值发生变化时我们可以进行额外操作 console.log(`大家好,我系${newVal}`); say(newVal); }, }); }); data.name = '渣渣辉'; //大家好,我系渣渣辉 //戏我演过很多,可游戏我只玩贪玩懒月 

1.2 Data hijacking advantage

The industry is divided into two large schools, is a one-way data binding React headed, the other is Angular, Vue-based two-way data binding.

In fact, three major frameworks are either two-way binding can also be a one-way binding, such as React can bind manually onChange and value two-way binding, you can also call some way binding libraries, Vue also joined the props this way api stream, but are not mainstream selling point.

Unidirectional or bidirectional merits beyond our scope, we need to discuss implementation compared to other two-way binding, and data hijacking advantage.

  1. Without displaying the call: for example, using the data hijacking Vue + publish subscribe directly informed of the changes and can drive view, the above example is relatively simple to achieve data.name = '渣渣辉'triggered directly after the change, and such Angular dirty detection is required to show the call markForCheck(you can use zone.js avoid displaying call, not expanded), react need to show the call setState.
  2. Accurate data that can change: still small example above, we hijacked setter property, when property values change, we can know the exact content changes newVal, so this part requires no additional diff, otherwise we only know the data occurs the change does not know exactly what data changed, this requires a lot of time to find changes in the value of diff, which is an additional performance loss.

1.3 Based on the data of the two-way binding hijacking realization of ideas

Data hijacking is a two-way binding the various programs more popular, the most famous is the realization of Vue.

Based on the data of the two-way binding is inseparable from hijacking Proxyand Object.definePropertyother methods of object / object attributes of "hijacking" We want to achieve a full two-way binding requires the following points.

  1. Use Proxyor Object.definePropertygeneration of the Observer were "hijacked" the property of the object / object, notify subscribers after the property has changed
  2. Compile parser parses the template Directive(instruction), gather instruction depends methods and data, and then waits for data changes render
  3. Watcher bridges belonging Compile and Observer, it will receive the data changes Observer generated, and the view is rendered with the instructions provided by the Compile, such view data changes cause changes

<Figure style = "display: block; margin: 22px auto; text-align: center;"> [Image Uploading ... (image-1f5ab-1526012269856-1)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

We see that although the use of the data Vue hijacking, but still can not be separated publish-subscribe model, the reason for the Series 2 did achieve Event Bus is , it is because we do not care to learn some of the principles in the framework or some popular libraries (such as Redux , Vuex), basically inseparable from the publish-subscribe model, and Event module is the classic patterns to achieve this, so if you are not familiar with the publish-subscribe model, it is recommended to read the series of articles 2.


2. Based on the two-way binding characteristics Object.defineProperty

About Object.definePropertythe already voluminous article on the web, we do not want to spend too much time in Object.definePropertythe above, this section we explain the main Object.definePropertyfeatures, convenience and the next Proxycomparison.

To Object.definePropertynot know Please read the documentation

Two years ago, someone wrote on Object.definePropertyrealization of the article , want in-depth understanding Object.definePropertyachieved recommended reading, we also do the relevant reference.

We recommend the above article is more complete realization of (400 lines of code), we provide only a minimalist version (20 lines) In this section and achieve a simplified version (150 lines), the reader can read step by step.

2.1 minimalist version of the two-way binding

We all know that Object.definePropertythe role is Hijack properties of an object, usually our property getterand setterhijacking methods, performs a specific action when properties of the object changes.

We are on the subject objof textattributes hijacking, printed in obtaining the value of this property 'get val', the DOM operation to change the property value when this is a minimalist two-way binding.

const obj = {};
Object.defineProperty(obj, 'text', {
  get: function() { console.log('get val');&emsp; }, set: function(newVal) { console.log('set val:' + newVal); document.getElementById('input').value = newVal; document.getElementById('span').innerHTML = newVal; } }); const input = document.getElementById('input'); input.addEventListener('keyup', function(e){ obj.text = e.target.value; }) 

Online examples of minimalist version of the two-way binding by Iwobi ( @xiaomuzhu ) ON CodePen .

2.2 upgrade

We soon discover that this so-called two-way binding looks and no exaggeration. . .

For the following reasons:

  1. We only monitor a property of an object can not be just a property, we need to listen to each object attribute.
  2. Violation of the open closed principle, if we understand the open closed principle , then, the above code is a clear violation of this principle, we all need each modification into the interior of the method, it is necessary resolutely put an end to.
  3. Code Coupling serious, our data, methods, and DOM are coupled together, it is the legendary spaghetti code.

So how do you solve the problem?

Vue operation is the addition of publish-subscribe model, combined with Object.definePropertyhijacking the ability to achieve a high availability of two-way binding.

First of all, we publish subscribe perspective that lump of code that we write the first part, you will find its monitor , publish and subscribe are written together, we first need to do is decoupled.

We subscribe to realize a distribution center, the message administrator (Dep), which is responsible for storing and distributing messages subscribers, whether subscribers or publishers need to rely on it.

  let uid = 0;
  // 用于储存订阅者并发布消息
  class Dep { constructor() { // 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher this.id = uid++; // 储存订阅者的数组 this.subs = []; } // 触发target上的Watcher中的addDep方法,参数为dep的实例本身 depend() { Dep.target.addDep(this); } // 添加订阅者 addSub(sub) { this.subs.push(sub); } notify() { // 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理 this.subs.forEach(sub => sub.update()); } } // 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher Dep.target = null; 

Now we need to implement the listener (Observer), used to monitor changes in property values.

// 监听者,监听对象属性值的变化
  class Observer {
    constructor(value) { this.value = value; this.walk(value); } // 遍历属性值并监听 walk(value) { Object.keys(value).forEach(key => this.convert(key, value[key])); } // 执行监听的具体方法 convert(key, val) { defineReactive(this.value, key, val); } } function defineReactive(obj, key, val) { const dep = new Dep(); // 给当前属性的值添加监听 let chlidOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { // 如果Dep类存在target属性,将其添加到dep实例的subs数组中 // target指向一个Watcher实例,每个Watcher都是一个订阅者 // Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法 if (Dep.target) { dep.depend(); } return val; }, set: newVal => { if (val === newVal) return; val = newVal; // 对新值进行监听 chlidOb = observe(newVal); // 通知所有订阅者,数值被改变了 dep.notify(); }, }); } function observe(value) { // 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听 if (!value || typeof value !== 'object') { return; } return new Observer(value); } 

Then the rest is simple, we need to implement a subscriber (Watcher).

  class Watcher {
    constructor(vm, expOrFn, cb) {
      this.depIds = {}; // hash储存订阅者的id,避免重复的订阅者 this.vm = vm; // 被订阅的数据一定来自于当前Vue实例 this.cb = cb; // 当数据更新时想要做的事情 this.expOrFn = expOrFn; // 被订阅的数据 this.val = this.get(); // 维护更新之前的数据 } // 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用 update() { this.run(); } addDep(dep) { // 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存 // 此判断是避免同id的Watcher被多次储存 if (!this.depIds.hasOwnProperty(dep.id)) { dep.addSub(this); this.depIds[dep.id] = dep; } } run() { const val = this.get(); console.log(val); if (val !== this.val) { this.val = val; this.cb.call(this.vm, val); } } get() { // 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者 Dep.target = this; const val = this.vm._data[this.expOrFn]; // 置空,用于下一个Watcher使用 Dep.target = null; return val; } } 

Then we finalize the Vue, the above method is mounted on the Vue.

  class Vue {
    constructor(options = {}) {
      // 简化了$options的处理 this.$options = options; // 简化了对data的处理 let data = (this._data = this.$options.data); // 将所有data最外层属性代理到Vue实例上 Object.keys(data).forEach(key => this._proxy(key)); // 监听数据 observe(data); } // 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者 $watch(expOrFn, cb) { new Watcher(this, expOrFn, cb); } _proxy(key) { Object.defineProperty(this, key, { configurable: true, enumerable: true, get: () => this._data[key], set: val => { this._data[key] = val; }, }); } } 

Facie effect:

<Figure style = "display: block; margin: 22px auto; text-align: center;"> [Image Uploading ... (image-1da193-1526012269854-0)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

Online examples of two-way binding implementation --- no loopholes edition by Iwobi ( @xiaomuzhu ) ON CodePen .

Thus, a simple two-way binding regarded by us realized.

2.3 Object.defineProperty defects

In fact, we have upgraded version of the two-way binding loopholes still exist, such as we attribute value to the array.

let demo = new Vue({
  data: {
    list: [1],
  },
});

const list = document.getElementById('list'); const btn = document.getElementById('btn'); btn.addEventListener('click', function() { demo.list.push(1); }); const render = arr => { const fragment = document.createDocumentFragment(); for (let i = 0; i < arr.length; i++) { const li = document.createElement('li'); li.textContent = arr[i]; fragment.appendChild(li); } list.appendChild(fragment); }; // 监听数组,每次数组变化则触发渲染函数,然而...无法监听 demo.$watch('list', list => render(list)); setTimeout( function() { alert(demo.list); }, 5000, ); 

Online examples of two-way binding - an array of vulnerabilities by Iwobi ( @xiaomuzhu ) ON CodePen .

Yes, Object.definePropertythe first flaw, can not monitor an array of changes. However Vue document mentions Vue can be detected by an array of changes, but only the following eight methods, vm.items[indexOfItem] = newValuethis is undetectable.

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

In fact, the author used some clever but useless here, the situation can not monitor the array hack out, Here's an example.

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; const arrayAugmentations = []; aryMethods.forEach((method)=> { // 这里是原生Array的原型方法 let original = Array.prototype[method]; // 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上 // 注意:是属性而非原型属性 arrayAugmentations[method] = function () { console.log('我被改变啦!'); // 调用对应的原生方法并返回结果 return original.apply(this, arguments); }; }); let list = ['a', 'b', 'c']; // 将我们要监听的数组的原型指针指向上面定义的空数组对象 // 别忘了这个空数组的属性上定义了我们封装好的push等方法 list.__proto__ = arrayAugmentations; list.push('d'); // 我被改变啦! 4 // 这里的list2没有被重新定义原型指针,所以就正常输出 let list2 = ['a', 'b', 'c']; list2.push('d'); // 4 

Since only eight methods for the hack, it is also an array of other attributes undetectable, many of them in the pit, you can read the document mentioned above.

We should note that in the implementation of the above, we use multiple passes through the object's properties traversal methods, which leads to Object.definePropertythe second flaw, only hijack properties of an object, so we need each attribute for each object traversal, if the property values are objects you need to traverse depth, apparently hijacked a complete object can be a better choice.

Object.keys(value).forEach(key => this.convert(key, value[key]));


3.Proxy realization of two-way binding characteristics

Proxy was officially released ES2015 specification, it set up a layer of "blocking" before the target object outside access to the object, this layer must first pass interception, thus providing a mechanism to filter access to the outside world and rewrite, we can think, Proxy is Object.definePropertya full range of enhanced and specific document can be viewed here ;

3.1 Proxy can directly monitor the object rather than the property

We still used in the text above Object.definePropertyto achieve a minimalist version of the two-way binding, for example, be rewritten by Proxy.

const input = document.getElementById('input');
const p = document.getElementById('p'); const obj = {}; const newObj = new Proxy(obj, { get: function(target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { console.log(target, key, value, receiver); if (key === 'text') { input.value = value; p.innerHTML = value; } return Reflect.set(target, key, value, receiver); }, }); input.addEventListener('keyup', function(e) { newObj.text = e.target.value; }); 

Examples of online Proxy Edition by Iwobi ( @xiaomuzhu ) ON CodePen .

We can see, Proxy directly can hijack the entire object and returns a new object, regardless of the extent or operation of the convenience features are much stronger than the bottom Object.defineProperty.

3.2 Proxy can directly monitor changes in the array

When we operate (push, shift, splice, etc.) on the array, it will trigger a corresponding method name and length change, we can take this operation, the above text Object.definePropertylist can not take effect rendering example.

const list = document.getElementById('list');
const btn = document.getElementById('btn'); // 渲染列表 const Render = { // 初始化 init: function(arr) { const fragment = document.createDocumentFragment(); for (let i = 0; i < arr.length; i++) { const li = document.createElement('li'); li.textContent = arr[i]; fragment.appendChild(li); } list.appendChild(fragment); }, // 我们只考虑了增加的情况,仅作为示例 change: function(val) { const li = document.createElement('li'); li.textContent = val; list.appendChild(li); }, }; // 初始数组 const arr = [1, 2, 3, 4]; // 监听数组 const newArr = new Proxy(arr, { get: function(target, key, receiver) { console.log(key); return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { console.log(target, key, value, receiver); if (key !== 'length') { Render.change(value); } return Reflect.set(target, key, value, receiver); }, }); // 初始化 window.onload = function() { Render.init(arr); } // push数字 btn.addEventListener('click', function() { newArr.push(6); }); 

Examples of online Proxy list rendered by Iwobi ( @xiaomuzhu ) ON CodePen .

Obviously, Proxy does not need so many hack (hack can not even achieve the perfect listener) can not pressure monitor changes in the array, we all know that the standard will always take precedence over hack.

Other advantages of 3.3 Proxy

Proxy up to 13 different interception method is not limited to apply, ownKeys, deleteProperty, has like are Object.definePropertynot available.

Proxy returns a new object, we can only achieve the purpose of operating the new object, and Object.definePropertycan only traverse the object properties directly modified.

Proxy as a new standard for browser makers will be the focus of ongoing performance optimization, it is the legendary new standard of performance bonuses.

Of course, the disadvantage is that Proxy compatibility issues, and can not be polished with polyfill, therefore Vue authors declare only need to wait until the next major version (3.0) in order to use Proxy rewrite.

Next Issue

Prepare a next issue we mainly talk about why we need front-end framework, or change several question is asked, why did you choose for this project Angular, Vue, React and other frameworks, rather than directly JQuery or js? Framework may not use any problems? What are the advantages in using frames? JQuery framework to solve the problem can not be resolved?

The problem is electrical surface artifact, open-ended questions very well and does not require face-to pull some of the details, but there are some students thinking skills gap and follow the trend of students learning framework easily exposed.

We will process this side to answer questions side to build a Mini version of Vue by Proxy, build Vue is that we continue to solve the various problems encountered without using the framework of the process.

 



Reference: https: //www.jianshu.com/p/2df6dcddb0d7

Jane book copyright reserved by the authors, are reproduced in any form, please contact the author to obtain authorization and indicate the source.

Guess you like

Origin www.cnblogs.com/still1/p/11008455.html