Ad/iklan :







Search.Pencarian Menu

Add text send email to rh3252705.adda@blogger.com or Click this (Text porn Will delete) | Tambah teks kirim email ke rh3252705.adda@blogger.com atau Klik ini (Teks porno akan dihapus)
Total post.pos : 13631+

[Go Make Things] A primer on JavaScript Proxies

Today, we're going to look at JS Proxies: what they are, how they work, and why you might want to use them.

Let's dig in!

What's a Proxy?

The Proxy object lets you detect when someone interacts with a property off an array or object, and run code in response.

You can use the new Proxy() constructor to create a new Proxy object. Pass in an array ([]) or object ({}) to create a Proxy from as an argument, along with a handler object that defines how to handle interactions (more on that shortly).

In this example, we're creating a Proxy from the wizards object, and passing in an empty object as our handler.

let wizard = {  	name: 'Merlin',  	tool: 'Wand'  };    // Create a Proxy object  let wizardProxy = new Proxy(wizard, {});  

The handler object and traps

The handler object tells the Proxy how to respond to interactions with the object or array properties. You can assign a handful of functions—called traps—that run as callbacks for different types of interactions.

There are over a dozen different trap methods, but the three most common are the get(), set(), and deleteProperty() methods. These run whenever someone gets, sets, or deletes a property on the array or object, respectively.

In this example, we get, set, and delete our object properties as normal. But, we'll also log some information into the console so you can see what happens when we make changes to our array or object. For simplicity, we can use the object property shorthand syntax.

// Create a Proxy object  let wizardProxy = new Proxy(wizard, {    	/**  	 * Runs when a property value is retrieved  	 * @param  {Object|Array}   obj The object or array the Proxy is handling  	 * @param  {String|Integer} key The property key or index  	 */  	get (obj, key) {  		console.log('get', obj, key, obj[key]);  		return obj[key];  	},    	/**  	 * Runs when a property is defined or updated  	 * @param  {Object|Array}   obj   The object or array the Proxy is handling  	 * @param  {String|Integer} key   The property key or index  	 * @param  {*}              value The value to assign to the property  	 */  	set (obj, key, value) {    		console.log('set', obj, key, value);    		// Update the property  		obj[key] = value;    		// Indicate success  		// This is required  		return true;    	},    	/**  	 * Runs when a property is deleted  	 * @param  {Object|Array}   obj   The object or array the Proxy is handling  	 * @param  {String|Integer} key   The property key or index  	 */  	deleteProperty (obj, key) {    		console.log('delete', obj, key, obj[key]);    		// Delete the property  		delete obj[key];    		// Indicate success  		// This is required  		return true;    	}    });  

Now, we can modify our Proxy object just like we would a plain object, and the Proxy handler trap methods will run our code in response.

// Runs the get() trap and logs...  // "get" {name: 'Merlin', tool: 'Wand'} "name" "Merlin"  let name = wizardProxy.name;    // Runs the set() trap and logs...  // "set" {name: 'Merlin', tool: 'Wand'} "age" 172  wizardProxy.age = 172;    // Runs the deleteProperty() trap and logs...  // "delete" {name: 'Merlin', tool: 'Wand'} "tool" "Wand"  delete wizardProxy.tool;  

Here's a demo.

If you're enjoying this tutorial, it's one of the many in-depth reference guides you can find over at the Lean Web Club.

Proxies and nesting

One challenge with Proxies is that they only detect changes to first-level properties of the object or array. Properties that are nested objects and arrays of the Proxy object aren't Proxies themselves, and aren't detected.

Here, we have a wizard object with a nested array of spells. We also have a handler object with get() and set() methods. They both log a message in the console, but otherwise maintain the default behavior.

We create a Proxy object with the wizard and handler objects, and assign it to the wizardProxy variable.

// An object with a nested array  let wizard = {  	name: 'Merlin',  	tool: 'wand',  	spells: ['Abbracadabra', 'Disappear']  };    // A handler object  let handler = {  	get (obj, key) {  		console.log('get', key);  		return obj[key];  	},  	set (obj, key, value) {  		console.log('set', key);  		obj[key] = value;  		return true;  	}  };    // Create a new Proxy  let wizardProxy = new Proxy(wizard, handler);  

If we add a property to the wizardProxy, or get a property value from it, a message logs to the console just like you'd expect.

// logs "get" "name" and "set" "age" respectively  let name = wizardProxy.name;  wizardProxy.age = 172;  

But, if we get a property from the wizardProxy.spells array, the handler.get() method runs when we retrieve the spells array, but not any specific properties from it.

But, if we use the Array.prototype.push() method to add a property to the wizardProxy.spells array, the handler.get() method runs when we retrieve the spells array, but the handler.set() method never runs.

