Soru Tüm işlev çağrılarını günlüğe kaydetmek için Javascript Function nesnesini geçersiz kılabilir miyim?


İşlev nesnesinin davranışını geçersiz kılabilir, böylece her işlev çağrısından önce davranışı enjekte edebilir ve sonra normal olarak devam edebilir miyim? Özellikle, (genel fikir kendi içinde ilgi çekici olsa da) her yerde konsol.log ifadeleri eklemek zorunda kalmadan konsola her işlev çağrısında oturum açabilir miyim? Ve sonra normal davranış devam ediyor?

Bunun büyük olasılıkla önemli performans sorunlarına sahip olacağını biliyorum; Geliştirme ortamımda bile bu koşuyu tipik olarak kullanmam konusunda hiçbir niyetim yok. Ama işe yararsa, çalışan kod üzerinde 1000 metrelik bir görünüm elde etmek için zarif bir çözüm gibi görünüyor. Ve cevabın bana javascript hakkında daha derin bir şey göstereceğinden şüpheleniyorum.


49
2018-03-07 23:24


Menşei


Umarım mümkün değil :-) - stefan
Şimdiden burada cevaplandı: stackoverflow.com/questions/5033836/... - Wayne Burkett
Gördüm ki ... tam aradığım şeyi yapmıyor. Tüm işlev çağrılarına bağlanan bir şey istiyorum, ki bu sadece global ad alanına çarpıyor ve aşağı inmiyor. Ben daha iyi bir cevap bulamazsam, işi tekrar tekrar yapmak için bir üs olarak kullanacağım, ancak bu, Function nesnesinin kendisinin ayarlanmasıyla yapılıp yapılamayacağını bilmek istiyorum. - Matthew Nichols
Teknik olarak, bu konuda gitmenin yolu muhtemelen bir wrapper yöntemi ile Function.prototype.call geçersiz kılmak olacaktır. Bunu Chrome konsolundan yaparsanız, yazdığınız sırada günlüklerin göründüğünü görürsünüz (yazdığınız şeyi değerlendirmek için işlevleri çalıştırır.) Ancak, gerçek bir sayfaya koymaya çalışırsanız, olağanüstü bir şekilde patlar. - Nathan Ostgard
@ Nathan .. Eğer bir kod örneği bir cevap olarak koyarsanız, büyük bir olasılıkla şişirilmiş olsun ya da olmasın kabul edilmiş olarak işaretleyeceğim, çünkü bu tam olarak aradığım derin mekaniğin bir türüdür. - Matthew Nichols


Cevaplar:


Açık cevap, aşağıdaki gibi bir şeydir:

var origCall = Function.prototype.call;
Function.prototype.call = function (thisArg) {
    console.log("calling a function");

    var args = Array.prototype.slice.call(arguments, 1);
    origCall.apply(thisArg, args);
};

Ama bu aslında hemen sonsuz bir döngüye giriyor, çünkü çok çağrı çağrısı console.log çağrıları yürüten bir işlev çağrısı yürütür console.logBir işlev çağrısını yürüten console.log, hangi...

Olduğum nokta, bunun mümkün olduğundan emin değilim.


21
2018-03-09 09:03



Sonsuz döngü problemini çözmek için, Function#caller eşittir Function#call ve bu durumda console.log aramak. - Mathias Bynens
Aslında, burada geçersiz kıldığınız Function.prototype öğesinden devralındığı gibi, özyineyi tetikleyen Array.prototype.slice.call (...). Function.prototype.call (...), "console.log (...); çalıştırdığınızda kullanılmaz." - Dan Phillimore
Sadece bunun yerine bir for döngüsü ile yineleyin. - Benjamin Gruenbaum


Fonksiyon çağrılarını engelleme

Buradaki birçok kişi geçersiz kılmaya çalıştı. Bazıları başarısız oldu, bazıları başarılı oldu. Bu eski soruya, iş yerime getirildiği için, bu yazı referans olarak kullanıldığından yanıt veriyorum.

Değiştirmemiz için yalnızca iki işlev çağrısı ile ilgili işlev vardır: .kall ve .apply. Her ikisinin de başarılı bir şekilde geçersiz kılınacağını göstereceğim.

TL; DR: OP'nin sorması mümkün değildir. Cevaplardaki başarı raporlarının bazıları, araya girme isteğimiz yüzünden değil, değerlendirmeden önce dahili olarak arama yapan konsoldan kaynaklanmaktadır.

Geçersiz kılma Function.prototype.call

Bu insanların ortaya koyduğu ilk fikir gibi görünüyor. Bazıları diğerlerinden daha başarılı oldu, ancak işte çalışan bir uygulama:

// Store the original
var origCall = Function.prototype.call;
Function.prototype.call = function () {
    // If console.log is allowed to stringify by itself, it will
    // call .call 9 gajillion times. Therefore, lets do it by ourselves.
    console.log("Calling",
                Function.prototype.toString.apply(this, []),
                "with:",
                Array.prototype.slice.apply(arguments, [1]).toString()
               );

    // A trace, for fun
   console.trace.apply(console, []);

   // The call. Apply is the only way we can pass all arguments, so don't touch that!
   origCall.apply(this, arguments);
};

Bu başarıyla Function.prototype.call kesişir

Bir spin için alsak, olur mu?

// Some tests
console.log("1"); // Does not show up
console.log.apply(console,["2"]); // Does not show up
console.log.call(console, "3"); // BINGO!

Bunun bir konsoldan çalıştırılmaması önemlidir. Çeşitli tarayıcılarda, çağrı yapan her türlü konsol aracı bulunur. çokŞu anda bir kullanıcıyı karıştırabilecek, her giriş için bir kez de dahil olmak üzere. Diğer bir hata da, konsolidasyona yönelik bir argüman olan, konsolidasyon argümanı ile ilgili olan, sadece sonsuz bir döngüye neden olan konsol.log argümanlarıdır.

Function.prototype.apply da geçersiz kılma

Peki, peki ya o zaman? Sahip olduğumuz tek sihirli çağrı işlevleri onlar da bunu deneyelim. İşte ikisini de yakalayan bir versiyona gider:

// Store apply and call
var origApply = Function.prototype.apply;
var origCall = Function.prototype.call;

// We need to be able to apply the original functions, so we need
// to restore the apply locally on both, including the apply itself.
origApply.apply = origApply;
origCall.apply = origApply;

// Some utility functions we want to work
Function.prototype.toString.apply = origApply;
Array.prototype.slice.apply = origApply;
console.trace.apply = origApply;

function logCall(t, a) {
    // If console.log is allowed to stringify by itself, it will
    // call .call 9 gajillion times. Therefore, do it ourselves.
    console.log("Calling",
                Function.prototype.toString.apply(t, []),
                "with:",
                Array.prototype.slice.apply(a, [1]).toString()
               );
    console.trace.apply(console, []);
}

Function.prototype.call = function () {
   logCall(this, arguments);
   origCall.apply(this, arguments);
};

Function.prototype.apply = function () {
    logCall(this, arguments);
    origApply.apply(this, arguments);
}

... Ve deneyelim!

// Some tests
console.log("1"); // Passes by unseen
console.log.apply(console,["2"]); // Caught
console.log.call(console, "3"); // Caught

Gördüğünüz gibi, arayan parantez fark edilmeden gider.

Sonuç

Neyse ki, çağrı parantezini JavaScript’ten ele geçiremezsiniz. Ancak, .call, parantez operatörünü işlev nesneleri üzerinde engellese bile, sonsuz bir döngüye neden olmadan orijinali nasıl arayabilirdik?

.Call / .apply'u geçersiz kılan tek şey, bu prototip işlevlerine açık çağrıları engellemektir. Konsol, bu hack ile kullanıldığında, çok ve çok sayıda spam olacaktır. Ayrıca, eğer kullanıldıysa, konsol API'sinin hızlı bir şekilde sonsuz bir döngüye neden olabileceğinden (konsol.log, dahili olmayan bir dizge verirse dahili olarak kullanılabilir.) Kullanıldığında çok dikkatli olunmalıdır.


15
2018-04-16 08:30



Bunu denediğimde şu hatayı alıyorum: "JavaScript çalışma zamanı hatası: Function.prototype.call: 'bu' bir Function nesnesi değil" satırında origCall.apply(this, arguments); - Sen Jacob
Hangi tarayıcıda? Ben sadece test ettim (son blobunu yapıştırın ve "şunu deneyin" kodunu çalıştırın) ve beklendiği gibi çalışır ...? - Kenny
Şey, şey bu Kullanılmış motorların eski versiyonlarında çalışmak. Çağrıyı geçersiz kıl ve yakalamak için kullanılır herşey işlev çağrıları. Bu daha yeni tarayıcı sürümlerinde düzeltildi. - Benjamin Gruenbaum


BAZI sonuçlar alıyorum ve aşağıdakilerle hiçbir sayfa çöküyor:

(function () {
  var 
    origCall = Function.prototype.call,
    log = document.getElementById ('call_log');  

  // Override call only if call_log element is present    
  log && (Function.prototype.call = function (self) {
    var r = (typeof self === 'string' ? '"' + self + '"' : self) + '.' + this + ' ('; 
    for (var i = 1; i < arguments.length; i++) r += (i > 1 ? ', ' : '') + arguments[i];  
    log.innerHTML += r + ')<br/>';



    this.apply (self, Array.prototype.slice.apply (arguments, [1]));
  });
}) ();

Yalnızca Chrome sürüm 9.xxx'te test edildi.

Bu kesinlikle tüm işlev çağrılarını kaydetmiyor, ancak bazılarını günlüğe kaydediyor! Şüpheliyim ki, sadece 'çağrı' ile ilgili gerçek çağrılar işleniyor


4
2018-03-09 10:31



Teşekkürler! Bu kodun bir kısmını, formatı ve konsol.log'a bağlanan bir şeyde kullandı. autolog.js. Deneyin ve ne düşündüğünüzü bana bildirin! Hala çok iş gerekiyor. - Gary S. Weaver
Teşekkürler, cevabımı bulabilirsiniz stackoverflow.com/questions/6921588 ayrıca yardımcı olur. - HBP
@ GaryS.Weaver - autolog'unuz bunu Chrome'da tekrar tekrar konsolumda yazdırmaya devam etsin: [object Arguments].slice () source: at Function.function_prototype_call_override (<anonymous>:76:17) [object HTMLBodyElement].anonymous ((object)[object Object], (object)[object Object],[object Object]) source: at Function.function_prototype_call_override (<anonymous>:76:17) - Johannes
@Johannes Bazı kütüphanelerle benzer sorunlarla karşılaştım. 'Uygula' işlevi tekrar 'ara' diyebilir ve bir döngüde sıkışır. Kodun kendisi için konsola giriş yapmanın daha iyi bir yaklaşıma yol açabileceğine karar verdim: github.com/garysweaver/noisify Not: autolog / etc'deki problemlerle gelecekteki yardım için. açmak konu Github projesinde. Rapor için teşekkürler. - Gary S. Weaver
@ GaryS.Weaver - evet noisify gördüm ama bir JS çözümü arıyordum. Yine de teşekkürler ve bana geri döndüğün için teşekkürler. - Johannes


Sadece hızlı bir test, ama benim için çalışıyor gibi görünüyor. Bu şekilde işe yaramayabilir, ama benim ikamemin bedeninde iken prototipi eski haline getiriyorum ve daha sonra çıkmadan önce “geri almamalıyım”.

Bu örnek sadece tüm işlev çağrılarını kaydeder - yine de tespit etmem gereken bazı ölümcül kusurlar olabilir; bunu bir kahve molası sırasında yapıyor

uygulama

callLog = [];

/* set up an override for the Function call prototype
 * @param func the new function wrapper
 */
function registerOverride(func) {
   oldCall = Function.prototype.call;
   Function.prototype.call = func;
}

/* restore you to your regular programming 
 */
function removeOverride() {
   Function.prototype.call = oldCall;
}

/* a simple example override
 * nb: if you use this from the node.js REPL you'll get a lot of buffer spam
 *     as every keypress is processed through a function
 * Any useful logging would ideally compact these calls
 */
function myCall() { 
   // first restore the normal call functionality
   Function.prototype.call = oldCall;

   // gather the data we wish to log
   var entry = {this:this, name:this.name, args:{}};
   for (var key in arguments) {
     if (arguments.hasOwnProperty(key)) {
      entry.args[key] = arguments[key];
     }
   }
   callLog.push(entry);

   // call the original (I may be doing this part naughtily, not a js guru)
   this(arguments);

   // put our override back in power
   Function.prototype.call = myCall;
}

kullanım

Büyük bir macunta yapılan aramalar dahil olmak üzere bazı sorunlar yaşadım, bu nedenle yukarıdaki işlevleri test etmek için REPL'e yazdıklarımı yazdım:

/* example usage
 * (only tested through the node.js REPL)
 */
registerOverride(myCall);
console.log("hello, world!");
removeOverride(myCall);
console.log(callLog);

3
2017-09-14 13:23





Geçersiz kılınabilir Function.prototype.callsadece sadece emin ol apply geçersiz kılma içindeki işlevler.

window.callLog = [];
Function.prototype.call = function() {
    Array.prototype.push.apply(window.callLog, [[this, arguments]]);
    return this.apply(arguments[0], Array.prototype.slice.apply(arguments,[1]));
};

1
2018-02-07 22:34



