天天看點

JS面向對象的三大特性簡述

1.文法

1.1 對象的聲明文法

1.1.1通過字面量方式建立對象

var obj = {}
    var obj1 = {
      name: 'hhupp',
      call: function() {
        console.log('CALL')
      }
    }
    console.log('obj', obj)
    console.log('obj1', obj1)
    obj1.call()
           
JS面向對象的三大特性簡述

 1.1.2通過new關鍵詞建立對象(不推薦--和通過字面量方式建立對象并無差别--且使用esLint會有eslint(no-new-object)提示報錯)

var obj = new Object()
    console.log('obj',obj)


    var obj1 = new Object()
    obj1.name = 'hhupp'
    obj1.call = function (){
      console.log('CALL');
    }
    console.log(obj1)
    obj1.call()
           

JS面向對象的三大特性簡述

1.1.3 通過構造函數建立對象

function mySelfObject (name,method) { // 自己編寫的構造函數
        this.name = name
        this.method = method
    }

    //  執行個體化1
    var obj = new mySelfObject()
    console.log('obj',obj)
    //  執行個體化2
    var obj1 = new mySelfObject('hhupp',function (){
      console.log('CALL')
    })
    console.log('obj1',obj1)
    obj1.method()
           

JS面向對象的三大特性簡述
2.特性

 面向對象的三大特性:封裝、繼承、多态。

2.1 封裝

        我們舉個例子,有兩隻小狗A和B這兩隻狗都屬于“狗”這個種族,但是它們是相對獨立的,A和B擁有自己的名字,品種,年齡,會跑,會叫等等,一隻狗就是一個機關,一個整體,然後才會有A和B之間的關系。我們平時所用的方法和類都是一種封裝,當我們在項目開發中,遇到一段功能的代碼在好多地方重複使用的時候,我們可以 把他單獨封裝成一個功能的方法,這樣在我們需要使用的地方直接調用就可以了。

優點:封裝的優勢在于定義隻可以在類内部進行對屬性的操作,外部無法對這些屬性指手畫腳,要想修改,也隻能通過你定義的封裝方法;

2.2.1 工廠模式封裝

工廠模式的原理:在方法内部建立一個object對象,對象的屬性由參數指定,方法直接挂載在對象上,最後傳回這個對象。相當于Dog方法是一個造狗工廠,創造了A和B兩隻狗,
function Dog(name, age, color) {
  var dog = new Object()
  dog.name = name
  dog.age = age
  dog.color = color
  return dog
}
var dogA = Dog('A', '1', '白色')
var dogB = Dog('B', '5', '黑色')
console.log('小狗A的屬性 == ', dogA)
console.log('小狗B的屬性 --> ', dogB)
           
JS面向對象的三大特性簡述
缺點: 無法知道對象的具體的類型

2.2.2 構造函數模式封裝

構造函數模式可以知道小狗A和小狗B這兩隻狗是Dog類型,原理是new關鍵字進行了以下的操作。

1. 建立一個全新的對象

2.這個對象會被執行[[prototype]]連接配接原型

3.方法調用中的this會綁定到新的對象

4.如果構造函數裡沒有傳回其他對象,那麼new構造會自動傳回這個新的對象

function Dog(name, age, color) {
  this.name = name
  this.age = age
  this.color = color
}
var dogA = new Dog('A', '1', '白色')
var dogB = new Dog('B', '5', '黑色')
console.log('小狗A的屬性 == ', dogA)
console.log('小狗B的屬性 --> ', dogB)
           
JS面向對象的三大特性簡述

上述的原型鍊圖示如下: 

JS面向對象的三大特性簡述

2.2 繼承

        繼承可以解決代碼複用的問題,讓代碼更加接近人類的思維。當多個類存在相同的屬性和方法時,可以從這些類中抽象出父類,在父類中定義這些相同的屬性和方法,子類隻需要通過繼承,就可以擁有父類中的屬性和方法,不需要重新進行定義。

2.2.1 通過原型鍊實作繼承

讓一個構造函數的原型是另一個類型的執行個體,那麼這個構造函數new出來的執行個體就具有該執行個體的屬性。

當我們試圖通路一個對象的屬性時,優先從對象本身查找,若未找到,則沿着該對象的原型鍊逐級往上查找,直到查詢到對應的屬性或者達到原型鍊的末尾。

// 父類的構造函數
    function Parent() {
      this.flag = false
      this.info = {
        name: 'hhupp',
        age: '18'
      }
    }
    Parent.prototype.getInfo = function() {
      console.log('flag', this.flag)
      console.log('info', this.info)
    }
    // 子類的構造函數
    function Children() {};
    // 将子類的原型指向父類的執行個體
    Children.prototype = new Parent()
    // 執行個體化一個子類1
    var children1 = new Children()
    children1.info.name = 'children1'
    // 執行個體化一個子類2
    var children2 = new Children()
    children2.flag = true
    children2.sex = '女'
    children2.info.name = 'children2'

    children1.getInfo() // 擷取children1的資料
    children2.getInfo() // 擷取children2的資料
           
JS面向對象的三大特性簡述

優點:代碼友善簡潔,便于了解

缺點:對象執行個體共享所有繼承的屬性和方法,建立子類型執行個體時不能傳遞參數,因為這個對象是一次性建立的(無法進行定制化操作)

2.2.2 借用構造函數繼承

        在子類型的構造函數中調用父類型的構造函數,通過apply()或者call()将父類的構造函數綁定在子對象上

