javaScript面向對象的程序設計(一)

  • 面向對象

面向對象可以理解成具有類的概念。 可以通過類創建任意多個具有相同屬性和方法的對象。由於javascript中沒有類的概念。故和基於類的語言有所不同。 ECMA-262將其定義爲無序屬性的集合。 七屬性可以包含基本值。對象和方法。 換種說法可以認爲對象就是一組沒有特定順序的值。對象的每一個屬性和方法都有屬於自己的名字。 而每一個名字有有對應一個值。即常說的鍵值對。

  • 理解對象

        1. 通過Object()實例一個對象。 併爲其添加屬性和方法

let  person = new Object();
person.name = "小王";
person.age = 24;
person.tall = 178;
person.getName = function () {
    console.log(this.name)
};

console.log(person)


{ name: '小王', age: 24, tall: 178, getName: [Function] }

       2. 通過字面量創建

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person)

{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }

兩種方法都創建了相同屬性的和方法的對象。 而在創建創建屬性的時候其屬性也攜帶一些特徵。 javaSript通過屬性類型來描述這些特徵

       3. 屬性特徵

ECMA-262第5版規定只有內部才具有特徵時。描述了屬性的各個特徵。這些特徵是爲實現javaScript引擎用的。 因此在js中不能直接訪問他們。 爲了表示是內部屬性。 規定將其放在兩對方括號中。 ESCAscript具有兩種屬性。數據屬性和訪問屬性

  • 數據屬性

數據屬性包含一個數據值的位置。在這個位置可以讀取和寫入值。數據屬性有4個描述其行爲的特性 

  1. [[Configurable]]:能否被delete刪除屬性重新定義  默認爲true
  2. [[Enumerable]]:能否被for-in枚舉 默認爲true
  3. [[Writable]]:能否修改屬性值 默認爲true
  4. [[Value]]:數據的數據 默認爲undefined

要修改默認屬性就要必須通過Object.defineProperty()方法。 該方法有三個參數。 參數一表示屬性所在的對象。參數二表示屬性的名字。 參數三表示一個描述符對象。  

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person);
console.log("------")
Object.defineProperty(person,"tall",{
    writable:false,
    value:2000
});
console.log(person.tall);
console.log('--------');
person.tall = 165;
console.log(person.tall);


{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }
------
2000
--------
2000

將對象的tall屬性設置爲可讀後。 我們嘗試去修改的時候是無法修改的。 這種操作在非嚴格模式下會被忽略。在嚴格模式下就會報錯。同樣的操作還適用於configurable。

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person);
console.log("------")
Object.defineProperty(person,"tall",{
    configurable:false,
    value:2000
});
console.log(person.tall);
console.log('--------');
delete person.tall;
console.log(person.tall);

{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }
------
2000
--------
2000

將tall屬性設置爲configurable等於false時調用delete時在這種操作在非嚴格模式下會被忽略。在嚴格模式下就會報錯。如果在將其修改爲true則會報錯。在調用Object.defineProperty方法將configurable修改爲true的同時修改writable爲false

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person);
console.log("------")
Object.defineProperty(person,"tall",{
    configurable:false,
    value:2000
});

Object.defineProperty(person,"tall",{
    configurable:true,
    writable:false
});

console.log(person);


