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


在寫面向對象編程思想-設計模式中的js部分的時候發現很多基礎知識不了解的話,是很難真正理解和讀懂js面向對象的代碼。為此,在這里先快速補上。然后繼續我們的面向對象編程思想-設計模式

什么是鴨子類型

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

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

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

這只鸚鵡同樣有“走路”和“咕咕咕”的方法,那在js的世界里就可以把它當成鴨子。
可以這樣調用:

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

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

所以js的世界沒有抽象和接口,但可以約定“我們都是鴨子”。

javascript的面向對象

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

封裝

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


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

繼承

  • 字面量表示:
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原型和閉包系列

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM