1. 程式人生 > 實用技巧 >淺談js面向物件的寫法

淺談js面向物件的寫法

淺談js面向物件

/**
 * 淺談js面向物件
 * author: Mr Lee (James Lee)
 */

/* 一、建立一個類  
建立一個類(具有相同屬性和行為的物件的集合 */

const User = function (id, name, age) {
    this.id = id
    this.name = name
    this.age = age
}

// User.prototype:原型物件相當於一個儲存公共方法的倉庫
User.prototype.getName = function () {
    return this.name
}

User.prototype.setName = function (name) {
    this.name = name
    // 鏈式程式設計
    return this
}

User.prototype.getAge = function () {
    return this.age
}

User.prototype.setAge = function (age) {
    this.age = age
    return this
}

User.prototype.getId = function () {
    return this.age
}
// 建立一個空物件,並改變this指向
var James = new User(520, 'Mr Lee', 18)
console.log(James.getName(), James.getAge())

// 鏈式程式設計
James.setName('Miss Lee').setAge(16)
console.log(James)
console.log('*******************************************************')

/* 二、封裝屬性與方法 */

const UserA = function (id, name, age, hobby) {
    this.id = id
    this.name = name
    this.age = age
    // 私有屬性 無法繼承   私有靜態屬性:使用閉包
    var hobby = hobby
    // 私有方法
    function setHobby(hobby) {
        hobby = hobby
    }

    function getHobby() {
        return hobby
    }

    this.getName = function () {
        return name
    }

    this.setName = function (name) {
        this.name = name
        return this
    }

    this.getAge = function () {
        return age
    }

    this.setAge = function (age) {
        this.age = age
        return this
    }
}

// 靜態公有屬性
UserA.isChinese = true
// 靜態公有方法
UserA.eat = function (food) {
    console.log('I am eating' + food)
}
// 靜態公有屬性、方法儲存區
UserA.prototype = {
    // 新建立物件可以通過原型鏈訪問到這些屬性和方法
}

const JamesA = new UserA(520, 'JamesA', 18, 'code')
console.log(JamesA)
console.log(JamesA.getName(), JamesA.getAge())

JamesA.setName('Miss JamesA')
JamesA.setAge(16)
console.log(JamesA.getName(), JamesA.getAge())

// 靜態公有屬性,必須通過類名訪問     
console.log(JamesA.isChinese, UserA.eat('apple'))

console.log('*******************************************************')

/* 三、使用閉包實現類的靜態變數
在C語言中,靜態變數 static, 儲存在RAM的靜態儲存區 */

/* 立即執行函式:一個閉包
給老專案新增新功能時,可以使用閉包,避免對專案其他功能產生副作用
注意:閉包引用不釋放,會導致記憶體洩漏;使用完UserB後置為null  UserB = null */
const UserB = (function () {
    // 靜態私有變數
    const isChinese = true
    // 靜態私有方法
    function eat(food) {
        console.log('I am eating' + food)
    }
    return function () {
        this.id = id
        this.name = name
        this.age = age
        /* ...... */
    }
})()

/* ************************************************************** */

/* 四、通過一個安全的方式建立物件
通過類來建立物件時,可能會忘記使用new */

// new的作用:糾正this的指向,指向新建立的物件;否則就是——誰用誰知道
const UserC = function (id, name, age) {
    this.id = id,
        this.name = name,
        this.age = age
}

const JamesC = UserC(110, 'JamesC', 18)
// undefined     this,誰用誰知道   嚴格模式執行undefined  否則指向window
console.log(JamesC)

// 怎樣避免沒有使用new 導致this指向異常?
const UserD = function (id, name, age) {
    // 檢測this指向是否異常,如果沒使用new,那麼this指向undefined或window
    var hasNew = this instanceof UserD
    if (hasNew) {
        this.id = id
        this.name = name
        this.age = age
    } else {
        // 否則就New一個
        return new UserD(id, name, age)
    }
}

const JamesD = UserD(110, 'JamesD', 18)

console.log(JamesD)

console.log('*******************************************************')

/* ************************************************************** */

/* 
封裝:隱藏底層的複雜性,提供一個統一的介面,降低模組之間的耦合性;隱藏資料,提高安全性
繼承:方便程式碼複用,提高程式的可拓展性
多型性:介面複用  
*/

/* 五、實現繼承 */

/* 通過類來繼承 */
// 子類的原型物件是父類的例項物件
// 建構函式自身沒有的屬性或方法會在原型鏈上向上查詢
function Human() {
    this.sleepTime = '8h'
    this.nature = ['sleep', 'eat', 'lazy']
    this.ability = function () {
        console.log('make some tools')
    }
}

