簡體   English   中英

定義一個函數,擴展函數原型,創建兩個實例,原型被修改?

[英]Define a function, extend the functions prototype, create two instances, prototype was modified?

有人可以告訴我為什么結果是什么,而不是我期望的結果。 這讓我瘋了!

var f = function(b){
    console.log(this.config);
    this.config.b = b;
}
f.prototype.config = {
    a: 'a',
    b: 'b'
};

var f1 = new f('bb');
var f2 = new f('bbb');

// logs
// { a: 'a', b: 'b' }
// { a: 'a', b: 'bb' }

// expected
// { a: 'a', b: 'b' }
// { a: 'a', b: 'b' }

它不是被修改的原型,而是你在原型上放置的config對象。 這是正確的行為,創建新實例時不會復制原型引用的對象。 f1.config === f2.config ,它們指向同一個對象。

原型鏈用於get操作的方式是這樣的:

  1. 你做了一些在對象上查找屬性的東西。 說, this.config
  2. 檢查對象本身以查看它是否具有該名稱的屬性。 如果是,則使用該屬性的副本並返回其值。 在您的情況下,對象沒有自己的config ,因此我們繼續下一步。
  3. 檢查對象的原型以查看是否具有該屬性。 如果是,則使用該屬性的副本並返回其值。 在你的情況下,這是真的,因為原型具有屬性。 但僅僅是為了完整性:
  4. 重復步驟3,根據需要繼續向上(向下?)原型鏈。
  5. 如果一切都沒有找到該屬性,返回undefined

set操作的工作方式不同; set操作總是更新或創建其所設置對象的屬性,從不進一步向下[原型?]原型鏈。)

因此,在您的情況下,由於您的實例沒有config屬性,我們將轉到原型。 由於原型確實具有config屬性,因此使用它。 該屬性的值是一個對象的引用,所以如果你改變對象(被分配給的一個屬性),它的改變,別的也使用該對象將會看到的變化。

另一種看待它的方法是做一個圖表:

+------+       +------+
|  f1  |       |  f2  |
+------+       +------+
   |              |
   +------+-------+
          |
          v
    +--------------------+       +--------+
    | [[Proto]] assigned |       | config |
    | via `new f`        |------>| object |
    +--------------------+       +--------+
                                     |
                             +-------+-------+
                             |               |
                             V               v
                     +------------+     +------------+
                     | a property |     | b property |
                     +------------+     +------------+

一種思考方式是將函數和原型完全取出:

var pseudoProto = {};               // A stand-in for the prototype...
pseudoProto.config = {              // ...with `config` on it
    a: 'a',
    b: 'b'
};
var f1 = {};                        // A blank object...
f1.pseudo = pseudoProto;            // ...referencing `pseudoProto`
var f2 = {};                        // Another blank object...
f2.pseudo = pseudoProto;            // ...also referencing `pseudoProto`
f1.pseudo.config.b = "bb";          // Change the `b` property on `config`
console.log(f2.pseudo.config.b);    // Logs "bb", of course

以一種非常真實的方式,這是通過new f()在幕后發生的事情。 你不能直接訪問指向原型的f1f2實例的屬性(規范稱它為[[Proto]]屬性),但它是真實的,它確實存在。 [僅供參考:ECMAScript規范的最新版本允許我們直接使用[[Proto]]屬性做一些事情,比如創建具有特定[[Proto]]原始對象(不通過函數),但仍然沒有讓我們直接進入酒店本身。]

很多時候你希望所有實例共享同一個對象(例如,函數對象!),因此原型是那些對象引用的正確位置; 但是如果您希望每個實例都有自己的對象副本,則需要在構造函數中創建該對象。 在你的情況下:

var f = function(b){
    this.config = {
        a: 'a',
        b: b
    };
}

var f1 = new f('bb');
console.log(f1.config);
var f2 = new f('bbb');
console.log(f2.config);
// Logs
// { a: 'a', b: 'bb' }
// { a: 'a', b: 'bbb' }

(注意我移動了console.log語句,因此我們在結束時看到結果而不是中間狀態。)

這是一個很好的咖啡豆示例,展示了這個問題:

Person = class
  config:
    name: "sin nombre"
  constructor: (config={}) ->
    @config.name = config.name  if config.name?
  getName: -> @config.name

Human = class extends Person

ben = new Human(name: 'Ben')
sonny = new Human()
alert ben.getName()
alert sonny.getName()

和解決方案:

Person = class
  config:
    name: "sin nombre"
  constructor: (config={}) ->
    @config = {}
    @config[key] = value  for own key, value of Person::config
    @config.name = config.name  if config.name?
  getName: -> @config.name

Human = class extends Person

ben = new Human(name: 'Ben')
sonny = new Human()
alert ben.getName()
alert sonny.getName()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM