我试图创建一个类似于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()
代替从另一个实例调用方法。怎么可能呢?
答案 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对象等。