js設(shè)計(jì)模式之面向?qū)ο蟆庋b

前言

最近一直在看typescript,主要的一個(gè)目的是為vue3.x做準(zhǔn)備。3.x版本尤雨溪大神采用typescript重寫底層,具體內(nèi)容可以查看[譯] 尤雨溪:Vue 3.0 計(jì)劃-掘金。在typescript里,幾乎完全采用ES6語(yǔ)法,其中最為重要的還是class類的使用,學(xué)到這塊就有點(diǎn)懵逼了。什么?繼承還可以這樣玩?什么共有私有什么派生類???我之前學(xué)了個(gè)假js吧???

于是回頭來重新梳理學(xué)習(xí)一下面對(duì)對(duì)象編程,這一系列文章全部是基于ES5語(yǔ)法規(guī)則,別跟我說為啥不直接用ES6,ES6它最終也是編譯成ES5運(yùn)行。
小子只是個(gè)前端菜鳥,對(duì)編程還沒有太深入的了解,只是個(gè)代碼搬運(yùn)工。如果文章有什么錯(cuò)誤或者是理解上的錯(cuò)誤,還請(qǐng)指條明路,感激不盡。

這一系列將以面對(duì)對(duì)象三個(gè)特性封裝、繼承、多態(tài)為題進(jìn)行講解,更好的理解面對(duì)對(duì)象編程。

面對(duì)過程編程和面對(duì)對(duì)象編程(OOP)

面對(duì)過程編程

面向過程就是分析出解決問題所需要的步驟,然后用函數(shù)把這些步驟一步一步實(shí)現(xiàn),使用的時(shí)候一個(gè)一個(gè)依次調(diào)用就可以了。

面對(duì)對(duì)象編程

面對(duì)對(duì)象編程就是將你的需求抽象成一個(gè)對(duì)象,針對(duì)這個(gè)對(duì)象分析其特征和動(dòng)作,這個(gè)對(duì)象我們稱之為

對(duì)象基礎(chǔ)知識(shí)

對(duì)象理解

程序員經(jīng)常會(huì)拿對(duì)象來開玩笑:沒對(duì)象?自己new一個(gè)唄,要啥樣都可以。那到底什么是對(duì)象呢?js中有一個(gè)說法是萬物皆對(duì)象對(duì)象就是可以擁有屬性和方法的一個(gè)集合。人,是一個(gè)對(duì)象,他有吃飯睡覺的屬性,有上班賺錢的方法。所有的一切都可以抽象為一個(gè)對(duì)象,只不過每個(gè)對(duì)象都屬于自己的屬性和方法。

什么叫面對(duì)對(duì)象編程???在js中,有兩種編程風(fēng)格。面對(duì)過程編程面對(duì)對(duì)象編程,我們寫輪播圖寫動(dòng)畫寫頁(yè)面,大部分都是面對(duì)過程編程(反正我個(gè)人目前是這樣)。

對(duì)象三大特征

封裝、繼承、多態(tài)

一個(gè)對(duì)象被創(chuàng)建,這中間發(fā)生什么 | new 關(guān)鍵字做了什么(面試題)

 1、創(chuàng)建一個(gè)新的對(duì)象,這個(gè)對(duì)象的類型是 `object`
 2、設(shè)置這個(gè)新的對(duì)象的內(nèi)部、可訪問性和[[prototype]]屬性為構(gòu)造函數(shù)(指prototype.construtor所指向的構(gòu)造函數(shù))中設(shè)置的
 3、將步驟1新創(chuàng)建的對(duì)象作為this的上下文 
 4、果該函數(shù)沒有返回對(duì)象,則返回this。

原型和原型鏈

  • 原型

JavaScript 規(guī)定,每一個(gè)構(gòu)造函數(shù)都有一個(gè) prototype 屬性,指向另一個(gè)對(duì)象。我們可以把所有對(duì)象實(shí)例需要共享的屬性和方法直接定義在 prototype 對(duì)象上。這個(gè)對(duì)象的所有屬性和方法,都會(huì)被構(gòu)造函數(shù)的所擁有。
prototype作為對(duì)象的內(nèi)部屬性,我們一般通過實(shí)例化對(duì)象的__proto__這個(gè)屬性進(jìn)行訪問。
在原型對(duì)象中還有一個(gè)屬性constructor,這個(gè)屬性對(duì)應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù)

