面向對象編程思想(前傳)--你必須知道的javascript

在寫面向對象編程思想-設計模式中的js部分的時候發現不少基礎知識不瞭解的話,是很難真正理解和讀懂js面向對象的代碼。爲此,在這裏先快速補上。而後繼續咱們的面向對象編程思想-設計模式javascript

什麼是鴨子類型

javascript是一門典型的動態類型語言,也就弱類型語言。
那什麼是鴨子類型:【若是它走起路來像鴨子,叫起來也是鴨子,那麼它就是鴨子】html

var 鴨子 = {
    走路: function () { },
    咕咕咕: function () { }
}

var 鸚鵡 = {
    走路: function () { },
    咕咕咕: function () { }
}

這隻鸚鵡一樣有「走路」和「咕咕咕」的方法,那在js的世界裏就能夠把它當成鴨子。
能夠這樣調用:java

var 鴨子們 = [];
鴨子們.push(鴨子);
鴨子們.push(鸚鵡);

for (var i = 0; i < 鴨子們.length; i++) {
    鴨子們[i].走路();
}

因此js的世界沒有抽象和接口,但能夠約定「咱們都是鴨子」。git

javascript的面向對象

javascript不只是直譯式腳本語言、動態類型、弱類型語言、函數爲一等公民的語言,它仍是基於原型的面嚮對象語言。面向對象三大特性:封裝、繼承、多態,下面咱們用js分別實現。github

封裝

var Person = (function () { 
    var sex = "純爺們";
    return {
        name: "農碼一輩子",
        getInfo: function () {
            console.log("name:" + this.name + ",sex:" + sex);
        }
    };
})();


雖然老的js語法沒有提供private等關鍵字,可是咱們能夠利用閉包來實現私有字段,達到封裝的目的。ajax

繼承

  • 字面量表示:
var Person = {
    name: "農碼一輩子",
    getName: function () {
        console.log(this.name);
    }
};
var obj = Person;
obj.getName();

  • 函數構造器:
var Person = function () {
    this.name = "農碼一輩子";    
}
Person.prototype.getName = function () {
    console.log(this.name);
}

var obj = function () { };
obj.prototype = new Person();//obj繼承於Person

var o = new obj();
o.getName();//直接調用原型中的getName(相似於C#中的調用父類方法)

多態

對於多態,其實上面的鴨子類型已經表現的很清楚了。編程

var 鴨子們 = [];
鴨子們.push(鴨子);
鴨子們.push(鸚鵡);

for (var i = 0; i < 鴨子們.length; i++) {
    鴨子們[i].走路();//對於鸚鵡來講,它多是跳着走。對於鴨子來講,它可能左右搖擺着走。這就是多態的表現。
}

對於鸚鵡來講,它多是跳着走。對於鴨子來講,它可能左右搖擺着走。這就是多態的表現。設計模式

原型

什麼是原型?在js中是沒有類的,那它怎麼建立對象。在C#中咱們能夠經過new關鍵字實例化一個對象,在js中咱們用new關鍵字構造一個原型對象。C#中一切對象繼承於Object,js中一切對象的原型是Object。數組

var Person = function () {
    this.name = "農碼一輩子";
    this.sex = "純爺們";
};
console.log(Person.prototype);


咱們不少時候給一個對象添加方法的時候就是寫在原型上,這是爲何?直接寫在對象裏會有問題嗎?下面咱們試試:閉包

var Person = function () {
    this.name = "農碼一輩子";
    this.sex = "純爺們";
    this.getInfo = function () {
        console.log("name:" + this.name + ",sex:" + this.sex);
    }
};


好像並看不出什麼問題。其實否則...

咱們發現,每次構造出來的對象中的方法都會去開闢一個空間。可是對象的方法都是同樣的,徹底沒有必要。 咱們能夠把方法放入原型。

這樣一來,不過咱們構造多少對象,其方法都是公用的(單例的)。
但是爲何會這樣呢?
首先,想一想原型這個詞,很形象,本來的模型。咱們來看一個繼承的例子:

var Person = function () {
    this.name = "農碼一輩子";
    this.sex = "純爺們";
    this.getInfo = function () {
        console.log("name:" + this.name + ",sex:" + this.sex);
    }
};
var Student = function () { };
Student.prototype = new Person();//繼承
var s1 = new Student();
var s2 = new Student();
console.log(s1.getInfo === s2.getInfo);


雖然getInfo在Person裏面是直接實現的,可是到了Student的原型(prototype)裏面就是一個Person對象的單例了。也就是說不管構造多少個Student對象其中的getInfo方法都是同一個。
可是,構造多個Person就有多個getInfo方法。因此,咱們應該把getInfo方法放入Person的原型中。

var Person = function () {
    this.name = "農碼一輩子";
    this.sex = "純爺們";   
};
Person.prototype.getInfo = function () {
    console.log("name:" + this.name + ",sex:" + this.sex);
};

咱們仔細推敲下這句話「把getInfo方法放入Person的原型中」,Person的原型是Object,那也就是說getInfo方法放到Object裏面去了?
是的,不信請看:

