0

我最近切换到 Object.create() 而不是 new 为了试验。如何实现多继承,比如classA-> classA's parent->classA's parent's parent等等?

例子:

var test = Object.create(null); 
test.prototype = {
    greet: function () {
        console.info('hello world');
    },

    name: 'myName'
};

var test2 = Object.create(test.prototype);
test2.prototype = {
    name: 'newName'
};

var test3 = Object.create(test2.prototype);
test3.prototype = {
    name: 'another Name'
};

虽然test2仍然可以打招呼,但test3显然不是因为我们使用的原型test2没有相关信息test,因此没有打招呼。

我读了几篇文章,强烈建议不要使用__proto__继承。这样做的正确javascripty方式是什么?

类似于以下内容,但使用 Object.create

test2.prototype = new test();
test2.constructor = test2;

test3.prototype = new test2();
test3.constructor = test3;

var a = new test3();
a.greet();
4

2 回答 2

1

使用 Object.create 对象直接从另一个继承,prototype属性在其中没有任何作用。在第一个示例中,我以与您之前所做的最接近的形式编写了内容,但是当您调用 Object.create 时,您不需要设置对象的属性。您可以在通话后设置它们而不会出现任何问题(参见第二个示例)。

var test1 = Object.create(null, {
    greet : {value : function() {
        console.info('hello world');
    }},

    name : {value : 'myName'}
});

var test2 = Object.create(test1, {
    name : {value : 'alteredProperty'}});

var test3 = Object.create(test2);

test3.greet(); // hello world
console.log(test3.name); // alteredProperty

更简单的示例(没有属性描述符):

var test1 = Object.create(null);
test1.greet = function() {
    console.info('hello world');
};
test1.name = 'myName';

var test2 = Object.create(test1);
test2.name = 'alteredProperty';

var test3 = Object.create(test2);

test3.greet();
console.log(test3.name);

正如 HMR 所指出的,每次test1创建对象时,都会创建一个新greet函数,这是不可取的。下一个示例通过将方法卸载到类似原型的对象来解决这个问题。

// proto object
var Test1 = {
  greet : function() { console.info('hello world ' + this.name); },
  name : 'Test1'
};

// instance of Test1
var test1 = Object.create(Test1);

// proto object inheriting from Test1
var Test2 = Object.create(Test1)
Test2.name = 'Test2';

// instance of Test2
var test2 = Object.create(Test2);

// proto object inheriting from Test2
var Test3 = Object.create(Test2);
Test3.size = 'big';


// instance of Test3
var test3 = Object.create(Test3);

test3.greet(); // hello world Test2
console.info(test3.name); // Test2
console.info(test3.size); // big
test3.name = 'Mike';
test3.greet(); // hello world Mike

如您所见,该示例与上面的示例非常相似,但不同之处在于您如何处理某些对象。一些对象(带有大写字母的对象)的行为类似于带有原型的构造函数:它们通常不直接使用,它们保存构建对象的方法和默认值。这纯粹是传统的,因为“类”的实例和继承的“类”具有完全相同的语法。由你来强制 proto 对象不被滥用。

奖金:

function isInstanceOf(child, parent) {
  return Object.prototype.isPrototypeOf.call(parent, child);
}

console.info(isInstanceOf(test3, Test3)); // true
console.info(isInstanceOf(test3, Test1)); // true
console.info(isInstanceOf(test2, Test3)); // false
于 2013-09-30T15:28:05.880 回答
0

使用 Tibos 的方式使用 Object.create 看起来它会将传入对象的所有成员放入prototype返回对象的所有成员中。

// in firefox firebug running
// an empty page
var Definition = {
  name : 'Test1'
};
//doesn't matter where it's defined
Definition.greet=function() { 
  console.log(this);//<-what is this in Chrome?
};
Definition.arr=[];
// instance of Test1
var test1 = Object.create(Definition);
var test2 = Object.create(Definition);
console.log(test1.greet===test2.greet);//true
delete test2.greet
delete test2.greet
delete test2.greet
delete test2.greet//can't delete it
test2.greet();
console.log(test1.greet===test2.greet);//true
console.log(test1.arr===test2.arr);//true
test1.arr.push(1);
console.log(test2.arr);//=[1]
var things=[];
for(thing in test1){
  things.push(thing);
}
console.log("all things in test1:",things);
things=[];
for(thing in test1){
  if(test1.hasOwnProperty(thing)){
    things.push(thing);
  }
}
console.log("instance things in test1:",things);//nothing, no instance variables

[更新]

正如我们从上面的代码中看到的,Object.create 返回一个对象,该对象在其原型上具有第一个参数的所有成员,而第二个参数则为其实例成员。(答案是 mccainz 在评论中有很长时间) 额外的好处(忽略 IE8 和多年未更新的浏览器)是您可以在实例成员上指定可枚举、可写和可配置的,并且您可以创建 getter 和 setter像赋值一样工作(instance.someprop=22 实际上可以是 instance.someprop(22))。

要指定特定于实例的成员,您可以使用多种模式。争论是,当使用这些模式时,你的代码看起来和使用 new 关键字一样“丑陋”甚至更糟糕,但这只是个人喜好,不会剥夺创建 getter 和 setter 或拥有额外控制(可枚举、可写和可配置的)。

一种模式是使用 init 函数:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB).init("Bob");

一个更复杂的利用额外控制的方法是:

var Person={
  talk:function(){console.log("I'm "+this.name);}
  //,other prototype stuff related to Person
};
var userCreator={
   processInstanceMembers:function(o,initObj){
     this.createName(o,initObj.name);
     Object.defineProperty(o,"_name",{writable:true});
     o.name=initObj.name;
   },
   get:function(initObj,inheritFrom){
     var ret=Object.create(inheritFrom||Person);
     this.processInstanceMembers(ret,initObj);
     return ret;
   },
   createName:function(o){//minimalise closure scope
     Object.defineProperty(o,"name",{
       get:function(){
         return this._name;
       },
       set:function(val){
         if(val.replace(/\s*/gm,"")===""){
           throw new Error("Name can't be empty, or only whitespaces");
         }
         this._name=val;
       },
       enumerable : true
     });
   }
};

//when creating an instance you can choose what to inherit from
//leave it out to inherit from Person
var u=userCreator.get({name:"Ben"});
u.talk();
u.name="Benji";
u.talk();
u.name="  ";//error, name can't be empty

不需要创建 Parent 的新实例来设置 Child 的继承,您可以将 Object.create 用于此或辅助函数:

var Child =function(){
  //get Parent's INSTANCE members defined in the 
  //parent function body with this.parentInstance=...
  Parent.apply(this,arguments);
}
Child.prototype=Object.create(Parent.prototype);
Child.prototype.constructor=Child;
Child.prototype.otherFn=function(){};

您可能会觉得很有趣,它有一个辅助函数,因此如果您不想使用,则不需要使用 Object.create。

于 2013-10-01T04:19:21.660 回答