Dissecting Vue.js reactive views from JavaScript property descriptors

You can see an application example first, click here

foreword

A JavaScript object is a collection of key-value pairs, which can have any number of unique keys. The keys can be of type String (String) or Mark (Symbol, a basic data type added to ES6), and each key corresponds to a value, which can be any value of any type. For properties in objects, JavaScript provides a property descriptor interface PropertyDescriptor. Most developers do not need to use it directly, but many frameworks and class libraries use it internally, such as avalon.js and Vue.js. This article introduces Property descriptors and related applications.

define object properties

Before introducing the description of object properties, let's first introduce how to define object properties. The most common way is to use the following way:


	var a = {
		name: 'jh'
	};

	// or 
	var b = {};
	b.name = 'jh';
	
	// or
	var c = {};
	var key = 'name';
	c[key] = 'jh';

This article uses the literal method to create objects, but JavaScript also provides other methods, such as, new Object(), Object.create(), for more information, please see Object Initialization .

Object.defineProperty()

The method usually used above cannot realize the operation of the property descriptor. We need to use a defineProperty()method, which defines a new property for an object or modifies an already defined property, accepts three parameters Object.defineProperty(obj, prop, descriptor), and returns the object after the operation:

  • obj, the object to be manipulated
  • property name
  • The property description object for the action property

	var x = {};
	Object.defineProperty(x, 'count', {});
	console.log(x); // Object {count: undefined}

Since an empty property description object is passed in, the property value of the output object is undefined. When using defineProperty()methods to manipulate properties, the default value of the description object is:

  • value: undefined
  • set: undefined
  • get: undefined
  • writable: false
  • enumerable: false,
  • configurable: false

If you do not use this method to define a property, the default description of the property is:

  • value: undefined
  • set: undefined
  • get: undefined
  • writable: true
  • enumerable: true,
  • configurable: true

Default values ​​can be overridden by explicit parameter value settings.

Of course, it also supports batch definition of object properties and description objects, using the ``Object.defineProperties()`` method, such as:


	var x = {};
	Object.defineProperties(x, {
		count: {
			value: 0
		},
		name: {
			value: 'jh'
		}
	});
	console.log(x); // Object {count: 0, name: 'jh'}

Read property description object

JavaScript supports us to read the description object of an object property, using the Object.getOwnPropertyDescriptor(obj, prop)method:


	var x = {
		name: 'jh'
	};
	Object.defineProperty(x, 'count', {});
	Object.getOwnPropertyDescriptor(x, 'count');
	Object.getOwnPropertyDescriptor(x, 'name');

	// Object {value: undefined, writable: false, enumerable: false, configurable: false}
	// Object {value: "jh", writable: true, enumerable: true, configurable: true}

This example also proves that when the attributes are defined in different ways, the default attribute description objects are different.

property description object

PropertyDescriptorThe API provides six instance properties to describe object properties, including: configurable, enumerable, get, set, value, writable.

value

Specify object property values:


	var x = {};
	Object.defineProperty(x, 'count', {
		value: 0
	});
	console.log(x); // Object {count: 0}

writable

Specifies whether object properties are mutable:


	var x = {};
	Object.defineProperty(x, 'count', {
		value: 0
	});
	console.log(x); // Object {count: 0}
	x.count = 1; // 静默失败,不会报错
	console.log(x); // Object {count: 0}

When using the defineProperty()method, there is by default writable: false, and it needs to be displayed writable: true.

Accessor functions (getter/setter)

Object properties can set accessor functions, use the getdeclared accessor getter function, and setdeclare the accessor setter function; if there is an accessor function, when accessing or setting the property, the corresponding accessor function will be called:

get

When reading the property value, call the function and assign the return value of the function to the property value;


	var x = {};
	Object.defineProperty(x, 'count', {
		get: function() {
			console.log('读取count属性 +1');
			return 0;
		}
	});
	console.log(x); // Object {count: 0}
	x.count = 1;
	// '读取count属性 +1'
	console.log(x.count); // 0

set

This function is called when the function value is set, and the function receives the set property value as a parameter:


	var x = {};
	Object.defineProperty(x, 'count', {
		set: function(val) {
			this.count = val;
		}
	});
	console.log(x);
	x.count = 1; 

Execute the appeal code, you will find an error, and the execution stack overflows:

stack overflow

