Предоставлять перехватчики для всех методов и свойств объектов XHR Глобально перехватывать AJAX

JavaScript Ajax jQuery ECMAScript 6

Резюме

✨Длинный текст читается минут десять

✨На просмотр уходит больше часа

✨Около 100 строк кода

Некоторое время назад я планировал написать фейковую консоль для мобильных телефонов, с помощью которой можно было бы видетьconsoleВывод этой функции завершен

Но потом я узнал, что есть проект команды TencentvConsoleДля справки, я обнаружил, что его функции намного богаче, вы можете видетьNetworkпанель и т.д.

Несмотря на то, что это кастрированная панель, вы все равно можете увидеть важную информацию, связанную с сетью.

Вот и подумал добавитьNetworkФункция панели

Чтобы реализовать эту функцию, я придумал несколько решений. Одно из них — инкапсулировать ajax, но это нереально. Очень недружелюбно позволять другим использовать свой собственный инкапсулированный ajax, что очень недружественно к ситуации, которую необходимо внедрить. в середине проекта, и нужно изменить много кода.

Так что это решение не рекомендуется

После выбора схемы, как здесь, как и реализацияconsoleКак и панель, метод ниже консоли перехватывается

при блокировкеconsoleМетод под объектом фактически переопределяет его метод

Напримерconsole.logфактическиlogМетод переопределяется в реальном исполненииlogДелайте то, что вы хотите сделать, прежде чем

идеи

Собственно об этом я и подумал сначалаXMLHrrpRequestОбъект имеет глобальную функцию ловушки, и документация, похоже, не находит соответствующего контента.

Позже я искал какие-то другие методы реализации, большинство из которых заключаются в том, что пряная куриная станция не знает, куда лезть, и нет ничего ценного.

Позже нашел репозиторий на github (Ajax-hook)

В этой библиотеке реализована возможность перехвата глобальных Ajax-запросов, она может использовать одноименный метод или атрибут в качестве хука для достижения эффекта перехвата.

Часть содержания относится к его идеям реализации

реально реализовано и настроеноconsoleПодобно тому, как базовая операция по-прежнему зависит от собственного объекта xhr, но была переписана как прокси на верхнем уровне.XMLHttpRequestобъект

Общая идея реализации может быть указана в следующих шагах

  1. сохранить собственный объект xhr
  2. БудуXMLHttpRequestобъект как новый объект
  3. Перепишите метод объекта xhr и поместите его в новый объект.
  4. Переопределите свойство и поместите его в новый объект

Основная цель переопределения метода — предоставить перехватчик для выполнения пользовательского содержимого перед выполнением собственного метода.

Переопределяющие свойства — это в основном некоторые свойства, запускаемые событиями, такие какonreadystatechange

В то же время есть и другие подводные камни.Многие свойства объекта xhr доступны только для чтения (этим также занимаетсяAjax-hookпонял)

Если в хуке изменено свойство только для чтения, спускаться вниз бесполезно, потому что модификация вообще не удалась.

Но теперь, когда у вас есть общая идея, давайте попробуем ее реализовать.

выполнить

Перед внедрением подумайте, как его использовать, чтобы процесс внедрения имел еще и общее направление

имя, позвони емуAny-XHRохватывать

когда используешьnew AnyXHR()создать экземпляр

Вы можете добавить хуки в конструкторе.Существует два типа хуков, одинbefore(вызов перед выполнением) крючки One isafter(вызов после выполнения) хук

Таким образом, конструктор принимает два параметра, оба из которых являются объектами: первый — ловушка, которая выполняется до вызова, а другой — после вызова.

внутри объектаkeyМетоды и атрибуты, соответствующие родному xhr, могут быть согласованы

	let anyxhr = new AnyXHR({
		send: function(...args) {
			console.log('before send');
		}
	}, {
		send: function(...args) {
			console.log('after send');
		}	
	});

Конструктор

