原型链详解

514 阅读6分钟

创建普通对象

我们知道,创建对象可以这样

var person = new Object();
person.name = “Nicholas”;
person.age = 29;
person.job = “Software Engineer”;
person.sayName = function(){ alert(this.name);};

这个对象有name, age, job属性,还有一个sayName()的方法。这是大家常用的创建对象的方法,可以不断添加新属性,但是如果要创建多个类似的对象的话,就需要不断地重复这段代码,耗费很多内存。

工厂模式

这时候可以用工厂模式来解决这个问题

function createPerson(name, age, job){ 
    var o = new Object(); //1.创建一个空对象并赋值给变量o
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
    alert(this.name);
    	};
	return o; //2.返回变量o
}
var person1 = createPerson(“Nicholas”, 29, “Software Engineer”); 
var person2 = createPerson(“Greg”, 27, “Doctor”);

工厂模式还可以更加简练,比如可以创建一个语法糖,省略掉上一段代码的1和2 这时候便涉及到了Constructor模式了

Constructor模式

function Person(name, age, job){ 
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){ 
        alert(this.name);
    };
}
var person1 = new Person(“Nicholas”, 29, “Software Engineer”); 
var person2 = new Person(“Greg”, 27, “Doctor”);

注意constructor总是以大写开头,这是它的书写规范。 相比工厂模式,当我们用new这个关键字的时候,它帮我们写了一下步骤:

  • 创建一个新对象
  • 将这个对象的__proto__属性绑定到原型上面
  • 将这个新对象赋给当前上下文(也就是this)
  • 自动返回this 使用contructor模式还有一个好处,可以帮你判断类型
alert(person1 instanceof Person); //true

同样的,contructor模式也存在一个问题,在上一段代码中,我们添加了一个sayName()的方法

this.sayName = function(){
    alert(this.name);
    	};

如果需要创建100个不同的person, 也需要创建100个sayName()函数.

alert(person1.sayName == person2.sayName); //false

但是这个函数代码都是一样的,实际上只需要创建一次即可。

function createPerson(name, age, job){ 
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
    	};
var person1 = createPerson('Nicholas', 29, 'Software Engineer'); 
var person2 = createPerson('Greg', 27, 'Doctor');

如果要创建很多不同的对象,就必须创建很多不同的全局方法,很容易产生冲突,这时候另一种方式就大显身手了(原型)

prototype

function Person(){ }
Person.prototype.name = “Nicholas”; 
Person.prototype.age = 29; 
Person.prototype.job = “Software Engineer”; 
Person.prototype.sayName = function(){
    alert(this.name); 
};
var person1 = new Person();
person1.sayName(); //”Nicholas”
var person2 = new Person(); 
person2.sayName(); //”Nicholas”
alert(person1.sayName == person2.sayName); //true

每个函数创建的时候,它的prototype属性也会被创建,指向一个普通的对象,这个对象会有一个constructor属性,指回这个函数,上面那段代码中 Person.prototype.constrctor会重新指向Person。而每次创建一个新的实例,实例的__proto__属性会指向这个ProtoType

当调用person1.sayName()的时候,它本身是没有这个方法的,所以会自动到person1.__proto__去找,如果还没有,就再上一层,去person1.__proto__.__proto__找,直到找到或者返回null为止。 读取person1的其他属性也是一样的,如果对象本身没有这个属性,会到它的___proto__去找,也就是Person.prototype,如果有的话,就返回这个值,没有的话,会再到Person.prototype.__proto__找,由于Person.prototype是个普通的对象实例,所以Person.prototype.__proto__指向的是Object.prototype

判断一个属性是否属于自身的对象还是对象的原型,可以用hasOwnProperty()判断

原型同样可以用下列代码的方式创建,但是会覆盖掉原来默认的Prototype, 也就没有默认的constructor属性。

function Person(){ }
Person.prototype = {
    name : “Nicholas”,
    age : 29,
    job : “Software Engineer”, 
    sayName : function () {
    alert(this.name); }
};

下面这段代码也会运行错误,原因是当当实例被创建的时候,它的__proto__已经指向了默认的Person.prototype,这个时候你将Person.prototype指向另一个地址,实例的__proto__是没办法访问到的

function Person(){ }
var person = new Person();
Person.prototype = {
    constructor: Person, 
    name : “Nicholas”, 
    age : 29,
    job : “Software Engineer”, 
    sayName : function () {
    alert(this.name); }
};
person.sayName(); //error

__proto__和prototype有什么区别呢,__proto__是在新的实例对象生成的时候,自动绑定到构造函数的prototype上面的,所有的对象都有__proto__属性。prototype则是在创建函数的时候,它的prototype属性也会被创建,指向一个普通的对象,这个对象会有一个constructor属性,指回这个函数。prototype只有函数才有的属性。