The above code countwill call setthe method when setting the property, and assigning the property within the method countwill trigger the setmethod again, so this will not work, JavaScript uses another way, usually the accessor function must be declared at the same time, the code is as follows:


	var x = {};
	Object.defineProperty(x, 'count', {
		get: function() {
			return this._count;
		},
		set: function(val) {
			console.log('设置count属性 +1');
			this._count = val;
		}
	});
	console.log(x); // Object {count: undefined}
	x.count = 1;
	// '设置count属性 +1'
	console.log(x.count); 1

In fact, when using a defineProperty()method to set a property, it is usually necessary to maintain a new internal variable inside the object (starting with an underscore _, indicating that it does not want to be accessed from the outside), as an intermediary for the accessor function.

Note: When the accessor description is set, it cannot be set valueand writabledescribed.

We found that after setting the property accessor function, we can achieve real-time monitoring of the property, which is very useful in practice, which will be confirmed later.

enumerable

Specify whether a property in the object can be enumerated, that is, for inwhether the use operation can be traversed:


	var x = {
		name: 'jh'
	};
	Object.defineProperty(x, 'count', {
		value: 0
	});
	for (var key in x) {
		console.log(key + ' is ' + x[key]);
	}
	// name is jh

The above property cannot be traversed count, because when the defineProperty()method is used, it is available by default enumerable: false, and the description needs to be declared explicitly:

	
	var x = {
		name: 'jh'
	};
	Object.defineProperty(x, 'count', {
		value: 0,
		enumerable: true
	});
	for (var key in x) {
		console.log(key + ' is ' + x[key]);
	}
	// name is jh
	// count is 0
	x.propertyIsEnumerable('count'); // true

configurable

This value specifies whether object property descriptions are mutable:


	var x = {};
	Object.defineProperty(x, 'count', { 
		value: 0, 
        writable: false
	});
	Object.defineProperty(x, 'count', { 
		value: 0, 
        writable: true
	});

Executing the above code will report an error, because defineProperty()the default is when using the method, and the configurable: falseoutput is as shown:

configurable:false

Modify as follows, you can:


	var x = {};
	Object.defineProperty(x, 'count', { 
		value: 0, 
        writable: false,
		configurable: true
	});
	x.count = 1;
	console.log(x.count); // 0
	Object.defineProperty(x, 'count', { 
        writable: true
	});
	x.count = 1;
	console.log(x.count); // 1

Property description and view model binding

Having introduced the property description object, let's take a look at its application in modern JavaScript frameworks and class libraries. At present, there are many frameworks and class libraries that implement one-way or even two-way binding of data and DOM views, such as React, angular.js, avalon.js, Vue.js, etc. It is easy to use them to make responsive updates to data changes. DOM views, and even views and models can be two-way bound and updated synchronously. Of course, the internal implementation principles of these frameworks and class libraries are mainly divided into three camps. This article takes Vue.js as an example. Vue.js is a popular responsive view layer class library. Its internal implementation of the responsive principle is the specific application of the attribute description introduced in this article in technology.

You can click here to see an example of a simple data view one-way binding implemented by native JavaScript. In this example, clicking the button can realize the automatic increment of the count, and the input content in the input box will be updated to the display DOM synchronously, and even changed in the console dataObject property value, DOM will respond to update, as shown in the figure:

Data View One-Way Binding Example

Click to see the full example code .

Data view one-way binding

The existing code is as follows:


	var data = {};
	var contentEl = document.querySelector('.content');


	Object.defineProperty(data, 'text', {
		writable: true,
		configurable: true,
		enumerable: true,
		get: function() {
			return contentEl.innerHTML;
		},
		set: function(val) {
			contentEl.innerHTML = val;
		}
	});

It is easy to see that when we set the textattribute of the data object, the value is set to the content of the view DOM element, and when the attribute value is accessed, the content of the view DOM element is returned, which simply realizes the data to One-way binding of the view, that is, when the data changes, the view is also updated.

The above is only the data view binding for one element, but a slightly experienced developer can encapsulate it according to the above ideas, and it is easy to implement a simple tool class for one-way binding of data to the view.

abstract encapsulation

Next, the above examples are simply abstracted and encapsulated. Click to view the complete example code .

First declare the data structure:


	window.data = {
        title: '数据视图单向绑定',
        content: '使用属性描述器实现数据视图绑定',
        count: 0
    };
	var attr = 'data-on'; // 约定好的语法,声明DOM绑定对象属性