У нас есть цель, давайте сделаем это сейчас, как мы хотим, мы будем использовать этоnewключевое слово для создания экземпляра объекта, используйте конструктор илиclassключевое слово для создания класса

Ключевое слово class используется здесь для объявления

	class AnyXHR {

		constructor(hooks = {}, execedHooks = {}) {
			this.hooks = hooks;
			this.execedHooks = execedHooks;
		}
		
		init() {
			// 初始化操作...
		}
		
		overwrite(proxyXHR) {
			// 处理xhr对象下属性和方法的重写
		}
	}

Сосредоточьтесь только здесь构造函数

构造函数Принимаются два объекта: хуки до выполнения и хуки после выполнения, соответствующие хукам, прикрепленным к экземпляру.hooksа такжеexecedHooksатрибут

Следуйте только что сказанным шагам, чтобы сохранить собственный объект xhr и добавить этот шаг в конструктор.

Затем выполните некоторые операции инициализации, чтобы переписать объект xhr во время инициализации.

	constructor(hooks = {}, execedHooks = {}) {
+		this.XHR = window.XMLHttpRequest;

		this.hooks = hooks;
		this.execedHooks = execedHooks;
		
+		this.init();
	}

метод инициализации

Что делать в init, чтобыXMLHttpRequestПереопределить каждый метод и свойство экземпляра xhr для переопределения

так вnew XMLHttpRequest()когдаnewНа выходе получается наш собственный переписанный экземпляр xhr.

	init() {
		
		window.XMLHttpRequest = function() {

		}
			
	}

поставь вот такXMLHttpRequestназначьте его новой функции, подобной этойXMLHttpRequestЭто не оригинал, глобальный доступ пользователя - это новая функция, написанная нами.

Далее следует реализовать переписывание какopen,onload,sendМетоды и свойства этих собственных экземпляров xhr

Но как его переписать?Теперь это пустая функция,как впустить пользователяnew XMLHttpRequestможно использовать послеsend,openкак насчет метода

Собственно, об этом упоминалось ранееОсновная цель переопределения метода — предоставить перехватчик для выполнения пользовательского содержимого перед выполнением собственного метода.

Поскольку мы хотим выполнить собственный метод, нам все равно нужно использовать собственный объект xhr.

просто возьмиopenдля

в пользователеnew XMLHttpRequestВ то же время необходимоnewоригинальный, который мы сохранилиXMLHttpRequestобъект

а потом переписатьXMLHttpRequestПовесьте один на экземплярsendМетод Что он делает, так это сначала выполняет пользовательский контент, а затем выполняет нашnewвне, сохраненоXMLHttpRequestОткрытый метод экземпляра объекта может одновременно передавать параметры

Звучит немного запутанно, можно рисовать картинки и внимательно смаковать

	init() {
+		let _this = this;		
		window.XMLHttpRequest = function() {
+ 		this._xhr = new _this.XHR();   // 在实例上挂一个保留的原生的xhr实例   

+			this.overwrite(this);
		}
			
	}

так в юзереnew XMLHttpRequestПри создании зарезервированного нативногоXMLHttpRequestпример

например, когда пользователь звонитsendметод, мы сначала выполняем то, что хотим сделать, а затем вызываемthis._xhr.sendВыполнить собственный экземпляр xhrsendЭто легко, не так ли?

После того, как мы закончили, мы входимoverwriteМетод Этот метод делает то, что было сказано в предыдущем предложении, переписывая каждый метод и свойство, фактически делая что-то до и после выполнения.

передача_this.overwriteКогда нам нужно передать это, это здесь указывает наnew XMLHttpRequestпример после

Все атрибуты и методы связаны с экземпляром для вызова пользователем, поэтому передайте это для удобства работы.

Вот перезапись с новой функциейwindow.XMLHttpRequestСтрелочные функции нельзя использовать при использовании стрелочных функций Стрелочные функции нельзя использовать в качестве конструкторов, поэтому здесь используется практика этой оговорки.

метод перезаписи

Чтобы переопределить методы и свойства, все, что вам нужно переопределить, этоsend,open,responseText,onload,onreadystatechangeподожди этих