image.png
  • 原型鏈

當(dāng)我們?cè)L問對(duì)象的一個(gè)屬性或方法時(shí),它會(huì)先在對(duì)象自身中尋找,如果有則直接使用,如果沒有則會(huì)去原型對(duì)象中尋找,如果找到則直接使用。如果沒有則去原型的原型中尋找,直到找到Object對(duì)象的原型,Object對(duì)象的原型沒有原型,如果在Object原型中依然沒有找到,則返回null。

image.png
function Person(name, age) {
    this.name = name 
    this.age= age

    this.action = function () {
        alert('hello word')
    }

}
var p = new Person('尤雨溪', 18) // 實(shí)例化Person這個(gè)對(duì)象

console.log(p.name) // 尤雨溪

/*
他們之間的關(guān)系

    構(gòu)造函數(shù)的原型 == 實(shí)例化p.__proto__
    構(gòu)造函數(shù) == 構(gòu)造函數(shù)原型的constructor
*/

 console.log(Person.prototype === p.__proto__) // true

 console.log(Person.prototype.constructor === Person) // true

console.log(p.constructor === Person) // true

面對(duì)對(duì)象—封裝

什么是封裝

封裝就是把客觀事物封裝成抽象的類,隱藏屬性和方法的實(shí)現(xiàn)細(xì)節(jié),僅對(duì)外公開接口。也就是說,封裝就是將屬性和方法組成一個(gè)類的過程就稱之為封裝。

對(duì)象封裝的幾種形式

對(duì)象字面量

  • 優(yōu)點(diǎn):代碼簡(jiǎn)單易懂
  • 缺點(diǎn):創(chuàng)建多個(gè)對(duì)象會(huì)產(chǎn)生大量的代碼,編寫麻煩,且并沒有實(shí)例與原型的概念
// 創(chuàng)建對(duì)象
var  Person = {
    name: "尤雨溪",
    age: "18",

    action : function() {
      alert('hello word!');
    }
}
Person.name
Person.action()


// 創(chuàng)建一個(gè)空對(duì)象 給空對(duì)象添加方法屬性
var Person = {}
Person.name = '尤雨溪'
Person.age=  18

Person.action  = function() {
   alert('hello word!');
}

工廠模式

  • 對(duì)象字面量的形式在創(chuàng)建多個(gè)對(duì)象的時(shí)候會(huì)產(chǎn)生大量代碼,如果我們把創(chuàng)建新對(duì)象、添加對(duì)象屬性、返回對(duì)象的過程放到這個(gè)函數(shù)中,每次需要?jiǎng)?chuàng)建對(duì)象的時(shí)候調(diào)用函數(shù)即可。
function createPerson(name, age) {
  var person = new Object();
  person.name = name;
  person.age = age;

  person.action= function() {
    alert('hello word!');
  };
  return person;
}

//生成實(shí)例
var p = createPerson('尤雨溪', 18);
var p2 = createPerson('阮一峰',20)

console.log(p.name)
p.action()
  • 優(yōu)點(diǎn):避免創(chuàng)建大量對(duì)象時(shí)代碼的臃腫
  • 缺點(diǎn): p1與p2之間沒有內(nèi)在聯(lián)系

構(gòu)造函數(shù)

所謂"構(gòu)造函數(shù)",其實(shí)就是一個(gè)普通函數(shù),但是內(nèi)部使用了this變量。對(duì)構(gòu)造函數(shù)使用new運(yùn)算符,就能生成實(shí)例,并且this變量會(huì)綁定在實(shí)例對(duì)象上。

function Person(name, age) {
    // 通過this來添加屬性方法
    this.name = name 
    this.age = age

    this.action = function () {
        alert('hello word')
    }
}
// 實(shí)例化對(duì)象
var p1 = new Person('尤雨溪', 18)
var p2 = new Person('阮一峰', 20)

上面我們說過,通過new關(guān)鍵字實(shí)例化出來的對(duì)象,都會(huì)有一個(gè)constructor屬性指向他們的構(gòu)造函數(shù)

