构造函数
JavaScript中的构造函数是创建对象时调用的函数,写法如下:
定义Person构造函数(默认构造函数首字母大写)
function Person(name, age){
this.name = name;
this.age = age;
this.say = function (){
console.log('My name is ' + this.name + ', age is ' + this.age);
};
}
var person1 = new Person('Mark', 18);
//调用构造函数时,new会自动帮你创建this对象,并返回。
console.log(person1.say());
//console: "My name is Mark, age is 18"
如上,构造函数定义了name与age属性,say方法。实例化一个名字为person1的对象,并调用say()方法。
构造函数允许你给对象配置相同的属性,但是构造函数并没有消除代码冗余,在上面的例子中,每一个对象都有自己的say()方法,这意味着如果实例化成百上千个对象,就会有相同
数量的函数做相同的事情,只是数据不同而已。
原型对象
如果所以实例化的对象共享一个方法,那么效率会被大大的提高,这就需要用到原型对象。
可以把原型对象看作对象的基类,所有创建的对象共享该原型对象。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.say = function (){
console.log('My name is ' + this.name + ', age is ' + this.age);
};
//Person.prototype 就是该构造函数的原型对象
var person1 = new Person('Mark', 18);
var person2 = new Person('Jerry', 22);
console.log(person1.say());
console.log(person2.say());
//console: "My name is Mark, age is 18"
//console: "My name is Jerry, age is 22"
如上,所有实例化的对象都会共享原型对象上的say()方法。
一个实例化的对象通过内部属性[[prototype]]跟踪其原型对象。该属性是一个指向该实例使用的原型对象的指针。当你new创建一个对象时,构造函数的原型对象会被赋给[[prototype]]属性,因为[[prototype]]是一个隐藏属性,你可以使用_proto_这个非标准方法来查看。
console.log(person1._proto_);
/**
* Object {}
* constructor: function Person(...)
* say: function()
* _proto_: Object
* /
你可以调用Object.getPrototypeOf()方法读取[[prototype]]属性的值。
Object.getPrototypeOf(person1) === Person.prototype;
//console: true
同样,你也可以使用isPrototypeOf()检查某个对象是不是另外一个对象的原型对象。
Person.prototype.isPrototypeOf(person1);
//console: true
众所周知,Object是一个泛用对象,所有对象都属于Object。
那么Object.getPrototypeOf() 与 isPrototypeOf() 方法可以理解在 Object.prototype 上具有的方法。
常用in查找对象中是否具有相应的属性,但是in操作符会对原型属性和自有属性都返回true。
var book = {'title': 'Object-Oriented'};
console.log('title' in book);
//console: true
console.log ('isPrototypeOf' in book);
//console: true
当你只想在对象中寻找自有属性时,应使用Object.hasOwnProperty()。
你可以用这样一个函数去鉴别一个属性是不是原型属性。
function isPrototypeProperty (obj, name){
return name in obj && !obj.hasOwnProperty(name);
}
原型对象的共享机制使得它成为一次性为所有实例对象定义的理想手段,可以在原型对象上存储其他类型的数据,但在存储时需要注意。
function Person(name){
this.name = name;
}
Person.prototype.list = [];
var person1 = new Person('Mark');
var person2 = new Person('Jerry');
person1.list.push('some');
person2.list.push('any');
console.log(person1.list);
//console: ['some', 'any']
很多开发者会使用另外一种更简洁的方式来扩充原型对象,如下:
function Person(name){
this.name = name;
}
Person.prototype = {
say: function (){
console.log('My name is' + this.name);
},
talk: function (){
console.log('Talk to Me');
}
};
上述定义方法的方式不需要多次键入Person.prototype,但是有一个问题需要注意。
var person1 = new Person('Mark');
console.log(person1.constructor === Person);
//console: false
console.log(person1.constructor === Object);
//console: true
constructor是原型对象才具有的一个属性,且constructor指向其构造函数,上述例子中的constructor应指向Person,但是为什么指向Object了呢?
是因为使用对象的字面形式改变了构造函数的属性。
constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。-- 参考链接
所以在使用对象的字面形式改写原型对象时,应手动改写constructor属性。
Person.prototype = {
constructor: Person,
say: function (){
console.log('My name is' + this.name);
},
talk: function (){
console.log('Talk to Me');
}
};
原型继承
我们先回顾Person构造函数:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.say = function (){
console.log('My name is ' + this.name + ', age is ' + this.age);
};
var person1 = new Person('Mark', 18);
原型链图如下:
原型链图现在我们要基于Person扩展出Child,可以先定义出Child:
function Child(weight){
Person.call(this, weight);
this.weight = weight;
}
但是此时Child创建对象的原型是:
new Child() ---> Child.prototype ---> Object.prototype ---> null
而我们想要的原型是:
new Child() ---> Child.prototype ---> Person.prototype ---> Object.prototype ---> null
首先,这么做:
Child.prototype = Person.prototype
是不可行的,Child与Person共享一个原型对象,那么Child是无用的。
接下来,我们可以这么做:
function Child(weight){
Person.call(this, weight);
this.weight = weight;
}
function N(){}
N.prototype = Person.prototype;
Child.prototype = new N();
Child.prototype.constructor = Child;
定义一个空函数N() 用来桥接,并且通过创建 new N()实例来实现。
这样,我们可以封装一个inherits()函数:
function inherits(Child, Parent) {
var N = function () {};
N.prototype = Parent.prototype;
Child.prototype = new N();
Child.prototype.constructor = Child;
}
可能会有人存在疑问,N()函数只是作为桥接来使用,那么为什么不忽略掉呢?
Child.prototype = new Person();
Child.prototype.constructor = Child;
/**
* 上述代码在大多数情况下是没有错误的,但是有一种情况:
*/
function Person (name){
this.name = name.toLowerCase();
}
Child.prototype = new Person();
/**
* 此时代码会出现错误,因为undefined并不存在toLowerCase方法。
* 那么解决这个错误就需要传入特定的类型值,这样就没办法做到通用性。
*/
本文参考
- 原型继承 - 廖雪峰
- JavaScript面向对象精要 - 尼古拉斯(Nicholas,C.,Zakas)
网友评论