Hooks

A JavaScript hook/reply ultility

Tính đến 21-05-2016. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/18715/126794/Hooks.js

// ==UserScript==
// @name			Hooks
// @namespace		[email protected]
// @description		A JavaScript hook/reply ultility
// @author			xymopen
// @version			1.1.2
// @grant			none
// @license			BSD 2-Clause
// @homepageURL		https://github.com/xymopen/JS_Utilities/blob/master/Hooks.js
// @updateURL		https://raw.githubusercontent.com/xymopen/JS_Utilities/master/Hooks.js
// ==/UserScript==

// TODO: JsDoc
// TODO: Unit test

var Hooks = ( function() {
	"use strict";
	
	/**
	 * Hooks functions or properties, getters/setters
	 * and methods of an object you are intrested in
	 */
	var Hooks = {
		/**
		 * hook a function
		 * @function apply
		 * @param {function} target				- a function to be hook
		 * @param {onApply} onApply				- the hook
		 */
		"apply": function apply( target, onApply ) {
			if ( "function" === typeof target && "function" === typeof onApply ) {
				return function() {
					return onApply.call( this, target, this, arguments );
				};
			} else {
				throw new TypeError();
			}
		},
		/**
		 * @callback onApply the hook
		 * @param {function} target				- the function hooked
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} argv				- the arguments pass to the target
		 */

		/**
		 * hook a property of an object
		 * @function property
		 * @param {object} target				- an object having or to have the property to be hooked
		 * @param {string} propertyName			- the name of the property to be hooked
		 * @param {onGet} onGet					- the hook call when about to get the property
		 * @param {onSet} onSet					- the hook call when about to set the property
		 */
		"property": function property( target, propertyName, onGet, onSet ) {
			var descriptor, oldValue;

			if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
				descriptor = Object.getOwnPropertyDescriptor( target, propertyName );

				if ( Object.prototype.hasOwnProperty.call( descriptor, "value" ) ) {
					oldValue = descriptor.value;

					delete descriptor.value;
					delete descriptor.writable;
				} else if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
					oldValue = descriptor.get.call( target );
				} else {
					oldValue = undefined;
				}
			} else {
				descriptor = {
					"configurable": true,
					"enumerable": true,
				};
				
				oldValue = undefined;
			}

			descriptor.get = function get() {
				return onGet.call( this, target, propertyName, oldValue );
			};

			descriptor.set = function set( newValue ) {
				oldValue = onSet.call( this, target, propertyName, oldValue, newValue );
				return oldValue;
			};

			Object.defineProperty( target, propertyName, descriptor );
		},
		/**
		 * the hook call when about to get the property
		 * @callback onGet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- the name of the property hooked
		 * @param {any} oldValue				- the current value of the property
		 */

		/**
		 * the hook call when about to set the property
		 * @callback onSet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- the name of the property hooked
		 * @param {any} oldValue				- the current value of the property
		 * @param {any} newValue				- the value about to be set to the property
		 */

		/**
		 * alias of #property but fill the #onSet automatically
		 * @function get
		 * @param {object} target				- an object having or to have the property to be hooked
		 * @param {string} propertyName			- he name of the property to be hooked
		 * @param {onGet} onGet					- the hook call when about to get the property
		 */
		"get": function get( target, propertyName, onGet ) {
			return Hooks.property( target, propertyName, onGet, function( target, propertyName, oldValue, newValue ) {
				return Hooks.Reply.get( arguments );
			} );
		},
		/**
		 * the hook call when about to get the property
		 * @callback onGet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- the name of the property hooked
		 * @param {any} oldValue				- the current value of the property
		 */

		/**
		 * alias of #property but fill the #onGet automatically
		 * @function set
		 * @param {object} target				- an object having or to have the property to be hooked
		 * @param {string} propertyName			- the name of the property to be hooked
		 * @param {onSet} onSet					- the hook call when about to set the property
		 */
		"set": function set( target, propertyName, onSet ) {
			return Hooks.property( target, propertyName, function( target, propertyName, oldValue ) {
				return Hooks.Reply.set( arguments );
			}, onSet );
		},
		/**
		 * the hook call when about to set the property
		 * @callback onSet
		 * @param {object} target				- the object having the property hooked
		 * @param {string} propertyName			- he name of the property hooked
		 * @param {any} oldValue				- the current value of the property
		 * @param {any} newValue				- the value about to be set to the property
		 */

		/**
		 * hook a method of an object
		 * @function method
		 * @param {object} target				- an object having or to have the method to be hooked
		 * @param {string} methodName			- the name of the method to be hooked
		 * @param {onApply} onApply				- the hook replace the method
		 */
		"method": function method( target, methodName, onApply ) {
			var method = target[ methodName ], descriptor;
			
			function hookMethod( method, onApply ) {
				return Hooks.apply( method, function( method, thisArg, argv ) {
					return onApply.call( this, target, methodName, method, thisArg, argv );
				} );
			};
			
			if ( "function" === typeof method ) {
				target[ methodName ] = hookMethod( method, onApply );
			} else {
				if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
					throw new Error( "ERR_PROPERTY_EXISTS_NOT_METHOD" );
				} else {
					Hooks.set( target, methodName, function onSet( target, propertyName, oldValue, newValue ) {
						Object.defineProperty( target, methodName, {
							"configurable": true,
							"enumerable": true,
							"value": "function" === typeof newValue ? hookMethod( newValue, onApply ) : newValue,
							"writable": true,
						} );
					} );
				}
			}
		},
		/**
		 * the hook replace the method
		 * @callback onApply
		 * @param {object} target				- the object having the method hooked
		 * @param {string} methodName			- the name of the method hooked
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} argv				- the arguments pass to the method
		 */
		
		/**
		 * hook a getter property of an object
		 * @function getter
		 * @param {object} target				- an object having the getter property to be hooked
		 * @param {string} propertyName			- the name of the getter property to be hooked
		 * @param {onGetter} onGetter				- the hook replace the getter
		 */
		"getter": function getter( target, propertyName, onGetter ) {
			return trap( target, propertyName, onGetter, "get", "ERR_NOT_A_GETTER" );
		},
		/**
		 * the hook replace the getter
		 * @callback onSetter
		 * @param {object} target				- the object having the getter property hooked
		 * @param {string} propertyName			- he name of the getter property hooked
		 * @param {function} getter				- the getter replaced
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} argv				- the arguments pass to the getter, should be #undefined
		 */

		/**
		 * hook a setter property of an object
		 * @function setter
		 * @param {object} target				- an object having the setter property to be hooked
		 * @param {string} propertyName			- the name of the setter property to be hooked
		 * @param {onSetter} onSetter				- the hook replace the setter
		 */
		"setter": function setter( target, propertyName, onSetter ) {
			return trap( target, propertyName, onSetter, "set", "ERR_NOT_A_SETTER" );
		},
		/**
		 * the hook replace the setter
		 * @callback onSetter
		 * @param {object} target				- the object having the setter property hooked
		 * @param {string} propertyName			- he name of the setter property hooked
		 * @param {function} setter				- the setter replaced
		 * @param {object|undefined} thisArg	- #this reference
		 * @param {arguments} argv				- the arguments pass to the setter, should be a right value
		 */
	};
	
	var Reply = {
		"apply": function apply( param ) {
			var target = param[ 0 ],
				thisArg = param[ 1 ],
				argv = param[ 2 ];
			
			return target.apply( thisArg, argv );
		},
		
		"get": function( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				oldValue = param[ 2 ],
				newValue = param[ 3 ];
			
			return param[ param.length - 1 ];
		},
		
		"getter": function( param ) {
			var target = param[ 0 ],
				propertyName = param[ 1 ],
				fn = param[ 2 ],
				thisArg = param[ 3 ],
				argv = param[ 4 ];
			
			return fn.apply( thisArg, argv );
		},
		
		"method": function method( param ) {
			var target = param[ 0 ],
				methodName = param[ 1 ],
				method = param[ 2 ],
				thisArg = param[ 3 ],
				argv = param[ 4 ];
			
			return method.apply( thisArg, argv );
		},
	};
	
	Reply.set = Reply.get;
	Reply.setter = Reply.getter;
	
	Hooks.Reply = Reply;

	return Hooks;
} )();