Вы должны сделать это один за другим... Это определенно не реально

Методы и свойства для переопределения мы можем получить, облегчив собственный экземпляр xhr.

Собственный экземпляр xhr был сохранен в перезаписанномXMLHttpRequestэкземпляр объекта_xhrатрибут

Таким образом, пересекая_xhrЭтот собственный экземпляр xhr может получить все методы и свойства, которые необходимо переопределить.keyа такжеvalue

	overwrite(proxyXHR) {
+		for (let key in proxyXHR._xhr) {

+		}
	}

здесьproxyXHRПараметр указывает на измененныйXMLHttpRequestпример

Методы и свойства присоединены к экземпляру

прямо сейчасproxyXHRвниз_xhrОбход свойств может получить все обходные свойства и методы ниже него.

Затем различайте методы и свойства и выполняйте другую обработку.

	overwrite(proxyXHR) {
		for (let key in proxyXHR._xhr) {
		
+			if (typeof proxyXHR._xhr[key] === 'function') {
+				this.overwriteMethod(key, proxyXHR);
+				continue;
+			}

+			this.overwriteAttributes(key, proxyXHR);

		}
	}

пройти черезtypeofЧтобы определить, является ли текущий обход свойством или методом, если это метод, вызовитеoverwriteMethodПереопределить этот метод

Если это атрибут, то передайтеoverwriteAttributesПереопределить это свойство

В то же время пройденное имя свойства и имя метода, а также измененныйXMLHttpRequestэкземпляр передан

Тогда давайте реализуем два метода здесь

overwriteMethod

Добавьте этот метод в класс

	class AnyXHR {
	
+		overwriteMethod(key, proxyXHR) {
			// 对方法进行重写
+		}	
	
	}

Что делает этот метод, так это переписывает метод под собственным экземпляром xhr.

На самом деле, строго говоря, не строго называть эту операцию перезаписью.

просто возьмиsendС точки зрения метода это не влияет на собственный экземпляр xhr.sendИзмените метод и напишите новый метод, который будет привязан к экземпляру xhr, реализованному вами, вместо собственного экземпляра xhr для окончательного выполнения.sendПроцесс по-прежнему вызывает роднойsendметод Просто сделайте еще две вещи до и после звонка

Итак, вот оболочка вокруг каждого метода

	overwriteMethod(key, proxyXHR) {
+		let hooks = this.hooks;	
+		let execedHooks = this.execedHooks;
	
+		proxyXHR[key] = (...args) => {

+		}
	}

первый сохранилhooksа такжеexecedHooksБудет использоваться часто

Затем к новому экземпляру xhr присоединяем одноименный метод, например, у нативного xhr естьsendпройти кsendКогда ключ здесьsendТаким образом, он будет висеть на новом экземпляре xhr.sendна замену родномуsendметод

При вызове метода он получает кучу параметров.Параметры подбрасывает js движок (или браузер).Здесь остальные параметры используются для их перехвата для формирования массива.При вызове хука или нативного метод, вы можете передать его снова.

Итак, что именно он здесь делает?

На самом деле, три шага

  • Если текущий метод имеет соответствующий хук, выполнить хук
  • Выполните соответствующий метод в собственном экземпляре xhr.
  • Посмотрите, есть ли какие-либо ловушки, которые необходимо выполнить после выполнения метода, соответствующего собственному экземпляру xhr.Если да, выполните его.
	overwriteMethod(key, proxyXHR) {
		let hooks = this.hooks;	
		let execedHooks = this.execedHooks;
	
		proxyXHR[key] = (...args) => {

+	      // 如果当前方法有对应的钩子 则执行钩子
+	      if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
+				return;
+	      }
	
+	      // 执行原生xhr实例中对应的方法
+	      const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
	
+	      // 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行
+	      execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);
	
+	      return res;

		}
	}

первый шаг первыйhooks[key]Это нужно для того, чтобы определить, имеет ли текущий метод соответствующую функцию ловушки.

