javaScript 面向对象编程--封装和prototype属性

一般面向对象的语言都具有三大特征,封装、继承,多态,例如像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中的构造函数大致具有以下特征:

  1. 构造函数就是一个普通的函数,但是首字母要大写例如 Person(name, age)
  2. 构造函数内部具有this,变量,并且this会绑定在实例对象上。
  3. 构造函数需要通过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);

![这里写图片描述](https://img-blog.csdn.net/20180621132640283?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2l0aGFubWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
通过构造函数的方法创建对象不仅消除那种字面量冗余的问题,也解决了原型对象和实例对象之间的关联问题,但是构造函数也具有一些问题,例如方法和属性的共有部分如何抽取问题,在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);

然后在看下控制台
![这里写图片描述](https://img-blog.csdn.net/20180621134121377?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2l0aGFubWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
可以看到两个对象有公共的部分,这种方式造成了内存的浪费,因为这两个对像所共有的方法,是在各自的内存中存放着,并不是存在公共的区域,那么我们能不能实现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方法。
![这里写图片描述](https://img-blog.csdn.net/20180621135039333?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2l0aGFubWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
通过原型链可以看到,`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]);
}

控制台打印结果
![这里写图片描述](https://img-blog.csdn.net/20180621140702448?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2l0aGFubWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
##### 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);//打印黑色

通过控制台可以发现,对象本身所具有的的方法,并没有被通过构造函数创建的方法所覆盖
![这里写图片描述](https://img-blog.csdn.net/20180621141822155?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2l0aGFubWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
###### 得出以下结论
当对象本身的属性或方法与原型的属性和方法同名的时候

 - 默认调用的对象自身的属性和方法
 - 通过原型增加的属性和方法是确实存在的
 - 函数本身的属性和方法要优先于原型的属性和方法

当通过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);//打印黑色
```
这里写图片描述

猜你喜欢

转载自www.cnblogs.com/ithanmang/p/9208926.html