js面向对象

一、封装

  原文链接:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html

  1.1 原始模型

var Cat = {//原型
   name : "",
  color :"" }

var cat1 = {};//实例
cat1.name = "小花";
cat1.color = "花";

var cat2 = {};//实例
cat2.name = "小黑";
cat2.name = "黑";

  这就是最简单的封装了,把两个属性封装在一个对象中,但是这样的封装有两个缺点

   1、如果写多个实例会很麻烦

  2、实例也原型之间没有任何关系

1.2 原始模型的改进

  写一个函数解决代码重复问题

  

function Cat(name,color){
  return {
    name : name,
    color:color
  }
}
var cat1 = Cat("小花",“花”);
var cat2 = Cat("小黑" , "黑");

  存在的问题:cat1和cat2之间没有任何内在关系,不能看出他们是同一原型的实例

1.3 构造函数模式

  为了解决从原型对象生成实例问题,js提供一个构造函数模式。

  所谓的构造函数就是普通的函数,内部使用this对象,对构造函数内部使用new运算符就可以生成实例,并且this变量会绑定在实例对象上

var Cat = function(name,color){
  this.name = name;
  this.color = color;
}
var cat1 = new Cat("小花",“花”);
var cat2 = new Cat("小黑",“黑”);

js提供了一个instanceof 运算符,验证原型对象和实例对象之间的关系
alert( cat1 instanceof Cat);//ture
alert( cat2 instanceof Cat);//true

  存在的问题:如果存在不变的属性或方法时,比如var Cat = function(name,color){  this.name = name;

  this.color = color;
  this.style = "猫科"
  this.eat = function(){
    alert("吃老鼠");
  }
}

var cat1 = new Cat("小黑",“黑”);
var cat2 = new Cat("小花",“花”) ;
alert(cat1.style);//猫科
cat1.eat();//吃老鼠
alert(cat2.style);//猫科
cat2.eat();//吃老鼠

  对于每一个实例对象,type和eat都是一样的内容,每一次生成一个实例,都必须为重复内容,多占用一些内存,既不环保也缺乏效率

解决办法:prototype模式

每一个构造函数都有一个prototype属性,指向prototype对象,这个对象的所有属性和方法都被构造函数的实例继承

这意味着,我们可以把那些不变的属性和方法直接定义在prototype上

var Cat = function(name,color){
  this.name = name;
  this.color = colr;
}
Cat.prototype = {
  constructor : Cat,
  style : "猫科",
  eat : function(){
    alert("吃老鼠");
  }
}
var cat1 = new Cat("小黑",“黑”);
cat1.eat();
alert(cat1.style);

var cat2 = new Cat("小花",“花”);
alert(cat2.style);
cat2.eat();

 prototype验证方法

 isPrototypeOf方法判断,某个prototype对象和实例之间的关系

  

alert( Cat.prototype isPrototypeOf (cat1) ) ;//true
alert(Cat.prototype isPrototypeOf( cat2 ) );//true

 instanceof 判断实例和父类之间的关系

alert( cat1 instanceof Cat);//true
alert(cat2 instanceof Cat);//true

  每一个实例对象都有hasOwnProperty()方法,判断属性是本地属性还是继承prototype的属性

alert( cat1 hasOwnProperty(name) );//true 为本地属性
alert( cat1 hasOwnProperty(eat) );//false 为继承属性

  in 运算符用于判断实例是否有某个属性,无论是本地属性还是继承属性

alert( "name" in cat1 );//true
alert("age" in cat1);//false

in还可以变量对象中的所有属性
for( var pro in cat1){
  alert("cat1的”+pro +"属性值为:"+cat1[pro]);
}

二、继承

方法一、构造函数的继承

function Animal (){
  this.species = "动物";
}

function Cat(name,color){
  this.name = name;
  this.color = color;
}
怎样才能是猫继承动物呢?
方法一:构造函数绑定
function Animal(){
  this.species = "动物";
}
function Cat(name,color){
  Animal.apply(this,arguments);
  this.name = name;
  this.color = color;
}
var cat1 = new Cat("小黑",“黑”);
alert( cat1.species );//“动物”

 方法二:原型模式

如果Cat的prototype成为Animal的实例,cat就可以继承Animal了
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat();
alert( cat1.species );//"动物"

  

任何一个构造函数的原型对象都有一个constructor属性,此属性都指向这个构造函数。
当没有Cat.prototype = new Animal();的时候,Cat.prototype.constructor指向的是Cat,但是继承了Animal后,Cat.prototype.constructor = Animal;
所以要进行更正,Cat.prototype.constructor = Cat;
更重要的是实例也有constructor属性,指向的是构造函数原型对象的constructor属性
alert( cat1.constructor == Cat.prototype.constructor);//true
因此当Cat.prototype = new Animal();后 cat1.constructor = Animal,cat1明明是使用构造函数Cat生成的,但此时确是Animal,导致了原型链的混乱,所以要手动更正constructor,
Cat.prototype.constructor = Cat

方法三:直接继承prototype
方法三是方法二的改进:由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
function Animal(){}
Animal.prototype.species = "动物";

然后将Cat的prototype指向Animal.prototype
Cat.prototype = Animal.prototype ;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("小黑",“黑”);
alert(cat1.species);//动物


  

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

所以,上面这一段代码其实是有问题的。请看第二行

  Cat.prototype.constructor = Cat;

这一句实际上把Animal.prototype对象的constructor属性也改掉了!

  alert(Animal.prototype.constructor); // Cat

方法四:利用空对象作为中介
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F()
Cat.prototype.constructor = Cat;
F作为空对象,几乎不占内存,此时修改Cat的prototype对象,就不会修改Animal的prototype对象了
将上面方法封装成一个函数方便使用
function extend(Child,Parent){
  var F = function(){};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype;
}

extend(Cat,Animal);
var cat1 = new Cat();
alert( cat1.species);//动物

这个是YUI库实现继承的方法

另外,说明一点,函数体最后一行

  Child.uber = Parent.prototype;

意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)

