js面向對象

1、封裝javascript

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

  1.1 原始模型java

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

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

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

  這就是最簡單的封裝了,把兩個屬性封裝在一個對象中,可是這樣的封裝有兩個缺點jquery

   一、若是寫多個實例會很麻煩編程

  二、實例也原型之間沒有任何關係數組

1.2 原始模型的改進app

  寫一個函數解決代碼重複問題函數

  

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

  存在的問題:cat1和cat2之間沒有任何內在關係,不能看出他們是同一原型的實例this

1.3 構造函數模式spa

  爲了解決從原型對象生成實例問題,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]);
}

2、繼承

方法1、構造函數的繼承

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);//動物

 3、非構造函數的繼承

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 - 依賴倒置原則:依賴於抽象
相關文章
相關標籤/搜索