在 运行 时向控制台方法添加动态值,并完整保留原始调用位置和行号
Add dynamic values to the console methods at run-time with preservation of original call position and line number intact
我做了以下 class 到 'hijack' console.log
函数。这背后的原因是我想添加和删除值
动态地。它将用于调试目的,因此 函数调用的来源 console.log()
很重要。在下面的代码中,我将在评论中解释我的逻辑。
export class ConsoleLog {
private _isActive = false;
private _nativeLogFn: any;
constructor() {
// ----------------------
// Store the native console.log function, so it can be restored later
// ----------------------
this._nativeLogFn = console.log;
}
public start() {
if (!this._isActive) {
// ----------------------
// Create a new function as replacement for the native console.log
// function. *** This will be the subject of my question ***
// ----------------------
console.log = console.log.bind(console, Math.random());
this._isActive = true;
}
}
public stop() {
if (this._isActive) {
// Restore to native function
console.log = this._nativeLogFn;
this._isActive = false;
}
}
}
此设置的问题在于,新函数是以静态形式分配的。
// Function random() generates a number at the moment I assign the function.
// Let's say it's the number *99* for example sake.
console.log.bind(console, Math.random());
每次调用console.log(...)
,都会输出99。所以它几乎是静态的。 (在你之前:不,我的目标不是输出随机数,大声笑,但我只是用它来测试输出是否是动态的。)。
烦人的部分是,将函数与
console.log.bind
是我发现实际保留的唯一方法
原始呼叫者和线路号码。
我写了下面的简单测试。
console.log('Before call, active?', 'no'); // native log
obj.start(); // Calls start and replaces the console.log function
console.log('foo'); // This will output 'our' 99 to the console.
console.log('bar'); // This will output 'our' 99 again.
obj.stop(); // Here we restore the native console.log function
console.log('stop called, not active'); // native log again
// Now if I call it again, the random number has changed. What is
// logical, because I re-assign the function.
obj.start(); // Calls start and replaces the console.log function
console.log('foo'); // This will output N to the console.
// But then I have to call start/log/stop all the time.
问题:如何在 运行 时向 console.log 添加值而不丢失原始调用者文件名和行号......并且没有一旦这个 class 使用 start() 启动,就会打扰图书馆消费者。
EDIT: Added a plkr: https://embed.plnkr.co/Zgrz1dRhSnu6OCEUmYN0
怎么样:
const consolelog = console.log;
console.log = function (...args) {
return consolelog.apply(this, [Math.random()].concat(args));
}
请注意,函数中的 this
不是 您的 class.
的实例
该函数是一个常规的匿名函数而不是箭头函数,因此函数范围将取决于执行。
编辑
好吧,没有apply
,这样更好:
console.log = function (...args) {
return consolelog(Math.random(), ...args);
}
第二次编辑
本来想说不可能的,后来有了突破:
function fn() {
return Math.random();
}
fn.valueOf = function () {
return this();
};
console.log = consolelog.bind(console, fn);
然后这个:console.log("message")
将输出如下内容:
function 0.4907970049205219 "message"
有正确的来电者,但我无法删除开头的 function
部分。
然后我又有了突破:
function fn() {
return Math.random();
}
fn.toString = function () {
return this().toString();
}
console.log = consolelog.bind(console, "%s", fn);
然后这个:console.log("message")
将输出:
0.9186478227998554 message
如您所愿,找对了来电者。
只有绑定到函数才有效,使用其他对象无效。
花费了我大部分的周末时间和大量的阅读和摆弄,但我最终利用 ES6 代理对象解决了它。我可能会添加非常强大的东西。解释在代码中。请不要犹豫,改进它或提出问题。
(根据@Bergi 的评论编辑)
这是 class:
export class ConsoleLog {
private _isActive = false;
private _nativeConsole: any;
private _proxiedConsole: any;
/**
* The Proxy constructor takes two arguments, an initial Object that you
* want to wrap with the proxy and a set of handler hooks.
* In other words, Proxies return a new (proxy) object which wraps the
* passed in object, but anything you do with either effects the other.
*
* ref: https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-3-proxies
* ref: http://exploringjs.com/es6/ch_proxies.html#_intercepting-method-calls
*/
/**
* Challenge:
* When we intercept a method call via a proxy, you can intercept the
* operation 'get' (getting property values) and you can intercept the
* operation 'apply' (calling a function), but there is no single operation
* for method calls that you could intercept. That’s why we need to treat
* them as two separate operations:
*
* First 'get' to retrieve a function, then an 'apply' to call that
* function. Therefore intercepting 'get' and return a function that
* executes the function 'call'.
*/
private _createProxy(originalObj: Object) {
const handler = {
/**
* 'get' is the trap-function.
* It will be invoked instead of the original method.
* e.a. console.log() will call: get(console, log) {}
*/
get(target: object, property: string) {
/**
* In this case, we use the trap as an interceptor. Meaning:
* We use this proxy as a sort of pre-function call.
* Important: This won't get invoked until a call to a the actual
* method is made.
*/
/**
* We grab the native method.
* This is the native method/function of your original/target object.
* e.a. console.log = console['log'] = target[property]
* e.a. console.info = console['info'] = target[property]
*/
const nativeFn: Function = target[property];
/**
* Here we bind the native method and add our dynamic content
*/
return nativeFn.bind(
this, `%cI have dynamic content: ${Math.random()}`, 'color:' +
' #f00;'
);
}
};
return new Proxy(originalObj, handler);
}
constructor() {
// Store the native console.log function so we can put it back later
this._nativeConsole = console;
// Create a proxy for the console Object
this._proxiedConsole = this._createProxy(console);
}
// ----------------------
// (Public) methods
// ----------------------
public start() {
if (!this._isActive) {
/**
* Replace the native console object with our proxied console object.
*/
console = <Console>this._proxiedConsole;
this._isActive = true;
}
}
public stop() {
if (this._isActive) {
// Restore to native console object
console = <Console>this._nativeConsole;
this._isActive = false;
}
}
}
这里是你自己看的代码:
const c: ConsoleLog = new ConsoleLog();
console.log('Hi, I am a normal console.log', ['hello', 'world']);
c.start(); // Start - replaces the console with the proxy
console.log('Hi, I am a proxied console.log');
console.log('I have dynamic content added!');
console.log('My source file and line number are also intact');
c.stop(); // Stop - replaces the proxy back to the original.
console.log('I am a normal again');
干杯!
如果您希望动态绑定您的函数,您可以在每次访问 .log
属性 时执行此操作。一个简单的 getter 就足够了,不需要使用 ES6 代理:
export class ConsoleLog {
constructor(message) {
this._isActive = false;
const nativeLog = console.log;
Object.defineProperty(console, "log", {
get: () => {
if (this._isActive)
return nativeLog.bind(console, message())
return nativeLog;
},
configurable: true
});
}
start() {
this._isActive = true;
}
stop() {
this._isActive = false;
}
}
new ConsoleLog(Math.random).start();
此答案展示了如何使用代理和 Object.bind 将参数注入现有 (object/API) 的函数。
这适用于保留控制台行号和文件引用的控制台。
// targetName is the name of the window object you want to inject arguments
// returns an injector object.
function injector(targetName){
const injectors = {}; // holds named injector functions
const _target = window[targetName]; // shadow of target
const proxy = new Proxy(_target, {
get: function(target, name) {
if (typeof injectors[name] === "function" &&
typeof _target[name] === "function") { // if both _target and injector a function
return _target[name].bind(_target, ...injectors[name]());
}
return _target[name];
},
});
return {
enable () { window[targetName] = proxy; return this },
disable () { window[targetName] = _target },
injector (name, func) { injectors[name] = func },
};
};
使用
// Example argument injector.
// Injector functions returns an array of arguments to inject
const logInfo = {
count : 0,
counter () { return ["ID : " + (logInfo.count++) + ":"] },
mode(){ return ["App closing"] },
}
实例化控制台注入器
// Create an injector for console
const consoleInjector = injector("console");
注入器的功能enable
、injector
、disable
用法
// Enable consoleInjector and add injector function.
consoleInjector.enable().injector("log", logInfo.counter);
console.log("testA"); // >> ID : 0: testA VM4115:29
console.log("testB"); // >> ID : 1: testB VM4115:31
// Replace existing injector function with another one.
consoleInjector.injector("log",logInfo.mode); // change the injector function
console.log("testC"); // >> App closing testC VM4115:34
console.log("testD",1,2,3,4); // App closing testD 1 2 3 4 VM4115:35
// Turn off console.log injector
consoleInjector.injector("log",undefined);
// or/and turns off injector and return console to normal
consoleInjector.disable();
console.log("testE"); // testE VM4115:42
我做了以下 class 到 'hijack' console.log
函数。这背后的原因是我想添加和删除值
动态地。它将用于调试目的,因此 函数调用的来源 console.log()
很重要。在下面的代码中,我将在评论中解释我的逻辑。
export class ConsoleLog {
private _isActive = false;
private _nativeLogFn: any;
constructor() {
// ----------------------
// Store the native console.log function, so it can be restored later
// ----------------------
this._nativeLogFn = console.log;
}
public start() {
if (!this._isActive) {
// ----------------------
// Create a new function as replacement for the native console.log
// function. *** This will be the subject of my question ***
// ----------------------
console.log = console.log.bind(console, Math.random());
this._isActive = true;
}
}
public stop() {
if (this._isActive) {
// Restore to native function
console.log = this._nativeLogFn;
this._isActive = false;
}
}
}
此设置的问题在于,新函数是以静态形式分配的。
// Function random() generates a number at the moment I assign the function.
// Let's say it's the number *99* for example sake.
console.log.bind(console, Math.random());
每次调用console.log(...)
,都会输出99。所以它几乎是静态的。 (在你之前:不,我的目标不是输出随机数,大声笑,但我只是用它来测试输出是否是动态的。)。
烦人的部分是,将函数与
console.log.bind
是我发现实际保留的唯一方法
原始呼叫者和线路号码。
我写了下面的简单测试。
console.log('Before call, active?', 'no'); // native log
obj.start(); // Calls start and replaces the console.log function
console.log('foo'); // This will output 'our' 99 to the console.
console.log('bar'); // This will output 'our' 99 again.
obj.stop(); // Here we restore the native console.log function
console.log('stop called, not active'); // native log again
// Now if I call it again, the random number has changed. What is
// logical, because I re-assign the function.
obj.start(); // Calls start and replaces the console.log function
console.log('foo'); // This will output N to the console.
// But then I have to call start/log/stop all the time.
问题:如何在 运行 时向 console.log 添加值而不丢失原始调用者文件名和行号......并且没有一旦这个 class 使用 start() 启动,就会打扰图书馆消费者。
EDIT: Added a plkr: https://embed.plnkr.co/Zgrz1dRhSnu6OCEUmYN0
怎么样:
const consolelog = console.log;
console.log = function (...args) {
return consolelog.apply(this, [Math.random()].concat(args));
}
请注意,函数中的 this
不是 您的 class.
的实例
该函数是一个常规的匿名函数而不是箭头函数,因此函数范围将取决于执行。
编辑
好吧,没有apply
,这样更好:
console.log = function (...args) {
return consolelog(Math.random(), ...args);
}
第二次编辑
本来想说不可能的,后来有了突破:
function fn() {
return Math.random();
}
fn.valueOf = function () {
return this();
};
console.log = consolelog.bind(console, fn);
然后这个:console.log("message")
将输出如下内容:
function 0.4907970049205219 "message"
有正确的来电者,但我无法删除开头的 function
部分。
然后我又有了突破:
function fn() {
return Math.random();
}
fn.toString = function () {
return this().toString();
}
console.log = consolelog.bind(console, "%s", fn);
然后这个:console.log("message")
将输出:
0.9186478227998554 message
如您所愿,找对了来电者。
只有绑定到函数才有效,使用其他对象无效。
花费了我大部分的周末时间和大量的阅读和摆弄,但我最终利用 ES6 代理对象解决了它。我可能会添加非常强大的东西。解释在代码中。请不要犹豫,改进它或提出问题。
(根据@Bergi 的评论编辑) 这是 class:
export class ConsoleLog {
private _isActive = false;
private _nativeConsole: any;
private _proxiedConsole: any;
/**
* The Proxy constructor takes two arguments, an initial Object that you
* want to wrap with the proxy and a set of handler hooks.
* In other words, Proxies return a new (proxy) object which wraps the
* passed in object, but anything you do with either effects the other.
*
* ref: https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-3-proxies
* ref: http://exploringjs.com/es6/ch_proxies.html#_intercepting-method-calls
*/
/**
* Challenge:
* When we intercept a method call via a proxy, you can intercept the
* operation 'get' (getting property values) and you can intercept the
* operation 'apply' (calling a function), but there is no single operation
* for method calls that you could intercept. That’s why we need to treat
* them as two separate operations:
*
* First 'get' to retrieve a function, then an 'apply' to call that
* function. Therefore intercepting 'get' and return a function that
* executes the function 'call'.
*/
private _createProxy(originalObj: Object) {
const handler = {
/**
* 'get' is the trap-function.
* It will be invoked instead of the original method.
* e.a. console.log() will call: get(console, log) {}
*/
get(target: object, property: string) {
/**
* In this case, we use the trap as an interceptor. Meaning:
* We use this proxy as a sort of pre-function call.
* Important: This won't get invoked until a call to a the actual
* method is made.
*/
/**
* We grab the native method.
* This is the native method/function of your original/target object.
* e.a. console.log = console['log'] = target[property]
* e.a. console.info = console['info'] = target[property]
*/
const nativeFn: Function = target[property];
/**
* Here we bind the native method and add our dynamic content
*/
return nativeFn.bind(
this, `%cI have dynamic content: ${Math.random()}`, 'color:' +
' #f00;'
);
}
};
return new Proxy(originalObj, handler);
}
constructor() {
// Store the native console.log function so we can put it back later
this._nativeConsole = console;
// Create a proxy for the console Object
this._proxiedConsole = this._createProxy(console);
}
// ----------------------
// (Public) methods
// ----------------------
public start() {
if (!this._isActive) {
/**
* Replace the native console object with our proxied console object.
*/
console = <Console>this._proxiedConsole;
this._isActive = true;
}
}
public stop() {
if (this._isActive) {
// Restore to native console object
console = <Console>this._nativeConsole;
this._isActive = false;
}
}
}
这里是你自己看的代码:
const c: ConsoleLog = new ConsoleLog();
console.log('Hi, I am a normal console.log', ['hello', 'world']);
c.start(); // Start - replaces the console with the proxy
console.log('Hi, I am a proxied console.log');
console.log('I have dynamic content added!');
console.log('My source file and line number are also intact');
c.stop(); // Stop - replaces the proxy back to the original.
console.log('I am a normal again');
干杯!
如果您希望动态绑定您的函数,您可以在每次访问 .log
属性 时执行此操作。一个简单的 getter 就足够了,不需要使用 ES6 代理:
export class ConsoleLog {
constructor(message) {
this._isActive = false;
const nativeLog = console.log;
Object.defineProperty(console, "log", {
get: () => {
if (this._isActive)
return nativeLog.bind(console, message())
return nativeLog;
},
configurable: true
});
}
start() {
this._isActive = true;
}
stop() {
this._isActive = false;
}
}
new ConsoleLog(Math.random).start();
此答案展示了如何使用代理和 Object.bind 将参数注入现有 (object/API) 的函数。
这适用于保留控制台行号和文件引用的控制台。
// targetName is the name of the window object you want to inject arguments
// returns an injector object.
function injector(targetName){
const injectors = {}; // holds named injector functions
const _target = window[targetName]; // shadow of target
const proxy = new Proxy(_target, {
get: function(target, name) {
if (typeof injectors[name] === "function" &&
typeof _target[name] === "function") { // if both _target and injector a function
return _target[name].bind(_target, ...injectors[name]());
}
return _target[name];
},
});
return {
enable () { window[targetName] = proxy; return this },
disable () { window[targetName] = _target },
injector (name, func) { injectors[name] = func },
};
};
使用
// Example argument injector.
// Injector functions returns an array of arguments to inject
const logInfo = {
count : 0,
counter () { return ["ID : " + (logInfo.count++) + ":"] },
mode(){ return ["App closing"] },
}
实例化控制台注入器
// Create an injector for console
const consoleInjector = injector("console");
注入器的功能enable
、injector
、disable
用法
// Enable consoleInjector and add injector function.
consoleInjector.enable().injector("log", logInfo.counter);
console.log("testA"); // >> ID : 0: testA VM4115:29
console.log("testB"); // >> ID : 1: testB VM4115:31
// Replace existing injector function with another one.
consoleInjector.injector("log",logInfo.mode); // change the injector function
console.log("testC"); // >> App closing testC VM4115:34
console.log("testD",1,2,3,4); // App closing testD 1 2 3 4 VM4115:35
// Turn off console.log injector
consoleInjector.injector("log",undefined);
// or/and turns off injector and return console to normal
consoleInjector.disable();
console.log("testE"); // testE VM4115:42