Then the encapsulation function processes the objects in batches, traverses the object properties, sets the description object and registers the callback when the property changes:


	// 为对象中每一个属性设置描述对象,尤其是存取器函数
    function defineDescriptors(obj) {
        for (var key in obj) {
            // 遍历属性
            defineDescriptor(obj, key, obj[key]);
        }

        // 为特定属性设置描述对象
        function defineDescriptor(obj, key, val) {
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get: function() {
                     var value = val;
                     return value;
                },
                set: function(newVal) {
                     if (newVal !== val) {
                         // 值发生变更才执行
                         val = newVal;
                         Observer.emit(key, newVal); // 触发更新DOM
                     }
                }
            });
            Observer.subscribe(key); // 为该属性注册回调
        }
    }

Manage events

Manage property change events and callbacks in a publish-subscribe model:


	// 使用发布/订阅模式,集中管理监控和触发回调事件
    var Observer = {
        watchers: {},
        subscribe: function(key) {
            var el = document.querySelector('[' + attr + '="'+ key + '"]');

            // demo
            var cb = function react(val) {
                el.innerHTML = val;
            }

            if (this.watchers[key]) {
                this.watchers[key].push(cb);
            } else {
                this.watchers[key] = [].concat(cb);
            }
        },
        emit: function(key, val) {
            var len = this.watchers[key] && this.watchers[key].length;

            if (len && len > 0) {
                for(var i = 0; i < len; i++) {
                    this.watchers[key][i](val);
                }
            }
        }
    };	

Initialize the instance

Finally initialize the instance:


	// 初始化demo
    function init() {
        defineDescriptors(data); // 处理数据对象
        var eles = document.querySelectorAll('[' + attr + ']');

        // 初始遍历DOM展示数据
        // 其实可以将该操作放到属性描述对象的get方法内,则在初始化时只需要对属性遍历访问即可
        for (var i = 0, len = eles.length; i < len; i++) {
            eles[i].innerHTML = data[eles[i].getAttribute(attr)];
        }

        // 辅助测试实例
        document.querySelector('.add').addEventListener('click', function(e) {
            data.count += 1;
        });

    }
    init();	

The html code reference is as follows:


	<h2 class="title" data-on="title"></h2>
    <div class="content" data-on="content"></div>
    <div class="count" data-on="count"></div>
    <div>
        请输入内容:
        <input type="text" class="content-input" placeholder="请输入内容">
    </div>
    <button class="add" onclick="">加1</button>

The responsive principle of Vue.js

The previous section implemented a simple one-way binding instance of data view. Now we briefly analyze the responsive one-way binding of Vue.js, mainly to understand how it tracks data changes.

dependency tracking

Vue.js allows us to datapass a JavaScript object as component data through parameters, and then Vue.js will traverse the properties of this object, use the Object.definePropertymethod to set the description object, and track the change of the property through the accessor function. The essential principle is the same as the previous section. The examples are similar, but the difference is that Vue.js creates Watcherlayers and records properties as dependencies in the process of component rendering. Later, when the dependencies setterare called, they will notify Watcherthe recalculation, so that the associated components can be recalculated. Update, as shown below:

Vue.js Responsive Schematic

When the component is mounted, the instance is watcherinstantiated and passed to the dependency management class. When the component is rendered, the object observation interface is used to traverse the incoming data object, create a dependency management instance for each property, and set the property description object. In the accessor function get function, the dependency management instance adds (records) the attribute as a dependency, and then when the dependency changes, the set function is triggered, and the dependency management instance is notified in this function, and the dependency management instance distributes the change to it. All watcherinstances are stored, watcherinstances are recomputed, and components are updated.

Therefore, it can be concluded that the responsive principle of Vue.js is dependency tracking . Through an observation object, for each property, set the accessor function and register a dependency management instance dep, and maintain an instance depfor each component instance . watcherWhen the instance is notified through the setter dep, depthe instance distributes the change to each watcherinstance, and the watcherinstance calculates and updates the component instance, that is, watchertracks depthe added dependencies. Object.defineProperty()The method provides technical support for this tracking, and the depinstance maintains this tracking relationship.

Simple analysis of source code

Next, a simple analysis of the Vue.js source code, starting with the processing of JavaScript objects and properties:

Observer

