從新認識javascript對象(一)——對象及其屬性

1、建立對象

javascript中有三種方法能夠建立一個對象:javascript

  1. 對象字面量
var obj = {
    name: 'jack',
    age: 12
}
複製代碼
  1. new 構造函數
var obj = new Object()
var obj1 = new Object({
    name: 'jack',
    age: 12
})
複製代碼
  1. Object.create()
var obj = Object.create({
    name: 'jack',
    age: 12
})
複製代碼

須要注意的是經過Object.create()建立的對象實際上等於將該對象的__proto__指向Object.create()裏面的參數對象,而obj自己是個空對象。java

var obj = Object.create({
    name: 'jack',
    age: 12
})
// 等價於 obj.__proto__ = { name: 'jack', age: 12 }
console.log(obj)  // {}
console.log(obj.__proto__)  // { name: 'jack', age: 12 }
obj.toString()   // '[object Object]'
複製代碼

若是往Object.create()裏面傳入的是null,則建立的對象不繼承Object的任何方法及屬性。bash

var obj = Object.create(null)
console.log(obj)  // {}
console.log(obj.__proto__)  // undefined
obj.toString()  // 報錯
複製代碼

若是想建立一個空對象,須要傳入Object.prototype函數

var obj = Object.create(Object.prototype)
// 和 {} 、new Object()同樣
複製代碼

2、對象的屬性

咱們知道,對象的屬性是由名字、值和一組特性組成(屬性的特性待會介紹)。在ES5中屬性值能夠用一個或兩個方法代替,這兩個方法就是gettersetter。由gettersetter定義的屬性稱爲「存儲器屬性」,它不一樣於數據類型的屬性,數據屬性只有一個簡單的值。咱們重點講解存儲器屬性。ui

當咱們查詢存儲器屬性時會調用getter方法(無參數)。這個方法返回值就是屬性存取表達式返回的值。this

當咱們設置存儲器屬性時會調用setter方法(有參數)。這個方法修改存儲器屬性的值。spa

var obj = {
    num: 12,
    age: 13,
    get num1 () {
        return this.num
    },
    set num1 (value) {
        this.num = value
    },
    get age1 () {
        return this.age
    }
}
obj.num1   // 12
obj.num1 = 120
obj.num1  // 120

obj.age1  // 13
obj.age1 = 130
obj.age1  // 13
複製代碼

存儲器屬性定義爲一個或者兩個和屬性同名的函數,這個函數定義沒有使用function關鍵字而是使用getsetprototype

能夠看出若是該屬性只有getter方法則只能讀取該屬性不能設置該屬性,一樣若是隻有setter方法就只能設置該屬性,不能讀取該屬性,只有當二者都有時才能正常讀取和設置屬性。code

3、對象屬性的特性

每一個對象的數據屬性都有四個特性(也能夠說是屬性描述符),分別爲:對象

  1. value 屬性的值
  2. writable 可寫性,若是爲false該值將不能被修改
  3. enumerable 可枚舉性,若是爲false將不能被枚舉
  4. configurable 可配置性,若是爲false將不能被配置,即不能被delete操做符刪除,不能更改這四個特性,一旦設爲false則沒法再設爲true,也就是一個不可逆過程

前面咱們講過存儲器屬性,每隔對象的存儲器屬性一樣也有四個特性,分別爲:

  1. get
  2. set
  3. enumerable
  4. configurable

若是想要得到一個對象某個屬性的這四個特性,能夠調用 Object.getOwnPropertyDescriptor() 方法,該方法接受兩個參數,第一個爲對象,第二個爲對象的屬性

var obj = {
    name: 'jack',
    age: 12,
    get age1 () {
        return this.age1
    },
    set age1(value) {
        this.age1 = value
    }
}
// 獲取數值屬性的特性
Object.getOwnPropertyDescriptor(obj,'name')
// {value: "jack", writable: true, enumerable: true, configurable: true}

// 獲取存儲器屬性的特性
Object.getOwnPropertyDescriptor(obj,'age1')
// {enumerable: true, configurable: true, get: ƒ, set: ƒ}

// 試圖獲取不存在的屬性,返回undefined
Object.getOwnPropertyDescriptor(obj, 'sex')  // undefined

// 試圖獲取原型上的屬性,返回undefined 
Object.getOwnPropertyDescriptor(obj, 'toString')  // undefined
複製代碼

從上面能夠看出,Object.getOwnPropertyDescriptor() 只能獲得自有屬性的描述符,要想得到繼承屬性的特性,咱們能夠把該對象的原型傳進去。

function Person () {
    this.name = 'sillywa'
}
Person.prototype.sex = 'boy'
Person.prototype.age = 13
var person1 = new Person()
Object.getOwnPropertyDescriptor(person1.__proto__, 'sex')
// {value: "boy", writable: true, enumerable: true, configurable: true}
複製代碼

