JavaScript經過原型委託的方式來實現對象與對象之間的繼承。 編程語言可分爲兩大類:一類是靜態類型語言,另外一類是動態類型語言html
JavaScript是一門動態類型語言 鴨子類型的概念(若是它走起來像鴨子,叫起來也是鴨子,那麼它就是鴨子) 鴨子類型知道咱們只關注對象的行爲,而不關注對象的自己(關注HAS-A,而不是IS-A) 面向接口編程,而不是面向實現編程node
若是一個對象有push和pop方法,而且提供了正確的實現,他就能夠被看成棧來使用。 若是一個對象有length屬性,也能夠依照下標來存取屬性(最好擁有slice和splice等方法),這個對象就能夠被看成數組來使用ajax
多態:同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。換句話說,給不一樣的對象發送同一個消息的時候,這些對象會根據這個消息分別給出不一樣的反饋。編程
多肽的思想是將"作什麼"和"誰去作以及怎樣去作"分離開來,也就是將"不變的事物"與"可能改變的事物"分離開來。動物都會叫,這是不變的,可是不一樣類型的動物具體怎麼叫是可變的。把不變的部分隔離出來,把可變的部分封裝起來,這給予咱們擴展程序的的能力。設計模式
var makeSound = function( animal ) {
animal.sound()
}
var Duck = function(){}
Duck.prototype.sound = function(){
console.log('嘎嘎嘎嘎嘎')
}
var Chicken = function(){}
Chicken.prototype.sound = function(){
console.log('咯咯咯')
}
makeSound(new Duck())
makeSound(new Chicken())
複製代碼
靜態類型的面嚮對象語言一般被設計爲能夠向上轉型:當給一個類變量 賦值時,這個變量的類型既可使用這個類自己,也可使用這個類的超類。這就像咱們在描述 天上的一隻麻雀或者一隻喜鵲時,一般說「一隻麻雀在飛」或者「一隻喜鵲在飛」。但若是想忽 略它們的具體類型,那麼也能夠說「一隻鳥在飛」。數組
使用繼承獲得多態效果 JavaScript 的多態瀏覽器
多態的思想就是把「作什麼」和「誰去作」分離開來,先要消除類型之間的耦合關係。若是類型之間的耦合關係沒有被消除,那麼咱們在makeSound 方法中指定了發出叫聲的對象是,它就不可能再被替換成另一個類型。 JavaScript對象的多態性是與生俱來的緩存
多態的最根本好處在於,你沒必要再向對象詢問「你是什麼類型」然後根據獲得的答 案調用對象的某個行爲——你只管調用該行爲就是了,其餘的一切多態機制都會爲你安 排穩當。 7 換句話說,多態最根本的做用就是經過把過程化的條件分支語句轉化爲對象的多態性,從而 消除這些條件分支語句。bash
「在電影的拍攝現場,當導演喊出「action」時,主角開始背臺詞,照明師負責打燈 光,後面的羣衆演員僞裝中槍倒地,道具師往鏡頭裏撒上雪花。在獲得同一個消息時, 每一個對象都知道本身應該作什麼。若是不利用對象的多態性,而是用面向過程的方式來 編寫這一段代碼,那麼至關於在電影開始拍攝以後,導演每次都要走到每一個人的面前, 確認它們的職業分工(類型),而後告訴他們要作什麼。若是映射到程序中,那麼程序 中將充斥着條件分支語句。」閉包
利用對象的多態性,導演在發佈消息時,就沒必要考慮各個對象接到消息後應該作什麼。對象應該作什麼並非臨時決定的,而是已經事先約定和排練完比的。每一個對象應該作什麼,已經成爲了該對象的一個方法,被安裝在對象的內部,每一個對象負責他們本身的行爲。因此這些對象能夠根據俄同一個消息,有條不紊分別進行各自的工做。 將行爲分佈在各個對象中,並讓這些對象各自負責本身的行爲,這正是面向對象設計的優勢。
var googleMap = {
show: function(){
console.log("開始渲染谷歌地圖")
}
}
var renderMap = function(){
googleMap.show();
}
renderMap()
複製代碼
對象的多態性提示咱們,"作什麼"和"怎麼去作"是能夠分開的。
var renderMap = function(map) {
if(map.show instanceof Function ) {
map.show();
}
}
renderMap(googleMap);
renderMap(baiduMap);
var sosoMap = {
show: function(){
console.log("開始渲染搜搜地圖")
}
}
renderMap(sosoMap)
複製代碼
在JavaScript這種將函數做爲一等對象的語言中,函數自己也是對象,函數用來封裝行爲而且可以被四處傳遞。當咱們對一些函數發出「調用」的消息時,這些函數會返回不一樣的執行結果,這是「多態」的一種體現,也是不少設計模式在JavaScript中能夠用高階函數代替實現的緣由
封裝 封裝數據 封裝的目的是將信息隱藏。封裝數據和封裝實現,封裝類型和封裝變化 除了ECMAScript6中提供的let以外,通常咱們經過函數來建立做用域
var myObject = (function(){
var _name = 'sven'; // 私有變量
return {
getName: function(){ // 公開(public)
return _name
}
}
})
複製代碼
封裝實現封裝類型和封裝變化 原型模式和基於原型繼承的JavaScript對象系統 在以類爲中心的面向對象編程語言中,類和對象的關係能夠想象成鑄劍模和鑄件的關係,對象老是從類中建立而來。而在原型編程思想中,類並非必需的,對象未必須要從類中建立而來,一個對象是經過克隆另一個對象所獲得的。 原型模式的實現關鍵,是語言自己是都提供了clone方法。ECMAScript 5提供了Object.create方法,能夠用來克隆對象。
var Plane = function(){
this.blood = 100;
this.attackLevel = 1;
this.defenseLevel = 1;
} //建立構造函數
var plane = new Plane() //經過new建立一個實例
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;
var clonePlane = Object.create(plane)
複製代碼
在不支持Object.creaate方法的瀏覽器中,則可使用如下代碼
Object.create = Object.create || function (obj) {
var F = function(){}
F.prototype = obj
return new F();
}
複製代碼
克隆是建立對象的手段
JavaScript自己是一門基於原型的面嚮對象語言,它的對象系統就是使用原型模式來搭建的,在這裏稱之爲原型編程範型也許更合適。 原型編程範型的一些規則 若是A對象是從B對象克隆來的,那麼B對象就是A對象的原型。 Object 是 Animal 的原型,而 Animal 是 Dog 的原型,它們之間造成了一 條原型鏈。這個原型鏈是頗有用處的,當咱們嘗試調用 Dog 對象的某個方法時,而它自己卻沒有 這個方法,那麼 Dog 對象會把這個請求委託給它的原型 Animal 對象,若是 Animal 對象也沒有這 個屬性,那麼請求會順着原型鏈繼續被委託給 Animal 對象的原型 Object 對象,這樣一來便能得 到繼承的效果,看起來就像 Animal 是 Dog 的「父類」,Object 是 Animal 的「父類」。
基於原型鏈的委託機制就是原型繼 承的本質。
如今咱們明白了原型編程中的一個重要特性,即當對象沒法響應某個請求時,會把該請求委 託給它本身的原型。 最後整理一下本節的描述,咱們能夠發現原型編程範型至少包括如下基本規則。
function Person( name ){ this.name = name;
};
Person.prototype.getName = function(){ return this.name;
};
var a = new Person( 'sven' )
console.log( a.name ); // 輸出:sven
console.log( a.getName() ); // 輸出:sven
console.log( Object.getPrototypeOf( a ) === Person.prototype );
// 輸出:true
複製代碼
在這裏 Person 並非類,而是函數構造器,JavaScript 的函數既能夠做爲普通函數被調用, 7 也能夠做爲構造器被調用。當使用 new 運算符來調用函數時,此時的函數就是一個構造器。 用 new 運算符來建立對象的過程,實際上也只是先克隆 Object.prototype 對象,再進行一些其餘額 外操做的過程。
對象會記住它的原型
就JavaScript的真正實現來講,其實並不能說對象有原型,而只能說對象的構造器有原型。對象把委託請求給它的構造器原型。 JavaScript給對象提供了一個__proto__的隱藏屬性,某個對象的__proto__屬性默認會指向它的構造器的原型對象,即{Constructor}.prototype。 實際上,__proto__就是對象跟"對象構造器的原型"聯繫起來的紐帶。正由於對象要經過__proto__屬性來記住它的構造器原型,因此咱們用objectFactory函數模擬用new建立對象時,須要手動給obj對象設置正確的__proto__指向。 obj.proto = Constructor.prototype; 經過這句代碼,咱們讓obj.__proto__指向Person.prototype,而不是原來的Object.prototype 若是對象沒法響應某個請求,它會把這個請求委託給它的構造器的原型 雖然JavaScript的對象最初都是由Object.prototype對象克隆而來的,但對象構造器的原型並不只限於Object.prototype上,而是能夠動態只想其餘對象。當對象a須要借用對象b的能力時,能夠有選擇性地把對象a的構造器的原型只想對象b,從而達到繼承的效果。
var obj = {name: 'sven'};
var A = function(){}
A.prototype = obj
var a = new A();
console.log(a.name)
複製代碼
首先,嘗試遍歷對象 a 中的全部屬性,但沒有找到 name 這個屬性。 查找 name 屬性的這個請求被委託給對象 a 的構造器的原型,它被 a.proto 記錄着而且指向 A.prototype,而 A.prototype 被設置爲對象 obj。 在對象 obj 中找到了 name 屬性,並返回它的值。
當咱們指望獲得一個'類'繼承自另外一個‘類’的效果時,每每會用下面的代碼來模擬實現:
var A = function(){} //創造構造函數
A.prototype = {name: 'sven'};//構造函數的原型指向一個字面量對象
var B = function(){};
B.prototype = new A();// 將構造器的原型指向另一個對象,和指向字面量同樣
var b = new B();
console.log(b.name)
複製代碼
但美中不足是在當前的 JavaScript 引擎下,經過 Object.create 來建立對象的效率並不高,通 常比經過構造函數建立對象要慢。此外還有一些值得注意的地方,好比經過設置構造器的 prototype 來實現原型繼承的時候,除了根對象 Object.prototype 自己以外,任何對象都會有一個 原型。而經過 Object.create( null )能夠建立出沒有原型的對象。 另外,ECMAScript 6 帶來了新的 Class 語法。這讓 JavaScript 看起來像是一門基於類的語言, 但其背後還是經過原型機制來建立對象。經過 Class 建立對象的一段簡單示例代碼1以下所示 :
Class Animal {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
Class Dog extends Animal {
constructor(name) {
super(name)
}
speck(){
return 'woof'
}
}
var dog = new Dog("Scamp")
console.log(dog.getName() + 'says' + dog.speak())
複製代碼
除去不經常使用的 with 和 eval 的狀況,具體到實際應用中,this 的指向大體能夠分爲如下 4 種。 做爲對象的方法調用。
當函數做爲對象的方法被調用時,this指向該對象:
var obj = {
a:1,
getA: function(){
alert(this === obj) // 輸出:true
alert(this.a) // 輸出:1
}
}
複製代碼
做爲普通函數調用。
當函數做爲普通函數調用時,this老是指向全局對象。
window.name = 'globalName'
var myObject = {
name: 'sven',
getName: function(){
return this.name
}
}
var getName = myObject.getName;將對象的方法賦給全局變量
console.log(getName()) // globalName
複製代碼
有時候咱們會遇到一些困擾,好比在 div 節點的事件函數內部,有一個局部的 callback 方法, callback 被做爲普通函數調用時,callback 內部的 this 指向了 window,但咱們每每是想讓它指向 該 div 節點,見以下代碼:
<html> <body>
<div id="div1">我是一個 div</div> </body>
<script>
window.id = 'window';
document.getElementById( 'div1' ).onclick = function(){ alert ( this.id ); // 輸出:'div1'
var callback = function(){
alert ( this.id );
callback();
};
</script> </html>
// 輸出:'window'
}
複製代碼
此時有一種簡單的解決方案,能夠用一個變量保存 div 節點的引用: 圖靈社區會員 軒轅 專享 尊重版權 26
document.getElementById( 'div1' ).onclick = function(){ var that = this; // 保存 div 的引用
var callback = function(){
alert ( that.id ); // 輸出:'div1' }
callback(); };
在 ECMAScript 5 的 strict 模式下,這種狀況下的 this 已經被規定爲不會指向全局對象,而 是 undefined:
function func(){ "use strict"
alert ( this ); func();
複製代碼
構造器調用。
JavaScript 中沒有類,可是能夠從構造器中建立對象,同時也提供了new運算符,使得構造器更像一個類 除了宿主提供的一些內置函數,大部分JavaScript函數均可以看成構造器使用。構造器的外表跟普通函數如出一轍,它們的區別在於被調用的方式。當用new運算符調用函數時,該函數總會返回一個對象,一般狀況下,構造器的this就指向返回的這個對象。 但用 new 調用構造器時,還要注意一個問題,若是構造器顯式地返回了一個 object 類型的對象,那麼這次運算結果最終會返回這個對象,而不是咱們以前期待的 this: Function.prototype.call 或 Function.prototype.apply 調用。 跟普通的函數調用相比,用Function.prototype.call或Function.prototype.apply能夠動態地改變傳入函數的this:
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
}
var obj2 = {
name: 'anne'
}
console.log(obj1.getName.call(obj2)) //輸出:anne
丟失的this
var obj = {
myName: 'sven',
getName: function(){ return this.myName;
} };
console.log( obj.getName() );
var getName2 = obj.getName; console.log( getName2() );
// 輸出:'sven' // 輸出:undefined
複製代碼
當調用 obj.getName 時,getName 方法是做爲 obj 對象的屬性被調用的,根據 2.1.1 節提到的規 律,此時的 this 指向 obj 對象,因此 obj.getName()輸出'sven'。 當用另一個變量 getName2 來引用 obj.getName,而且調用 getName2 時,根據 2.1.2 節提到的 規律,此時是普通函數調用方式,this 是指向全局 window 的,因此程序的執行結果是 undefined。
1.封裝變量
閉包能夠幫助把一些不須要暴露在全局的變量封裝成"私有變量"。
var mult = function(){
var a = 1;
for(var i = 0,l = arguments.length;i < l; i++){
a = a* arguments[i]
}
return a;
}
複製代碼
加入緩存機制來提升這個函數的性能
var cache = {}
var mult = function(){
var args = Array.prototypr.join.call(arguments, ',')
if(cache[args]){
return cache[args]
}
var a = 1;
for(var i = 0, l = arguments.length; i < l; i++){
a = a* arguments[i]
}
return cache[args] = a
}
alert(mult(1,2,3)) ;//輸出6
alert(mult(1,2,3)) ;//輸出6
複製代碼
優化全局變量cache
var mult = (function(){
var cache = {}
return function(){
var args = Array.prototype.join.call(arguments, ',')
if (args in cache) {
return cache[args]
}
var a = 1;
for(var i = 0, l = arguments.length; i<l; i++){
a = a* arguments[i]
}
return cache[args] = a
}
})()
複製代碼
提煉函數是代碼重構中的一種常見技巧。 若是在一個大函數中有一些代碼可以獨立出來,咱們經常把這些代碼塊封裝在獨立的小函數裏面。獨立出來的小函數有助於代碼複用,若是這些小函數有一個良好的命名,它們自己也起到了註釋的做用。 若是這些小函數不須要在程序的其餘地方使用,最好是把它們用閉包封閉起來。
2.延續局部變量的壽命
img 對象常常用於進行數據上報。
var report = function(src) {
var img = new Image();
img.src = src
}
report('http://xx.com/getUserInfo')
複製代碼
img是report函數的局部變量,當report函數的調用結束後,img局部變量隨機被銷燬,而此時或許還沒來得及發出HTTP請求,因此這次請求就會丟失掉。把img變量用閉包封閉起來,便能解決請求丟失的問題。
var report = (function(){
var imgs = []
return function( src ){
var img = new Image()
imgs.push(img)
img.src = src
}
})()
report('http://xx.com/getUserInfo')
複製代碼
當退出函數後,局部變量 imgs 並無消失,而是彷佛一直在某個地方 存活着。這是由於當執行 var f = func();時,f 返回了一個匿名函數的引用,它能夠訪問到 func() 被調用時產生的環境,而局部變量 a 一直處在這個環境裏。既然局部變量所在的環境還能被外界 訪問,這個局部變量就有了不被銷燬的理由。在這裏產生了一個閉包結構,局部變量的生命看起 來被延續了。
過程與數據的結合是形容面向對象中的「對象」時常用的表達。對象以方法的形式包含了過程,而閉包則是在過程當中以環境的形式包含了數據。一般用面向對象思想能實現的功能,用閉包也能實現。反之亦然。
var extent = function(){
var value = 0;
return {
call: function(){
value++;
console.log(value)
}
}
}
var extent = extent()
extent.call(); // 1
extent.call(); // 2
extent.call(); // 3
var extent = {
value: 0,
call: function(){
this.value++;
console.log(this.value)
}
}
extent.call(); // 1
extent.call(); // 2
extent.call(); // 3
複製代碼
或者:
var Extent = function() {
this.value = 0;
}
extent.prototype.call = function(){
this.value ++
console.log(this.value)
}
var extent = new Extent();
extent.call(); // 1
extent.call(); // 2
extent.call(); // 3
複製代碼
面向對象模的方式
<html>
<body>
<button id="execute">點擊我執行命令</button>
<button id="undo">點擊我執行命令</button>
</body>
</html>
<script>
var Tv = {
open: function(){
console.log("打開電視機")
},
close: function(){
console.log("關閉電視機")
}
}
var OpenTvCommand = function(receiver){
this.receiver = receiver;
}
OpenTvCommand .prototype.undo = function(){
this.receiver.close()
}
OpenTvCommand .prototype.execute = function(){
this.receiver.open()
}
var setCommand = function(command){
document.getElementById('execute').onclick = function(){
command.execute()
}
document.getElementById('undo').onclick = function(){
command.undo()
}
}
setCommand(new OpenTvCommand(Tv))
</script>
複製代碼
命令模式的意圖是把請求封裝爲對象,從而分離請求的發起者和請求的接收者(執行者)之 間的耦合關係。在命令被執行以前,能夠預先往命令對象中植入命令的接收者。 但在 JavaScript 中,函數做爲一等對象,自己就能夠四處傳遞,用函數對象而不是普通對象 來封裝請求顯得更加簡單和天然。若是須要往函數對象中預先植入命令的接收者,那麼閉包能夠 完成這個工做。在面向對象版本的命令模式中,預先植入的命令接收者被當成對象的屬性保存起 來;而在閉包版本的命令模式中,命令接收者會被封閉在閉包造成的環境中,代碼以下
閉包形式
var Tv = {
open: function(){
console.log('打開電視機')
},
close:function(){
console.log('關上電視機')
}
}
var createCommand = function(receiver) {
var excute = function(){
return receiver.open()
}
var undo = function(){
reutn receiver.close()
}
return {
execute: execute,
undo: undo
}
}
var setCommand = function( command ){
document.getElementById( 'execute' ).onclick = function(){
command.execute(); // 輸出:打開電視機 }
document.getElementById( 'undo' ).onclick = function(){ command.undo(); // 輸出:關閉電視機
} };
setCommand( createCommand( Tv ) );
複製代碼
高階函數是至少知足下列條件之一的函數。
1.函數能夠做爲參數被傳遞;
1.回調函數
var getUserInfo = function(userId, callback){
$.ajax('http://xxx.com/getUserInfo?' + userId, funtion(data){
if(typeof callback === 'function'){
callback(data)
}
})
}
getUserInfo(13157, function(data){
alert(data.userName)
})
var appendDiv = function(){
for(var i = 0; i < 100; i++){
var div = document.createElement('div')
div.innerHTML = i
document.body.appendChild(div)
div.style.display = 'none'
}
}
appendDiv()
複製代碼
轉化爲可複用的函數
var appendDiv = function(callback){
for (var i = 0; i< 100; i++){
var div = document.createElement('div')
div.innerHtml = i
document.body.appendChild(div)
if(typeof callback === 'function'){
callback(div)
}
}
}
appendDiv(function(node){
node.style.display = 'none'
})
複製代碼
能夠看到,隱藏節點的請求其實是由客戶發起的,可是客戶並不知道節點什麼 時候會建立好,因而把隱藏節點的邏輯放在回調函數中,「委託」給 appendDiv 方法。appendDiv 方法當 然知道節點何時建立好,因此在節點建立好的時候,appendDiv 會執行以前客戶傳入的回 調函數。
Array.prototype.sort
複製代碼
Array.prototype.sort 接受一個函數看成參數,這個函數裏面封裝了數組元素的排序規則。從 Array.prototype.sort 的使用能夠看到,咱們的目的是對數組進行排序,這是不變的部分;而使 用什麼規則去排序,則是可變的部分。把可變的部分封裝在函數參數裏,動態傳入 Array.prototype.sort,使 Array.prototype.sort 方法成爲了一個很是靈活的方法 //從小到大排列
[1,4,3].sort(function(a, b){
return a - b
})
複製代碼
2.函數能夠做爲返回值輸出。
1.判斷數據類型
var isString = function(obj){
return Object.prototype.toString.call(obj) === '[object String]'
}
var isArray = function(obj){
return Object.prototype.toString.call(obj) === '[object Array]'
}
var isNumber = function(obj){
return Object.prototype.toString.call(obj) === '[object Number]'
}
var isTyoe = function(type){
return function(obj){
return Object.prototype.toString.call(obj) === '[object'+type+']'
}
}
複製代碼