First, Vue.js also provides an abstract interface to observe objects, set storage functions for object properties, collect property dependencies and distribute dependency updates:


	var Observer = function Observer (value) {
  		this.value = value;
  		this.dep = new Dep(); // 管理对象依赖
  		this.vmCount = 0;
  		def(value, '__ob__', this); // 缓存处理的对象,标记该对象已处理
  		if (Array.isArray(value)) {
    		var augment = hasProto
      		? protoAugment
      		: copyAugment;
    		augment(value, arrayMethods, arrayKeys);
    		this.observeArray(value);
  		} else {
    		this.walk(value);
  		}
	};

The above code focuses on two nodes, this.observeArray(value)and this.walk(value);:

  1. If it is an object, call the walk()method, traverse the object properties, and convert the properties to reactive:

    
    		Observer.prototype.walk = function walk (obj) {
    			var keys = Object.keys(obj);
    			for (var i = 0; i < keys.length; i++) {
    			defineReactive$$1(obj, keys[i], obj[keys[i]]);
    			}
    		};
    

    As you can see, the final property description object is set by calling a defineReactive$$1()method.

  2. If the value is an array of objects, additional processing is required. The calling observeArray()method generates an instance for each object Observer, and traverses and monitors the properties of the object:

    
    		Observer.prototype.observeArray = function observeArray (items) {
    			for (var i = 0, l = items.length; i < l; i++) {
    			observe(items[i]);
    			}
    		};
    

    observeThe core is to call a function for each array item :

    
    	function observe(value, asRootData) {
    	if (!isObject(value)) {
        	return // 只需要处理对象
    	}
    	var ob;
    	if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        	ob = value.__ob__; // 处理过的则直接读取缓存
    	} else if (
        	observerState.shouldConvert &&
        	!isServerRendering() &&
        	(Array.isArray(value) || isPlainObject(value)) &&
        	Object.isExtensible(value) &&
        	!value._isVue) {
       	    ob = new Observer(value); // 处理该对象
    	}
    	if (asRootData && ob) {
        	ob.vmCount++;
    	}
    	return ob
    	}
    

    After the call ob = new Observer(value);, it returns to the result of the first case: the call defineReactive$$1()method generates the reactive property.

Generate reactive properties

The source code is as follows:


	function defineReactive$$1 (obj,key,val,customSetter) {
  		var dep = new Dep(); // 管理属性依赖

  		var property = Object.getOwnPropertyDescriptor(obj, key);
  		if (property && property.configurable === false) {
    		return
 		}

 		// 之前已经设置了的get/set需要合并调用
  		var getter = property && property.get; 
  		var setter = property && property.set;

  		var childOb = observe(val); // 属性值也可能是对象,需要递归观察处理
  		Object.defineProperty(obj, key, {
    		enumerable: true,
    		configurable: true,
    		get: function reactiveGetter () {
      			var value = getter ? getter.call(obj) : val;
      			if (Dep.target) { // 管理依赖对象存在指向的watcher实例
        			dep.depend(); // 添加依赖(记录)
        			if (childOb) { // 属性值为对象
          				childOb.dep.depend(); // 属性值对象也需要添加依赖
        			}
        			if (Array.isArray(value)) {
          				dependArray(value); // 处理数组
        			}
      			}
      			return value
    		},
    		set: function reactiveSetter (newVal) {
      			var value = getter ? getter.call(obj) : val;
      			/* eslint-disable no-self-compare */
      			if (newVal === value || (newVal !== newVal && value !== value)) {
        		return // 未发生变更不需要往后执行
      			}
      			/* eslint-enable no-self-compare */
      			if ("development" !== 'production' && customSetter) {
        			customSetter();
      			}
      			if (setter) {
        			setter.call(obj, newVal); // 更新属性值
      			} else {
        			val = newVal; // 更新属性值
      			}
      			childOb = observe(newVal); // 每次值变更时需要重新观察,因为可能值为对象
      			dep.notify(); // 发布更新事件
    		}
 		});
	}

This method uses the Object.defineProperty()method to set the property description object, and the logic is concentrated inside the property accessor function:

  1. get: return the property value, if it watcherexists, recursively record the dependency;
  2. set: When the property value changes, update the property value and call the dep.notify()method to publish the update event;

Manage dependencies