Все функции или методы ловушек, которые необходимо перехватить, сохраняются в объекте ловушек, когда мы выполняемnew AnyXHR(..)прошел, когда

Если есть, выполните его и передайте параметр хуку.Если функция хука вернет false, она прервется и не выйдет из строя.Это может иметь эффект перехвата.

В противном случае спуститесь и выполните соответствующий метод в собственном экземпляре xhr.

Собственный экземпляр xhr помещается_xhrВ этом свойстве так пройтиproxyXHR._xhr[key]Вы можете получить к нему доступ и использовать параметры одновременноapplyПросто отбросьте его, просто передайте и одновременно поймайте возвращаемое значение.

После завершения третьего шага

Посмотрите, есть ли хук, который будет выполнен после выполнения нативного метода xhr.Если есть, выполните его, а затем передайте возвращаемое значение.

После этого верните возвращаемое значение метода, соответствующее собственному экземпляру xhr после выполнения.

На этом прокси и перехват метода завершены, можно пробовать.

не забудьте отметитьthis.overwriteAttributes(key, proxyXHR);эта линия

Ввод в эксплуатацию в первый раз

Хотя кода пока не так много, но я не обошёл всё сразу, я всё равно очень устал, можно налить стакан воды и отдохнуть.

Полный код пока выглядит следующим образом

	class AnyXHR {

		constructor(hooks = {}, execedHooks = {}) {
			this.XHR = window.XMLHttpRequest;
	
			this.hooks = hooks;
			this.execedHooks = execedHooks;
			
			this.init();
		}
		
		init() {
			let _this = this;		
			window.XMLHttpRequest = function() {
	 			this._xhr = new _this.XHR();   // 在实例上挂一个保留的原生的xhr实例   
	
				_this.overwrite(this);
			}
		}
		
		overwrite(proxyXHR) {
			for (let key in proxyXHR._xhr) {
			
				if (typeof proxyXHR._xhr[key] === 'function') {
					this.overwriteMethod(key, proxyXHR);
					continue;
				}
	
				// this.overwriteAttributes(key, proxyXHR);
	
			}
		}
		
		overwriteMethod(key, proxyXHR) {
			let hooks = this.hooks;	
			let execedHooks = this.execedHooks;
		
			proxyXHR[key] = (...args) => {
	
		      // 如果当前方法有对应的钩子 则执行钩子
		      if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
					return;
		      }
		
		      // 执行原生xhr实例中对应的方法
		      const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
		
		      // 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行
		      execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);
		
		      return res;
	
			}
		}
	}

Попробуйте первую отладку

	new AnyXHR({
		open: function () {
			console.log(this);
		}
	});
	
	var xhr = new XMLHttpRequest();
	
	xhr.open('GET', '/abc?a=1&b=2', true);
	
	xhr.send();

Вы можете открыть консоль, чтобы увидеть, есть ли вывод, и вы можете наблюдать за объектом и_xhrСядьте и сравните содержимое в свойствах

метод overwriteAttributes

Этот метод немного сложнее реализовать, и содержимое будет обойдено.

Может быть, есть идея, зачем слушать или прокси или давать属性Какая польза от предоставления крючков?

картинаresponseTextЭто свойство на самом деле не является целью

Цель состоит в том, чтобы дать что-то вродеonreadystatechange,onloadТакие свойства обертывают слой

Эти свойства немного похожи на события или просто события.

При его использовании нужно вручную присвоить им значение, и он будет автоматически вызываться в соответствующий момент.Можно сказать нативныйxhrпредоставленный крючок

картинаsend,openМожет быть перехвачен при отправке запроса

а такжеonreadystatechange,onloadЗапросы, которые можно использовать для перехвата ответов от сервисов

Поэтому необходимо обернуть эти свойства

Теперь, когда вы знаете, зачем нужно оборачивать свойства, возникает вопрос, как оборачивать свойства.

Этоonloadподать пример

xhr.onload = function() {
	...
};

