JavaScript中級指南-03 面向對象編程

今天在羣裏有大佬發了一份杭州二線公司中的某一道面試題, 引發了討論, 所以我順便回顧並總結了一下關於面向對象編程的筆記。面試

function Child() {}
function Parent() {
  this.a = 1
}
const c = new Child()
Child.prototype = new Parent()
console.log(c.a) // 答案是什麼?
複製代碼

面向對象 VS 面向過程

在沒有學習面向對象的編程時, 我全部的代碼都是基於面向過程編程的, 那麼面向對象編程和麪向過程編程是如何理解呢?編程

面向過程

面向過程就是分析出解決問題所須要的步驟,而後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就能夠了。
複製代碼

可是若是想要複用(例如一個小彈窗)和擴展就比較麻煩, 須要去修改代碼才能實現數組

面向對象

面向對象是把構成問題事務分解成各個對象,創建對象的目的不是爲了完成一個步驟,
而是爲了描敘某個事物在整個解決問題的步驟中的行爲。
複製代碼

例如: 咱們封裝了一個類(抽象的寫成一個類),仍是彈窗的例子,咱們只須要將觸發彈窗的元素和不用的樣式傳入便可展現不一樣的彈窗bash

面向對象

三大特性

  • 封裝
  • 繼承
  • 多態

關於new

  • 知識點一: 普通函數和new執行函數的區別
function fn () {console.log(this)}
fn() // this指向window
new fn() // this指向{}
複製代碼

1. new執行的函數, 函數內部默認生成了一個對象函數

2. 函數內部的this默認指向了這個new生成的對象性能

3. new執行函數生成的這個對象, 是函數的默認返回值學習

  • 知識點二: 萬物皆對象
在我以前的JavaScript基礎筆記中你們能夠去查看js中的七大數據類型
其建立方式有:
let str = '番茄' // 字面量
let str = String('番茄') // 字面量
let str = new String('番茄') // 直接量, 字符串類型對象
// 其他的數據類型大體相同
複製代碼

看到了這裏你們大概應該理解爲何會說萬物皆對象了, 你們能夠本身實踐去打印一下最頂層的__proto__大數據

構造函數 / 類

function Person () {} // 行業內默認函數名首字母大寫便是構造函數 / 類
let p1 = new Person()
let p2 = new Person()
console.log(p1 === p2) // false 
複製代碼

構造函數new執行返回的對象每次都是一個最新的, 永遠不會有相等的狀況ui

  • 私有屬性
function Person (obj) {
    this.name = obj.name
    this.age = obj.age
} 
let p1 = new Person({name: '番茄', age: 18})
let p2 = new Person({name: '炒蛋', age: 22})
console.log(p1.name) // 番茄
console.log(p2.name) // 炒蛋
複製代碼
  • 共有屬性
function Person () {} 
// prototype ===> 原型
Person.prototype = {
    playGame(value) {
        console.log(value)
    }
}
let p1 = new Person()
let p2 = new Person()
p1.playGame('打籃球') // 打籃球
p1.playGame('踢足球') // 踢足球
複製代碼

經過上述列子, 能夠得出一個結論, 在構造函數Person身上的prototype也就是原型中的方法, 是你們共有的方法, Person的實例化對象均可以調用。this

  • 高大上的名字
function Person () {}   // 構造函數或者類
Person.prototype        // 構造函數的原型
new Person()            // 構造函數的實例化或者類的實例化
let p1 = new Person()   // 實例或者Person的實例化對象
複製代碼

原型

原型本質是一個JSON格式的對象{}

考慮一個問題,對象是被這個對象對應的構造函數/類實例出來的, 那麼原型也是個對象,原型這個對象也是這個原型對象對應的類的實例出來的, 由此而延伸出原型鏈

哈哈 有沒有一種雞生蛋,蛋生雞的趕腳~~

原型的擴展

function Person () {}
// 方法一
Person.prototype.add = function () {}
// 方法二
Person.prototype = {
    // 注意若是經過等號賦值擴展原型, 必定要把constructor屬性指向當前構造函數 / 類
    constructor: Person, 
    add () {}
}
複製代碼

繼承

  • 私有屬性的繼承是模仿一個類似的
  • 原型的繼承是指同一個

ES5構造函數的寫法

function Person (name, age) {
    this.name = name
    this.age = age
 }
 Person.prototype = {
    constructor: Person,
    add (){},
    sub (){},
    ......
 }
 const obj = new Person('番茄', 20)
複製代碼

ES5構造函數的繼承方法