Vue.js needs to manage the dependencies of objects, notify the watcherupdate components when the properties are updated, and then update the view. The Vue.js management dependency interface is implemented in the publish-subscribe mode. The source code is as follows:


	var uid$1 = 0;
	var Dep = function Dep () {
  		this.id = uid$1++; // 依赖管理实例id
  		this.subs = []; // 订阅该依赖管理实例的watcher实例数组
	};
	Dep.prototype.depend = function depend () { // 添加依赖
  		if (Dep.target) {
   		 	Dep.target.addDep(this); // 调用watcher实例方法订阅此依赖管理实例
  		}
	};
	Dep.target = null; // watcher实例
	var targetStack = []; // 维护watcher实例栈

	function pushTarget (_target) {
  		if (Dep.target) { targetStack.push(Dep.target); }
  		Dep.target = _target; // 初始化Dep指向的watcher实例
	}

	function popTarget () {
  		Dep.target = targetStack.pop();
	}
subscription

As before, when generating a responsive property to set the accessor function for the property, the method is called in the get function to dep.depend()add a dependency, and the method is called within the method Dep.target.addDep(this);, that is, the method of the pointed watcherinstance is called, addDepand the dependency management instance is subscribed:


	Watcher.prototype.addDep = function addDep (dep) {
  		var id = dep.id;
  		if (!this.newDepIds.has(id)) { // 是否已订阅
    		this.newDepIds.add(id); // watcher实例维护的依赖管理实例id集合
    		this.newDeps.push(dep); // watcher实例维护的依赖管理实例数组
    		if (!this.depIds.has(id)) { // watcher实例维护的依赖管理实例id集合
				// 调用传递过来的依赖管理实例方法,添加此watcher实例为订阅者
      			dep.addSub(this); 
    		}
  		}
	};

watcherInstances may track multiple properties at the same time (that is, subscribe to multiple dependency management instances), so it is necessary to maintain an array to store multiple subscription dependency management instances, and record the id of each instance, so as to determine whether it has been subscribed, and then call dependency management. Instance addSubmethods:


	Dep.prototype.addSub = function addSub (sub) {
  		this.subs.push(sub); // 实现watcher到依赖管理实例的订阅关系
	};

This method simply adds a subscription to the dependency management instance in the subscriptions array watcher.

release

When a property changes, a method is called within the property's accessor set function dep.notify(), publishing this property change:


	Dep.prototype.notify = function notify () {
  		// 复制订阅者数组
  		var subs = this.subs.slice();
  		for (var i = 0, l = subs.length; i < l; i++) {
    		subs[i].update(); // 分发变更
  		}
	};

trigger update

As mentioned earlier, in Vue.js, the watcherlayer tracks dependency changes, and when a change occurs, the component is notified to update:


	Watcher.prototype.update = function update () {
  		/* istanbul ignore else */
  		if (this.lazy) {
    		this.dirty = true;
  		} else if (this.sync) { // 同步
    		this.run();
  		} else { // 异步
    		queueWatcher(this); // 最后也是调用run()方法
  		}
	};

Call runthe method to notify the component to update:


	Watcher.prototype.run = function run () {
  		if (this.active) {
    		var value = this.get(); // 获取新属性值
    		if (value !== this.value || // 若值
				isObject(value) || this.deep) {
      			var oldValue = this.value; // 缓存旧值
      			this.value = value; // 设置新值
      			if (this.user) {
        			try {
          				this.cb.call(this.vm, value, oldValue);
        			} catch (e) {
          				handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
        			}
      			} else {
       			 	this.cb.call(this.vm, value, oldValue);
      			}
    		}
  		}
	};

Call this.get()the method. In fact, you will see later that the update of the attribute value and the update of the component are processed in this method. Here it is judged that when the attribute changes, the callback function passed to the instance during initialization is called cb, and the callback function accepts both the old and new values ​​of the attribute. parameter, this callback usually watchexists only for the declared listening property, otherwise it defaults to an empty function.

Track dependency interface instantiation

Each responsive property is Watchertracked by an instance, and for different properties (data, computed, watch), Vue.js performs some differential processing. The following is the main logic of the interface:


	var Watcher = function Watcher (vm,expOrFn,cb,options) {
		this.cb = cb;
		...
		// parse expression for getter
  		if (typeof expOrFn === 'function') {
    		this.getter = expOrFn;
  		} else {
   		 	this.getter = parsePath(expOrFn);
  		}
		this.value = this.lazy
    		? undefined
    		: this.get();
	};

WatcherWhen the instance is initialized , the expOrFnparameter (expression or function) is parsed into an extended getter this.getter, and then the method is called this.get(), returning the value as the this.valuevalue:


	Watcher.prototype.get = function get () {
  		pushTarget(this); // 入栈watcher实例
  		var value;
  		var vm = this.vm;
  		if (this.user) {
    		try {
      			value = this.getter.call(vm, vm); // 通过this.getter获取新值
    		} catch (e) {
      			handleError(e, vm, ("getter for watcher \"" +
 (this.expression) + "\""));
    		}
  		} else {
    		value = this.getter.call(vm, vm); // 通过this.getter获取新值
  		}
  			
  		if (this.deep) { // 深度递归遍历对象追踪依赖
    		traverse(value);
  		}
  		popTarget(); // 出栈watcher实例
  		this.cleanupDeps(); // 清空缓存依赖
  		return value // 返回新值
	};

It should be noted here that for dataproperties, not computedproperties or watchproperties, their watcherinstances are this.getterusually functions updateComponent, that is, rendering and updating components, and the method returns getundefined. The value is the return value of this method.computedthis.getterget

data common attribute

The Vue.jsdata property is an object, and the object observation interface needs to be called new Observer(value):


	function observe (value, asRootData) {
		if (!isObject(value)) {
    		return
  		}
  		var ob;
		ob = new Observer(value); // 对象观察实例
		return ob;
	}

	// 初始处理data属性
	function initData (vm) {
		// 调用observe函数
		observe(data, true /* asRootData */);
	}
computed property

Vue.js handles computed properties differently. It is a variable that can directly call Watcherthe interface and pass the computation rules specified by its properties as the extension of properties getter, namely:


	// 初始处理computed计算属性
	function initComputed (vm, computed) {
		for (var key in computed) {
    		var userDef = computed[key]; // 对应的计算规则
			// 传递给watcher实例的this.getter -- 拓展getter
    		var getter = typeof userDef === 'function' ? 
				userDef : userDef.get; 
			watchers[key] = new Watcher(vm, 
				getter, noop, computedWatcherOptions);
		}
	}
watch property

The watch property is different, the property is a variable or an expression, and unlike the computed property, it needs to specify a callback function after the change event occurs:


	function initWatch (vm, watch) {
		for (var key in watch) {
			var handler = watch[key];
			createWatcher(vm, key, handler[i]); // 传递回调
		}
	}
	function createWatcher (vm, key, handler) {
		vm.$watch(key, handler, options); // 回调
	}
	Vue.prototype.$watch = function (expOrFn, cb, options) {
		// 实例化watcher,并传递回调
		var watcher = new Watcher(vm, expOrFn, cb, options);
	}
Initialize the connection between Watcher and the dependency management interface

No matter what kind of property is finally implemented by the watcherinterface to track the dependency, and when the component is mounted, it will initialize an Watcherinstance, bind to Dep.target, that is Watcher, Depestablish a connection with, so that the property dependency can be tracked when the component is rendered:

	function mountComponent (vm, el, hydrating) {
		...
		updateComponent = function () {
      		vm._update(vm._render(), hydrating);
			...
    	};
		...
		vm._watcher = new Watcher(vm, updateComponent, noop);
		...
	}

As above, pass the updateComponentmethod to the watcherinstance, which triggers the vm._render()rendering method of the component instance and triggers the update of the component. This mountComponent()method will be $mount()called in the public method of the mounted component:


	// public mount method
	Vue$3.prototype.$mount = function (el, hydrating) {
  		el = el && inBrowser ? query(el) : undefined;
  		return mountComponent(this, el, hydrating)
	};

Summarize

So far, the introduction and application of the JavaScript property descriptor interface, as well as its responsive practical principles in Vue.js have been basically explained. This time, the summary is from principle to application, and then to practical analysis, which takes a lot of effort. , but the gains are proportional, not only have a deeper understanding of the basics of JavaScript, but also more familiar with the responsive design principles of Vue.js, and the familiarity with its source code has also been greatly improved. More summaries to share.

refer to

  1. Object.defineProperty
  2. Vue.js Reactivity in Depth
  3. Object Initializer

Link to this article: Dissecting Vue.js responsive views from JavaScript property descriptors

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325449840&siteId=291194637