引子

独家解析Javascript原型继承已经比较全面的分析了自定义函数类型,JS内置基本类(undefined, null, bool, number, string, symbol)和JS内置对象类型(Error, Date, Function)的design-timeprototype, 以及run-time的__proto__原型链。鉴于函数是JS的一等公民,另辟新篇介绍函数的原型及其应用。

JS函数类型的构造

通常情况下定义一个函数:

function add1(a, b) {
    return a + b;
}

console.log(add1(1,2)) //3

通过new Function也能构造:

var add2 = new Function('a', 'b', 'return a + b');
console.log(add2(1,2)) //3

两种方式达到的目的是一致的。而add1和add2的run-time __proto__都是Function.prototype, 可以看作add1和add2都是透过new Function构造出来的,而function关键子就是调用new Function构造的便利途径。

add1.__proto__ = Function.prototype //true
add2.__proto__ = Function.prototype //true

函数本身也是对象,它遵循独家解析Javascript原型继承所描述的自定义函数类型对象的原型法则。

//add1 创自于Function类型,是Function的实例
add1.__proto__ //[Function]
add instanceof Function //true

// add1 也继承了object类型的原型
add1.__proto__.__proto__ //{}
add1 instanceof Object //true

// 所以add1 run-time __proto__原型链的顶端同样是null
add1.__proto__.__proto__.__proto__ // null

AOP编程实现

至此我们了解到,透过funtion关键字定义的函数,实质是Fuction类型的实例,和通过new Function方式构造本身是一样的。所以我们通过修改Function的design-time的prototype,来实现面向切面编程(AOP)

值得一提的是:我们(程序员)一般情况下只推荐修改design-time的prototype,而不去更改run-time的__proto__, 否则修改run-time的__proto__会造成程序难以维护和阅读,增加不确定的运行错误。Object.setPrototypeOf可以更改对象实例run-time的__proto__

面向切面编程(AOP,Aspect-Oritented Programming), 简而言之,可理解为函数调用时,在该函数执行前或/和执行后做一些通用的工作,比如做log,记录执行时间等。这需要一个代理函数,把原始函数打扮成具有做log等功能的新函数。实际中调用该代理函数

function foo() {
     console.log('foo runs');
}

foo(); // foo runs

现在我们想在foo函数执行前后分别打印log,而不修改foo函数本身。

Function.prototype.before = function() {
    var _self = this;
    return function() {
        console.log('before foo calls');
        return _self.apply(this, arguments);
    }
}

Function.prototype.after = function() {
    var _self = this;
    return function() {
        var ret = _self.apply(this, arguments);
        console.log('after foo calls');
        return ret;
    }
}

//这里foo就具有了执行前后打印log的功能
foo = foo.before().after();
foo();
// before foo calls
// foo runs
// after foo calls

把打印log的功能提出来,做成更通用的逻辑。

Function.prototype.before = function(beforeFn) {
    var _self = this;
    return function() {
        beforeFn.apply(this, arguments);
        return _self.apply(this, arguments);
    }
}

Function.prototype.after = function(afterFn) {
    var _self = this;
    return function() {
        var ret = _self.apply(this, arguments);
        afterFn.apply(this, arguments);
        return ret;
    }
}

//包装成具有log功能的新的foo函数
foo = foo.before(function() {
    console.log('foo enters')
}).after(function(){
    console.log('foo exits')
});
foo();
// foo enters
// foo runs
// foo exits

由此,可把函数调用和通用的log能功能挂接(hook)起来了。

你可能感兴趣的文章

杰克船长 作者 · 7月15日

如果这篇文章对您有用,烦请给赞。谢谢了。当然有问题的话,还请不吝赐教。

+2 回复

载入中...
杰克船长 杰克船长

220 声望

发布于专栏

全栈开发

作为10多年的码农,做了6年的C# 和5年的js相关的开发。在本栏目 准备把相关的技术学习心得,技术本身的特性和优缺点,以自己的视角做些解读

3 人关注

系列文章