JavaScript 物件導向精要(一)

dwqs發表於2016-08-10

資料型別

在JavaScript中,資料型別分為兩類:

原始型別

儲存一些簡單資料,如true,5等。JavaScript共有5中原始型別:

  • boolean:布林,值為true或false
  • number:數字,值為任何整型會浮點數
  • string:字串,值為由單引號或雙引號括出的單個字元或連續字元(JavaScript不區分字元型別)
  • null:空型別,其僅有一個值:nulll
  • undefined:未定義,其僅有一個值:undefined
var name = "Pomy";
var blog = "http://www.ido321.com";
var age = 22;
alert(typeof blog); //"string"
alert(typeof age); //"number"

原始型別的值是直接儲存在變數中,並可以用 typeof 進行檢測。但是typeof對null的檢測是返回object,而不是返回null:

//彈出Not null
if(typeof null){
    alert("Not null");   
}else{
    alert("null");
}

所以檢測null時,最好用全等於(===),其還能避免強制型別轉換:

console.log("21" === 21);  //false
console.log("21" == 21);  //true
console.log(undefined == null);  //true
console.log(undefined === null);  //false

對於字串、數字或者布林值,其都有對應的方法,這些方法來自於對應的原始封裝型別:String、Number和Boolean。原始封裝型別將被自動建立。

var name = "Pomy";
var char = name.charAt(0);
console.log(char);  //"P"

在JavaScript引擎中發生的事情:

var name = "Pomy";
var temp = new String(name);
var char = temp.charAt(0);
temp = null;
console.log(char);  //"P"

字串物件的引用在用完之後立即被銷燬,所以不能給字串新增屬性,並且instanceof檢測對應型別時均返回false:

var name = "Pomy";
name.age = 21;
console.log(name.age);   //undefined
console.log(name instanceof String);  //false

引用型別

儲存為物件,實質是指向記憶體位置的引用,所以不在變數中儲存物件。除了自定義的物件,JavaScript提供了6中內建型別:

  • Array:陣列型別,以數字為索引的一組值的有序列表
  • Date:日期和時間型別
  • Error:執行期錯誤型別
  • Function:函式型別
  • Object:通用物件型別
  • RegExp:正規表示式型別

可以用new來例項化每一個物件,或者用字面量形式來建立物件:

var obj = new Object;
var own = {
            name:"Pomy",
            blog:"http://www.ido321.com",
            "my age":22
        };
console.log(own.blog);    //訪問屬性
console.log(own["my age"]); 
obj = null;  //解除引用

obj 並不包含物件例項,而是一個指向記憶體中實際物件所在位置的指標(或者說引用)。因為typeof對所有非函式的引用型別均返回object,所以需要用instanceof來檢測引用型別。

函式

在JavaScript中,函式就是物件。使函式不同於其他物件的決定性特性是函式存在一個被稱為[[Call]]的內部屬性。內部屬性無法通過程式碼訪問而是定義了程式碼執行時的行為。

建立形式

1、函式宣告:用function關鍵字,會被提升至上下文
2、函式表示式:不能被提升
3、例項化Function內建型別

sayHi();    //函式提升
function sayHi(){
    console.log("Hello");
}
//其他等效等效方式
/*
var sayHi = function(){
     console.log("Hello");
}
var sayHi = new Function(" console.log(\"Hello\");");
*/

引數

JavaScript函式的另外一個獨特之處在於可以給函式傳遞任意數量的引數。函式引數被儲存在arguments類陣列物件中,其自動存在函式中,能通過數字索引來引用引數,但它不是陣列例項:

alert(Array.isArray(arguments));   //false

類陣列物件arguments 儲存的是函式的實參,但並不會忽略形參。因而,arguments.length返回實參列表的長度,arguments.callee.length返回形參列表的長度。

function ref(value){
    return value;
}
console.log(ref("Hi"));
console.log(ref("Hi",22));
console.log(ref.length);  //1

函式中的this

關於this的問題,可參考此文:JavaScript中的this

