一般面向对象的语言都具有三大特征,封装、继承,多态,例如像java 和c++。但是javaScript它可以说不是完全的面向对象的语言,因为它没有类的概念,但是它又是面向对象的,因为它可以封装属性和方法,并且也可以实现继承。
1、字面量模式
字面量的模式来创建一个对象
var person = {
name : 'wzj',
age : 25
}
console.log(person.name);// wzj
console.log(typeof person);//Object
但是这种方式比较麻烦,如果生成多个实例,写起来比较繁琐。
2、通过函数模式
可以通过函数来解决上面代码重复的问题
function person(name, age){
var person = new Object();
person.name = name;
person.age = age;
return person;
}
var person1 = person('wzj',25);//会返回一个对象
var person2 = person('dxy',22);//也会返回一个对象
console.log(person.name);//wzj
可以看出二者并没有必然的联系,不能反映他们是同一个 person 创建出来的。
我们通过控制台可以看到,这两个对象不仅具有age和name属性还有一个 proto属性,这个是对象的原型链,由于我们是直接通过在函数中实例化Object构造函数,所以此时这两个对象的原型链的constructor属性丢指向Object()函数,并且继承了Object()的所有方法.
3、通过构造函数封装属性和方法
构造函数的概念我们并不陌生,在java中构造函数是用来实现对象属性的初始化的,并且构造函数也可以实现重载。但是在javaScript中的构造函数和一般的函数没有多大区别。
javaScript中的构造函数大致具有以下特征:
- 构造函数就是一个普通的函数,但是首字母要大写例如
Person(name, age)
。 - 构造函数内部具有
this
,变量,并且this会绑定在实例对象上。 - 构造函数需要通过
new
关键字来实例化对象,并且通过new
关键字创建的对象都具有constructor属性。
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
}
}
var person1 = new Person('wzj',25);
var person2 = new Person('dxy',23);
//二者指向同一个构造函数,返回true
console.log(person1.constructor == person2.constructor);
//可以判断出实例对象和原型对象之间的关系,返回true
console.log(person1 instanceof Person);
通过构造函数的方法创建对象不仅消除那种字面量冗余的问题,也解决了原型对象和实例对象之间的关联问题,但是构造函数也具有一些问题,例如方法和属性的共有部分如何抽取问题,在java中我们可以声明接口来实现共有方法的抽取,那么在javaScript中我们如何实现呢?
4、构造函数的一些问题
首先来看一个示例:
function Car(name, color){
this.name = name;
this.color = color;
this.sayName = function () {
console.log(this.name);
}
this.material = '金属';
this.start = function () {
console.log("启动引擎!")
}
}
var car1 = new Car('梅赛德斯',"黑色");
var car2 = new Car('凯迪拉克','白色');
//这个方法每个对象都具有
car1.start();
car2.start();
//返回false,因为所有的实例的这个方法 都是存在一个自己独立的地址中
console.log(car1.start == car2.start);
然后在看下控制台
可以看到两个对象有公共的部分,这种方式造成了内存的浪费,因为这两个对像所共有的方法,是在各自的内存中存放着,并不是存在公共的区域,那么我们能不能实现java中样把这种共有的方法,声明为static的放在类加载区呢,很显然不能,因为javaScript没有类的概念的。
并且,浏览没有类加载区的概念,所以我们不能借鉴。但是并不是说不能实现那种方式。
javaScript规定 每个构造函数都默认具有prototype
属性,并且这个属性指向一个对象,这个被指向的对象的所有属性和方法都将被次构造函数所继承。
function Car2(name, color){
this.name = name;
this.color = color;
this.sayName = function () {
console.log(this.name);
}
}
//通过 prototype 来实现共有方法的抽取,并且实现了对象方法的继承
Car2.prototype.start = function () {
console.log("引擎启动!")
}
Car2.prototype.material = '金属';
/**
1. 此时所有的实例的start方法和material属性,其实都指向同一个内存地址,即 指向prototype对象.
2. @type {Car2}
*/
var car1 = new Car2('梅赛德斯','黑色');
var car2 = new Car2('凯迪拉克','白色');
car1.start();
car2.start();
console.log(car1.start == car2.start);//返回true,因为所有实例的方法都指向同一个地址
通过控制台可以发现,car1和car2自身并没有start()
方法和material
属性,但是可以调用start方法。
通过原型链可以看到,start()方法和material属性
存在。因此可以通过构造函数的prototype属性来实现方法和属性的共有和扩展。
5、验证prototype的一般方法
- 1、
isPrototypeOf()
: 用来判断,某个prototype对象和某个实例之间的关系. - 2、
hasOwnPrototype()
: 用来判断,某个属性是实例自身的属性还是继承自Prototype对象的属性. - 3、
in
: 用来判断某个实例是否含有某个属性,不管是否是自身属性,还可以用来遍历某个对象的 所有属性。
这些方法是继承自Object()对象.
console.log(Car2.prototype.isPrototypeOf(car1));//true
console.log(car1.hasOwnProperty('start'));//false start是 prototype对象的属性
console.log(car1.hasOwnProperty('name')); //true name是car1自身的属性
console.log('start' in car1);//true
console.log('name' in car1); //true
console.log('age' in car1); //false
console.log('-------------------------\n')
for (var prop in car1){
console.log("car1["+prop+"] = "+car1[prop]);
}
控制台打印结果
5、prototype的使用
知道了prototype的作用,name应该怎么使用它呢?如果通过原型添加得方法和对象自身所具有的方法和属性重复了,会优先调用哪一个方法?
function Car2(name, color){
this.name = name;
this.color = color;
this.sayName = function () {
console.log(this.name);
}
}
//通过 prototype 来实现共有方法的抽取,并且实现了对象方法的继承
Car2.prototype.start = function () {
console.log("引擎启动!")
}
Car2.prototype.material = '金属';
Car2.prototype.name = "default";
Car2.prototype.sayName = function () {
console.log(this.name+"覆盖构造函数中已经具有的方法!")
}
var car = new Car2('梅赛德斯','黑色');
car.sayName();//打印梅赛德斯
console.log(car.name);//打印梅赛德斯
console.log(car.material);//打印黑色
通过控制台可以发现,对象本身所具有的的方法,并没有被通过构造函数创建的方法所覆盖
得出以下结论
当对象本身的属性或方法与原型的属性和方法同名的时候
- 默认调用的对象自身的属性和方法
- 通过原型增加的属性和方法是确实存在的
- 函数本身的属性和方法要优先于原型的属性和方法
当通过delete方法删除对象中的方法的时候不会删除构造函数中的.
var car = new Car2('梅赛德斯','黑色');
Car2.prototype.name = "default";
Car2.prototype.sayName = function () {
console.log(this.name+"覆盖构造函数中已经具有的方法!")
}
//删除对象car 中的方法sayName(),此时对象本身并不具有这个方法
delete (car.sayName);
car.sayName();//打印default覆盖构造函数中已经具有的方法!
console.log(car.name);//打印default
console.log(car.material);//打印黑色