Js 面向對象之封裝,繼承

封裝 ,繼承 
 
封裝 ?
面向對象有三大特性,封裝、繼承和多態。對於ES5來講,沒有class(類)的概念,而且因爲JS的函數級做用域(函數內部的變量在函數外訪問不到),因此咱們就能夠模擬 class (類)的概念,在ES5中,類其實就是保存了一個函數的變量,這個函數有本身的屬性和方法;將屬性和方法組成一個類的過程就是封裝。
那麼,若是咱們要把"屬性"(property)和"方法"(method),封裝成一個對象,甚至要從原型對象生成一個實例對象,咱們應該怎麼作呢?
 
原始模式
function Cat (name, color) {
  return {
    name: name,
    color: color
  }
}
var cat1 = Cat("大毛", "黃色");//{name: "大毛", color: "黃色"}
var cat2 = Cat("二毛", "黑色");//{name: "二毛", color: "黑色"}

這種模式並不能看出來 cat1 和 cat2 是同一個原型對象的實例css

構造函數模式html

爲了解決從原型對象生成實例的問題,Js提供了一個構造函數(Constructor)模式。
所謂"構造函數",其實就是一個普通函數,可是內部使用了this變量,對構造函數使用new運算符,就能生成實例,而且this變量會綁定在實例對象上
 function Cat (name, color) {
    this.name = name;
    this.color = color;
    this.age = "5";
 }
 var cat1 = new Cat("大毛", "黃色");
 var cat2 = new Cat("二毛", "黑色");
  cat1.constructor == Cat;//true
  cat2.constructor == Cat; //true
  cat1.constructor == cat2.constructor//true
JS 還提供了 instanceof 運算符,用來校驗原型對象與實例對象的關係 
cat1 instanceof Cat ;// true
表面上好像沒什麼問題,可是實際上這樣作,有一個小小的不優雅,那就是對於每個實例對象,age屬性都會生成一遍,每生成一個實例,都必須爲重複的內容,多佔用一些內存,這樣既不環保,也缺少效率。那麼有辦法解決嗎 ? 答案固然是有的:
 
Prototype模式
Javascript規定,每個構造函數都有一個prototype屬性,指向另外一個對象。這個對象的全部屬性和方法,都會被構造函數的實例繼承。這意味着,咱們能夠把那些不變的屬性和方法,直接定義在prototype對象上;
 function Cat (name, color) {
    this.name = name;
    this.color = color;
 }
  Cat.prototype.age = "10";
 var cat1 = new Cat("大毛", "黃色");
 var cat2 = new Cat("二毛", "黑色");
  cat1.age; // 10;
  cat2.age; // 10;
這時全部實例的age屬性,其實都是同一個內存地址,指向prototype對象,所以就提升了運行效率;
 
繼承 ?
 
Prototype 原型繼承
// 建立父構造函數
function SuperClass(name){
  this.name = name;
  this.showName = function(){
    alert(this.name);
  }
}
// 設置父構造器的原型對象
SuperClass.prototype.Age = '123';
// 建立子構造函數
function SubClass(){}
// 設置子構造函數的原型對象實現繼承
SubClass.prototype = SuperClass.prototype;
//生成實例
var child = new SubClass()

child.name // undefined
child.Age // 123
 
上述的Prototype模式已經實現了繼承,但僅僅繼承了父構造函數的prototype 原型上的成員,並不能繼承父構造函數的其它成員,那麼還有別的方法來實現繼承嘛 ? 答案固然是有的;
 
原型鏈繼承
 
//即 子構造函數.prototype = new 父構造函數()
// 建立父構造函數
function SuperClass(){
    this.name = 'HiSen';
    this.age = 25;
    this.showName = function(){
        console.log(this.name);
    }
}
// 設置父構造函數的原型
SuperClass.prototype.friends = ['js', 'css'];
SuperClass.prototype.showAge = function(){
    console.log(this.age);
}
// 建立子構造函數
function SubClass(){}
// 實現繼承
SubClass.prototype = new SuperClass();
// 修改子構造函數的原型的構造器屬性,由於此時繼承了父構造函數指向 //SuperClass; 因此要修改一下。
SubClass.prototype.constructor = SubClass;