Когда пользователь присваивает такое значение, мы должны сделать какой-то ответ

Процесс захвата этого задания

тогда иди посмотриhooksЕсть ли в этом массивеonloadкрюк

Если есть, можно выполнить хук перед выполнением загрузки собственного экземпляра xhr.

Когда возникает проблема, как насчет обычных атрибутов, таких какresponseType

Тогда эти атрибуты не будут обрабатываться и напрямую связаны с новым экземпляром xhr.

Другая проблема, как отличить общие атрибуты от таких же атрибутов, как события?

На самом деле, просто наблюдайтеonАтрибут в начале совпадает с атрибутом события.

Итак, подведем итог

  • Посмотрите, начинается ли атрибут с on
  • Если нет, просто повесьте трубку
  • Если да, посмотрите, есть ли хук для выполнения
  • Если есть, оберните его и сначала выполните хук, а затем выполните тело
  • Если нет ответственности, напрямую присвойте значение и повесьте трубку

Логика понятна, легко найти?

Хорошо, давайте сделаем это

? ? Подождите, как монитору пользователя датьonloadТакое назначение атрибута а? ? ?

ты можешь остановиться и подумать об этом

Эту часть можно использовать в ES5getterа такжеsetterметод

Этот пункт знаний должен быть вам очень знаком. Если вы не знакомы с ним, вы можете перейти на MDN.

С помощью этих двух методов вы можете отслеживать действия пользователя по назначению и извлечению свойства, а также выполнять некоторые дополнительные действия.

Тогда как установить метод get/set для вставляемого свойства?

ES5 обеспечиваетObject.definePropertyметод

Вы можете определить атрибуты для объекта и указать его дескриптор атрибута.Описатель атрибута может описывать, может ли атрибут быть записан, может ли он быть пронумерован, его установка/получение и т. д.

Конечно, также возможно определить методы получения/установки для свойства с помощью литералов.

После долгих разговоров давайте реализуем этот метод.

	overwriteAttributes(key, proxyXHR) {
		Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
	}

Вот строка кода, которая используетObject.definePropertyПовесьте атрибут на экземпляр xhr, реализованный вами, имя атрибута - это переданный ключ, а затем используйтеsetProperyDescriptorМетод генерирует дескриптор атрибута при передаче ключа и экземпляра.

В дескрипторе будет сгенерирован метод get/set, то есть операция, когда атрибут присваивается и извлекается.

метод setProperyDescriptor

Добавьте тот же метод в класс

	setProperyDescriptor(key, proxyXHR) {
	
	}

Вы можете получить имя атрибута (ключ), которое будет добавлено, и экземпляр объекта xhr, реализованный самостоятельно.

свойства должны быть прикреплены к этому экземпляру

	setProperyDescriptor(key, proxyXHR) {
+		let obj = Object.create(null);
+		let _this = this;
		
	}

дескриптор свойства на самом деле является объектом

использовать здесьObject.create(null)для создания абсолютно чистого объекта, чтобы предотвратить случайное появление некоторых беспорядочных свойств в описании

тогда держи это

Затем реализовать набор

	setProperyDescriptor(key, proxyXHR) {
		let obj = Object.create(null);
		let _this = this;
		
+		obj.set = function(val) {

+		}
	}

Метод set будет использоваться, когда свойству будет присвоено значение (например,obj.a = 1) и он получит параметр, который является значением присваивания (значение справа от знака равенства)

Затем вы можете выполнить шаги, перечисленные ранее

  • Посмотрите, начинается ли атрибут с on
  • Если нет, просто повесьте трубку
  • Если да, посмотрите, есть ли хук для выполнения
  • Если есть, оберните его и сначала выполните хук, а затем выполните тело
  • Если нет ответственности, напрямую присвойте значение и повесьте трубку
	setProperyDescriptor(key, proxyXHR) {
		let obj = Object.create(null);
		let _this = this;
		
		obj.set = function(val) {

+			// 看看属性是不是on打头 如果不是 直接挂上去
+			if (!key.startsWith('on')) {
+				proxyXHR['__' + key] = val;
+				return;
+			}

+			// 如果是 则看看有没有要执行的钩子
+			if (_this.hooks[key]) {
				
+				// 如果有 则包装一下 先执行钩子 再执行本体
+				this._xhr[key] = function(...args) {
+					(_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args));
+				}
				
+				return;
+			}

+			// 如果没有 责直接赋值挂上去
+			this._xhr[key] = val;
		}
		