const UserE = function (id, name, age) {
    this.id = id
    this.name = name
    this.age = age
}

UserE.prototype = new Human()

const JamesE = new UserE(110, 'JamesE', 18)
// 修改複雜型別產生副作用
JamesE.nature.push('handsome')

const BrainE = new UserE(110, 'BrainE', 18)
// 通過原型鏈物件繼承的屬性與方法
console.log(JamesE.sleepTime)
JamesE.ability()

/* 
注意:使用類繼承,子類對父類中的複雜型別資料採用的是引用訪問,
如果其中一個子類修改了引用型別的資料 
那麼將會對另外一個物件產生副作用
*/
console.log(BrainE.nature)

console.log('-------------------------------------------------------')

// 類繼承的另一種形式
function inheritObj(obj) {
    // 建立一個空函式物件
    function Func() { }
    Func.prototype = obj
    return new Func()
}

var Person = {
    nickname: '',
    name: '',
    age: 0,
    hobby: [],
    getHobby: function () {
        console.log(this.hobby)
    }
}

const personA = inheritObj(Person)
const PersonB = inheritObj(Person)

personA.nickname = 'James'
personA.name = 'Mr Lee'
personA.age = 20
var arr = ['games', 'music', 'sports']
arr.forEach((item) => {
    personA.hobby.push(item)
})

PersonB.name = 'Brain'

personA.getHobby()
// PersonB繼承的複雜型別屬性hobby跟PersonA的一致
PersonB.getHobby()
console.log(PersonB)

console.log('*******************************************************')


/* 通過建構函式繼承 */
// 精華:call、apply
function HumanA() {
    this.sleepTime = '8h'
    this.nature = ['sleep', 'eat', 'lazy']
}

HumanA.prototype.ability = function () {
    console.log('make some tools')
}

const UserF = function (id, name, age) {
    // 在子類的建構函式環境中執行一次父類的建構函式實現
    HumanA.call(this)
    this.id = id
    this.name = name
    this.age = age
}

const JamesF = new UserF(112, 'JamesF', 20)
JamesF.nature.push('handsome')

const BrainF = new UserF(112, 'BrainF', 20)

console.log(JamesF)
console.log(BrainF)

console.log('*******************************************************')

/* error : 該繼承方式沒有涉及原型鏈prototype,所以子類無法繼承父類原型方法
如果要繼承該方法,就必須將方法放在建構函式中;但是這樣創建出來的物件例項都會單獨擁有一份方法,而不是共用 */
/* JamesF.ability() */

/* 為了解決上面遇到的問題,可以將類繼承與構造器繼承結合在一起 */
/* 組合繼承  類繼承 + 建構函式繼承 */

function HumanB() {
    this.sleepTime = '8h'
    this.nature = ['sleep', 'eat', 'lazy']
}

HumanB.prototype.getNature = function () {
    console.log(this.nature)
}

const UserG = function (id, name, age) {
    // 建構函式繼承   避免了繼承父類複雜型別屬性時,繼承的是引用
    HumanB.call(this)
    this.id = id
    this.name = name
    this.age = age
}
// 類繼承  可以訪問到父類原型鏈上的方法
// 注意:這裡會再次執行一遍父類構造
UserG.prototype = new HumanB()

UserG.prototype.ability = function () {
    console.log('make some tools')
}

const JamesG = new UserG(10, 'JamesG', 20)
const BrainG = new UserG(10, 'BrainG', 20)
JamesG.nature.push('handsome')
// 可以訪問父類原型鏈上的方法,同時修改繼承下來的複雜型別屬性,不會對其他例項物件造成影響
JamesG.getNature()
BrainG.getNature()

JamesG.ability()
/* 缺點:
1. 在使用建構函式繼承的時候,執行了一遍父類建構函式;當實現子類的類繼承時,又會再次執行一遍父類構造 
2. 子類不是父類的例項,但是子類的原型是父類的例項
*/

console.log('*******************************************************')

/* 寄生式繼承 */

// function inheritObj(obj) {
//     // 建立一個空函式物件
//     function Func() { }
//     Func.prototype = obj
//     return new Func()
// }

const PersonA = {
    name: '',
    age: 0,
    gender: 'male',
    hobby: []
}

function createPerson(obj) {
    // new一個新物件
    var newObj = new inheritObj(obj)
    newObj.getName = function () {
        console.log(this.name)
        return this
    }
    newObj.getAge = function () {
        console.log(this.age)
        return this
    }
    newObj.getHobby = function () {
        console.log(this.hobby)
        return this
    }
    return newObj
}

