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);

这里写图片描述
通过构造函数的方法创建对象不仅消除那种字面量冗余的问题,也解决了原型对象和实例对象之间的关联问题,但是构造函数也具有一些问题,例如方法和属性的共有部分如何抽取问题,在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);//打印黑色

这里写图片描述

猜你喜欢

转载自blog.csdn.net/ithanmang/article/details/80759951