Object.create链接继承

时间:2013-09-30 15:20:00

标签: javascript object inheritance orm ecmascript-5

我最近为了试验而切换到了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();

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

使用Object.create就像Tibos看起来一样,将传入的对象的所有成员都放到返回对象的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))。

要指定特定于实例的成员,可以使用多种模式。争论是,当使用这些模式时,您的代码看起来就像&#34;丑陋&#34;甚至比使用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(){};

你可能会发现this很有趣,它有一个辅助功能,所以如果你不想,你就不需要使用Object.create。