+		obj.get = function() {
+			return proxyXHR['__' + key] || this._xhr[key];
+		}
	
+		return obj;
	}

На первом этапе оценивается, включен он или нет, и если нет, то он будет прикреплен к экземпляру как есть.

Затем посмотрите, есть ли текущее свойство в списке хуков, если да, упакуйте присваиваемое значение (val) с хуком и превратите его в функцию

В функции сначала выполняется хук, а затем выполняется назначенное значение.Пока человек, который его использует, не слеп, значение здесь в основном состоит в том, что функция не запускалась, поэтому вызывайте ее напрямую, не оценивая, и параметры передаются в прошлом.

Если хука нет, просто назначьте его напрямую

Этот метод набора в порядке

Метод get проще, просто получите значение

Может быть, есть одно место, которое я не совсем понимаю, а именноproxyXHR['__' + key] = val;

также в методе get

почему есть__Префикс на самом деле такой

Рассмотрим такой сценарий, вы можете получить его, когда запрос на перехват вернетсяresponseTextАтрибуты

Это свойство является значением, возвращаемым сервером.

может потребоваться в это времяresponseTypeЕдиная обработка типов данных

Если это JSON, тоthis.responseText = JSON.parse(this.response)

Затем с радостью переходите к успешной функции обратного вызоваresponseText

В итоге сообщалось об ошибке при доступе к свойствам или методам на него.После печати обнаружилось, что он все-таки строка и передача не удалась.

На самом деле, потому чтоresponseTextдоступен только для чтения в теге этого атрибутаwritableдаfalse

Таким образом, вы можете использовать прокси-атрибут для решения

Напримерthis.responseText = JSON.parse(this.responseText)когда

Во-первых, получить его в соответствии с методом getresponseTextЕще нет__responseTextАтрибут, поэтому вернитесь к собственному экземпляру xhr и получите значение, возвращаемое сервером.

Затем после разбора он снова назначается

При копировании в экземпляре xhr, реализованном вами, будет еще один__responseTextАтрибут его значение обрабатывается

пройти после этогоresponseTextЗначение, полученное с помощью метода get, равно__responseTextзначение

Это решает проблему только для чтения собственных атрибутов экземпляра xhr через слой прокси атрибута.

Таким образом, большая часть логики завершена.this.overwriteAttributes(key, proxyXHR);удалить комментарий

вторая отладка

Здесь может закружиться голова, не переживайте сильно, просто внимательно следите за рисунком, налейте стакан воды и успокойтесь

Это полный код на данный момент, вы можете попробовать его

class AnyXHR {

  constructor(hooks = {}, execedHooks = {}) {
    this.XHR = window.XMLHttpRequest;

    this.hooks = hooks;
    this.execedHooks = execedHooks;

    this.init();
  }

  init() {
    let _this = this;
    window.XMLHttpRequest = function () {
      this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例   

      _this.overwrite(this);
    }
  }

  overwrite(proxyXHR) {
    for (let key in proxyXHR._xhr) {

      if (typeof proxyXHR._xhr[key] === 'function') {
        this.overwriteMethod(key, proxyXHR);
        continue;
      }

      this.overwriteAttributes(key, proxyXHR);

    }
  }

  overwriteMethod(key, proxyXHR) {
    let hooks = this.hooks;
    let execedHooks = this.execedHooks;

    proxyXHR[key] = (...args) => {

      // 如果当前方法有对应的钩子 则执行钩子
      if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
        return;
      }

      // 执行原生xhr实例中对应的方法
      const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);