以上能夠看出,咱們經過對象字面量和new運算符建立的對象的屬性它們的writable,enumerable,configurable都有true,默認都是可寫、可枚舉、可配置。若是要修改屬性的特性能夠調用Object.defineProperty()

var obj = {
    name: 'sillywa'
}
// 將name屬性設爲不可枚舉並將其值設爲jack
Object.defineProperty(obj, 'name', {
    value: 'jack',
    enumerable: false
})
Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "jack", writable: true, enumerable: false, configurable: true}

// 新增age屬性
Object.defineProperty(obj, 'age', {
    value: 12
})
Object.getOwnPropertyDescriptor(obj, 'age')
// {value: 12, writable: false, enumerable: false, configurable: false}

// 將name變爲存儲器屬性
Object.defineProperty(obj, 'name', {
    get: function () {
        return 0
    }
})
Object.getOwnPropertyDescriptor(obj, 'name')
// {set: undefined, enumerable: false, configurable: true, get: ƒ}

obj.age = 78
obj.age  // 12
複製代碼

須要注意的是經過Object.defineProperty() 建立的屬性其writable, enumerable, configurable 都爲false。嘗試修改不寫的屬性不會報錯,但也不會修改,只有在嚴格模式下才會報錯。

若是須要同時修改和建立多個屬性,可使用Object.defineProperties()

var obj = Object.defineProperties({},{
    name: {
        value: 'sillywa',
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        get: function () {
            return 'hello' + this.name
        },
        set: function (value) {
            this.name = 'jack'
        },
        enumerable: true,
        configurable: true
    }
})
複製代碼

4、屬性的設置和屏蔽

咱們知道當咱們書寫如下代碼時

obj.foo = 'bar'

若是obj存在一個名爲foo的普通數據訪問屬性,這條賦值語句只會修改已有的屬性值。

若是foo不是直接存在於obj中,[[prototype]]鏈就會被遍歷,若是原型鏈上找不到foofoo就直接被添加到obj上。

然而,若是原型鏈上找到了foo屬性,狀況就有些不同了。

若是屬性foo既出如今obj中也在其原型鏈中,那麼obj中包含的foo屬性就會屏蔽原型鏈裏面的foo屬性,這就是屬性屏蔽,原理就是屬性的查找規則。

下面咱們看一下若是foo不直接存在於obj中,而是在其原型鏈中時,obj.foo = 'bar'會出現的三種狀況:

  1. 若是原型鏈中存在名爲foo的普通數據訪問屬性而且其writabletrue,那麼就會直接在obj中添加foo屬性,它是屬性屏蔽。
  2. 若是原型鏈中存在foo,但其writablafalse,那麼沒法修改已有屬性或者在obj中建立屏蔽屬性。若是運行在嚴格模式下,會拋出一個錯誤。不然這條賦值語句會被忽略,不會發生屬性屏蔽。
  3. 若是原型鏈上存在foo而且它是一個setter,那就必定會調用這個setterfoo不會被添加到obj中,也不會從新定義這個setter

大多數人認爲,若是向原型鏈中已存在的屬性賦值,就必定會發生屬性屏蔽,但以上三種狀況只有一種是如此。

若是但願在任何狀況下都屏蔽foo,那就不能使用=操做符來賦值,而是使用Object.defineProperty()來向obj中添加foo

狀況一:

function Person() { }
Person.prototype.foo = 'foo'
var obj = new Person()
obj.foo = 'bar'
obj.foo   // 'bar'
複製代碼

狀況二:

function Person() {}
Object.defineProperty(Person.prototype,'foo',{
    writable: false,
    enumerable: true,
    configurable: true,
    value: 'foo'
})
var obj = new Person()
obj.foo = 'bar'
obj.foo  // 'foo'
複製代碼

狀況三:

function Person() {}
Person.prototype = {
    constructor: Person,
    name: 'foo',
    set foo (value) {
        this.name = value
    },
    get foo () {
        return this.name
    }
}
var obj = new Person()
obj.foo = 'bar'
obj.foo  // 'bar'
// obj中並無foo這個屬性,只是調用了setter
obj.hasOwnProperty('foo') // false
複製代碼

有些狀況下會隱式產生屏蔽,必定要注意,思考一下代碼:

var obj = {
    a: 2
}
var myObj = Object.create(obj)
obj.a  // 2
myObj.a  // 2

obj.hasOwnProperty('a')  // true
myObj.hasOwnProperty('a')  // false

myObj.a ++  // 隱式屏蔽

obj.a  // 2
myObj.a  // 3

myObj.hasOwnProperty('a')  // true
複製代碼

儘管myObj.a ++看起來是查找並增長obj.a的屬性,可是別忘了++操做符至關於myObj.a = myObj.a + 1;所以++操做首先會經過原型鏈查找到obj.a,並讀取其值爲2,而後加1賦值給myObj.a

相關文章
相關標籤/搜索