對象創建
創建對象的方式有兩種
第一種是使用對象字面量的方式,也就是聲明形式
let obj = {
key: value
//...
}
第二種是使用構造方式
let obj = new Object()
obj.key = value
這兩種方式雖然形式不同,但產生的是同一種對象
在實際應用中我們也會使用構造函數來生成實例對象
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
return 'I am ' + this.name
}
let p = new Person('feng')
在ES6中,我們還可以利用class
關鍵字來創建對象
class Person {
constructor(name) {
this.name = name
}
say() {
return 'I am ' + this.name
}
}
typeof Person // "function"
Person === Person.prototype.constructor // true
let p = new Person('feng')
p.say === Person.prototype.say // true
我們可以把ES6的class
看做是一種語法糖,ES6的類作爲構造函數的另一種寫法。ES6的類與構造函數的主要區別在於:ES6類必須使用new
調用,而構造函數可以當作普通函數執行
屬性
對象包含了存儲在特定命名的位置上的任意類型的值,我們稱這些值爲屬性。在對象中,屬性名是字符串
let obj = {
a: 1 // 屬性
}
訪問在obj
對象中的a
,有兩種方法
第一種是使用.
操作符,通常稱爲屬性訪問
,只能訪問與標識符相同命名規範的屬性
obj.a // 1
第二種是使用[]
操作符,通常稱爲鍵訪問
,可以訪問任何兼容 UTF-8/unicode 的字符串類型的屬性名
let a = 'a'
obj['a'] // 1
obj[a] // 1
屬性描述符
在JavaScript中,對象的屬性用屬性描述符來表示。屬性有四種性質:值、可寫性、可配置性、可枚舉性
let obj = {
a: 1
}
Object.getOwnPropertyDescriptor( obj, "a" )
// { value: 1, writable: true, enumerable: true, configurable: true }
可寫性
當writable
爲false
時,屬性變爲不可寫,也就意味着值不可改變
let obj = {}
Object.defineProperty( obj, "a", {
value: 1,
writable: false, // 不可寫
configurable: true,
enumerable: true
} )
obj.a = 2
obj.a // 1
可配置性
當configurable
爲false
時,就意味着我們不可以使用Object.defineProperty
來修改該屬性的描述符定義,同時也無法使用delete
操作符對它進行刪除
唯一例外的是,當configurable
爲false
時,writeable
也可以從true
變爲false
let obj = {
a: 1
}
Object.defineProperty( obj, "a", {
value: 2,
writable: true,
configurable: false,
enumerable: true
} )
Object.defineProperty( obj, "a", {
value: 3,
writable: true,
configurable: true,
enumerable: true
} ) // TypeError
Object.defineProperty( obj, "a", {
value: 3,
writable: false,
configurable: false,
enumerable: true
} )
obj.a // 3
delete obj.a
obj.a // 3
可枚舉性
當enumerable
爲false
時,就意味着這個屬性不會在特定的枚舉操作中出現
let obj = {
a: 1,
b: 2
}
Object.defineProperty( obj, "a", { enumerable: false } )
obj.a // 1
for (let k in obj) {
console.log( k, obj[k] )
} // b 2
setter和getter
利用取值函數和存值函數可以攔截指定屬性的存取行爲
let obj = {
b: 1,
set a(value) {
this.b = value*10
}
}
obj.a = 1
obj.b // 10
Object.defineProperty( obj, "c", {
get: function(){ return this.b * 2 },
enumerable: true
} )
obj.c // 20
原型
在JavaScript中只有對象,沒有類
對象之間用原型聯繫起來
__proto__
與prototype
prototype
是構造函數的屬性,而 __proto__
是對象的屬性
每個對象的__proto__
屬性指向自身構造函數的prototype
Object.prototype.__proto__ // null
Object.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype //true
let obj = {}
obj.__proto__=== Object.prototype // true
Function.prototype // [Function]
Function.prototype.__proto__ === Object.prototype // true
function Foo() {}
Foo.__proto__ === Function.prototype //true
let f1 = new Foo()
f1.__proto__ === Foo.prototype // ture
雖然有一點不可思議,但是結合上述文字以及代碼,我們可以得到如下結論:
- 原型鏈的最頂端是內建的
Object.prototype
,在向上思考就是哲學的範疇了 Object
對象是由Funtion
函數創建出來的Function
是一種對象,因爲對象是由函數創建出來的,所以它是Function
函數創建出來的- 使用字面量的方式創建的對象是由
Object
對象創建的 Function.prototype
指向一個對象,並且這個對象是由Object
對象創建的- 一個自定義的函數是由
Function
函數創建的 - 構造函數可以創建對象
簡單的說,對象以自己爲模版可以創建更多的對象,而函數既能創建函數又能創建對象,同時函數也是一種對象
鏈接
原型鏈的機制是一種存在於一個對象上的內部鏈接,它指向一個其他對象
原型鏈使得代碼的複用性增強,同時對象的的屬性的訪問變得複雜起來,
let obj = {
a: 1
}
let obj1 = Object.create( obj )
obj1.__proto__ === obj //true
obj1.a // 1
obj.b = 2
obj1.b // 2
Object.defineProperty( obj, "b", { writable: false } )
obj1.b = 3
obj1.b // 2
Object.defineProperty( obj, "c", {
get: function(){ return this.b * 2 },
enumerable: true
} )
obj1.c // 4
for (let k in obj1) {
console.log(k, obj1[k])
}
/*
a 1
b 2
c 4
*/
以obj
對象爲模板,創建出了obj1
對象,或者說obj1
對象的原型是obj
對象。通過觀察,我們可以推論,在對對象的屬性進行訪問時,先查找對象內部是否存在名稱匹配的屬性,如果不存在,就沿着原型鏈向上查找,直到找到匹配的屬性,或者已經到達原型鏈的最頂端
構造器
構造器是什麼不重要,重要的是它做了什麼
function Foo() {}
Foo.prototype.constructor === Foo //true
function Bar() {}
Foo.prototype = new Bar()
Foo.prototype.constructor = Foo
let obj = new Foo()
obj // Foo{}
obj.__proto__.__proto__ === Bar.prototype // true
挖一個坑
由於javascript特有的原型機制,在實際項目中可能會遇到兩種截然不同的設計方式,一種是傳統的面向對象的設計方式,另一種則是我在《你不知道的JS》一書中所瞭解的面向委託的設計,讀者感興趣可以去自己去了解一下