常见的创建对象的方式为下列代码,这样避免了共享对象的冲突

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = [“Shelby”, “Court”];
}
Person.prototype = {
    constructor: Person, 
    sayName : function () {
        alert(this.name); 
    }
};
var person1 = new Person(“Nicholas”, 29, “Software Engineer”); 
var person2 = new Person(“Greg”, 27, “Doctor”);
person1.friends.push(“Van”);
alert(person1.friends); //”Shelby,Court,Van” 
alert(person2.friends); //”Shelby,Court” 
alert(person1.friends === person2.friends); //false 
alert(person1.sayName === person2.sayName); //true

原型继承

  1. 构造函数继承

缺点:每次创建一个子实例,父方法会重复构建多次,并且不会继承父类的prototype

function SuperType(){ 
    this.property = true;
    this.run = function() {}
}
function SubType(){ 
    SuperType.call(this)
}
var instance = new SubType(); 
  1. 类式继承

缺点:一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类

function SuperType(){ 
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){ 
    return this.property;
};
function SubType(){ 
    this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){ 
    return this.subproperty;
};
var instance = new SubType(); 
alert(instance.getSuperValue()); //true

通过SubType.prototype = new SuperType();完成了原型的继承。为什么要用SuperType的实例来绑定SubTypeprototype呢,因为这个实例的__proto__属性会指向SuperType.prototype, 通过这个方式来继承SuperType的属性和方法

如果不手动绑定构造函数的prototype, 它也会自动生成默认的prototype, 生成的protoype是一个普通对象,这个对象的constructor属性指回构造函数,__proto__属性指向Object的prototype, 因为这个对象也是Object的实例,而实例的__proto__指向构造函数的prototype,这个构造函数就是Object. 所以即使我们不定义toString()valueOf()等函数,也可以直接调用,因为这些函数已经在Object里面定义好了

如果要添加SubType的方法可以

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){ 
    return this.property;
};
function SubType(){ 
    this.subproperty = false;
}
//inherit from SuperType 
SubType.prototype = new SuperType();
//new method
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
//override existing method
SubType.prototype.getSuperValue = function (){
    return false;
};
// 下面的方式是错的,SubType.prototype被重新赋值,不能够再访问父函数的方法了
<!--SubType.prototype = {-->
<!--    getSubValue: function (){-->
<!--        return this.subproperty;-->
<!--    }-->
<!--}-->
var instance = new SubType(); 
alert(instance.getSuperValue()); //false

原型链存在的问题


function SuperType(){
    this.colors = [“red”, “blue”, “green”];
}
function SubType(){ }
//inherit from SuperType 
SubType.prototype = new SuperType();
var instance1 = new SubType(); instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”
var instance2 = new SubType(); 
alert(instance2.colors); //”red,blue,green,black”

解决办法


function SuperType(){
    this.colors = [“red”, “blue”, “green”];
}
function SubType(){ //inherit from SuperType 
    SuperType.call(this);
}
var instance1 = new SubType(); 
instance1.colors.push(“black”); 
alert(instance1.colors); //”red,blue,green,black”
var instance2 = new SubType();
alert(instance2.colors); //”red,blue,green”

但是上述方法也存在子对象无法引用父对象prototype中的方法等问题

  1. 组合继承
function SuperType(name){
    this.name = name;
    this.colors = [“red”, “blue”, “green”];
}
SuperType.prototype.sayName = function(){    
    alert(this.name);
};
function SubType(name, age){
 //inherit properties 
    SuperType.call(this, name);
    this.age = age; 
}
//inherit methods
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType(“Nicholas”, 29); instance1.colors.push(“black”); 
alert(instance1.colors); //”red,blue,green,black”
instance1.sayName(); //”Nicholas”;
instance1.sayAge(); //29
var instance2 = new SubType(“Greg”, 27); alert(instance2.colors); //”red,blue,green”
instance2.sayName(); //”Greg”; 
instance2.sayAge(); //27

上述方法会调用SuperType的constructor方法两次

  1. 优化
function object(o){ 
    function F(){}
    F.prototype = o;
    return new F(); 
}

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); prototype.constructor = subType; 
    subType.prototype = prototype;
}

function SuperType(name){
    this.name = name;
    this.colors = [“red”, “blue”, “green”];
}
SuperType.prototype.sayName = function(){         
    alert(this.name);
};
function SubType(name, age){ 
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){ 
    alert(this.age);
};

或者

// Shape - 父类(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?',
  rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
  rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

使用Object.create()是将对象继承到__proto__属性上