Javascript: Bir İşlevi Genişletin


98

Bunu istememin ana nedeni, ilklendirme fonksiyonumu genişletmek istemem.

Bunun gibi bir şey:

// main.js

window.onload = init();
function init(){
     doSomething();
}

// extend.js

function extends init(){
    doSomethingHereToo();
}

Bu yüzden PHP'de bir sınıfı genişlettiğim gibi bir işlevi genişletmek istiyorum.

Ve onu diğer dosyalardan da genişletmek istiyorum, bu yüzden örneğin orijinal init fonksiyonuna main.jsve genişletilmiş fonksiyona sahibim extended.js.



Yanıtlar:


106

Gerçekte ne yapmaya çalıştığınızın ve bunu yaptığınız bağlamın daha geniş bir bakış açısıyla, eminim size sorunuza gerçek cevaptan daha iyi bir cevap verebiliriz.

Ama işte gerçek bir cevap:

Bu işlevleri bir yerde bir özelliğe atıyorsanız, bunun yerine orijinal işlevi sarabilir ve yerine yeni işlevinizi özelliğe koyabilirsiniz:

// Original code in main.js
var theProperty = init;

function init(){
     doSomething();
}

// Extending it by replacing and wrapping, in extended.js
theProperty = (function(old) {
    function extendsInit() {
        old();
        doSomething();
    }

    return extendsInit;
})(theProperty);

İşlevleriniz halihazırda bir nesnede değilse, yukarıdakileri kolaylaştırmak için muhtemelen onları oraya koymak istersiniz. Örneğin:

// In main.js
var MyLibrary = {
    init: function init() {
    }
};

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.call(MyLibrary); // Use #call in case `init` uses `this`
        doSomething();
    }
})();

Ancak bunu yapmanın daha iyi yolları var. Örneğin, initfonksiyonları kaydetmenin bir yolunu sağlamak gibi .

// In main.js
var MyLibrary = (function() {
    var initFunctions = [];
    return {
        init: function init() {
            var fns = initFunctions;
            initFunctions = undefined;
            for (var index = 0; index < fns.length; ++index) {
                try { fns[index](); } catch (e) { }
            }
        },
        addInitFunction: function addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
            }
        }
    };
})();

Burada 2020'de (veya gerçekten ~ 2016'dan sonra herhangi bir zamanda), bu biraz daha derli toplu yazılabilir:

// In main.js
const MyLibrary = (() => {
    let initFunctions = [];
    return {
        init() {
            const fns = initFunctions;
            initFunctions = undefined;
            for (const fn of fns) {
                try { fn(); } catch (e) { }
            }
        },
        addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
                // Or: `Promise.resolve().then(() => fn());`
                // (Not `.then(fn)` just to avoid passing it an argument)
            }
        }
    };
})();

Harika bir cevap için teşekkürler. İkinci örnekle ilgili sorunum, genişlettiğim işlevin sonucuna ihtiyacım olabilmesidir.
Gerhard Davids

64

Bunu yapmanın birkaç yolu vardır, amacınızın ne olduğuna bağlıdır, sadece işlevi de yürütmek istiyorsanız ve aynı bağlamda kullanabilirsiniz .apply():

function init(){
  doSomething();
}
function myFunc(){
  init.apply(this, arguments);
  doSomethingHereToo();
}

Daha yenisiyle değiştirmek isterseniz init, şöyle görünür:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};

2
Bazen .callbunun yerine yöntemi isteyebilirsiniz .apply. Bu StackOverflow sorusuna bakın .
MrDanA

@Nick, JavaScript örneğinizi mevcut bir işlevi genişletmek için çok yararlı buldum, ancak aynı şeyin jQuery aracılığıyla nasıl yapılacağını merak ediyordum?
Sunil

+1 Teşekkürler. Orijinal js'yi değiştirmeden bazı 3. taraf eklentilerini yamamak istiyorsanız bu gerçekten kullanışlıdır.
GFoley83

1
Parametreleri ve dönüş değerlerini bekleyen işlevlerle nasıl kullanılacağından emin değilim.
Gerfried

5

Diğer yöntemler harikadır ancak init'e eklenmiş herhangi bir prototip işlevi korumazlar. Bunu aşmak için aşağıdakileri yapabilirsiniz (Nick Craver'ın gönderisinden esinlenerek).

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();

5

Başka bir seçenek de şunlar olabilir:

var initial = function() {
    console.log( 'initial function!' );
}

var iWantToExecuteThisOneToo = function () {
    console.log( 'the other function that i wanted to execute!' );
}

function extendFunction( oldOne, newOne ) {
    return (function() {
        oldOne();
        newOne();
    })();
}

