物件建立模式

完顏阿骨折發表於2019-04-11

每一個物件都是基於引用型別建立的, 也可以是開發人員自己建立的

建立物件

字面量模式

  • 缺點:
    • 會產生大量重複程式碼, 使用變體 工廠模式
    let Person = {
        name: 'agan',
        age: 30,
        job: 'it',
        sayName: function() {
            console.log(this.name)
        }
    }
複製程式碼

工廠模式

這種模式抽象了建立具體函式的過程, 用函式來封裝以特定介面建立物件的細節

    function createPerson(name, age, job) {
        let o = new Object()
        o.name = name
        o.age = age
        o.job = job
        o.sayName = function() {
            console.log(this.name)
        }
        return o
    }
    let p1 = createPerson('agan1', 30, 'it')
    let p2 = createPerson('agan2', 40, 'cs')
複製程式碼

函式 createPerson 接受三個引數來構建一個物件, 該函式可以多次呼叫. 工廠模式雖然解決了建立多個相似物件的問題, 卻沒有解決物件識別的問題(即怎樣知道一個物件的型別)

建構函式模式

建構函式可以用來建立特定型別的物件, 我們可以建立自定義的建構函式, 從而可以定義自定義物件型別的屬性和方法

  • 任何函式都可以當成建構函式
  • 只要把一個函式通過new的方式來進行呼叫, 我們就把這一次函式的呼叫方式稱之為: 建構函式的呼叫
    function Person(name, age, job) {
        this.name = name
        this.age = age
        this.job = job
        this.sayName = function() {
            console.log(this.name)
        }
    }
    let p1 = new Person('agan1', 30, 'it')
    let p2 = new Person('agan2', 40, 'cs')
複製程式碼

PersoncreatePerson 不同:

  1. 沒有顯式建立物件
  2. 將屬性與方法給了 this
  3. 沒有 return 語句

p1p2 儲存著 Person 的兩個不同例項, 但都有一個 constructor (建構函式) 屬性, 指向 Person

    console(p1.constructor == Person); //true
    console(p2.constructor == Person); //true
複製程式碼

constructor 是用來表示物件型別的, instanceof 檢測物件型別更靠譜

    alert(person1 instanceof Object);  //true
    alert(person1 instanceof Person);  //true
    alert(person2 instanceof Object);  //true
    alert(person2 instanceof Person);  //true
複製程式碼
建構函式的執行過程

var p1=new Person();

  • 1、建立一個物件 (我們把這個物件稱之為Person建構函式的例項)- _p1
  • 2、建立一個內部物件,this,將this指向該例項(_p1)
  • 3、執行函式內部的程式碼,其中,操作this的部分就是操作了該例項(_p1)
  • 4、返回值:
    • a、如果函式沒有返回值(沒有return語句),那麼就會返回建構函式的例項(p1)

    • b、如果函式返回了一個基本資料型別的值,那麼本次建構函式的返回值是該例項(_p1)

          function fn(){}
          var f1 = new fn();    //f1就是fn的例項
          function fn2(){
              return "abc";
          }
          var f2 = new fn2();   //f2是fn2建構函式的例項
      複製程式碼
    • c、如果函式返回了一個複雜資料型別的值, 那麼本次函式的返回值就是該值

          function fn3(){
              return [1,3,5]; 
              //陣列是一個物件型別的值,
              //所以陣列是一個複雜資料型別的值
              //-->本次建構函式的真正返回值就是該陣列
              //-->不再是fn3建構函式的例項
          }
          var f3 = new fn3();   //f3還是fn3的例項嗎?錯
          //f3值為[1,3,5]
      複製程式碼
將建構函式當函式
  • 任何函式都可以是建構函式, 必須使用 new 呼叫
  • 任何函式只要不通過 new 呼叫就是普通函式
    // 當作建構函式
    let person = new Person("agan", 29, "se");
    person.sayName() // 'agan'
    
    // 作為普通函式呼叫
    Person("agan1", 27, "IT"); // 新增到 window
    window.sayName(); // "agan1"
    
    // 在另一個物件的作用域中呼叫
    var o = new Object();
    Person.call(o, "agan3", 25, "Nurse")
    o.sayName(); // "agan3"
複製程式碼
建構函式問題

就是每個方法都要在每個例項上重新建立一遍, sayName 指向不同的地址

    person1.sayName === person2.sayName // true
複製程式碼