// 父類的構造函數
    function Parent(sex) {
      this.info = {
        name: 'hhupp',
        age: '18',
        sex: sex
      }
    }
    // 子類的構造函數
    function Children(sex) {
      Parent.call(this, sex)
    };

    var children1 = new Children('男')
    children1.info.name = 'children1'
    var children2 = new Children('女')
    children2.info.name = 'children2'

    console.log(children1)
    console.log(children2)
           
JS面向對象的三大特性簡述

 優點:解決了原型鍊實作繼承不能傳參的問題和父類原型共享的問題。

 缺點:借用構造函數實作繼承的缺點是方法都在構造函數中定義,無法實作函數複用。在父類型的原型中定義的方法對子類型來說都是不可見的,導緻所有類型隻能使用構造函數的模式。

2.2.3 組合繼承 (經典繼承)

将原型鍊和借用構造函數的方式組合到一塊,使用原型鍊實作對原型屬性和方法的繼承,通過借用構造函數來實作對執行個體屬性的繼承,這樣既通過再原型上定義方法實作了函數複用,又能保證每個執行個體都有自己的屬性。
// 父類的構造函數
    function Parent(sex) {
      console.log('執行父類構造函數' + sex)
      this.info = {
        name: 'hhupp',
        age: '18',
        sex: sex
      }
    }
    Parent.prototype.getStaffInfo = function() {
      console.log(this.info.name, this.info.sex)
    }
    // 子類的構造函數
    function Children(sex) {
      Parent.call(this, sex)
    };

    Children.prototype = new Parent()

    var children1 = new Children('男')
    children1.info.name = 'children1'
    var children2 = new Children('女')
    children2.info.name = 'children2'

    children1.getStaffInfo()
    console.log(children1)
    children2.getStaffInfo()
    console.log(children2)
           
JS面向對象的三大特性簡述

 優點:解決了原型鍊繼承和借用構造函數繼承造成的影響

 缺點:無論在什麼情況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另一次是在子類型構造函數的内部。

2.2.4 原型式繼承

方法一:借用構造函數在一個函數A内部建立一個臨時的構造函數,将傳入的對象作為這個構造函數的原型,傳回這個臨時類型的一個新執行個體。 函數A本質上是對傳入的對象進行了一次淺拷貝。

function createObject(obj) {
      function Fun() {}
      Fun.prototype = obj
      return new Fun()
    }

    let person = {
      name: 'HHUPP',
      age: 18,
      hoby: ['唱', '跳'],
      showHoby() {
        console.log('my hoby is:', this.hoby)
      }
    }

    let child1 = createObject(person)
    child1.name = 'LSY'
    child1.hoby.push('rap')
    let child2 = createObject(person)
    child2.name = 'ZQJ'
    child2.hoby.push('籃球')

    console.log(child1)
    console.log(child2)
    console.log(person)
           
JS面向對象的三大特性簡述

 方法二:Object.create()

Object.create ()是吧現有對象的屬性,挂到建立對象的原型上, 建立對象為空對象ES5通過增加Object.create()方法将原型式繼承的概念規範化了,這個方法接收兩個參數,作為新對象原型的對象,以及給新對象定義額外屬性的對象(可選),在隻有一個參數時,Object.create()與上述的方法A效果相同。
let person = {
      name: 'HHUPP',
      age: 18,
      hoby: ['唱', '跳'],
      showHoby() {
        console.log('my hoby is:', this.hoby)
      }
    }

    let child1 = Object.create(person)
    child1.name = 'LSY'
    child1.hoby.push('rap')
    let child2 = Object.create(person)
    child2.name = 'ZQJ'
    child2.hoby.push('籃球')

    console.log(child1)
    console.log(child2)
    console.log(person)
           
JS面向對象的三大特性簡述

 2.2.5 ES6 Class實作繼承

原理和ES5的繼承一樣,實質上是先創造子類的執行個體對象this,然後将父類的方法添加到this上。ES6的繼承機制完全不同,實質是先将父類執行個體對象的屬性和方法,加到this上面(是以必須先調用super方法),然後再用子類的構造函數修改this

優點: 文法簡單易懂,操作友善。

 缺點:存在浏覽器不支援ES6文法。

2.3 多态

js的多态:

        子類對父類的方法進行重寫,調用這個方法時,會預設執行子類的方法,即實作多态。不同子類這行父類的同名方法會有不同的結果,除了子類重寫父類的方法外,還有函數參數個數,和參數類型的多态。

function Person() {
      Person.prototype.initfun = function() {
        this.init()
      }
    }
    function Sun1 () {
      this.init = function() {
        console.log('1')
      }
    }
    function Sun2() {
      this.init = function() {
        console.log('2')
      }
    }
    Sun1.prototype = new Person()
    Sun2.prototype = new Person()
    new Sun1().initfun() // Sun1的init方法 // 1
    new Sun2().initfun() // Sun2的init方法 // 2
           

 參數個數不同, 執行不同的代碼

利用arguments.length判斷來實作
function fun() {
      if (arguments.length === 0) {
        console.log('參數為0')
      } else if (arguments.length === 1) {
        console.log('一個參數', arguments[0])
      } else if (arguments.length === 2) {
        console.log('兩個參數', arguments[0], arguments[1])
      }
    }
    fun()
    fun(1)
    fun(1, 2)
           

 參數類型不同的多态

利用typeof判斷argument的類型進行判斷
function fun2() {
      if (typeof arguments[0] === 'object' && typeof arguments[1] === 'string') {
        console.log('類型1')
      }else {
        console.log('類型2')
      }
    }
fun2({a: 1}, 's')
fun2({a: 1}, 1)
           

繼續閱讀