Şimdi eğer yerel işlevlerin nasıl filtreleneceğini anlayabilseydim ... - colllin
davidwalsh.name/detect-native-function yerel işlevleri algılamak için - Christopher Roy


Otomatik bir işlem kullanarak dosyayı tanıtmanın en kolay yolunu buldum. Bu küçük aracı kendim için daha kolay hale getirdim. Belki başka biri yararlı bulacaktır. Temelde awk, ama bir Javascript programcısının kullanması daha kolay.

// This tool reads a file and builds a buffer of say ten lines.  
// When a line falls off the end of the buffer, it gets written to the output file. 
// When a line is read from the input file, it gets written to the first line of the buffer. 
// After each occurrence of a line being read from the input file and/or written to the output 
// file, a routine is given control.  The routine has the option of operating on the buffer.  
// It can insert a line before or after a line that is there, based on the lines surrounding. 
// 
// The immediate case is that if I have a set of lines like this: 
// 
//             getNum: function (a, c) {
//                 console.log(`getNum: function (a, c) {`);
//                 console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
//                 console.log(`arguments.length = ${arguments.length}`);
//                 for (var i = 0; i < arguments.length; i++) { console.log(`arguments[${i}] = ${arguments[i] ? arguments[i].toString().substr(0,100) : 'falsey'}`); }
//                 var d = b.isStrNum(a) ? (c && b.isString(c) ? RegExp(c) : b.getNumRegx).exec(a) : null;
//                 return d ? d[0] : null
//             },
//             compareNums: function (a, c, d) {
//                 console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
// 
// I want to change that to a set of lines like this: 
// 
//             getNum: function (a, c) {
//                 console.log(`getNum: function (a, c) {`);
//                 console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
//                 console.log(`arguments.length = ${arguments.length}`);
//                 for (var i = 0; i < arguments.length; i++) { console.log(`arguments[${i}] = ${arguments[i] ? arguments[i].toString().substr(0,100) : 'falsey'}`); }
//                 var d = b.isStrNum(a) ? (c && b.isString(c) ? RegExp(c) : b.getNumRegx).exec(a) : null;
//                 return d ? d[0] : null
//             },
//             compareNums: function (a, c, d) {
//                 console.log(`compareNums: function (a, c, d) {`);
//                 console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
// 
// We are trying to figure out how a set of functions work, and I want each function to report 
// its name when we enter it.
// 
// To save time, options and the function that is called on each cycle appear at the beginning 
// of this file.  Ideally, they would be --something options on the command line. 


const readline = require('readline');


//------------------------------------------------------------------------------------------------

// Here are the things that would properly be options on the command line.  Put here for 
// speed of building the tool. 

const frameSize = 10;
const shouldReportFrame = false;

function reportFrame() {
    for (i = frame.length - 1; i >= 0; i--) {
        console.error(`${i}.  ${frame[i]}`);  // Using the error stream because the stdout stream may have been coopted. 
    }
}

function processFrame() {
    // console.log(`********  ${frame[0]}`);
    // if (frame[0].search('console.log(\`arguments.callee = \$\{arguments.callee.toString().substr(0,100)\}\`);') !== -1) {
    // if (frame[0].search('arguments.callee') !== -1) {
    // if (frame[0].search(/console.log\(`arguments.callee = \$\{arguments.callee.toString\(\).substr\(0,100\)\}`\);/) !== -1) {
    var matchArray = frame[0].match(/([ \t]*)console.log\(`arguments.callee = \$\{arguments.callee.toString\(\).substr\(0,100\)\}`\);/);
    if (matchArray) {
        // console.log('********  Matched');
        frame.splice(1, 0, `${matchArray[1]}console.log('${frame[1]}');`);
    }
}

//------------------------------------------------------------------------------------------------


var i;
var frame = [];

const rl = readline.createInterface({
    input: process.stdin
});

rl.on('line', line => {
    if (frame.length > frameSize - 1) {
        for (i = frame.length - 1; i > frameSize - 2; i--) {
            process.stdout.write(`${frame[i]}\n`);
        }
    }
    frame.splice(frameSize - 1, frame.length - frameSize + 1);
    frame.splice(0, 0, line);
    if (shouldReportFrame) reportFrame();
    processFrame();
    // process.stdout.write(`${line}\n`);  // readline gives us the line with the newline stripped off
});

rl.on('close', () => {
    for (i = frame.length - 1; i > -1; i--) {
        process.stdout.write(`${frame[i]}\n`);
    }
});


// Notes
// 
// We are not going to control the writing to the output stream.  In particular, we are not 
// going to listen for drain events.  Nodejs' buffering may get overwhelmed. 
// 

0
2018-02-22 20:46