console.log(p1.constructor === Person)     // true
console.log(p2.constructor === Person)     // true
  • 優(yōu)點(diǎn):實(shí)例化對(duì)象和構(gòu)造函數(shù)之間存在關(guān)聯(lián)
  • 缺點(diǎn):浪費(fèi)內(nèi)存,構(gòu)造函數(shù)中定義的方法名action一樣,但實(shí)例化出來的對(duì)象名不一樣,造成一個(gè)內(nèi)存的浪費(fèi)。

原型模式

上面我們說過,每個(gè)對(duì)象被實(shí)例化的時(shí)候,都會(huì)有一個(gè)原型prototype屬性,這個(gè)屬性是可以被其他對(duì)象繼承的。所以我們可以將對(duì)象共有的屬性方法,放到原型上,這樣每次通過new出來的對(duì)象,在構(gòu)造函數(shù)中的方法被多次創(chuàng)建。

function Person(name, age) {
    // 通過this來添加屬性方法
    this.name = name 
    this.age = age
}

// 將共同擁有的方法掛在到原型上
Person.prototype.action = function () {
        alert('hello wodr')
    }
// 實(shí)例化對(duì)象
var p1 = new Person('尤雨溪', 18)
var p2 = new Person('阮一峰', 20)

p1.action()
p2.action()

屬性和方法的類型

通過原型模式我們可以將方法屬性直接放在原型上,所有的實(shí)例對(duì)象都可以訪問這個(gè)屬性方法,我們稱為公有方法/屬性。熟悉ES6的朋友知道,在用class定義類的時(shí)候,我們可以定義里面屬性方法的類型。是保護(hù)類型,還是公開類型,還是私有類型,不同類型之間是不能相互訪問的。在ES6中我們可以通過public等關(guān)鍵字來定義,那我們ES5是怎樣區(qū)分他們的一個(gè)類型呢?

我之前在學(xué)習(xí)面對(duì)對(duì)象這一塊的時(shí)候沒有太過注意這一塊內(nèi)容,現(xiàn)在我們開發(fā)也很少注意到這一塊內(nèi)容。其實(shí)在快速開發(fā)的時(shí)期,我們很少注意到這些javascrip基礎(chǔ)理論知識(shí),更缺乏對(duì)原生js的理解。

在面向?qū)ο笾校總€(gè)屬性都有它的一個(gè)類型,可以分為共有、私有、靜態(tài)三種。

共有屬性和方法

從字面量上的意思理解,共有,就是所有對(duì)象都有的屬性或方法。

function Person(name, age) {
    // 通過this來添加屬性方法

    this.name = name  // 共有屬性
    this.age = age        
}

// 將共同擁有的方法掛在到原型上
Person.prototype.action = function () {  // 共有方法
    console.log(`${this.name}是干${this.job}的`)
}
// 實(shí)例化對(duì)象
var p1 = new Person('尤雨溪', '程序員')

p1.action()

從上面代碼可以看出,通過this添加的屬性或者在原型上添加的屬性和方法都是共有的

  • 調(diào)用規(guī)則: 調(diào)用公有方法,我們必需先實(shí)例化對(duì)象

私有屬性和方法

私有屬性的理解可以從函數(shù)的作用域出發(fā),函數(shù)內(nèi)部定義的變量在函數(shù)外部無法訪問。我們看下面栗子

function Person(name, job) {
    // 通過this來添加屬性方法
    var name = name  // 私有屬性
    var job = job

    function action () { // 私有方法
       console.log(`${name}是干${job}的`)
    }
}
// 實(shí)例化對(duì)象
var p = new Person('尤雨溪',  '程序員')

console.log(p.name)       // undefined
p.action()   //  p.action is not a function 報(bào)錯(cuò)

當(dāng)實(shí)例化創(chuàng)建對(duì)象的時(shí)候,通過var定義的局部變量是無法在外界被訪問,他只能在構(gòu)造函數(shù)內(nèi)部進(jìn)行使用,并且不能通過this訪問,我們吧代碼變動(dòng)一下再看。