      // 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行
      execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);

      return res;

    }
  }

  setProperyDescriptor(key, proxyXHR) {
    let obj = Object.create(null);
    let _this = this;

    obj.set = function (val) {

      // 看看属性是不是on打头 如果不是 直接挂上去
      if (!key.startsWith('on')) {
        proxyXHR['__' + key] = val;
        return;
      }

      // 如果是 则看看有没有要执行的钩子
      if (_this.hooks[key]) {

        // 如果有 则包装一下 先执行钩子 再执行本体
        this._xhr[key] = function (...args) {
          (_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args));
        }

        return;
      }

      // 如果没有 责直接赋值挂上去
      this._xhr[key] = val;
    }

    obj.get = function () {
      return proxyXHR['__' + key] || this._xhr[key];
    }

    return obj;
  }

  overwriteAttributes(key, proxyXHR) {
    Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
  }
}

передача

new AnyXHR({
  open: function () {
    console.log('open');
  },
  onload: function () {
    console.log('onload');
  },
  onreadystatechange: function() {
    console.log('onreadystatechange');
  }
});


$.get('/aaa', {
  b: 2,
  c: 3
}).done(function (data) {
  console.log(1);
});

var xhr = new XMLHttpRequest();

xhr.open('GET', '/abc?a=1&b=2', true);

xhr.send();

xhr.onreadystatechange = function() {
  console.log(1);
}

Введите jquery, чтобы попытаться перехватить

Посмотрите на консоль, чтобы увидеть результаты

Есть еще некоторый контент, который нужно завершить, чтобы сделать весь класс более полным, но остальное очень простое содержание.

синглтон

Потому что это глобальный перехват и есть только один глобальныйXMLHttpRequestОбъект, поэтому здесь он должен быть разработан как синглтон

Синглтон — это один из шаблонов проектирования, он не такой загадочный, он означает, что существует только один экземпляр во всем мире.

Другими словами, вы должны двигать руками и ногами.new AnyXHRполучить тот же экземпляр

Изменить конструктор

	constructor(hooks = {}, execedHooks = {}) {
		// 单例
+		if (AnyXHR.instance) {
+			return AnyXHR.instance;
+		}
		
		this.XHR = window.XMLHttpRequest;
		
		this.hooks = hooks;
		this.execedHooks = execedHooks;
		this.init();
	
+		AnyXHR.instance = this;
	}

Войдите в конструктор, чтобы судить первымAnyXHRЕсть ли зависший экземпляр на

Затем, после завершения процесса создания, поместитеAnyXHRПросто повесьте пример, так что ни на чтоnewВсе получают один и тот же экземпляр

В то же время добавьте метод, чтобы легко получить экземпляр

	getInstance() {
	  return AnyXHR.instance;
	}

добавить хук динамически

Все хуки хранятся в двух объектах. Каждое выполнение метода будет считывать эти два объекта, поэтому, пока объект изменяется, хуки могут добавляться динамически.

Итак, добавьте метод добавления

	add(key, value, execed = false) {
	  if (execed) {
	    this.execedHooks[key] = value;
	  } else {
	    this.hooks[key] = value;
	  }
	  return this;
	}

вkey valueДва параметра соответствуют имени свойства или имени и значению метода.

execedУказывает, выполняется ли собственный метод, а затем выполняется.Этот параметр используется для определения того, к какому объекту он добавляется.

Таким же образом снять крючок и опорожнить крючок очень просто.

снять крючок

	rmHook(name, isExeced = false) {
	  let target = (isExeced ? this.execedHooks : this.hooks);
	  delete target[name];
	}

пустой крючок

	clearHook() {
		this.hooks = {};
		this.execedHooks = {};
	}

Отменить перехват глобального мониторинга

Этот шаг на самом деле очень прост, перепишите нашу собственную реализациюXMLHttpRequestПросто будь оригиналом

Мы сохранили оригиналthis.XHRначальство

	unset() {
		window.XMLHttpRequest = this.XHR;
	}

