无痛脱发术-A.extend()

181 阅读2分钟

一道面试题,像是最锋利的剃刀,比时间杀猪刀更恐怖,它是让你的头发在不知不觉间就无痛落下了。

function A() {
  this.name = 'apple';
}

A.prototype.getName = function () {
  return this.name;
}
// 补全,使下面能运行
// ...
var B = A.extend({
  initialize: function () {
    this.superclass.initialize.call(this);
    this.total = 3;
  },
  say: function () {
    return '我有' + this.total + '个' + this.getName();
  }
})

var b = new B();
console.log(b.say());// 我有3个apple

这道题看上去,第一印象是继承。但是又有个 initilize 在搞事。感觉不处理这个 initilize 搞屎棍,没办法顺利进行。不过可以得出一部分代码,如:

Function.prototype.extend = function (obj) {
    // ...
}

如果先不管 initilize ,当做简单继承来处理,还能继续写下去

Function.prototype.extend = function (obj) {
    var F = function() {};
    F.prototype = new this();
    F.prototype.constructor = F;
    for (let key in obj) {
        F.prototype[key] = obj[key];
    }
    return F;
}

我们来看一下答案,这里已经实现了继承了,所以答案是:

// 我有undefined个apple

很好,果然是对的,如我所料,不处理 initilize 这个搞屎棍,是不可能革命成功的。

仔细分析一下代码,假如运行 initilizetotal 这个属性要挂载到 A 上面去,要不然,结果还是 我有undefined个apple 。那么 this.superclass.initialize.call(this) 应该是关键的提示。如果 this 要指向 A 那么 A === this.superclass.initialize ,这样就有希望看到革命成功了。那么 initilize 的运行环境是 obj ?还是extend ? 其实都不是,最后是 b.say() 所以 initialize 应该挂载在 extend 里面的 F 上 。那么,需要在 extend 那部分代码进行一些修改:

Function.prototype.extend = function (obj) {
   	var self = this;
    var F = function() {
        // 这样在F的环境下,this.superclass.initialize === self === A
        this.superclass = {initialize: self}; 
        if (obj.initialize) {
            obj.initialize.call(this);
        }
    };
    F.prototype = new self();
    F.prototype.constructor = F;
    for (let key in obj) {
        // 用过了,不必要放进去了
        if (key !== 'initialize') {
            F.prototype[key] = obj[key];
        }
    }
    return F;
}

function A() {
  this.name = 'apple';
}

A.prototype.getName = function () {
  return this.name;
}

var B = A.extend({
  initialize: function () {
    // this.superclass === {initialize: A},那么
   // this.superclass.initialize.call(this) === A.call(this)
    this.superclass.initialize.call(this);
    this.total = 3;
  },
  say: function () {
    return '我有' + this.total + '个' + this.getName();
  }
})

var b = new B();
console.log(b.say());// 我有3个apple