var extendedFunction = extendFunction( initial, iWantToExecuteThisOneToo );

1

2017+ çözümü

İşlev uzantıları fikri, ES6'dan beri yerel olarak desteklenen işlevsel paradigmadan gelir:

function init(){
    doSomething();
}

// extend.js

init = (f => u => { f(u)
    doSomethingHereToo();
})(init);

init();

@ TJCrowder'ın yığın dökümü konusundaki endişesine göre, tarayıcılar bugün durumu çok daha iyi ele alıyor. Bu kodu test.html'ye kaydedip çalıştırırsanız,

test.html:3 Uncaught ReferenceError: doSomething is not defined
    at init (test.html:3)
    at test.html:8
    at test.html:12

Hat 12: başlatma çağrısı, 8. Hat: başlatma uzantısı, 3. Hat: tanımlanmamış doSomething()çağrı.

Not: Yıllar önce, acemi olduğum zamanlarda sorumu nazikçe yanıtlayan kıdemli TJ Crowder'a büyük saygı duyuyorum. Yıllar sonra hala saygılı tavrı hatırlıyorum ve güzel örneği izlemeye çalışıyorum.


"@ TJCrowder'ın yığın dökümü konusundaki endişesine göre, tarayıcılar bugün durumu çok daha iyi ele alıyor." Gerçekten de, bu gelişmeler şartnamede bile kodlanmıştır. (Ayrıca 2011'de bile adlandırılmış işlev ifadeleriyle anonim sorunu önleyebilirdim; IE onları IE8 ve altında yanlış anlasa da onları yanlış anlama şekli zararsız olurdu. Yaşa ve öğren. :-))
TJ Crowder

(Ve çok nazik yorumlarınız için teşekkür ederim. Gerçekten minnettarız!)
TJ Crowder

0

Bu çok basit ve anlaşılır. Koda bak. Javascript uzantısının ardındaki temel kavramı anlamaya çalışın.

Öncelikle javascript fonksiyonunu genişletelim.

function Base(props) {
    const _props = props
    this.getProps = () => _props

    // We can make method private by not binding it to this object. 
    // Hence it is not exposed when we return this.
    const privateMethod = () => "do internal stuff" 

    return this
}

Aşağıdaki şekilde çocuk işlevi oluşturarak bu işlevi genişletebilirsiniz.

function Child(props) {
    const parent = Base(props)
    this.getMessage = () => `Message is ${parent.getProps()}`;

    // You can remove the line below to extend as in private inheritance, 
    // not exposing parent function properties and method.
    this.prototype = parent
    return this
}

Artık Çocuk işlevini aşağıdaki gibi kullanabilirsiniz,

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Javascript sınıflarını bu şekilde genişleterek de Javascript Fonksiyonu oluşturabiliriz.

class BaseClass {
    constructor(props) {
        this.props = props
        // You can remove the line below to make getProps method private. 
        // As it will not be binded to this, but let it be
        this.getProps = this.getProps.bind(this)
    }

    getProps() {
        return this.props
    }
}

Bu sınıfı şu şekilde Çocuk fonksiyonu ile genişletelim,

function Child(props) {
    let parent = new BaseClass(props)
    const getMessage = () => `Message is ${parent.getProps()}`;
    return { ...parent, getMessage} // I have used spread operator. 
}

Yine benzer sonuç elde etmek için Çocuk işlevini aşağıdaki gibi kullanabilirsiniz,

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Javascript çok kolay bir dildir. Neredeyse her şeyi yapabiliriz. Mutlu JavaScripting ... Umarım davanızda kullanabileceğiniz bir fikir verebildim.


-1

ExtendFunction.js kullanın

init = extendFunction(init, function(args) {
  doSomethingHereToo();
});

Ancak sizin özel durumunuzda, global onload işlevini genişletmek daha kolaydır:

extendFunction('onload', function(args) {
  doSomethingHereToo();
});

Aslında sorunuzu gerçekten beğendim, farklı kullanım durumları hakkında düşünmemi sağlıyor.

Javascript olayları için, işleyicileri gerçekten eklemek ve kaldırmak istiyorsunuz - ancak expandFunction için, daha sonra nasıl yapabilirsiniz işlevselliği kaldırabilirsiniz ? Genişletilmiş işlevlere kolayca bir .revert yöntemi ekleyebilirim, böylece init = init.revert()orijinal işlevi döndürürdüm. Açıkçası bu, oldukça kötü bir koda yol açabilir, ancak belki de kod tabanının yabancı bir kısmına dokunmadan bir şeyler yapmanıza izin verir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.