Переслушать перехват

Поскольку это синглтон, перезапустите прослушиватель, просто очистите синглтон и обновите его.

	reset() {
	  AnyXHR.instance = null;
	  AnyXHR.instance = new AnyXHR(this.hooks, this.execedHooks);
	}

полный код

class AnyXHR {
/**
 * 构造函数
 * @param {*} hooks 
 * @param {*} execedHooks 
 */
  constructor(hooks = {}, execedHooks = {}) {
    // 单例
    if (AnyXHR.instance) {
      return AnyXHR.instance;
    }

    this.XHR = window.XMLHttpRequest;

    this.hooks = hooks;
    this.execedHooks = execedHooks;
    this.init();

    AnyXHR.instance = this;
  }

  /**
   * 初始化 重写xhr对象
   */
  init() {
    let _this = this;

    window.XMLHttpRequest = function() {
      this._xhr = new _this.XHR();

      _this.overwrite(this);
    }

  }

  /**
   * 添加勾子
   * @param {*} key 
   * @param {*} value 
   */
  add(key, value, execed = false) {
    if (execed) {
      this.execedHooks[key] = value;
    } else {
      this.hooks[key] = value;
    }
    return this;
  }

  /**
   * 处理重写
   * @param {*} xhr 
   */
  overwrite(proxyXHR) {
    for (let key in proxyXHR._xhr) {
      
      if (typeof proxyXHR._xhr[key] === 'function') {
        this.overwriteMethod(key, proxyXHR);
        continue;
      }

      this.overwriteAttributes(key, proxyXHR);
    }
  }

  /**
   * 重写方法
   * @param {*} key 
   */
  overwriteMethod(key, proxyXHR) {
    let hooks = this.hooks;
    let execedHooks = this.execedHooks;

    proxyXHR[key] = (...args) => {
      // 拦截
      if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) {
        return;
      }

      // 执行方法本体
      const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);

      // 方法本体执行后的钩子
      execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res);

      return res;
    };
  }

  /**
   * 重写属性
   * @param {*} key 
   */
  overwriteAttributes(key, proxyXHR) {
    Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
  }

  /**
   * 设置属性的属性描述
   * @param {*} key 
   */
  setProperyDescriptor(key, proxyXHR) {
    let obj = Object.create(null);
    let _this = this;

    obj.set = function(val) {

      // 如果不是on打头的属性
      if (!key.startsWith('on')) {
        proxyXHR['__' + key] = val;
        return;
      }

      if (_this.hooks[key]) {

        this._xhr[key] = function(...args) {
          (_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args));
        }

        return;
      }

      this._xhr[key] = val;
    }

    obj.get = function() {
      return proxyXHR['__' + key] || this._xhr[key];
    }

    return obj;
  }

  /**
   * 获取实例
   */
  getInstance() {
    return AnyXHR.instance;
  }

  /**
   * 删除钩子
   * @param {*} name 
   */
  rmHook(name, isExeced = false) {
    let target = (isExeced ? this.execedHooks : this.hooks);
    delete target[name];
  }

  /**
   * 清空钩子
   */
  clearHook() {
    this.hooks = {};
    this.execedHooks = {};
  }

  /**
   * 取消监听
   */
  unset() {
    window.XMLHttpRequest = this.XHR;
  }

  /**
   * 重新监听
   */
  reset() {
    AnyXHR.instance = null;
    AnyXHR.instance = new AnyXHR(this.hooks, this.execedHooks);
  }
}

Заканчивать

На этом все готово, из-за отсутствия тестирования могут быть баги.

Так же есть некоторые дефекты.Все хуки должны быть синхронными.Если асинхронный то последовательность будет хаотичной.Эта проблема будет решена позже.Если вам интересно,можете попробовать сами.

Кроме того, этот метод перехвата в принципе применим к любому объекту и может использоваться гибко.

исходный код

просто используйтеXMLHttpRequestAjax-запросы могут быть перехвачены им