1.prototype对象
1.1构造函数的缺点
JavaScript通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。
function Cat (name, color) { this.name = name; this.color = color; } var cat1 = new Cat('大毛', '白色'); cat1.name // '大毛' cat1.color // '白色'
上面代码的Cat函数是一个构造函数,函数内部定义了name属性和color属性,所有实例对象都会生成这两个属性。但是,这样做是对系统资源的浪费,因为同一个构造函数的对象实例之间,无法共享属性。
function Cat(name, color) { this.name = name; this.color = color; this.meow = function () { console.log('mew, mew, mew...'); }; } var cat1 = new Cat('大毛', '白色'); var cat2 = new Cat('二毛', '黑色'); cat1.meow === cat2.meow // false
上面代码中,cat1和cat2是同一个构造函数的实例。但是,它们的meow方法是不一样的,就是说每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。
1.2 prototype属性的作用
在JavaScript语言中,每一个对象都有一个对应的原型对象,被称为prototype对象。定义在原型对象上的所有属性和方法,都能被派生对象继承。这就是JavaScript继承机制的基本设计。
除了这种方法,JavaScript还提供了另一种定义实例对象的方法。我们知道,构造函数是一个函数,同时也是一个对象,也有自己的属性和方法,其中有一个prototype属性指向另一个对象,一般称为prototype对象。该对象非常特别,只要定义在它上面的属性和方法,能被所有实例对象共享。也就是说,构造函数生成实例对象时,自动为实例对象分配了一个prototype属性。
function Animal (name) { this.name = name; } Animal.prototype.color = "white"; var cat1 = new Animal('大毛'); var cat2 = new Animal('二毛'); cat1.color // 'white' cat2.color // 'white'
上面代码对构造函数Animal的prototype对象,添加了一个color属性。结果,实例对象cat1和cat2都带有该属性。
更特别的是,只要修改prototype对象,变动就立刻会体现在实例对象。
Animal.prototype.color = "yellow"; cat1.color // 'yellow' cat2.color // 'yellow'
上面代码将prototype对象的color属性的值改为yellow,两个实例对象的color属性的值立刻就跟着变了。这是因为实例对象其实没有color属性,都是读取prototype对象的color属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype对象去寻找该属性或方法。这就是prototype对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去prototype对象寻找这个属性或方法。
cat1.color = 'black'; cat2.color // 'yellow' Animal.prototype.color // "yellow";
上面代码将实例对象cat1的color属性改为black,就使得它不用再去prototype对象读取color属性,后者的值依然为yellow。
总而言之,prototype对象的作用,就是定义所有实例对象共享的属性和方法,所以它也被称为实例对象的原型,而实例对象可以视作从prototype对象衍生出来的。
Animal.prototype.walk = function () { console.log(this.name + ' is walking.'); };
上面代码在Animal.protype对象上面定义了一个walk方法,这个方法将可以在所有Animal实例对象上面调用。
1.3原型链
由于JavaScript的所有对象都有构造函数,而所有构造函数都有prototype属性(其实是所有函数都有prototype属性),所以所有对象都有自己的prototype原型对象。
因此,一个对象的属性和方法,有可能是定义它自身上面,也有可能定义在它的原型对象上面(就像上面代码中的walk方法)。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。因为追根溯源,最源头的对象都是从Object构造函数生成(使用new Object()命令),所以如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype。那么,Object.prototype有没有原型呢?回答可以是有,也可以是没有,因为Object.prototype的原型是没有任何属性和方法的null。
Object.getPrototypeOf(Object.prototype) // null
上面代码表示Object.prototype对象的原型是null,由于null没有任何属性,所以原型链到此为止。
“原型链”的作用在于,当读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。以此类推,如果直到最顶层的Object.prototype还是找不到,则返回undefined。