2014-02-14 64 views
2

我想創建構造函數的構造函數。 有關此主題:JavaScript build a constructor of constructors,似乎唯一的解決方案是:JavaScript更好的修改函數原型的方法

Function.prototype.add = function(name, value) { 
    this.prototype[name] = value; 
}; 
Function.prototype.remove = function(name) { 
    delete this.prototype[name]; 
}; 

但我不希望修改通用Function原型...也:

var A = new ConstBuilder().add('test', function() { 
    console.log('test'); 
}).getConstructor(); 

但我不不希望在構造函數本身有一個對象包裝。

問題是,通常構造函數會創建新對象,從構造函數原型繼承方法。我試圖做的是instanciates功能,而不是對象,但修改函數原型屬性的唯一方法是這樣來修改它的屬性__proto__

var constructorPrototype = { 
    add : function(name, value) { 
     this.prototype[name] = value ; 
    } 
} ; 

var ConstBuilder = function() { 
    var constructor = function() {} ; 
    constructor.prototype = {} ; 
    // The only way (?), but quite deprecated... 
    constructor.__proto__ = constructorPrototype ; 
    return constructor ; 
} ; 

// Not working way... 
//ConstBuilder.prototype = constructorPrototype ; 

var A = new ConstBuilder() ; 
A.add('test', function() { 
    console.log('test') ; 
}) ; 

var a = new A() ; 
a.test() ; // "test" 

constructorPrototype.remove : function() { 
    delete this.prototype[name] ; 
} ; 

A.remove('test') ; 

a.test() ; // Error: test is not a function. 

注意A.prototype不是A.__proto__A.prototype(new A).__proto__

它通過修改__proto__完美工作,真是太遺憾了。 我讀到Firefox已經集成了一個「Object.setPrototypeOf()」,但它只是實驗性的。

它是另一種做我想做的事情的方式嗎?

+0

a.constructor.prototype == a .__ proto__,除非你打破link.does,這有助於你的跨瀏覽器實現嗎? – dandavis

+0

它不是'a .__ proto',我正在修改,它是'A .__ proto__'這是一個函數,不是'ConstBuilder'的實例,因爲ConstBuilder直接返回它而不是返回它的新實例。 – Tot

+0

對不起,我沒有注意到你在代碼中使用了「a」;我廣泛地講話。所以,X.constructor.prototype == X .__ proto__ – dandavis

回答

7

確實。做你想做的唯一方法是改變你正在返回的函數的__proto__屬性。然而,這並非壞事。事實上,ES6 Harmony將把它標準化爲Object.setPrototypeOf功能。

但是,我會建議你不要改變對象的[[Prototype]],因爲它會讓你的程序很慢。有一個更快的替代方案可供選擇:

不要使用原型

傳統的原型是用來定義在某一類型的對象操作功能。這些函數專門針對某個參數,被稱爲方法。

例如,obj.func(a, b, c)專門研究objobj的實例。另一方面,func(obj, a, b, c)不專注於任何參數(即obj可以是任何值)。

在此之後例如,你可以重寫addremove如下:

function add(func, name, value) { 
    func.prototype[name] = value; 
} 

function remove(func, name) { 
    delete func.prototype[name]; 
} 

現在你可以在任何你想要的功能使用addremove。你根本不必擔心遺傳。

唯一的問題是命名空間衝突。假設你已經有一個名爲add的函數:你做什麼?答案很簡單。您創建一個新的命名空間:

Function.add = function (func, name, value) { 
    func.prototype[name] = value; 
}; 

Function.remove = function remove(func, name) { 
    delete func.prototype[name]; 
}; 

事實上,這正是本機JavaScript API通常所做的。例如:

  1. Object.create
  2. Object.getPrototypeOf
  3. Object.setPrototypeOf

等等等等等等。

關鍵是這樣的:泛化總是比專業化好。我們使用原型來專業化。我們使用普通函數進行推廣。有很多的推廣的優勢在專業化:

  1. 你不需要像call方法和apply「unspecialize」專用功能。
  2. 您不必擔心繼承和原型鏈。
  3. 您的代碼更清晰,更易於理解。

這就是我總是比專業化更喜歡泛化的原因。我曾經使用原型的唯一原因是創建聯合類型。例如:

function Shape(constructor) { 
    this.constructor = constructor; 
} 

function Circle(x, y, r) { 
    this.x = x; 
    this.y = y; 
    this.r = r; 
} 

function Rectangle(x1, y1, x2, y2) { 
    this.x1 = x1; 
    this.y1 = y1; 
    this.x2 = x2; 
    this.y2 = y2; 
} 

Circle.prototype = new Shape(Circle); 
Rectangle.prototype = new Shape(Rectangle); 

,而不是添加方法Circle.prototypeRectangle.prototype我做的不是跟隨:

Circle.area = function (circle) { 
    return Math.PI * circle.r * circle.r; 
}; 

Rectangle.area = function (rectangle) { 
    return Math.abs((rectangle.x2 - rectangle.x1) * (rectangle.y2 - rectangle.y1)); 
}; 

Shape.prototype.area = function() { 
    return this.constructor.area(this); 
}; 

現在你可以使用Circle.area(notCircleInstance)代替Circle.prototype.area.call(notCircleInstance)。這是一個雙贏的局面。泛化總是比專業化好。

+0

非常感謝,這是我正在尋找的答案。我知道'__proto__'變異很慢,但我不知道爲什麼。你能告訴我爲什麼這麼慢嗎?我不想推廣這些方法('add','remove'),因爲我創建了一個可調整框架,可以添加其他函數來修改新的構造函數,我不認爲這是一個好主意修改'Function'原型。我給出了這兩個函數,但實際上有近十個人被構造函數使用,推廣它們會很麻煩,因爲它們不是必須「適用於」每個函數。 – Tot

+0

待續:http://stackoverflow.com/questions/21840926/javascript-why-manipulating-proto-is-slow – Tot