【问题标题】:Understanding better Javascript OOP architecture [duplicate]了解更好的 Javascript OOP 架构 [重复]
【发布时间】:2013-06-23 05:59:14
【问题描述】:

当我阅读 Angularjs 的 UI 插件的一些示例时,我偶然发现了一些代码,这些代码表明我的 Javascript 知识是可以提高的:

以下是 Angular 提供者内部的一个类:

function Dialog(opts) {

            var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
            this._open = false;

            this.backdropEl = createElement(options.backdropClass);
            if(options.backdropFade){
                // ...
            }

            this.handleLocationChange = function() {
                self.close();
            };

            // more functions
        }

非常简单。但在该类之外,还有原型函数,例如上面调用的close()

Dialog.prototype.open = function(templateUrl, controller){
            var self = this, options = this.options;

            // .. some code
        };

现在我不明白为什么该函数被声明为原型,而是在类本身内部声明handleLocationChange。

我如何决定选择哪种方法?

完整的要点可以在here找到

【问题讨论】:

  • 从代码显示来看,两者是可以互换的。您可能正盯着几棵树挡住您的森林视野。程序员在周一和周五写的代码不同,也许就像我妈妈常说的“这就是上帝创造的方式”。
  • @dandavis 我不这么认为,该函数被放置在构造函数体内,因此它可以利用名为self 的闭包值这可能是因为handleLocationChange 作为回调的变量传递在某些时候,this 上下文会丢失。有关详细信息,请参阅我的答案。我很好奇为什么他们会增加闭包变量和实例拥有函数的权重,因为还有其他解决方案不需要消耗额外的内存。这可能听起来微不足道,但在像谷歌文档这样的应用程序中,这可能会加起来。
  • @HMR: 好吧 self 和 this 一样,可以从原型方法中访问,与内联方法相同。它可以连接为 this.close() 或 self.handleLocationChange,如果需要,绑定或调用会在以后处理任何不合理的 this 值。
  • @dandavis 你不能在任何原型函数中使用 self 因为 self 在构造函数体中被声明为 var self=this 所以 self 在函数体之外的任何地方都超出了范围(原型函数在外部声明函数体)。这个问题不是另一个问题的重复,因为 self 变量不用作私有变量,我怀疑 Angular 需要它,因为他们的事件/发布/订阅系统,handleLocationChange 是在没有引用特定对象的情况下传递的。
  • @HMR。我的意思是在显示的代码中,self===this。因此,在原型方法中,this.x 与构造函数中的 self.x 相同。

标签: javascript oop architecture prototype


【解决方案1】:

考虑这两种情况:

Dialog.prototype.open = function...

Dialog.open = function....

第一种情况 - 通过调用 new Dialog() 创建的每个对象都将具有此 open 函数

第二种情况与对话框对象无关,视为静态函数。

编辑

在这里找到了一个很好的答案:javascript-class-method-vs-class-prototype-method

【讨论】:

  • handleLocationChange 被声明为 this.handleLocationChange ,因此使用 new Dialog 创建的每个对话框不仅具有 handleLocationChange 它还有自己的 handleLocationChange,而不是与原型上的其他实例共享它。我不确定他们为什么会这样做,但还没有使用过 Angular,但我很想知道。
  • 我认为它应该被你覆盖。由于它现在所做的一切 - 调用 close()
  • 我认为重要的是它调用self.close。
【解决方案2】:

函数 open 将由使用 new Dialog() 创建的所有对象共享。并且对于不同的对象,handleLocationChange 将有所不同。

【讨论】:

  • +1 你是对的,我什至要补充一点,向原型添加函数会导致该原型的所有现有(和未来)对象“继承”该函数
  • 其中一个大啊哈!时刻。那么我可以说,如果我将 handleLocationChange 声明为原型函数,它不会只关闭一个特定的对话框吗?
  • 我明白了,handleLocationChange 是一个handler,它响应特定实例的事件。您可以在原型上声明它,但您必须在实例上触发事件: instanceOfDialog.trigger("locationChange");我的猜测是 Angular 的事件系统不是这样工作的?
  • @user2422960 它可能会得到一个错误,因为this 不引用对话框实例并且self 只能在对话框主体中使用(原型函数在对话框函数主体之外)。我已经更新了关于为什么我认为它们在构造函数的主体中包含事件处理程序的答案。
