奇怪的`绑定'行为

时间:2016-07-14 07:02:18

标签: javascript

我试图创建一个类似于React的createClass函数的函数。它应该采用POJO并将其转换为构造函数,可以使用其他参数调用它。

以下是代码:

function createClass(obj) {
  return function(args = {}) {
    Object.keys(obj).forEach(k => this[k] = obj[k]);
    Object.keys(args).forEach(k => this[k] = args[k]);
    Object.keys(this.actions).forEach(k => this.actions[k] = this.actions[k].bind(this));
    console.log('binded to', this);
  }
}

const MyClass = createClass({
  actions: {
    foo: function() {
      console.log('actions.foo');
      this.request();
    }
  },
  foo: function() {
    console.log('foo');
    this.request();
  }
});

const req1 = function() {
  console.log('req1 called')
}

const req2 = function() {
  console.log('req2 called')
}

const c1 = new MyClass({request: req1});
const c2 = new MyClass({request: req2});

// As expected
c1.request();
c2.request();
console.log('---')

// As expected
c1.foo();
c2.foo();
console.log('---')

// Error: both call req1
c1.actions.foo();
c2.actions.foo();
console.log('---')

我不明白为什么调用c2.foo()按预期工作,但调用c2.actions.foo()代替从另一个实例调用方法。怎么可能呢?

此外,there is jsbin example

1 个答案:

答案 0 :(得分:6)

因为在这一行:

Object.keys(obj).forEach(k => this[k] = obj[k]);

...您正在将actions(引用,而不是数组)复制到新对象。稍后您更新该阵列的内容,但他们都共享同一个。由于您在bind中的功能上调用action第一时间,他们将被绑定到第一个实例;随后,他们赢了,因为bind的重点是它忽略了你用它调用的this,所以在绑定函数上调用bind并不是做任何事情(关于this)。 E.g:

var f = function() { console.log(this.name); };
var f1 = f.bind({name: "first"});
f1();                                                         // "first"
var f2 = f1.bind({name: "second"});
//       ^^------ Note we're using an already-bound function
f2();                                                         // Also "first"

因此,通过actions对函数的任何调用都将始终使用this调用这些函数来引用您创建的第一个实例。

解决方案:复制数组内容(至少是浅拷贝),例如使用Object.assign

以下代码删除了一些测试,但检查了c1.actions === c2.actions,表明它是true(他们使用相同的数组):



function createClass(obj) {
  return function(args = {}) {
    Object.keys(obj).forEach(k => this[k] = obj[k]);
    Object.keys(args).forEach(k => this[k] = args[k]);
    Object.keys(this.actions).forEach(k => this.actions[k] = this.actions[k].bind(this));
    //console.log('binded to', this);
  }
}

const MyClass = createClass({
  actions: {
    foo: function() {
      //console.log('actions.foo');
      this.request();
    }
  },
  foo: function() {
    //console.log('foo');
    this.request();
  }
});

const req1 = function() {
  console.log('req1 called')
}

const req2 = function() {
  console.log('req2 called')
}

const c1 = new MyClass({request: req1});
const c2 = new MyClass({request: req2});

console.log(c1.actions === c2.actions); // true




这里的代码首先复制了actions

function createClass(obj) {
  return function(args = {}) {
    Object.keys(obj).forEach(k => {
      if (k === "actions") {
        this[k] = Object.assign({}, obj[k]);
      } else {
        this[k] = obj[k];
      }
    });
    Object.keys(args).forEach(k => this[k] = args[k]);
    Object.keys(this.actions).forEach(k => this.actions[k] = this.actions[k].bind(this));
    //console.log('binded to', this);
  }
}

示例:



function createClass(obj) {
  return function(args = {}) {
    Object.keys(obj).forEach(k => {
      if (k === "actions") {
        this[k] = Object.assign({}, obj[k]);
      } else {
        this[k] = obj[k];
      }
    });
    Object.keys(args).forEach(k => this[k] = args[k]);
    Object.keys(this.actions).forEach(k => this.actions[k] = this.actions[k].bind(this));
    //console.log('binded to', this);
  }
}

const MyClass = createClass({
  actions: {
    foo: function() {
      console.log('actions.foo');
      this.request();
    }
  },
  foo: function() {
    console.log('foo');
    this.request();
  }
});

const req1 = function() {
  console.log('req1 called')
}

const req2 = function() {
  console.log('req2 called')
}

const c1 = new MyClass({request: req1});
const c2 = new MyClass({request: req2});

// As expected
c1.request();
c2.request();
console.log('---');

// As expected
c1.foo();
c2.foo();
console.log('---');

// Error: both call req1
c1.actions.foo();
c2.actions.foo();
console.log('---');




或者您可以概括并始终复制非功能对象:

function createClass(obj) {
  return function(args = {}) {
    Object.keys(obj).forEach(k => {
      const src = obj[k];
      if (Array.isArray(src)) {
        this[k] = src.slice();
      } else if (typeof src === "object") {
        this[k] = Object.assign({}, src);
      } else {
        this[k] = src;
      }
    });
    Object.keys(args).forEach(k => this[k] = args[k]);
    Object.keys(this.actions).forEach(k => this.actions[k] = this.actions[k].bind(this));
    //console.log('binded to', this);
  }
}

示例:



function createClass(obj) {
  return function(args = {}) {
    Object.keys(obj).forEach(k => {
      const src = obj[k];
      if (Array.isArray(src)) {
        this[k] = src.slice();
      } else if (typeof src === "object") {
        this[k] = Object.assign({}, src);
      } else {
        this[k] = src;
      }
    });
    Object.keys(args).forEach(k => this[k] = args[k]);
    Object.keys(this.actions).forEach(k => this.actions[k] = this.actions[k].bind(this));
    //console.log('binded to', this);
  }
}

const MyClass = createClass({
  actions: {
    foo: function() {
      console.log('actions.foo');
      this.request();
    }
  },
  foo: function() {
    console.log('foo');
    this.request();
  }
});

const req1 = function() {
  console.log('req1 called')
}

const req2 = function() {
  console.log('req2 called')
}

const c1 = new MyClass({request: req1});
const c2 = new MyClass({request: req2});

// As expected
c1.request();
c2.request();
console.log('---');

// As expected
c1.foo();
c2.foo();
console.log('---');

// Error: both call req1
c1.actions.foo();
c2.actions.foo();
console.log('---');




我可能没有涵盖那里的所有边缘案例,你必须决定是否满足(比如)RegExp或Date对象等。