若是原型和原型的原型都實現了一樣的方法呢?咱們來猜猜下面會打印哪一個版本

var Person = function () {
    this.name = "農碼一輩子"; 
};
var Student = function () { }; 
Student.prototype = new Person();//繼承
var stu = new Student();

Student.prototype.getName = function () {
    console.log("個人名字:" + this.name);
}
Person.prototype.getName = function () {
    console.log("My name is:" + this.name);
}

stu.getName();

若是註釋掉中文版呢?

有沒有以爲特神奇,具體緣由咱們用圖來回答:

從另個一角度說,若是對象實現了原型中已有的方法那就等效於C#中虛方法重寫了。

this指向

var name = "張三";
var obj = {
    name:"李四",
    getName: function(){
        console.log(this.name);
    }
}

obj.getName();


這個結果你們應該沒什麼疑問。
接着看下面的:

window.name = "張三";
var obj = {
    name:"李四",
    getName: function(){
        console.log(this.name);
    }
} 
//obj.getName();
window.func = obj.getName;
window.func();


暈了沒有?不要緊,告訴你們一個簡單實用的方法:方法是被誰「.」出來的,this就指向的誰

call

"方法是被誰「.」出來的,this就指向的誰",這個口訣不必定適用全部方法。爲何這麼說呢?請看下面:

window.name = "張三";
var obj = {
    name: "李四",
    getName: function () {
        console.log(this.name);
    }
}
//obj.getName();
window.func = obj.getName;
window.func.call(obj);


雖然仍是window點的,但this已經指向了obj。
由於call能夠改變this執行。
這個特性很是有用。好比,咱們要編寫一個下拉選中事件。

function func() {
    console.log("我點擊了" + $(this).find("option:selected").text());
}

$("#element1").change(function () {
    func.call(this);
});
$("#element2").change(function () {
    func.call(this);
});

在寫func方法的時候不用考慮具體是那個下拉框元素。

apply

apply和call區別不大。

function func(age, sex) {
    console.log("name:" + this.name + ",age:" + age + ",sex:" + sex);
}

var obj = {
    name: "曉梅"
}

func.call(obj, "18", "妹子");
func.apply(obj,["18","小美女"]);


call和apply第一個參數都是this指向的對象。call第二個和之後的參數對應方法func的參數。而apply的第二個參數是個數組,包含方法的全部參數。

band

function func(age, sex) {
    console.log("name:" + this.name + ",age:" + age + ",sex:" + sex);
}

var obj = {
    name: "曉梅"
}
var func1 = func.bind(obj, "18", "妹子");
func1();

和apply、call的區別是,只是改變this指向並不執行。且參數傳入方式和call同樣。

js中的閉包

什麼是閉包?個人理解是存在不能被回收的變量就是閉包。
最多見最大的一個閉包就是全局變量,定義了就不會被銷燬,除非自動設爲null。
而咱們平時說的和使用的閉包卻非如此,但一樣會產生不會被銷燬的變量。好比咱們以前說的私有變量示例:

var Person = (function () { 
    var sex = "純爺們";
    return {
        name: "農碼一輩子",
        getInfo: function () {
            console.log("name:" + this.name + ",sex:" + sex);
        }
    };
})();

之因此說它是閉包,那是由於sex這個字段是永遠不會被銷燬。你想一想,若是被銷燬了,那咱們調用getInfo的時候豈不是找不到sex字段了。因此不是不會銷燬,而是不能銷燬。
閉包的做用不只僅是私有化。咱們再來一例:

for (var i = 0; i < 10; i++) {
    var t = setTimeout(function () {
        console.log(i);
    }, 100);
}


並非咱們想象的那樣打印0到9。
由於計時器還沒開始循環就執行完了。而此時變量i已是10。
咱們能夠經過閉包爲每次循環保存一個閉包變量。

for (var i = 0; i < 10; i++) {
    (function (i) {
        var t = setTimeout(function () {
            console.log(i);
        }, 100);
    })(i);
}

什麼是高階函數

「高階函數」名字特牛逼。其實咱們在js中常用。
仍是私有變量的例子:

var Person = (function () { 
    var sex = "純爺們";
    return {
        name: "農碼一輩子",
        getInfo: function () {
            console.log("name:" + this.name + ",sex:" + sex);
        }
    };
})();
  • 當函數作被return時,那麼就是高階函數。
var getInfo = function (callback) {
    $.ajax('url', function (data) {
        if (typeof callback === 'function') {
            callback(data);
        }
    });
}
getInfo(function (data) {
    alert(data.userName);
});

getInfo在執行的時候,傳入的參數是個函數。

  • 當函數被當成參數傳遞時,那麼這也是高階函數。

 

本文已同步至索引目錄:《設計模式學習》
【demo】:https://github.com/zhaopeiym/BlogDemoCode
【推薦】:深刻理解javascript原型和閉包系列

 

生命在於運動,靜止是[ ]。

相關文章
相關標籤/搜索