【解决方案3】:

我认为handleLocationChange是从注册监听器但不注册this上下文的事件触发对象调用的,所以当它被触发时你不能使用this,因为它指的是handleLocationChange。为了克服这个问题,他们选择设置一个 closure 引用 this(= self 变量)并使用 self 调用其他实例函数。基本上它存储了一个在创建时已知但在执行 handleLocationChange 时不知道的值。

下面是一些显示问题的代码:

var eventSystem={
  events:{},
  add:function(eventname,fnCallback){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push(fnCallback);
  },
  trigger:function(eventname){
    if(!this.events[eventname]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[eventname].length;i++){
      this.events[eventname][i]();
    }
  }
};

var person=function(name){
  this.name=name;
};
person.prototype.sayName=function(){
  console.log("this is now:",this.toString());
    // logs this is now: function (){ console.log("this is now:...
    // so this is now the sayName function not the person instance
  console.log(this.name);//undefined: sayName doesn't have a name property
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event

以下是设置闭包引用的解决方法

var eventSystem={
  events:{},
  add:function(eventname,fnCallback){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push(fnCallback);
  },
  trigger:function(eventname){
    if(!this.events[eventname]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[eventname].length;i++){
      this.events[eventname][i]();
    }
  }
};

var person=function(name){
  var self=this;// set closure ref to this
  this.name=name;
  this.sayName=function(){
    console.log(self.name);//use closure ref to get this
       // logs jon
  }
};
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event

下面是对事件系统的修复,以处理 this 上下文:

var eventSystem={
  events:{},
  add:function(eventname,fnCallback,thisRef){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push({
       "callback":fnCallback,//store the event handler
       "thisRef":thisRef//store the this context
     });
  },
  trigger:function(eventname){
    if(!this.events[eventname]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[eventname].length;i++){
      this.events[eventname][i].callback.call(
        this.events[eventname][i].thisRef);
    }
  }
};

var person=function(name){
  this.name=name;
};
person.prototype.sayName=function(){
  console.log("this is now:",this);//referring to person instance
    // with the name jon
  console.log(this.name);//logs jon
  console.log(this instanceof person);//true
}


var jon=new person("jon");
eventSystem.add("sayname",jon.sayName,jon);//add extra parameter for this ref
eventSystem.trigger("sayname");//trigger the event

上面使用的模式不是事件系统(认为它是 pulisher 订阅者),因为事件通常会在对象(按钮、输入、对话框)上触发或调用,但如果是更多类似实现的事件系统,它将是很容易获得正确的 this 上下文,因为您在实例上或从实例(如 myButton 或 myDialog)触发事件。

有关事件系统的实现,请参见以下代码:

var eventSystem={
  add:function(eventname,fnCallback){
     if(!this.events[eventname]){
      this.events[eventname]=[];
     }
     this.events[eventname].push(fnCallback);
  },
  //change in trigger as it's passing the event object now
  trigger:function(event){
    if(!this.events[event.type]){
      return;
    }
    var i=0;
    for(i=0;i<this.events[event.type].length;i++){
      this.events[event.type][i](event);
    }
  },
  initES:function(){//set the instance variables needed
    this.events=this.events||{};
  }
};
function addProtos(o,protos){
  for(item in protos){
    o.prototype[item]=protos[item];
  }
}
var person=function(name){
  this.name=name;
  this.initES();//needed to initialeze eventsystem
};
// make person capable of storing event handlers
// and triggering them
addProtos(person,eventSystem);
person.prototype.askQuestion=function(){
  //asking a question will trigger an "answer" event
  this.trigger({type:"answer",target:this});
}
// handler for when jon will fire an answer event
function answerHandler(event){
  console.log("answer from:",event.target);
  console.log("name of the person:",event.target.name);
}

var jon=new person("jon");
jon.add("answer",answerHandler);//add event listener
jon.askQuestion();//triggers the answer event from within jon
jon.trigger({type:"answer",target:jon});//trigger the event externally

不知道为什么 Angular 选择使用闭包来“破坏”原型,因为示例表明还有其他选择。也许有人可以解释一下谁更熟悉 Angular。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-28
    • 2018-11-01
    • 2015-01-12
    • 1970-01-01
    • 2017-09-23
    • 2016-11-08
    • 2011-09-02
    • 1970-01-01
    相关资源
    最近更新 更多