const JamesLee = createPerson(PersonA)
JamesLee.name = 'JamesLee'
JamesLee.hobby = ['music', 'sports']

const BrainChen = createPerson(PersonA)
BrainChen.name = 'BrainChen'
// 子類例項物件繼承自父類的複雜型別屬性不會相互影響
JamesLee.getName().getAge().getHobby()
BrainChen.getName().getAge().getHobby()

console.log('*******************************************************')


/* 終極繼承方式:寄生 + 組合 */

const UserH = function (id, name, age) {
    this.id = id
    this.name = name
    this.age = age
}
// 父類原型方法
UserH.prototype.getName = function () {
    console.log(this.name)
}

const PersonH = function (id, name, age) {
    // 建構函式繼承   避免複雜型別屬性繼承的是引用  
    UserH.call(this, id, name, age)
    // 子類拓展屬性
    this.hobby = []
}

/* 
通過組合繼承,無法繼承父類的原型物件,畢竟不是通過prototype 
而且組合繼承有個缺點:就是父類的建構函式在子類的建構函式中執行一次外,還需要在類繼承的時候再次呼叫一次
但是隻需要 繼承父類的原型物件 即可,沒必要去呼叫兩次
*/
function inheritPrototype(sub, sup) {
    // 建立一個臨時物件,繼承父類的原型物件
    var temp = inheritObj(sup.prototype)
    // 糾正臨時物件構造器指向
    temp.constructor = sub
    // 設定子類的原型
    sub.prototype = temp
}
// 子類繼承父類的原型物件   
inheritPrototype(PersonH, UserH)
// 子類在原型物件上新增方法
PersonH.prototype.getHobby = function () {
    console.log(this.hobby)
    return this
}

const JamesH = new PersonH(111, 'JamesH', 18)
const BrainH = new PersonH(110, 'BrainH', 16)

JamesH.hobby = ['music', 'game']
JamesH.getHobby().getName()

BrainH.hobby = ['sports']
BrainH.getHobby().getName()

console.log('*******************************************************')


/* 六、實現多繼承   java不支援,c++支援*/
// javascript僅一條原型鏈,理論上不支援多繼承,但是可以向java一樣通過多重繼承來實現多繼承

// 實現一個單繼承 ------屬性複製(淺拷貝)
// Object.assign(target, source)
const extend = function (target, source) {
    for (let property in source) {
        target[property] = source[property]
    }
    return target
}

const JamesI = {
    name: 'james',
    age: 18,
    gender: 'male'
}

const BrainI = {}

const res = extend(BrainI, JamesI)
console.log(res)
// 這種複製僅對簡單型別有效,複雜型別拷貝的是引用
console.log('*******************************************************')

// 單繼承------屬性複製(深拷貝)
const extendX = function (target, source) {
    for (let property in source) {
        // 判斷屬性型別
        const type = Object.prototype.toString.call(source[property])

        if (type === '[object Array]') {
            target[property] = []
            source[property].forEach(i => target[property].push(i))
        } else if (type === '[object Object]') {
            target[property] = {}
            extendX(target[property], source[property])
        }
        target[property] = source[property]
    }
    return target
}

const JamesJ = {
    name: 'Mr Lee',
    nickname: 'James Lee',
    hobby: ['music', 'sports'],
    grade: {
        math: 100,
        english: 100,
    }
}

const BrainJ = {}

const resX = extendX(BrainJ, JamesJ)
console.log(resX)

console.log('*******************************************************')

/* 實現多繼承--------拷貝多個物件屬性 */

const multiInherit = function() {
    let length = arguments.length
    let target = arguments[0]
    let source
    for(let i=1; i<length; i++) {
        source = arguments[i]
        extendX(target, source)
    }
    return target
}

// Object.prototype.multiInherit = multiInherit

const brand = {
    brandName: '',
    brandValue: '',
}

const appearance = {
    height: '',
    width: '',
    color: ['red','green','pink']
}

const car = {
    price: '',
    manufacturer: ''
}

const tesla = {}
multiInherit(tesla,car,appearance,brand)

console.log(tesla)

console.log('*******************************************************')

/* 實現多型性-------一個方法的多種呼叫方式*/

const add = function() {
    var num = arguments.length
    const pickUp = {
        0: zero,
        1: one,
        2: two
    }

    function zero() {
        return 0
    }

    function one() {
        return 1
    }

    function two() {
        return 2
    }
    
    return pickUp[num]()
}
// 引數型別或數量不同,返回的結果就不同
console.log(add(),add(''),add('',''))