{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }
------
/Users/apple/Desktop/javaScript/object.js:17
Object.defineProperty(person,"tall",{
       ^

TypeError: Cannot redefine property: tall
    at Function.defineProperty (<anonymous>)
    at Object.<anonymous> (/Users/apple/Desktop/javaScript/object.js:17:8)
    at Module._compile (internal/modules/cjs/loader.js:1156:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1176:10)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47

也就是說可以調用Object.defineProperty修改同一屬性。但是將configurable設置成false後就受限制了

  • 訪問屬性

訪問器屬性不包含數據值,它們包含一對getter和setter函數(這兩個函數都不是必須的)。在讀取訪問器屬性時會調用getter函數,這個函數會負責返回有效的值,在寫入訪問器屬性時,會調用setter函數並傳入新值。這個函數負責決定如何處理數據。訪問器屬性有如下4個特性

  1. [[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,這個特性的默認值爲true
  2. [[Enumerable]]:表示能否通過for-in循環返回屬性,這個特性的默認值爲true
  3. [[Get]]:在讀取屬性時調用的函數。默認值爲undefined
  4. [[Set]]:在寫入屬性時調用的函數。默認值爲undefined

訪問器屬性不能直接訪問。 要通過Object.defineProperty()來定義

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}


Object.defineProperty(person,'getData',{
    get(){
        return this.age
    },
    set(v) {
        if (v > 28){
            this.age = v;
            this.tall = '身高與體重不符合'
        }
    }
});


console.log(person.getData);

person.getData = 40;

console.log(person.tall)


24
身高與體重不符合

上述代碼定義了訪問器。 不難發現在直接使用訪問器時。其實調用的事get方法並且返回對應的值。 當修改訪問器的值後從而導致了對象另一個屬性的變化 ,要說明的是get和set方法不一定要同時存在。 只指定get方法意味着不能寫。 反之只能寫不能讀。

  • 定義多個屬性

可以通過Object.defineProperties()給對象添加多個屬相。 該方法需要兩個參數。 參數一表示需要添加或者修改的對象。 第二個對象的屬性與第一個對象中要添加和修改的屬性藥對應

let person = {};

Object.defineProperties(person,{
    name:{
        value:"Jack"
    },
    age:{
        value:24
    },
    getName:{
        get() {
            return this.name
        },
        set(v) {
            this.name = v
        }
    },
});


console.log(person.name)

console.log(person.getName)


Jack
Jack
  • 讀取屬性的特徵

Object.getOwnPropertyDescriptor()可以返回給定屬性的描述符。 接受兩個參數。 參數一表示所在的對象。參數二表示表示要讀去的屬性描述符的名稱。若果是訪問器則返回get。set。enumerable  configurable。 如果是數據屬性則返回value。writable   enumerable  configurable。

let person = {};

Object.defineProperties(person,{
    name:{
        value:"Jack"
    },
    age:{
        value:24
    },
    getName:{
        get() {
            return this.name
        },
        set(v) {
            this.name = v
        }
    },
});


let a = Object.getOwnPropertyDescriptor(person,"getName");
console.log(a)


let b = Object.getOwnPropertyDescriptor(person,'name');
console.log(b)


{
  get: [Function: get],
  set: [Function: set],
  enumerable: false,
  configurable: false
}
{
  value: 'Jack',
  writable: false,
  enumerable: false,
  configurable: false
}
  • 創建對象 

      1。 工廠模式

function createdProse(name,age,tall) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.tall = tall;
    obj.getName = function () {
        return age.name
    }
    return obj
}

let a = createdProse("jack", 23, 178);
console.log(a)

let b = createdProse('Alice',32,173);
console.log(b)

{ name: 'jack', age: 23, tall: 178, getName: [Function] }
{ name: 'Alice', age: 32, tall: 173, getName: [Function] }

工廠模式能接受參數創建多個所需要的對象,但問題在於無法確定對象類型

function createdProse(name,age,tall) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.tall = tall;
    obj.getName = function () {
        return age.name
    }
    return obj
}

let a = createdProse("jack", 23, 178);

console.log(typeof a === 'object')

true

   2 構造函數模式

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
console.log(a)
let b = new Person("Alice",24,"蘭州");
console.log(b)

Person { name: 'Jack', age: 28, city: '成都', getName: [Function] }
Person { name: 'Alice', age: 24, city: '蘭州', getName: [Function] }

與工廠模式相比有以下幾點不同

  • Person()取代了createdProse(). 而且Person()函數的首字母使用了大寫。這個主要是爲了和普通函數區別開來。 說到底構造函數畢竟也是函數。只是用來創建對象的。
  • 沒有顯示的創建對象
  • 直接將屬性和方法賦值給了this對象
  • 沒有return語句

要創建Person的實例對象就必須使用new關鍵字。這個調用方式會經歷一下幾個步驟

  • 創建了一個新的對象
  • 將構造函數的作用域賦給新的對象所以this指向了這個對象
  • 執行構造函數的代碼將屬性和方法添加給新的對象
  • 返回新的對象

a,b兩個對象分別保存着不同的實例對象。 這兩個對象都有一個constructor屬性。 而且該屬性指向Person

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"蘭州");
console.log(a.constructor === Person)
console.log(b.constructor === Person)


true
true
instanceof可以更可靠的檢查類型。我們創建的對象不但是Objcet的實例還是Person的實例
function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"蘭州");
console.log(a instanceof Object)
console.log(a instanceof Person)

true
true
  • 將構造函數當作函數

任何函數如果能通過new關鍵字調用就可以將其當作構造函數。 如果不能通過new關鍵字調用就和普通函數沒什麼區別

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
a.getName()

Person("Jack",28,"成都");
window.getName()

let c = new Object()
Person.call(c,...["Jack",28,"成都"])
c.getName()

Jack
Jack
Jack

上述第一種調用爲典型的構造函數調用。 第二種調用其實是將屬性和方法添加在了window上。 此時this實際上指向的是window對象,第三章通過claa方法使其在某個特殊的作用域中調用Person().故此時c也就擁有了對象所有的屬性和方法。

  • 構造函數的問題

使用構造函數的主要問題在每個方法都會在實例化的基礎上重新創建一次。 上面的a,b對象都包含一個名爲getName的方法。 但是這兩個方法不在同一個實例中。 在js中函數也是對象。也就是說每定義一個方法就相當於創建了一個對象。

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = {
        name : this.name
    }
}

let a = new  Person("Jack",28,"成都");
console.log(a.getName.name)

Jack

所以不同實例上的同名函數是不想等的

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"蘭州");
console.log(a.getName === b.getName)

false

爲了解決這一問題出現瞭如下寫法

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = getName
}

getName = function(){
    return this.name
}
let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"蘭州");
console.log(a.getName === b.getName)

true


這樣寫解決了定義相同函數解決同一問題的的做法。 但是新的問題有隨之產生了。就是這個全局方法只能通過某個對象來調用。這就顯得又些奇怪了。 而且在存在很多方法的情況下顯然不是最好的選擇。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章