1. 程式人生 > 實用技巧 >面向物件程式設計思想--你必須知道的javascript

面向物件程式設計思想--你必須知道的javascript

閱讀目錄

在寫面向物件程式設計思想-設計模式中的js部分的時候發現很多基礎知識不瞭解的話,是很難真正理解和讀懂js面向物件的程式碼。為此,在這裡先快速補上。然後繼續我們的面向物件程式設計思想-設計模式。 什麼是鴨子型別 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原型和閉包系列

***************轉摘:https://www.cnblogs.com/zhaopei/p/6623460.html#autoid-4-0