5

什么是原型

首先,原型是一个对象。而且所有的对象都有一个原型(有一种例外:当把对象的原型设为null时),并且任何对象都可以成为一个原型。

当我们定义一个对象时 var a = new Object(); 默认的原型在原型链的顶端。

原型有什么好处

原型最大的好处体现在它的 共享 的特性。所有原型对象的实例对象共享它所包含的属性和方法。所以我们常用利用原型来创建对象,也就是 原型模式

原型模式

原型模式 是一种用来创建多个实例对象的方法,我们常常把它和 构造函数结合起来用来创建特定类型的对象。

我们创建的每一个函数都有一个 prototype 属性,这个属性是一个指针,指向一个对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。这个对象就是实际上通过调用构造函数而创建的 实例对象 的原型对象。看代码:

// 构造函数
function Person(){};

Person.prototype.name = "darko";
Person.prototype.age = 21;
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
person1.sayName();  // "darko"

var person2 = new Person(); 
person2.sayName();  // "darko"

我们将所有的属性和sayName()方法添加到了构造函数Personprototype属性中,构造函数成了空函数。但是即便如此,我们也可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法。

构造函数,实例对象和原型对象的关系

实例对象就是通过构造函数创造的,默认拥有一个constructor属性指向其构造函数。

原型对象就是构造函数的属性prototype指向的那个对象,同时也是基于构造函数生成的实例对象的原型对象。在默认情况下,所有的原型对象都会自动获得一个constructor属性,这个属性是一个指针,指向其构造函数。

实例对象可以访问原型对象上的属性和方法。在实例对象的内部有一个属性(内部属性)[[Prototype]]指向其原型对象。有一种非标准方法__proto__访问[[Prototype]]

在上面的例子中person1person2就是实例对象,构造函数为Person,原型对象为Person.prototype

来,看个栗子(还是上面那段代码):

alert(person1.constructor === Person);  // true

alert(Person.prototype.constructor === Person);  // true

alerta(person1.__proto__ === Person.prototype); // true

来看个图你就什么都懂了:
图片描述

理解prototype,getPrototypeOf和 proto 之间的不同

prototype是函数的一个默认属性,只有函数对象才有

Object.getPrototypeOf()方法用来返回实例对象内部属性[[prototype]]的值。这是ES5中定义的用来获取原型对象的标准方法。

__proto__属性是获取原型对象的非标准方法(IE不支持)
看个栗子(还是上面那段代码):

alert(Object.getPrototypeOf(person1) === Person.prototype); // true
alert(Object.getPrototypeOf(person1).name); // "darko"

alert(person1.__proto__ === Person.prototype);    // true
alert(person1.__proto__.name);  // "darko"

原型模式下的对象

每次查找对象的每个属性,都是一次搜索。搜索从实例对象本身开始,如果在实例对象中找到,停止查找,返回值。如果没有则继续搜索实例对象指向的原型对象。

若实例对象中属性和其指向的原型对象的属性重名,实例对象中的属性屏蔽原型对象中的那个属性。
举个栗子:

function Person(){};

Person.prototype.name = "darko";
Person.prototype.age = 21;
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
var person2 = new Person();

person1.name = "leon";
person1.sayName();   // "leon",来自实例
person2.sayName()   // "darko",来自原型

delete person1.name;
person1.sayName();  // "darko",来自原型

可以利用hasOwnProperty()方法判断一个属性是位于实例中,还是原型中。只有在属性来自实例中时,才会返回true。通常和in操作符配合使用。

// 接上
alert("name" in person1);   // true
alert(person1.hasOwnProperty("name"));  // false

原生对象的原型

所有的原生引用类型都在其原构造函数的原型上定义了方法,例如,Array.prototype.sort()方法,正是由于原型的共享特性,我们定义的数组才可以使用sort()方法等一系列的方法。
举个栗子:

var num = [1, 5, 3, 7, 9];
num.sort(); // 1,3,5,7,9
alert(num.constructor === Array);   // true
alert(num.__proto__ === Array.prototype);    // true
alert(num.__proto__.__proto__ === Object.prototype);    //true

数组对象num本身就是构造器Array的实例对象,而Arrayprototype属性指向的对象上定义了sort()方法,所以新定义了num对象经过搜索找到了sort()方法,并调用了方法。

原型的动态性

由于在原型中查找值的过程是一次搜索,所以对原型对象的任何修改都能立即从实例上反应出来。
举个栗子:

function Person(){};
var firend = new Person();
// 修改原型
Person.prototype.sayHi = function(){
    alert("Hi");
}

firend.sayHi(); // "Hi"

但是若将原型重写,来看看有什么不同:

function Person(){};
Person.prototype.name = "darko";
var firend = new Person();
// 重写了原型对象
Person.prototype = {
    constructor: Person,  // 注意:重写原型对象,所以此时的constructor属性变成了新对象的构造函数,默认为Object构造函数,应该将其设置回适当的值
    sayHi: function(){
        alert("Hi");
    }
}

alert(friend.name); // "darko"
firend.sayHi(); // error

这说明,重写原型对象切断了现有原型和任何之前已经存在的实例对象之间的联系,它们引用的仍是最初的原型。

如果你觉得我写的还可以,点一下推荐吧。


Darko
713 声望27 粉丝