Hooks

A JavaScript hook/reply ultility

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/18715/661566/Hooks.js

  1. // ==UserScript==
  2. // @name Hooks
  3. // @namespace xuyiming.open@outlook.com
  4. // @description A JavaScript hook/reply ultility
  5. // @author xymopen
  6. // @version 1.1.5
  7. // @grant none
  8. // @license BSD 2-Clause
  9. // ==/UserScript==
  10.  
  11. // @updateURL https://raw.githubusercontent.com/xymopen/JS_Misc/master/Hooks.js
  12.  
  13. // I was finishing unit test and found there are some bugs about the logic
  14.  
  15. /**
  16. * Hooks functions or properties, getters/setters
  17. * and methods of an object you are intrested in
  18. */
  19.  
  20. var Hooks = ( function () {
  21. "use strict";
  22.  
  23. var Functions = {
  24. /**
  25. * wrapper a function
  26. * @param {Function} target
  27. * @param {(Function, Object?, Arguments) => void} onApply
  28. */
  29. apply: function apply( target, onApply ) {
  30. if ( "function" === typeof target && "function" === typeof onApply ) {
  31. var fn = function () {
  32. return onApply.call( this, target, this, arguments );
  33. };
  34.  
  35. fn.prototype = target.prototype;
  36.  
  37. return fn;
  38. } else {
  39. throw new TypeError();
  40. }
  41. }
  42. };
  43.  
  44. /**
  45. * Hooks functions or properties, getters/setters
  46. * and methods of an object you are intrested in
  47. */
  48. var Hooks = {
  49. /**
  50. * hook a property of an object
  51. * @param {Object} target - an object having or to have the property to be hooked
  52. * @param {String} propertyName - the name of the property to be hooked
  53. * @param {Function} onGet - the hook call when about to get the property
  54. * @param {Function} onSet - the hook call when about to set the property
  55. */
  56. property: function property( target, propertyName, onGet, onSet ) {
  57. var descriptor, oldValue;
  58.  
  59. if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
  60. descriptor = Object.getOwnPropertyDescriptor( target, propertyName );
  61.  
  62. if ( Object.prototype.hasOwnProperty.call( descriptor, "value" ) ) {
  63. oldValue = descriptor.value;
  64.  
  65. delete descriptor.value;
  66. delete descriptor.writable;
  67. } else if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
  68. oldValue = descriptor.get.call( target );
  69. } else {
  70. oldValue = undefined;
  71. }
  72. } else {
  73. descriptor = {
  74. configurable: true,
  75. enumerable: true,
  76. };
  77.  
  78. oldValue = undefined;
  79. }
  80.  
  81. descriptor.get = function get() {
  82. return onGet.call( this, target, propertyName, oldValue );
  83. };
  84.  
  85. descriptor.set = function set( newValue ) {
  86. oldValue = onSet.call( this, target, propertyName, oldValue, newValue );
  87. };
  88.  
  89. Object.defineProperty( target, propertyName, descriptor );
  90. },
  91. /**
  92. * the hook call when about to get the property
  93. * @callback onGet
  94. * @param {object} target - the object having the property hooked
  95. * @param {string} propertyName - the name of the property hooked
  96. * @param {any} oldValue - the current value of the property
  97. */
  98.  
  99. /**
  100. * the hook call when about to set the property
  101. * @callback onSet
  102. * @param {object} target - the object having the property hooked
  103. * @param {string} propertyName - the name of the property hooked
  104. * @param {any} oldValue - the current value of the property
  105. * @param {any} newValue - the value about to be set to the property
  106. */
  107.  
  108. /**
  109. * alias of #property but fill the #onSet automatically
  110. * @function get
  111. * @param {Object} target - an object having or to have the property to be hooked
  112. * @param {String} propertyName - he name of the property to be hooked
  113. * @param {onGet} onGet - the hook call when about to get the property
  114. */
  115. get: function get( target, propertyName, onGet ) {
  116. return Hooks.property( target, propertyName, onGet, function ( target, propertyName, oldValue, newValue ) {
  117. return Hooks.Reply.set( arguments );
  118. } );
  119. },
  120.  
  121. /**
  122. * alias of #property but fill the #onGet automatically
  123. * @function set
  124. * @param {Object} target - an object having or to have the property to be hooked
  125. * @param {String} propertyName - the name of the property to be hooked
  126. * @param {onSet} onSet - the hook call when about to set the property
  127. */
  128. set: function set( target, propertyName, onSet ) {
  129. return Hooks.property( target, propertyName, function ( target, propertyName, oldValue ) {
  130. return Hooks.Reply.get( arguments );
  131. }, onSet );
  132. },
  133.  
  134. /**
  135. * hook a getter property of an object
  136. * @function getter
  137. * @param {object} target - an object having the getter property to be hooked
  138. * @param {string} propertyName - the name of the getter property to be hooked
  139. * @param {onGetter} onGetter - the hook replace the getter
  140. */
  141. getter: function getter( target, propertyName, onGetter ) {
  142. var descriptor;
  143.  
  144. if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
  145. descriptor = Object.getOwnPropertyDescriptor( target, propertyName );
  146.  
  147. if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
  148. descriptor.get = Functions.apply( descriptor.get, function ( getter, thisArg, args ) {
  149. return onGetter.call( this, target, propertyName, getter, thisArg, args );
  150. } );
  151.  
  152. Object.defineProperty( target, propertyName, descriptor );
  153. }
  154. }
  155. },
  156. /**
  157. * the hook replace the getter
  158. * @callback onSetter
  159. * @param {object} target - the object having the getter property hooked
  160. * @param {string} propertyName - he name of the getter property hooked
  161. * @param {function} getter - the getter replaced
  162. * @param {object|undefined} thisArg - #this reference
  163. * @param {arguments} args - the arguments pass to the getter, should be #undefined
  164. */
  165.  
  166. /**
  167. * hook a setter property of an object
  168. * @function setter
  169. * @param {object} target - an object having the setter property to be hooked
  170. * @param {string} propertyName - the name of the setter property to be hooked
  171. * @param {onSetter} onSetter - the hook replace the setter
  172. */
  173. setter: function setter( target, propertyName, onSetter ) {
  174. var descriptor;
  175.  
  176. if ( Object.prototype.hasOwnProperty.call( target, propertyName ) ) {
  177. descriptor = Object.getOwnPropertyDescriptor( target, propertyName );
  178.  
  179. if ( Object.prototype.hasOwnProperty.call( descriptor, "set" ) ) {
  180. descriptor.set = Functions.apply( descriptor.set, function ( setter, thisArg, args ) {
  181. onSetter.call( this, target, propertyName, setter, thisArg, args );
  182. } );
  183.  
  184. Object.defineProperty( target, propertyName, descriptor );
  185. }
  186. }
  187. },
  188. /**
  189. * the hook replace the setter
  190. * @callback onSetter
  191. * @param {object} target - the object having the setter property hooked
  192. * @param {string} propertyName - he name of the setter property hooked
  193. * @param {function} setter - the setter replaced
  194. * @param {object|undefined} thisArg - #this reference
  195. * @param {arguments} args - the arguments pass to the setter, should be a right value
  196. */
  197.  
  198. /**
  199. * hook a method of an object
  200. * @function method
  201. * @param {object} target - an object having or to have the method to be hooked
  202. * @param {string} methodName - the name of the method to be hooked
  203. * @param {onApply} onApply - the hook replace the method
  204. */
  205. method: function method( target, methodName, onApply ) {
  206. var method = target[ methodName ];
  207.  
  208. function hook( method ) {
  209. return Functions.apply( method, function ( method, thisArg, args ) {
  210. return onApply.call( this, target, methodName, method, thisArg, args );
  211. } );
  212. };
  213.  
  214. if ( "function" === typeof method ) {
  215. target[ methodName ] = hook( method );
  216. } else if ( !Object.prototype.hasOwnProperty.call( target, methodName ) ) {
  217. Object.defineProperty( target, methodName, {
  218. configurable: true,
  219. enumerable: true,
  220. set: function ( value ) {
  221. Object.defineProperty( target, methodName, {
  222. configurable: true,
  223. enumerable: true,
  224. value: typeof value === "function" ? hook( value ) : value,
  225. writable: true
  226. } );
  227. },
  228. get: function () {
  229. return undefined;
  230. }
  231. } );
  232. }
  233. },
  234. /**
  235. * the hook replace the method
  236. * @callback onApply
  237. * @param {object} target - the object having the method hooked
  238. * @param {string} methodName - the name of the method hooked
  239. * @param {object|undefined} thisArg - #this reference
  240. * @param {arguments} args - the arguments pass to the method
  241. */
  242. };
  243.  
  244. Hooks.Reply = {
  245. get: function ( param ) {
  246. var target = param[ 0 ],
  247. propertyName = param[ 1 ],
  248. oldValue = param[ 2 ];
  249.  
  250. return oldValue;
  251. },
  252.  
  253. set: function ( param ) {
  254. var target = param[ 0 ],
  255. propertyName = param[ 1 ],
  256. oldValue = param[ 2 ],
  257. newValue = param[ 3 ];
  258.  
  259. return newValue;
  260. },
  261.  
  262. getter: function ( param ) {
  263. var target = param[ 0 ],
  264. propertyName = param[ 1 ],
  265. getter = param[ 2 ],
  266. thisArg = param[ 3 ],
  267. args = param[ 4 ];
  268.  
  269. return getter.apply( thisArg, args );
  270. },
  271.  
  272. setter: function ( param ) {
  273. var target = param[ 0 ],
  274. propertyName = param[ 1 ],
  275. setter = param[ 2 ],
  276. thisArg = param[ 3 ],
  277. args = param[ 4 ];
  278.  
  279. setter.apply( thisArg, args );
  280. },
  281.  
  282. method: function method( param ) {
  283. var target = param[ 0 ],
  284. methodName = param[ 1 ],
  285. method = param[ 2 ],
  286. thisArg = param[ 3 ],
  287. args = param[ 4 ];
  288.  
  289. return method.apply( thisArg, args );
  290. },
  291. };
  292.  
  293. return Hooks;
  294. } )();