1

prototype是JavaScript比较难理解的一部分,绕来绕去的。不理出头绪来,理解原型的概念是件头疼的事情。
为了方便后面的说明,先从对象说起。

对象

广义对象

JavaScript里任何事物都是对象,不管什么,都是从Objec衍生出来的。function,array,string,{},都是对象。只是大家的功能各有不同。Object就像女娲,JavaScript世界的任何事物,都是它“创造”的。这里的对象是广义的泛指的对象,是一切事物的统称。

狭义对象

狭义对象是指一般的对象类型,通过var p = new Object()或者通过var p = {}的方式创建出来的东西。用来表示某一种事物,包含事物的属性和方法。后面我们说的对象就是狭义对象,专门指JavaScript里产生出来的实例对象。其他的函数、数组等我们只从它本身的功能角度来看待,不当作对象,看成是执行功能、储存数据的一种工具。

对象的产生

对象的创建

我们创建一个对象最常用的方法时var p = {},这只是JavaScript创建对象的快捷方式,其根本是通过var p = new Object()的方式产生。这种方式是通过构造函数来创建对象的。

构造函数

构造函数创建对象

如何通过构造函数来创建对象呢?网上有很多资料,这里简单叙述一下。有如下构造函数:

function Person(name) {
    this.name = name; // 属性
    this.run = function() { // 方法
        console.log("I'm running");
    }
}

上面是一个简单的构造函数。构造函数它首先是一个函数,跟我们平常写的函数是同一类。不同的是它里面有一个this指针,指向通过它产生的实例对象。另外,首字母大写是我们用以区分构造函数和普通函数的语法习惯,没有强制规定一定要大写。但为了方便理解最好用首字母大写的命名习惯来命名。

var p = new Person();

通过new的方式创建实例。上面变量p就是一个实例对象,它包含了一个name属性和一个run方法。用new 构造函数创建对象的过程有两步:

  1. 在内存中开辟一块内存地址,用以存放新的实例对象
  2. 实例对象调用构造函数,那么里面的this指针指向这个实例,从而为对象设置了name属性和run方法。

name属性和run方法将是实例对象自身的东西,属于对象自有。相当于你看到的结果是这样一个对象:

p = {
    name: "Pelemy",
    run: functioin() {
        cosole.log("I'm running");
    }
}

实例对象重复创建的问题

按照上面的步骤创建对象,每执行一次 new Person() 就会有一个新的对象产生。

var p1 = new Person(); // 新对象,新的内存空间
var p2 = new Person(); // 新对象,新的内存空间
console.log(p1 === p2); // false
console.log(p1.run === p2.run) // false

每个对象都有自己name属性和run方法。这里有个问题,每个实例对象run方法的实现都是一样的,这是一个雷同的方法。雷同的方法每个人都有一个,这很浪费资源。如果有一个地方共享,同一种类的对象都去共享的地方取就好了,不需要每个都留一个备份在自己身上。为了处理这种情况,prototype产生了。

构造函数的prototype属性

构造函数中this指针设置的属性和方法将是新实例对象自身拥有的属性和方法,我们叫本地属性和方法。为了使各个产生的实例对象不重复设置相同的属性或方法,JavaScript把这部分放到了构造函数的一个地方,这个地方就是构造函数的prototype属性指向的对象(注意这里的对象也是前面说的狭义对象)。prototype本意是原型、蓝图,在这里我认为把它叫做“引用属性”来理解更贴切。还是以前面的例子。

function Person(name) {
    this.name = name; // 定义本地属性
}
Person.prototype.run = function() { // 定义引用方法
    console.log("I'm running");
}

这里可能会突然让人头疼。Person.prototype.run突然多了这么长一串,连续三个点。我们一个个看。首先把prototype当作一个属性,就像我们常写一个对象的属性那样,比如 car.color, car.speed。这里也一样,prototype是构造函数的一个属性,它的值是一个对象

Person.prototype = {
 // properties and methods
}

这个对象就是将来我们用来存放共享属性和方法的。这些属性和方法可以被实例对象引用。注意是引用,也就是自己没有,指向那里去调用就行了。然后在这个对象里定义run方法

Person.prototype = {
   // properties and methods
   run: function() {
     console.log("I'm running");
   }
}

当然,我们这里只是为了多定义一个run方法,而不是定义整个prototype的对象(这样会把这个对象的其他方法擦掉,只剩下run方法)。所以定义整个引用方法的方式就是
object.run = ... 即
Person.prototype.run = ...
这样新创建的实例再也不用自己定义这个方法,只要从共享对象上引用就好了。举例:

function Person(name) {
    this.name = name; // 属性
    this.myfunction = function() {
        console.log("just work for me");
    }
}
Person.prototype.run = function (distance) {
    console.log("I've run " + distance + " meters");
}
var p1 = new Person("Pelemy");
var p2 = new Person("Summmy");
console.log(p1.name); // Pelemy
p1.run(100); // I've run 100 meters
console.log(p2.name); // Summy
p2.run(200); // I've run 200 meters

p1,p2的本身没有run方法(构造函数里没定义),但都能执行到,他们是引用prototype里的run方法。执行方法时,先在本地查找是否有这个方法,没有则向上寻找prototype对象是否有,有则执行这个方法。这里可以通过hasOwnProperty方法来判断本地是否有这个方法,沿用上面的例子

console.log(p1.hasOwnProperty("run")); // false
console.log(p1.hasOwnProperty("myfunction"); // true;
console.log(p1.hasOwnProperty("name"); // true;

把构造函数的定义视为本地属性定义,把prototype属性对象视为引用属性定义,这样分开来理解,就会轻松多了。

对象的引用对象与构造函数的prototype属性的关系

实例对象创建后,构造函数中的属性和方法成为本地属性和方法,prototype中的属性和方法成为引用属性和方法。我想知道一个实例对象产生后,怎么知道这个对象是从哪里引用的?对象不像构造函数,生下来就有prototype属性,连接着引用对象。但对象有一个只读的属性__proto__,指向的就是它的引用对象(这叫隐式原型对象)。这个对象和构造函数的prototype指向的是同一个对象。因为引用对象是放在那里供别人引用的,不会复制或重新产生,所以它是被直接定义到实例对象的__proto__的。

function Person(name) {
    this.name = name; // 属性
}

var p = new Person();
console.log(p.__proto__ === Person.prototype); // true

prototype对象怎么产生

这个prototype对象是怎么来的呢?构造函数在JavaScript中产生时,随即就有一个它的引用对象(prototype属性指向的对象)产生了。它是伴随着构造函数产生的。

延伸

  • prototype是构造函数生来就有的属性,对象是没有的。
  • 构造函数的prototype属性的值是一个对象。即Person.prototype是一个对象
  • prototype的属性和方法是共用属性和方法,是所有实例对象都有,不同的属性和方法在构造函数实现,按这样创建对象就是类的继承的方式了。产生的实例对象相当于都从父类继承过来,这就是为什么把引用的这个对象叫原型(不叫引用或共享)的原因了。

总结

  1. 构造函数中定义的属性和方法是本地属性和方法,prototype指向的对象定义的属性和方法是引用属性和方法。
  2. prototype定义的属性和方法能被实例共同引用,是共同的部分,相当于每个对象都有和引用对象同样的属性和方法,而自身的方法就通过构造函数来呈现。

李一枫
98 声望0 粉丝

喜欢学习,喜欢清爽整洁,喜欢映入眼帘的美。