//生成實例
var child = new SubClass();
console.log(child.name);
// HiSen console.log(child.age);// 25 child.showName();// HiSen child.showAge();// 25 console.log(child.friends); // ['js','css'] // 當咱們改變friends的時候, 父構造函數的原型對象的也會變化 child.friends.push('html'); console.log(child.friends);// ["js", "css", "html"] var father = new SuperClass(); console.log(father.friends);// ["js", "css", "html"]

此時再看:發現子構造函數 不只繼承了父構造函數原型 prototype 上的成員,也繼承了其它成員。但是修改子構造函數的屬性時,咱們發現父構造函數的原型對象也對應修改,那有沒有辦法屏蔽這一種狀況呢 ? 接着往下看:數組

 

拷貝實現繼承函數

說到拷貝,可能會分深拷貝和淺拷貝,其實:this

淺拷貝是對象的屬性的引用,而不是對象自己; (淺拷貝只拷貝一層,若是存在多層仍是會影響原對象)spa

深拷貝是建立一個新的內存地址保存值 ; (與原對象互不影響)prototype

下邊我列舉兩個拷貝的方法來實踐一下:code

淺拷貝htm

例舉一個簡單的淺拷貝: 對象形式對象

function clone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
}

深拷貝

對象形式的深拷貝

function clone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            if (typeof source[i] === 'object') {
                target[i] = clone(source[i]); // 注意這裏
            } else {
                target[i] = source[i];
            }
        }
    }

    return target;
}

 數組形式的深拷貝

function clone(source) {
      var out = [],i = 0,len = source.length;
      for (; i < len; i++) {
      if (source[i] instanceof Array){
        out[i] = clone(arr[i]);
      }
        else out[i] = source[i];
      }
      return out;
}

 

固然出了以上這些實現繼承的方法之外還有更多的方式一樣能夠實現繼承,例如:

Object.create();繼承

ECMAScript 5 中引入了一個新方法: Object.create()。能夠調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數:
 
Object.create()方法接受兩個參數:Object.create(obj,propertiesObject) ;
obj:一個對象,應該是新建立的對象的原型。
propertiesObject:可選。該參數對象是一組屬性與值,該對象的屬性名稱將是新建立的對象的屬性名稱,值是屬性描述符(這些屬性描述符的結構與 Object.defineProperties()的第二個參數同樣)。注意:該參數對象不能是  undefined,另外只有該對象中自身擁有的可枚舉的屬性纔有效,也就是說該對象的原型鏈上屬性是無效的。
 
var obj = {
    "a":'123', fun :function () { alert(1) } } 
var jc = Object.create(obj); jc.a; //123 jc.fun();//1

咱們能夠看到,jc 繼承了 obj 的屬性;同時也繼承了 obj 對象的方法;

ES6鍾提供了一個方法 Object.assign();

Object.assign是ES6的新函數。Object.assign() 方法能夠把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,而後返回目標對象。

Object.assign(target, ...sources)

target:目標對象。
sources:任意多個源對象。

var obj = { a: {a: "hello", b: 21} };
var initalObj = Object.assign({}, obj);

initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"

 

最後再說一種最簡單的方式,轉成字符串 - 再轉回來;

var obj1 = { o: { a: 1 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
 
ECMAScript6 引入了一套新的關鍵字用來實現 class。使用基於類語言的開發人員會對這些結構感到熟悉,但它們是不一樣的。JavaScript 仍然基於原型。這些新的關鍵字包括 class, constructor,static,extends 和 super。敬請期待...
相關文章
相關標籤/搜索