JavaScript提供了三個方法用於改變this的指向:call、apply和bind。三個函式的第一個引數都是指定this的值,其他引數均作為引數傳遞給函式。

物件

物件是一種引用型別,建立物件常見的兩種方式:Object建構函式和物件字面量形式:

var per1 = {
    name:"Pomy",
    blog:"http://www.ido321.com"
};
var per2 = new Object;
per2.name = "不寫程式碼的碼農";

屬性操作

在JavaScript中,可以隨時為物件新增屬性:

per1.age = 0;
per1.sayName = function(){
    alert(this.name);   //"Pomy"
}

因而,在檢測物件屬性是否存在時,常犯的一個錯誤是:

//結果是false
if(per1.age){
    alert(true)
}else{
    alert(false);
}

per1.age 是存在的,但是其值是0,所以不能滿足if條件。if判斷中的值是一個物件、非空字串、非零數字或true時,判斷會評估為真;而當值是一個null、undefined、0、false、NaN或空字串時評估為假。

因而,檢測屬性是否存在時,有另外的兩種方式:in和hasOwnProperty(),前者會檢測原型屬性和自有(例項)屬性,後者只檢測自有(例項)屬性。

console.log("age" in per1);  //true
console.log(per1.hasOwnProperty("age"));  //true
console.log("toString" in per1);  //true
console.log(per1.hasOwnProperty("toString"));  //false

物件per1並沒有定義toString,該屬性繼承於Object.prototype,所以in和hasOwnProperty()檢測該屬性時出現差異。如果只想判斷一個物件屬性是不是原型,可以利用如下方法:

function isPrototypeProperty(obj,name){
    return name in obj && !obj.hasOwnProperty(name);
}

若要刪除一個屬性,用delete操作符,用於刪除自有屬性,不能刪除原型屬性。

per1.toString = function(){
    console.log("per1物件");
};
console.log(per1.hasOwnProperty("toString"));   //true
per1.toString();   //"per1物件"
delete per1.toString;
console.log(per1.hasOwnProperty("toString"));   //false
console.log(per1.toString());  //[object Object]

有時需要列舉物件的可列舉屬性,也有兩種方式:for-in迴圈和Object.keys(),前者依舊會遍歷出原型屬性,後者只返回自有屬性。所有可列舉屬性的內部屬性[[Enumerable]]的值均為true。

var per3 = {
    name:"Pomy",
    blog:"http://www.ido321.com",
    age:22,
    getAge:function(){
        return this.age;
    }
};

實際上,大部分原生屬性的[[Enumerable]]的值均為false,即該屬性不能列舉。可以通過propertyIsEnumerable()檢測屬性是否可以列舉:

console.log(per3.propertyIsEnumerable("name"));  //true
var pros = Object.keys(per3);  //返回可列舉屬性的名字陣列
console.log("length" in pros);  //true
console.log(pros.propertyIsEnumerable("length"));  //false

屬性name是自定義的,可列舉;屬性length是Array.prototype的內建屬性,不可列舉。

屬性型別

屬性有兩種型別:資料屬性和訪問器屬性。二者均具有四個屬性特徵:

  • 資料屬性:[[Enumerable]]、[[Configurable]]、[[Value]]和[[Writable]]
  • 訪問器屬性:[[Enumerable]]、[[Configurable]]、[[Get]]和[[Set]]

[[Enumerable]] :布林值,屬性是否可列舉,自定義屬性預設是true。
[[Configurable]] :布林值,屬性是否可配置(可修改或可刪除),自定義屬性預設是true。它是不可逆的,即設定成false後,再設定成true會報錯。
[[Value]]:儲存屬性的值。
[[Writable]]:布林值,屬性是否可寫,所有屬性預設可寫。
[[Get]]:獲取屬性值。
[[Set]]:設定屬性值。

ES 5提供了兩個方法用於設定這些內部屬性:
Object.defineProperty(obj,pro,desc_map) 和 Object.defineProperties(obj,pro_map)。利用這兩個方法為per3新增一個屬性和建立一個新物件per4:

Object.defineProperty(per3,"sex",{
    value:"male",
    enumerable:false,
    configurable:false, //屬性不能刪除和修改,該值也不能被設定成true
});
console.log(per3.sex);  //'male'
console.log(per3.propertyIsEnumerable("sex"));  //false
delete per3.sex;    //不能刪除
per3.sex = "female"; //不能修改
console.log(per3.sex);  //'male'
Object.defineProperty(per3,"sex",{
    configurable:true, //報錯
});
per4 = {};
Object.defineProperties(per4,{
    name:{
        value:"dwqs",
        writable:true
    },
    blog:{
        value:"http://blog.92fenxiang.com"
    },
    Name:{
        get:function(){
            return this.name;
        },
        set:function(value){
            this.name = value;
        },
        enumerable:true,
        configurable:true
    }
});
console.log(per4.name); //dwqs
per4.Name = "Pomy";
console.log(per4.Name); //Pomy

需要注意的是,通過這兩種方式來定義新屬性時,如果不指定特徵值,則預設是false,也不能建立同時具有資料特徵和訪問器特徵的屬性。可以通過Object.getOwnPropertyDescriptor()方法來獲取屬性特徵的描述,接受兩個引數:物件和屬性名。若屬性存在,則返回屬性描述物件。

var desc = Object.getOwnPropertyDescriptor(per4,"name");
console.log(desc.enumerable); //false
console.log(desc.configurable); //false
console.log(desc.writable); //true

根據屬性的屬性型別,返回的屬性描述物件包含其對應的四個屬性特徵。

禁止修改物件

物件和屬性一樣具有指導其行為的內部特徵。其中,[[Extensible]]是一個布林值,指明改物件本身是否可以被修改([[Extensible]]值為true)。建立的物件預設都是可以擴充套件的,可以隨時新增新的屬性。
ES5提供了三種方式:

  • Object.preventExtensions(obj):建立不可擴充套件的obj物件,可以利用Object.isExtensible(obj)來檢測obj是否可以擴充套件。嚴格模式下給不擴充套件物件新增屬性會報錯,非嚴格模式下則新增失敗。
  • Object.seal(obj):封印物件,此時obj的屬性變成只讀,不能新增、改變或刪除屬性(所有屬性都不可配置),其[[Extensible]]值為false,[[Configurable]]值為false。可以利用Object.isSealed(obj)來檢測obj是否被封印。
  • Object.freeze(obj):凍結物件,不能在凍結物件上新增或刪除屬性,不能改變屬性型別,也不能寫入任何資料型別。可以利用Object.isFrozen(obj)來檢測obj是否被凍結。 注意:凍結物件和封印物件均要在嚴格模式下使用。
"use strict";
var per5 = {
    name:"Pomy"
};
console.log(Object.isExtensible(per5));   //true
console.log(Object.isSealed(per5));         //false
console.log(Object.isFrozen(per5));       //false
Object.freeze(per5);
console.log(Object.isExtensible(per5));   //false
console.log(Object.isSealed(per5));       //true
console.log(Object.isFrozen(per5));       //true
per5.name="dwqs";
console.log(per5.name);   //"Pomy"
per5.Hi = function(){
    console.log("Hi");
};
console.log("Hi" in per5);  //false
delete per5.name;
console.log(per5.name);  //"Pomy"
var desc = Object.getOwnPropertyDescriptor(per5,"name");
console.log(desc.configurable);  //false
console.log(desc.writable);  //false

注意,禁止修改物件的三個方法只對物件的自有屬性有效,對原型物件的屬性無效,仍然可以在原型上新增或修改屬性。

function Person(name){
    this.name = name;
}
var person1 = new Person("Pomy");
var person2 = new Person("dwqs");
Object.freeze(person1);
Person.prototype.Hi = function(){
    console.log("Hi");
};
person1.Hi();  //"Hi";
person2.Hi();  //"Hi";

JavaScript物件導向精要(二)

相關文章