Javascript: 扩展函数

我需要它的主要原因是我想扩展我的 initialize 函数。

就像这样:

// main.js


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


// extend.js


function extends init(){
doSomethingHereToo();
}

所以我想扩展一个函数,就像我在 PHP 中扩展一个类一样。

我还想从其他文件扩展它,例如,我在 main.js中有原始的 init 函数,在 extended.js中有扩展函数。

161096 次浏览

有几种方法可以做到这一点,这取决于你的目的是什么,如果你只是想执行函数以及在相同的上下文中,你可以使用 .apply():

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

如果你想用一个更新的 init来代替它,它应该是这样的:

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

对于你实际上正在尝试做的事情以及你正在做的背景,我相信我们可以给你一个比 字面意思更好的答案。

但这里有一个字面上的答案:

If you're assigning these functions to some property somewhere, you can wrap the original function and put your replacement on the property instead:

// 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);

如果你的函数还没有在一个对象上,你可能想把它们放在那里来促进上面的操作。例如:

// 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();
}
})();

但是有更好的方法可以做到这一点,例如,提供一种注册 init函数的方法。

// 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);
}
}
};
})();

Here in 2020 (or really any time after ~2016), that can be written a bit more compactly:

// 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)
}
}
};
})();

其他方法很棒,但是它们不保留任何附加到 init 的原型函数。为了解决这个问题,你可以做到以下几点(灵感来自 Nick Craver 的帖子)。

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

使用 ExtendFunction.js

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

但是在您的特定情况下,扩展全局 onload 函数更容易:

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

I actually really like your question, it's making me think about different use cases.

对于 javascript 事件,您确实希望添加和删除处理程序-但是对于 extfunction,以后如何使用 拿开功能呢?我可以很容易地添加一个。将方法还原为扩展函数,这样 init = init.revert()将返回原始函数。显然,这可能会导致一些非常糟糕的代码,但是也许它可以让您在不触及代码库的外部部分的情况下完成某些工作。

另一个选择可能是:

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 );

这非常简单明了,看看代码,试着掌握 javascript 扩展背后的基本概念。

首先让我们扩展 javascript 函数。

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
}

You can extend this function by creating child function in following way

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
}

Now you can use Child function as follows,

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

我们也可以通过扩展 Javascript 类来创建 Javascript 函数,如下所示。

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
}
}

Let us extend this class with Child function like this,

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

同样可以使用 Child 函数得到类似的结果,

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

Javascript 是一种非常简单的语言。我们几乎无所不能。JavaScript 快乐... 希望我能够给你一个在你的案例中使用的想法。

2017 + 解决方案

函数扩展的想法来自于功能性范式,它自 ES6以来就得到了本地支持:

function init(){
doSomething();
}


// extend.js


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


init();

根据@TJCrowder 对堆栈转储的担忧,今天的浏览器能够更好地处理这种情况。如果将此代码保存到 Test.html并运行它,则会得到

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

第12行: init 调用,第8行: init 扩展,第3行: 未定义的 doSomething()调用。

注: 非常尊敬老兵 T.J。克劳德,他在很多年前,当我还是个新手的时候,友好地回答了我的问题。多年以后,我仍然记得那种尊敬的态度,我努力学习那些好的榜样。