这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

方法五:拷贝继承
将父对象的所有属性和方法都拷贝到子对象中
function Animal(){}
Anima.prototype.species = "动物";

function extend2( Child,Parent){
  var p = Parent.prototype;
  var c = Child,prototype;
  for(var i in p){
    c[i] = p[i];
  }
  Child.uber = p
}

extend2( Cat,Animal );
var cat1 = new Cat("小花",“花”);
alert(cat1.species);//动物

 三、非构造函数的继承

var Chines  = {
  nation : "中国"
};
var Doctor = {
  career:"医生";
}
让医生继承中国,成为中国医生。

  方法一:object()方法

function object(parent){
  var F = function(){};
  F.prototype = parent;
  return new F();
}

var Doctor = object(Chinese);
Doctor.career = "医生";
alert(Doctor.nation);//中国

  方法二:浅拷贝

function extend3(p){
  var c = {};
  for(var i in p){
    c[i] = p[i];
  }
  c.uber = p;
  return c;
}

var Doctor = extend3(Chines);
Doctor.career = "医生";
alert(Doctor.nation);//中国

 

但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

请看,现在给Chinese添加一个"出生地"属性,它的值是一个数组。

  Chinese.birthPlaces = ['北京','上海','香港'];

通过extendCopy()函数,Doctor继承了Chinese。

  var Doctor = extendCopy(Chinese);

然后,我们为Doctor的"出生地"添加一个城市:

  Doctor.birthPlaces.push('厦门');

发生了什么事?Chinese的"出生地"也被改掉了!

  alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门

  alert(Chinese.birthPlaces); //北京, 上海, 香港, 厦门

所以,extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做"浅拷贝"。这是早期jQuery实现继承的方式。

方法三:深拷贝

function deepCopy(parent,c){
  var c = c || {};
  for( var i in parent){
    if( typeof(parent[i] == "object") ){
      c[i] = (parent[i].constructor == Array ) ? [] : {};
      deepCopy(parent[i] , c[i]);
    }else{
      c[i] = parent[i];
    }
  }
  return c;
}

var Doctor = deepCopy(Chinese);
Doctor.carrer = "医生";
Chinese.brithPlace = ["上海",“北京”,“南京”]
alert(Doctor.nation);//"中国"
Doctor.brithPlace.push ("内蒙古");
alert(Doctor.brithPlace);//["上海",“北京”,“南京”,“内蒙古”]

  目前jquery库使用的是这种继承方法

 另加:面向对象抽象:原文链接:http://www.cnblogs.com/wangfupeng1988/p/3687346.html

如何更好的解决变化的问题?

提取抽象,隔离具体

什么是“抽象”? 抽象就是不变的东西,

什么是“具体”?具体是实际执行的,

我们应该依赖于抽象编程,而不是依赖于具体编程。应该把程序中的共性抽象出来,并且把具体实现的部分隔离开来,让他们都依赖于抽象,并且互不影响。这其实就是设计。

2.3 SOLID五大原则

系统设计的5大原则,简写分别是S、O、L、I、D。

  • S - 类职责单一原则: 即职责划分要清晰,不同职责的不要搅和在一起。每个类应该只有一个让他发生改变的原因。
  • O - 开放封闭原则: 对扩展开发,对修改封闭。即如果系统要变化,就去扩展、新增新类,不要修改现有的类。
  • L - LISKOV原则: 子类应该能充分覆盖父类,并且让使用者分不出差别。
  • I - 接口分离原则:每个接口只管一个功能,不要出现“胖接口”。增加功能时,要加接口,而不是改接口
  • D - 依赖倒置原则:具体应该依赖于抽象,而不是抽象依赖于具体,即低层要依赖于高层。

如果详细分析这5大原则,其实他们都是围绕着“提取抽象、隔离具体”来的。

  • S - 类职责单一原则: 隔离
  • O - 开放封闭原则: 依赖于抽象,隔离具体
  • L - LISKOV原则:抽象
  • I - 接口独立原则:隔离
  • D - 依赖倒置原则:依赖于抽象

 

 



  

 

 


 

  

原文地址:https://www.cnblogs.com/wxiaona/p/6019764.html