封裝 ,繼承
封裝 ?
面向對象有三大特性,封裝、繼承和多態。對於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 是同一個原型對象的實例
構造函數模式
為了解決從原型對象生成實例的問題,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 上的成員,也繼承了其它成員。可是修改子構造函數的屬性時,我們發現父構造函數的原型對象也對應修改,那有沒有辦法屏蔽這一種情況呢 ? 接着往下看:
拷貝實現繼承
說到拷貝,可能會分深拷貝和淺拷貝,其實:
淺拷貝是對象的屬性的引用,而不是對象本身; (淺拷貝只拷貝一層,如果存在多層還是會影響原對象)
深拷貝是創建一個新的內存地址保存值 ; (與原對象互不影響)
下邊我列舉兩個拷貝的方法來實踐一下:
淺拷貝
例舉一個簡單的淺拷貝: 對象形式
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。敬請期待...