JavaScript中的构造器(constructor)和原型(prototype)

构造器

JavaScript中的构造器其实就是方法/函数,我们新建一个方法,然后使用new去创建对象,可以说这个方法就是被创建的对象的原型.如果一个方法被作为对象的原型,约定俗成它应该大写字母开头.

1
2
3
4
5
6
7
8
9
10
function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}
var person1 = new Person("Nick");
var person2 = new Person("Craig");
console.log(person1 instanceof  Person);    // true
console.log(person2 instanceof  Person);    // true

如果方法不接受参数,在使用new创建的时候可以省去括号. 在通过函数创建对象的时候,函数里的this自动指向创建出来的对象.

1
2
function Person(){...}
var person1 = new Person;

每个新建的对象都有一个constructor property,它指向创建这个对象的方法.如果这个对象是使用Obejct literal或者new Object方法创建的,那么该对象的constructor指向object. 这里需要指出的是在使用person1.constructor的时候由于constructor不是person1的ownproperty,所以系统会自动去创建这个instance的function里的prototype里去找,即Person里的一个名字叫做prototype的arribute,它可能的定义是:

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
    this.prototype = {
      constructor: Person,
      toString: function() {console.log('xxx');}
      ...
    }
}
1
2
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true

在使用构造器的时候前面要加上new关键字,表示被赋值的是一个对象,否则只是简单的方法调用.

1
2
3
4
var person1 = Person("Nicholas");         // note: missing "new"
console.log(person1 instanceof Person);   // false
console.log(typeof person1);              // "undefined"
console.log(name);                        // "Nicholas"

这里需要专门所以说函数和对象的区别,函数的定义是function(){},对象的定义是var obj = {}或者var obj = new Object().对象里的内容是键值对{key: value},函数里的内容和Java的构造函数里的内容类似function(name){this.name = name}. 函数可以用来创建对象.

原型

在创建对象时,我们有时会有这样一个疑虑:既然一些方法处理的事情是一样的,不过接受的参数不一样,我们为什么不能让许多对象共用这些方法呢?这就是prototype的由来, 和Java里的static有点像.

每个对象都有一个prototype,prototype自身也是一个对象,它也有自己的property.几乎每一个函数都有自己的prototype property,所有通过该方法创建的对象都能访问该函数的propotype.我们可以把一个对象的原型想象成它的一个模板,这个对象从它的模板原型中得到一些property,然后基于这个继续扩展.

1
2
3
4
5
6
var book = {
    title: "the principle of object oriented javascript"
};
console.log("hasOwnProperty" in book);                            // true
console.log(book.hasOwnProperty("hasOwnProperty"));               // false
console.log(Object.prototype.hasOwnProperty("hasOwnProperty"));   // true

每一个对象在被创建后都有自己的[[Prototype]]property,它指向创建它的方法的prototype.

当在试图访问一个对象上得某个property时,系统会先查看当前对象是否有这个own property,如果有则返回,否则继续查找它的[[Prototype]]指向的对象(即创建这个对象的方法的property)是否有这个property,有则返回否则返回undefined.prototype property不能被复写或者删除:

1
2
3
4
5
6
7
8
9
10
var object = {};
console.log(object.toString());  // [object Object]
object.toString = function() {
    return "[object Custom]";
};
console.log(object.toString());  // [object Custom]
delete object.toString;
console.log(object.toString());  // [object Object]
delete object.toString;
console.log(object.toString());  // [object Object]

上面的代码首先打印原型的toString方法,然后试图修改创建这个object的方法的prototype(Object的prototype),第二次成功打印出修改后的值.其实它并不是修改了Object里的prototype,而是创建了自己的一个own property,这个own property和prototype中的toString有相同的函数签名.然后我们试图删除prototype里的toString方法,这只是删除了自己定义的own property,最后一次是真正尝试删除prototype里的toString方法,但是并不成功,因为它不能在对象上删除.

创建prototype

1
2
3
4
5
6
7
8
9
10
function Car(brand) {
    this.brand = brand;
}
Car.prototype.sayBrand = function() {
    console.log(this.brand);
};
var car1 = new Car("BMW");
var car2 = new Car("Benz");
car1.sayBrand();                       // BMW
car2.sayBrand();                       // Benz

因为prototype是被所有对象共用的,这也许会产生一些潜在问题,即一个对象对prototype做出的改变会影响到另一个对象.

1
2
3
4
5
6
console.log("5. ------");
Car.prototype.manufacureLocation = [];
car1.manufacureLocation.push("Chongqing");
car2.manufacureLocation.push("Beijing");
console.log(car1.manufacureLocation);      // [ 'Chongqing', 'Beijing' ]
console.log(car2.manufacureLocation);      // [ 'Chongqing', 'Beijing' ]

同时创建多个prototype的property

1
2
3
4
5
6
7
8
9
10
11
function University(name) {
    this.name = name
}
University.prototype = {
    sayName: function() {
        console.log(this.name)
    },
    toString: function() {
        console.log("WUSTL")
    }
};

这样做很方便,但是它也有一个潜在的问题,即复写了方法自身的prototype.之前,在方法被创建时,它在prototype里有自己的constructor,它指向方法自身.但是在上面的University.prototype = {…}语句其实是使用Object literal方法对University方法的prototype重新赋值了,所以它的prototype里的constructor指向了新的对象的prototype,即object得prototype.

1
2
3
4
var univ = new University("Washington University");
console.log(univ instanceof University);     // true
console.log(univ.constructor == University); // false
console.log(univ.constructor == Object);     // true

为了避免这种情况的出现,我们可以显式制定方法的constructor的指向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// solution to the above issue
function Computer(brand) {
    this.brand = brand
}
Computer.prototype = {
    constructor: Computer,
    sayBrand: function() {
        console.log("brand is " + this.brand)
    },
    statePrice: function() {
        console.log("price is quite high")
    }
};
var computer = new Computer("Lenovo");
console.log(computer instanceof Computer);     // true
console.log(computer.constructor == Computer); // true
1
2
3
4
5
6
7
var computer1 = new Computer("Apple");
var computer2 = new Computer("Dell");
Computer.prototype.quote = function(quote) {
    console.log("quote of " + this.brand + " is " + quote)
};
computer1.quote("Think Different"); // quote of Apple is Think Different
computer2.quote("Whatever");        // quote of Dell is Whatever