// Update the nested array  // logs "get" "spells"  wizardProxy.spells.push('Heal');  

How to handle nested arrays and objects in a Proxy object

To detect nested arrays and objects inside a Proxy object, we first need to move the handler object into a function that returns the object.

function handler () {  	return {  		get (obj, key) {  			console.log('get', key);  			return obj[key];  		},  		set (obj, key, value) {  			console.log('set', key);  			obj[key] = value;  			return true;  		}  	};  }    // Create a new Proxy  let wizardProxy = new Proxy(wizard, handler());  

Inside the handler.get() method, we need to check if the value of the property (the obj[key]) is an array or object.

If it is, we'll pass it into the new Proxy() constructor and return that, recursively passing in the handler() function. If not, we'll return it as-is.

The typeof operator returns object for all sorts of things that aren't plain objects ({}), so we'll use a different approach to figure that out. We can use the call() method on the Object.prototype.toString() method, and pass in the item we want to check. This will return the prototype name.

// returns [object Array]  Object.prototype.toString.call([]);    // [object Object]  Object.prototype.toString.call({});  

We'll create an array with [object Object] and [object Array] in it, then use the Array.prototype.includes() method to check if the string returned by Object.prototype.toString.call(obj[key]) is one of those two values.

If it is, we'll return new Proxy(), passing the obj[key] and handler() in as arguments.

function handler () {  	return {  		get (obj, key) {  			console.log('get', key);    			// If the item is an object or array, return a proxy  			let nested = ['[object Object]', '[object Array]'];  			let type = Object.prototype.toString.call(obj[key]);  			if (nested.includes(type)) {  				return new Proxy(obj[key], handler());  			}    			return obj[key];  		},  		set (obj, key, value) {  			console.log('set', key);  			obj[key] = value;  			return true;  		}  	};  }  

Now, when we Array.prototype.push() an item in the wizardProxy.spells array, our handler.set() method actually runs.

wizardProxy.spells.push('Heal');  

How to avoid creating a Proxy of a Proxy

Proxies are opaque. There's no native property you can look at to determine if an object is already a Proxy or not.

With our current code, it's possible to create a Proxy from a Proxy, which results in the handler() function running on the same array or object multiple times. If this happens a few times over, the browser can lag or even crash.

let data = new Proxy({  	wizards: {  		list: ['Gandalf', 'Radagast', 'Merlin']  	},  	witches: {  		list: ['Ursula', 'Wicked Witch Of The West', 'Malificent']  	}  }, handler());    /**   * Reverse the witches and wizards   * After a few dozen swaps, the browser will lag or crash   */  function swapMagic () {  	let tempCache = data.wizards.list;  	data.wizards.list = data.witches.list;  	data.witches.list = tempCache;  }  

While there isn't a browser-native way to check if an array or object is already a Proxy, we can add one using the handler object.

In the handler.get() method, we'll first check if the key being retrieved is _isProxy. If so, we'll return true.

This isn't an actual property of the object. It's an internal dummy property that only returns true if the handler.get() method is being run. If that happens, we know that the property is already a Proxy object.

// A handler object  function handler () {  	return {  		get (obj, key) {    			// If the key is "_isProxy", return true  			// This will only happen if the property is already a Proxy  			if (key === '_isProxy') return true;    			// ...    		},  		// ...  	};  }  

If the property is an array or object, we'll check if the _isProxy property returns true.

If it does, the array or object is already being managed by the handler object and is already Proxy, so we can return it as-is. If not, it's a plain array or object, and we can safely return a new Proxy().

// A handler object  function handler () {  	return {  		get (obj, key) {    			// If the key is "_isProxy", return true  			// This will only happen if the property is already a Proxy  			if (key === '_isProxy') return true;    			// If the item is an object or array and not already a Proxy, return a new Proxy  			let nested = ['[object Object]', '[object Array]'];  			let type = Object.prototype.toString.call(obj[key]);  			if (nested.includes(type) && !obj[key]._isProxy) {  				return new Proxy(obj[key], handler());  			}    			// Otherwise, return the property  			return obj[key];    		},  		// ...  	};  }  

With these two little additions, we avoid nesting arrays and objects in multiple Proxy handlers, and the performance issues that come along with it.

Join the Lean Web Club! Coaching. Courses. Coding resources. Get the skills, confidence, and support you need to learn front-end web development and achieve long-term success.

Cheers,
Chris

Want to share this with others or read it later? View it in a browser.

Share :

Facebook Twitter Google+
0 Komentar untuk "[Go Make Things] A primer on JavaScript Proxies"

Back To Top