版博士V2.0程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

72 lines
2.8 KiB

  1. const copyProperty = (to, from, property, ignoreNonConfigurable) => {
  2. // `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
  3. // `Function#prototype` is non-writable and non-configurable so can never be modified.
  4. if (property === 'length' || property === 'prototype') {
  5. return;
  6. }
  7. // `Function#arguments` and `Function#caller` should not be copied. They were reported to be present in `Reflect.ownKeys` for some devices in React Native (#41), so we explicitly ignore them here.
  8. if (property === 'arguments' || property === 'caller') {
  9. return;
  10. }
  11. const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
  12. const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
  13. if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
  14. return;
  15. }
  16. Object.defineProperty(to, property, fromDescriptor);
  17. };
  18. // `Object.defineProperty()` throws if the property exists, is not configurable and either:
  19. // - one its descriptors is changed
  20. // - it is non-writable and its value is changed
  21. const canCopyProperty = function (toDescriptor, fromDescriptor) {
  22. return toDescriptor === undefined || toDescriptor.configurable || (
  23. toDescriptor.writable === fromDescriptor.writable &&
  24. toDescriptor.enumerable === fromDescriptor.enumerable &&
  25. toDescriptor.configurable === fromDescriptor.configurable &&
  26. (toDescriptor.writable || toDescriptor.value === fromDescriptor.value)
  27. );
  28. };
  29. const changePrototype = (to, from) => {
  30. const fromPrototype = Object.getPrototypeOf(from);
  31. if (fromPrototype === Object.getPrototypeOf(to)) {
  32. return;
  33. }
  34. Object.setPrototypeOf(to, fromPrototype);
  35. };
  36. const wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/\n${fromBody}`;
  37. const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString');
  38. const toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, 'name');
  39. // We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
  40. // We use `bind()` instead of a closure for the same reason.
  41. // Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
  42. const changeToString = (to, from, name) => {
  43. const withName = name === '' ? '' : `with ${name.trim()}() `;
  44. const newToString = wrappedToString.bind(null, withName, from.toString());
  45. // Ensure `to.toString.toString` is non-enumerable and has the same `same`
  46. Object.defineProperty(newToString, 'name', toStringName);
  47. Object.defineProperty(to, 'toString', {...toStringDescriptor, value: newToString});
  48. };
  49. export default function mimicFunction(to, from, {ignoreNonConfigurable = false} = {}) {
  50. const {name} = to;
  51. for (const property of Reflect.ownKeys(from)) {
  52. copyProperty(to, from, property, ignoreNonConfigurable);
  53. }
  54. changePrototype(to, from);
  55. changeToString(to, from, name);
  56. return to;
  57. }