淺談js面向物件的寫法
阿新 • • 發佈:2020-09-06
淺談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('',''))