function Person(name, job) {
    // 通過this來添加屬性方法
    var name = name  // 私有屬性
    var job = job

    function action1 () { // 私有方法
       console.log(`${name}是干${job}的`, '通過this調(diào)用')
    }
  
    function action2 () { // 私有方法
       console.log(`${name}是干${job}的`, '在內(nèi)部調(diào)用')
    }
    
    // 這樣是不能調(diào)用的
    this.action1()
    this.name

    action()
}
// 實(shí)例化對(duì)象
var p = new Person('尤雨溪',  '程序員')    //尤雨溪是干程序員的 在內(nèi)部調(diào)用



  • 調(diào)用規(guī)則: 對(duì)象的私有方法和屬性,外部是不可以訪問的,只能在內(nèi)部使用

特權(quán)方法

在函數(shù)內(nèi)部通過this創(chuàng)建的屬性和方法,在創(chuàng)建對(duì)象時(shí),每個(gè)對(duì)象自身都會(huì)擁有一份并且可以在外部可以訪問到。通過this創(chuàng)建的屬性可以看作對(duì)象的共有屬性,而通過this創(chuàng)建的方法不但可以訪問這些對(duì)象的共有屬性方法,還能訪問自身的私有屬性方法,被稱之為特權(quán)方法

function Person(name, job) {
    // 通過this來添加屬性方法
    var name = name  // 私有屬性
    this.job = job   // 共有屬性

    this.action = function () { // 特權(quán)方法
        console.log(`${name}是干${this.job}的`)
    }

}
// 實(shí)例化對(duì)象
var p = new Person('尤雨溪', '程序員')
console.log(p.name)
p.action()     // 尤雨溪是干程序員的

  • 調(diào)用規(guī)則:通過實(shí)例化,可以訪問共有屬性方法和私有屬性方法

靜態(tài)屬性和方法

共有屬性方法和私有屬性方法都用通過new創(chuàng)建對(duì)象才能訪問,而靜態(tài)屬性方法不需要通過new關(guān)鍵字創(chuàng)建,就可以直接訪問。


function Person(name, job) {
    var name = name  // 私有屬性
    this.job = job   // 共有屬性

    this.action = function () { // 特權(quán)方法
        console.log(`${name}是干${this.job}的`)
    }

}
Person.attr = '靜態(tài)屬性'
Person.say = function () {
    console.log('大家好,我是靜態(tài)方法')
}

console.log(Person.attr) // 靜態(tài)屬性
Person.say() // 大家好,我是靜態(tài)方法

// 實(shí)例化對(duì)象
var p = new Person('尤雨溪', '程序員')
console.log(p.attr ) // undefined
p.say()     // 報(bào)錯(cuò)

  • 調(diào)用規(guī)則:靜態(tài)方法無需實(shí)例化對(duì)象,便可以調(diào)用,對(duì)象實(shí)例不能調(diào)用對(duì)象的靜態(tài)方法,只能調(diào)用實(shí)例自身的靜態(tài)屬性和方法。

靜態(tài)類

前面我們說過,對(duì)象創(chuàng)建有好幾種形式,那我們通過字面量的形式創(chuàng)建的對(duì)象,它的類型時(shí)什么呢?對(duì)象字面量形式創(chuàng)建的對(duì)象添加的方法只能時(shí)靜態(tài)屬性和靜態(tài)方法,而包含靜態(tài)屬性和靜態(tài)方法的類叫靜態(tài)類

var person = {
    name: "尤雨溪",
    age: "18",

    action : function() {
      console.log('hello word!');
    }
}
person.attr = '我是靜態(tài)屬性'
person.say = function () {
    console.log('大家好,我是靜態(tài)方法')
}

console.log(person.name)
person.action()

person.attr 
person.say()
  • 調(diào)用規(guī)則:不需要通過new創(chuàng)建對(duì)象

相關(guān)文章

[譯] 尤雨溪:Vue 3.0 計(jì)劃-掘金
js面向過程編程與面向?qū)ο缶幊痰膮^(qū)別
JS面向?qū)ο缶幊讨庋b
Javascript 面向?qū)ο?共有方法,私有方法,特權(quán)方法,靜態(tài)屬性和方法,靜態(tài)類)示例講解


更多文章訪問個(gè)人博客:http://www.lfanliu.top

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容