關於繼承能夠理解爲 "兒子繼承老子, 老子有的兒子都有"

  • 方案一(有缺陷)
function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
// 注意猶豫原型是對象,引用型數據,Person1原型的擴展會致使Person的原型也出現擴展的方法
Person1.prototype = Person.prototype 
Person1.prototype.play = function(value){
    console.log(value)
}
var p2 = new Person1({name:"雞蛋", age: 118, sex: "男"})
複製代碼

方案一 : 實現繼承, 可是子類的原型擴展父類也能夠用,不合適PASS

  • 方案二(有缺陷)
function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
Person1.prototype = new Person({})
Person1.prototype.play = function(value){
    console.log(value)
}
var p2 = new Person1({name:"雞蛋", age: 118, sex: "男"})
複製代碼

方案二 : 實現繼承, 而且子類原型擴展不會影響父類原型, 可是子類的原型上出現了父類的屬性,而且值爲undefined, 不合適PASS

  • 方案三(解決方案)

藉助一個無用的構造函數

function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
// 這是一個無用的構造函數, 沒有私有屬性
function Person2(){}
Person2.prototype = Person.prototype
// 這是一個無用的構造函數, 它的原型等於Person的原型
Person1.prototype = new Person2()
Person1.prototype.play = function(value){
    console.log(value)
}
var p2 = new Person1({name:"雞蛋", age: 118, sex: "男"})
複製代碼
  • 方案四(完美解決方案)

藉助寄生組合繼承

function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
Person1.prototype = Object.create(Person.prototype)
Person1.prototype.play = function(value){
    console.log(value)
}
var p2 = new Person1({name:"雞蛋", age: 118, sex: "男"})
複製代碼

ES6構造函數的寫法

// 經過class關鍵字聲明
class Person {
    // 私有屬性(固然這不是真正意義上的私有屬性, 只是爲了區分)
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    // 公有方法, 注意ES6寫法中, constructor以外的方法都是公有方法, 且不容許定義屬性。ES5寫法是能夠的。
    add () {}
    sub () {}
}
const obj = new Person('番茄', 18) // 注意ES6寫法必須經過new執行, 不然報錯
複製代碼

ES6構造函數的繼承方法

class Parent {
    constructor(data){
        this.name = data.name
    }
    add (value) {
        console.log(value)
    }
}
// 子類繼承Person
class Person extends Parent {
    constructor(data) {
        super(data) // 子類繼承父類, 必須在子類的constructor方法中調用super方法
        this.age = data.age
    }
    play (value) {
        console.log(value)
    }
}
let p1 = new Parent({name: "番茄"})
let p2 = new Person({name: "番茄", age: 18})
複製代碼

實例對象的__proto__和構造函數的prototype的關係

function Person () {}
Person.prototype.add = function () {}
const p1 = new Person()

// 仔細觀察這個對象會發現
console.log(p1) 
// 實例的__proto__等於構造函數的prototype
console.log(p1.__proto__ === Person.prototype) 
複製代碼

注意: 原型的擴展必定要在實例化以前

得出結論: 實例的__proto__指向了這個實例對應的類的prototype

for in (題外話)

for in 遍歷性能極其的差, 緣由就是它在遍歷的過程當中會遍歷原型上的屬性

hasOwnProperty(題外話)

console.log(實例.hasOwnProperty('屬性名')) // 判斷是不是原型上的屬性
複製代碼

關於對象的深淺拷貝(題外話)

  • 方案一 ( 支持 {}, [], function拷貝 )
// data要拷貝的數組或者對象, deep是否開啓深淺拷貝
// 支持函數拷貝, 由於函數通常在定義的時候就已經規定了好了執行的內容, 只是傳參的不一樣
function extend(data, deep) {
    // deep true  深拷貝, false 淺拷貝
    var obj = {}
    // 判斷要拷貝的數據是否是數組
    if (data instanceof Array) {
        obj = []
    }
    for (let key in data) {
        let value = data[key]
        // 肯定value是否是引用型數據, 前提deep 是true, 而且value的類型爲對象, 且不爲null
        //  deep不傳爲undefined, 雙重取反即爲false, 默認淺拷貝
        obj[key] = (!!deep && typeof value === 'object' && value !== null) ? extend(value, deep) : value
    }
    return obj
}
複製代碼
  • 方案二 ( 支持 {}, [] )

簡單粗暴的方法, 可是它不支持函數

var obj = {
    a: 1,
    b: {
        bb: 1,
        cc: 2
    }
}
var obj2 = JSON.parse(JSON.stringify(obj)) // 數組也同樣
複製代碼
相關文章
相關標籤/搜索