可以將 sayName 方法轉移到建構函式外部解決這個問題

    function Person(name, age, job) {
        this.name = name
        this.age = age
        this.job = job
        this.sayName = sayName
    }
    function sayName() {
        console.log(this.name)
    }
    let person1 = new Person("agan1", 29, "se");
    let person2 = new Person("agan2", 27, "it");
    
    person1.sayName === person2.sayName  // true
複製程式碼

上面程式碼中將 sayName 函式轉移到了建構函式的外部, 在函式內部我們將 sayName 指向這個全域性函式, 這樣就解決了多個函式做一件事情的問題, 但又帶來了兩個新的問題:

  • 全域性作用域的函式只能被某個物件呼叫
  • 如果物件需要多個方法那麼就需要定義多個全域性函式

原型模式

我們建立的每一個函式都有一個 prototype 屬性, 這是一個指標, 指向了一個物件, 這個物件包含了特定型別物件的所有公共屬性和方法, 相當於一個父類

    function Person() {
    }
    
    Person.prototype.name = 'agan'
    Person.prototype.age = '30'
    Person.prototype.job = 'it'
    Person.prototype.sayName = function() {
        console.log(this.name)
    }
    
    let p1 = new Person()
    let p2 = new Person()
    
    p1.sayName() // 'agan'
    p2.sayName() // 'agan'

    console.log(p1.sayName === p2.sayName) // true
複製程式碼

上述程式碼中 person1person2 訪問的是同一組物件和方法 假如我們想在所有的例項中共享一個資料, 那麼我們可以使用原型模式, 可是一般例項都有自己的屬性

組合使用建構函式與原型模式

建構函式用於建立例項屬性, 原型模式用於定義方法和共享的屬性

    function Person(name, age, job) {
        this.name = name
        this.age = age
        this.job = job
        this.friends = ['a', 'b']
    }
    
    Person.prototype = {
        constructor: Person,
        sayName: function() {
            console.log(this.name)
        }
    }
    
    var p1 = new Person('agan', 29, 'it')
    var p2 = new Person('agan2', 30, 'cs')
    p1.friends.push('c')
    console.log(p1.friends) // 'a' 'b' 'c'
    console.log(p2.friends) // 'a' 'b'
    console.log(p1.friends === p2.friends) // false
    console.log(p1.sayName === p2.sayName) // true
複製程式碼

在上面的程式碼中, 例項屬性是定義在建構函式中的, 例項共享的屬性和方法是在原型中定義的

動態原型模式

動態原型模式將所有資訊都封裝在了建構函式中, 在建構函式中初始化原型

    function Person(name, age, job) {
        this.name = name
        this.age = age
        this.job = job
        
        // 方法
        if (typeof this.sayName !== 'function') {
            Person.prototype.sayName = function() {
                console.log(this.name)
            }
        }
    }
    let f = new Person('agan', 30, 'it')
    f.sayName()
複製程式碼

上述程式碼中, sayName方法只有在不存在的情況下, 才會新增到原型中, 這段程式碼只會在初次呼叫建構函式時才會執行

寄生建構函式模式

基本思想: 建立一個函式, 該函式用來封裝建立物件的程式碼, 然後再返回建立的物件 跟工廠模式類似

    function Person(name, age, job) {
        let o = new Object()
        o.name = name
        o.age = age
        o.job = job
        o.sayName = function() {
            console.log(this.name)
        }
        return o
    }
    let f = new Person('agan', 20, 'it')
    f.sayName() // 'agan'
複製程式碼
  • 說明:
    • 返回的物件與建構函式或者與建構函式的原型屬 性之間沒有關係
    • 建構函式返回的物件與在建構函式外部建立的物件沒有什麼不同
    • 不能依賴 instanceof 操作符來確定物件型別

穩妥建構函式

穩妥物件: 指的是沒有公共屬性, 其方法也不引用 this 物件

  • 穩妥建構函式與寄生建構函式不同點:
    • 函式內部新建立物件的例項方法不引用 this
    • 函式外部不使用 new 操作符呼叫建構函式
    • 函式內部中變數不能掛在到要返回的變數上
    function Person(name, age, job) {
        let o = new Object()
        // 定義私有變數和函式
        let name = name
        let age = age
        let job = job
        
        // 新增方法
        o.sayName = function() {
            console.log(name)
        }
        return o
    }
複製程式碼

這種模式中建立的物件, 除了使用 sayName 方法外沒有其他方法訪問name的值

    let f = Person('agan', 30, 'it')
    f.sayName() // agan
複製程式碼

相關文章