我可以将构造函数的原型方法绑定到构造的实例,同时保持关注点分离吗?

时间:2017-12-20 01:51:06

标签: javascript

假设我有一个对象构造函数和一个原型方法,比如:

function Human(name) {
  this.name = name;
}

Human.prototype.sayName = function(){
  console.log('my name' + this.name);
};

在我的代码中,我已经定义了一个人类实例:

let jeff = new Human('jeff');

最后我希望将jeff.sayName作为回调传递给其他一些函数,例如(对于一个特别简单的例子)

function callFunction(callback) {
  callback();
}

callFunction(jeff.sayName);

当我在上面调用callFunction(jeff.sayName)时,上下文需要绑定到jeff本身,就像 callFunction(jeff.sayName.bind(jeff))。但这很笨重,而且每次我使用这种方法作为回调时,我都不必担心这种逻辑。

另一种方法是用{/ p>之类的东西替换Human.prototype.sayName

Human.prototype.createSayName = function(context){
  return function() {
    console.log(context.name);
  };
};

然后用

定义函数
jeff.sayName = jeff.createSayName(jeff)

但这有点尴尬,我希望jeffHuman.prototype继承的方法保持不可知。

理想情况下,我想在Human.prototype上处理这个逻辑,比如

Human.prototype.sayName.bind(WhateverObjectHoldsThisMethod)

但我不知道javascript有办法做到这一点。

TL; DR我想要一种将对象的原型方法绑定到任何对象继承它的方法,而不必每次将该方法作为参数传递或每次定义新对象时都这样做。对不起,如果这没什么意义。谢谢!

3 个答案:

答案 0 :(得分:3)

由于词法环境,范围解析,原型继承和环境记录在JavaScript中的工作方式,如果不修改调用回调函数的函数,就无法提出要求。

但是,你可以 - 而不是传递Human#sayName引用作为回调 - 使用箭头函数,而该函数又调用你想要调用的Human#sayName引用。

它并不完美,但它简单,干净,易读。



function Human(name) {
  this.name = name;
}

Human.prototype.sayName = function(){
  console.log('my name' + this.name);
};

let jeff = new Human('jeff');

function callFunction(callback) {
  callback();
}

callFunction(_ => jeff.sayName());




为了更好地理解我之前引用的那些花哨的单词,以及它们如何在JavaScript中工作,我建议阅读ECMAScript 2017 Language Specification的第8.1节。第8.1.1.3小节具有您正在寻找的具体信息,但到目前为止的其余部分对于理解该小节是必要的。

基本上,当您将Human#sayName传递给callFunction时,您已将引用传递给原始sayName函数,因此您可能会这样做: (原谅双关语)

function callFunction(callback) {
  callback();
}

callFunction(function(){
  console.log('my name' + this.name);
});

函数的内容在执行之前不会被评估,这意味着在执行时,this的值已经改变。要添加到崩溃中,原始函数不知道您请求它通过哪个实例。它实际上从未存在于jeff对象上。它存在于函数对象的原型中,当您执行对象属性查找时,JavaScript引擎会搜索原型链以查找该函数。

你很可能得到你所要求的行为,但不会受到你所规定的限制。例如,如果函数不必存在于原型链上,而是可以存在于实例上(请记住,这会为每个实例创建一个新的函数对象,因此会增加成本),您可以定义函数在构造函数中,然后使用不会被覆盖的标识符存储对正确this的引用:



function Human(name) {
  const _this = this;
  this.name = name;
  this.sayName = function(){
    console.log('my name' + _this.name);
  };
}

let jeff = new Human('jeff');

function callFunction(callback) {
  const _this = { name: 'hello' }; // does not affect output
  callback();
  callback.call(_this); // does not affect output
}

callFunction(jeff.sayName);




这将是一个更安全的选项,因为您知道_this将始终引用您希望它在构造函数的上下文中引用的对象,该函数对象中定义的所有函数对象将继承其父作用域的标识符,这些标识符不会受到调用上下文的影响。

或者,你可以更进一步,而不是完全依赖this的价值:



function Human(name) {
  const sayName = function(){
    console.log('my name' + name);
  };
  
  Object.assign(this, { name, sayName });
}

let jeff = new Human('jeff');

function callFunction(callback) {
  const name = 'hello'; // does not affect output
  callback();
  callback.call({ name: 'world' }); // does not affect output
}

callFunction(jeff.sayName);




这有以下优点:

  • 更容易阅读,
  • 少代码,
  • 允许您明确说明通过对象公开的属性和方法,以及
  • 永远不必担心this的价值。

答案 1 :(得分:1)

扩展TinyGiant的答案,你可以使用箭头功能,但是如果你把它与一个getter结合起来,你可以将它定义为原型的方法,而不是将你的回调定义为箭头函数,这可能更多灵活取决于您的需求。像这样:

function Human(name) {
  this.name = name;
}

Object.defineProperty(Human.prototype, "sayName", {
  get: function() {
    return () => {
      console.log("my name is", this.name);
    }
  }
});


function callfunction(callback) {
  callback();
}

let jeff = new Human('jeff');

callfunction(jeff.sayName);

// just to show it works even as a regular function
jeff.sayName();

// in fact it overrides every context you bind
jeff.sayName.bind(window)()

答案 2 :(得分:-1)

我想你可能想要实现这个



function Human(name) {
  this.name = name;
}

Human.prototype.sayName = function() {
  console.log('my name' + this.name);
};

let jeff = new Human('jeff');

function callFunction(callback) {
  callback();
}

callFunction(function() {
  jeff.sayName()
});




另一个猜测,一个绑定到实例的原型,它有效,但有反模式



function Human(name) {
  this.name = name;
}

const jeff = new Human('jeff');

Human.prototype.sayName = function() {
  console.log('my name' + jeff.name);
};

function callFunction(callback) {
  callback();
}
callFunction(jeff.sayName);




另一个猜测,选举



function Human(name) {
  this.name = name;
}


Human.prototype.sayName = function() {
  console.log('my name' + this.name);
};


Human.prototype.reflectName = function(item) {
  this.sayName = () => item.sayName()
};

const jeff = new Human('jeff');

const tod = new Human('tod');
tod.reflectName(jeff)

tod.sayName()