1 JavaScript對(duì)象與實(shí)例
1.1 原型對(duì)象的屬性
?JavaScript是一種面向?qū)ο蟮恼Z言,但它沒有采用Java那種基于類( class-based:總是先有類,再?gòu)念惾?shí)例化一個(gè)對(duì)象,類與類之間又可能會(huì)形成繼承、組合等關(guān)系)的方式來實(shí)現(xiàn),而是采用基于原型(prototype-based:通過“復(fù)制原型”的方式來創(chuàng)建新對(duì)象,其實(shí)不是真的復(fù)制,而是建立一個(gè)對(duì)原型的引用,被復(fù)制的母體稱為原型對(duì)象)的方式,所以JavaScript中的原型對(duì)象有著更復(fù)雜的屬性。
?原型對(duì)象提供以下八種屬性:
?數(shù)據(jù)屬性:
?1、value:就是屬性的值,默認(rèn)為undefined;
?2、writable:決定能否修改屬性的值,默認(rèn)為true;
?3、enumerable:決定能否通過for-in循環(huán)返回屬性,默認(rèn)為true;
?4、configurable:決定該屬性能否被刪除或者改變特征值,默認(rèn)為true。
?備注:可以使用內(nèi)置函數(shù)Object.getOwnPropertyDescripter來查看數(shù)據(jù)屬性:
var o = { a: 1 }; // {}是 new Object()的簡(jiǎn)寫。
o.b = 2; // a和b皆為數(shù)據(jù)屬性
Object.getOwnPropertyDescriptor(o,"a") // {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b") // {value: 2, writable: true, enumerable: true, configurable: true}
?訪問器屬性(這個(gè)屬性不包含數(shù)據(jù)值,包含的是一對(duì)get和set方法,在讀寫訪問器屬性時(shí),就是通過這兩個(gè)方法來進(jìn)行操作處理的):
?1、Get:在讀取屬性時(shí)調(diào)用的函數(shù),默認(rèn)為undefined;
?2、Set:在寫入屬性時(shí)調(diào)用的函數(shù),默認(rèn)為undefined;
?3、enumerable:決定能否通過for-in循環(huán)返回屬性,默認(rèn)為false;
?4、configurable:決定該屬性能否被刪除或者改變特征值,默認(rèn)為false。
?注意:如果我們要想改變屬性的特征,或者定義訪問器屬性,要通過Object.defineProperty()方法,這個(gè)方法有三個(gè)參數(shù):屬性所在的對(duì)象,屬性名,一個(gè)描述符對(duì)象,例子如下:
var o = {
get a() {
return 1
}
};
console.log(o.a); // o.a每次都得到1
// 訪問器屬性修改:
var o = {
a: 1
};
Object.defineProperty(o, "a", {
get: function() {
return this.a;
}
// 若只指定get方法,不指定set方法,那么默認(rèn)該屬性是只讀
set: function(newValue) {
if (newValue !== this.a) {
this.a = newValue + 1;
}
}
});
console.log(o.a); // 1
o.a = 2;
console.log(o.a); // 3
//其他兩個(gè)特性configurable,enumerable的修改可以參照數(shù)據(jù)屬性
1.2 原型對(duì)象與實(shí)例對(duì)象的關(guān)系 ★
1.2.1 原型鏈
?當(dāng)我們?nèi)ピL問一個(gè)object的屬性(普通屬性或者方法)時(shí),運(yùn)行的過程如下:
?1、如果當(dāng)前的object中有這個(gè)屬性,就返回它的值,訪問結(jié)束;
?2、如果當(dāng)前object中沒有這個(gè)屬性,就往當(dāng)前object的__ proto __屬性指向的object(prototype object)里找,如果找到,就返回它的值,訪問結(jié)束;
?3、如果還沒找到,就不斷重復(fù)上一步驟的動(dòng)作,直到?jīng)]有上級(jí)prototype,此時(shí)返回undefined這個(gè)值,實(shí)際運(yùn)行中,是一直找到Object.prototype這個(gè)object的,因?yàn)樗?code>__ proto __等于null;
console.log(typeof Object.prototype); // "object"
console.log(Object.prototype.__proto__ === null); // true
?這個(gè)過程和Java中“在類繼承樹上一直往上找,直到Object類中都找不到為止”很相似。
1.2.2 __ proto __、constructor和prototype屬性
?1、__ proto __所指向的對(duì)象,真正將它的屬性分享給它所屬的對(duì)象(即它的原型對(duì)象),所有的對(duì)象都有__ proto __屬性,它是一個(gè)內(nèi)置屬性,被用于繼承。
?2、constructor這個(gè)屬性的含義就是將當(dāng)前對(duì)象指向其關(guān)聯(lián)的構(gòu)造函數(shù),所有函數(shù)最終的構(gòu)造函數(shù)都指向Function(最終Boss)。
?3、prototype是一個(gè)只屬于function函數(shù)的屬性,也并不是構(gòu)造函數(shù)專有,它的含義是函數(shù)的原型對(duì)象。當(dāng)使用new方法調(diào)用該構(gòu)造函數(shù)的時(shí)候,它被用于構(gòu)建新對(duì)象的__ proto __。另外它不可寫,不可枚舉,不可配置。
?4、①__ proto __和constructor屬性是對(duì)象所獨(dú)有的;②prototype屬性是函數(shù)所獨(dú)有的,但因?yàn)楹瘮?shù)也是一種對(duì)象,所以函數(shù)也擁有__ proto __和constructor屬性;③這三個(gè)屬性值都是對(duì)對(duì)象或者函數(shù)的引用,而不是一個(gè)包含名稱的字符串。
?例子如下:
function Person() {
}
var person1 = new Person();
//__proto__和prototype屬性:
console.log(person1.__proto__ === person.prototype); // true
//constructor屬性
console.log(person1.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.constructor === Person); // false
console.log(Person.constructor === Function); // true
?為什么上面的person1和Person.prototype的constructor屬性都指向Person函數(shù)?
?因?yàn)?code>Person.prototype就是原型對(duì)象,也就是實(shí)例對(duì)象person1的原型,原型對(duì)象是實(shí)例對(duì)象的母體,它們的構(gòu)造函數(shù)都是一樣的。
?如何去判斷一個(gè)屬性是存在于實(shí)例對(duì)象中,還是原型對(duì)象中?
?1、Object.hasOwnProperty()方法(屬性只有存在于當(dāng)前對(duì)象中才會(huì)返回true):
function Person() {
}
var person1 = new Person();
person1.name = 'ringoD';
console.log(person1.hasOwnProperty('name')); // true
?2、in操作符(in則會(huì)遍歷原型鏈上的所有屬性,不管是實(shí)例對(duì)象上的,還是原型對(duì)象上的):
function Person() {
}
var person1 = new Person();
person1.name = 'ringoD';
console.log('name' in person1); // true
console.log('age' in person1); // true
?3、Object.keys()方法(此方法可以獲取指定對(duì)象可枚舉的屬性的名字)
var keys = Object.keys(Person.prototype)
console.log(keys) // ['age']
?4、Object.getOwnPropertyNames()方法(此方法可以獲取指定對(duì)象所有屬性的名字,包括不可枚舉的)
1.2.3 call和construct
?1、call()方法:foo.call(this, arg1,arg2,arg3),這里的this是指所填入實(shí)例的上下文環(huán)境,代碼在該this內(nèi)生效;
function Dog(name) {
this.name = name;
}
Dog.prototype.say = function(message) {
console.log(this.name + ' say ' + message);
}
let dog = new Dog('小黃');
dog.say('汪汪'); //小黃 say 汪汪
function Cat(name) {
this.name = name;
}
let cat = new Cat('小喵');
dog.say.call(cat, '喵喵'); //小喵 say 喵喵
//此時(shí)dog.say的this的指向已經(jīng)通過call()方法改變了,指向的是Cat,this.name就是小喵,傳進(jìn)的參數(shù)是喵喵
?2、延伸:函數(shù)對(duì)象的定義是:具有[[Call]]私有屬性的對(duì)象,而構(gòu)造器對(duì)象的定義是:具有[[Construct]]私有屬性的對(duì)象;
?3、延伸2:任何對(duì)象只需要實(shí)現(xiàn)[[call]],它就是一個(gè)函數(shù)對(duì)象,可以去作為函數(shù)被調(diào)用。而如果它能實(shí)現(xiàn)[[construct]],它就是一個(gè)構(gòu)造器對(duì)象,可以作為構(gòu)造器被調(diào)用。
1.2.4 new 操作符
1.2.4.1 ES5
function Person() {
this.name = name
}
// 給原型對(duì)象加個(gè)describe函數(shù)
Person.prototype.describe = function() {
console.log('Hello, my name is ' + this.name + '.');
}
var person1 = new Person('ringoD');
person1.describe() // Hello, my name is ringoD.
1.2.4.1 ES6
class Person {
constructor(name) {
this.name = name;
}
describe() {
console.log('Hello, my name is ' + this.name + '.');
}
}
var person1 = new Person('ringoD');
person1.describe() // Hello, my name is ringoD .
?繼承:
class Person {
constructor(name) {
this.name = name;
}
describe() {
console.log('Hello, my name is ' + this.name + '.');
}
}
class Man extends Person {
constructor(name, age) {
super(name);
this.age = age;
// 在這里,super()作用是,代表調(diào)用了父類的構(gòu)造方法,函數(shù)返回了子類的實(shí)例;
// 因?yàn)樽宇惖臉?gòu)造方法是根據(jù)父類構(gòu)建的,因此this關(guān)鍵字必須在調(diào)用super()函數(shù)之后使用,否則會(huì)報(bào)錯(cuò)。
// 并且,ES6 要求子類構(gòu)造函數(shù)constructor() 內(nèi)必須調(diào)用super()方法。
}
describe() {
console.log('Hello,I am a man,my name is ' + this.name + ',I am' + this.age + 'years old.');
}
}
var person1 = new Man('ringoD', 18);
person1.describe(); // Hello ,I am a man, my name is ringoD,I am 18years old.
?靜態(tài)方法:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static say() {
console.log('say everyone');
}
}
People.say(); // 'hello everyone'
var people1 = new Person;
people.say(); // TypeError: people.say is not a function
?雖然class和extends實(shí)質(zhì)上是作為語法糖,用來統(tǒng)一了程序員對(duì)基于類的面向?qū)ο蟮哪M,但是確確實(shí)實(shí)讓JS在對(duì)象寫法上更加清晰,new運(yùn)算符和class搭配使用,讓function回歸原本的函數(shù)語義,值得一提的是,在ES6之后用 => 語法創(chuàng)建的函數(shù)僅僅是函數(shù),它們無法被當(dāng)作構(gòu)造器使用。
2.1 JavaScript中的對(duì)象分類
?在1.x章節(jié)中出現(xiàn)的所有對(duì)象都是內(nèi)置對(duì)象中的普通對(duì)象(由{}語法、Object構(gòu)造器或者class關(guān)鍵字定義類創(chuàng)建的對(duì)象,它能夠使用原型繼承方法)。
?所有對(duì)象分類:
??1、宿主對(duì)象(host Objects):由JavaScript宿主環(huán)境提供的對(duì)象,它們的行為完全由宿主環(huán)境決定。
??2、內(nèi)置對(duì)象(Built-in Objects):由JavaScript語言提供的對(duì)象。
???2、1固有對(duì)象(Intrinsic Objects ):由標(biāo)準(zhǔn)規(guī)定,隨著JavaScript運(yùn)行時(shí)創(chuàng)建而自動(dòng)創(chuàng)建的對(duì)象實(shí)例。
???2、2原生對(duì)象(Native Objects):可以由用戶通過Array、RegExp等內(nèi)置構(gòu)造器或者特殊語法創(chuàng)建的對(duì)象。
???2、3普通對(duì)象(Ordinary Objects)。
?宿主對(duì)象詳解:
?所有非內(nèi)置對(duì)象都是宿主對(duì)象(host object),即由 ECMAScript 實(shí)現(xiàn)的宿主環(huán)境提供的對(duì)象,所有 BOM 和 DOM 對(duì)象都是宿主對(duì)象。我們最為熟悉的,就是在瀏覽器環(huán)境中的全局對(duì)象window,window上又有很多屬性,如document,宿主對(duì)象也分為固有的和用戶可創(chuàng)建的兩種,比如document.createElement就可以創(chuàng)建一些 DOM 對(duì)象。
?固有對(duì)象詳解:
?固有對(duì)象在任何JS代碼執(zhí)行前就已經(jīng)被創(chuàng)建出來了,它們通常扮演者類似基礎(chǔ)庫(kù)的角色。Object和Function其實(shí)就是固有對(duì)象的一種。
?原生對(duì)象詳解:
?JavaScript的標(biāo)準(zhǔn)中提供了30多個(gè)原生構(gòu)造器,我們把通過這些構(gòu)造器構(gòu)造出的對(duì)象稱作原生對(duì)象,它們也無法用class/extend語法來繼承,所有這些原生對(duì)象都是為了特定能力或者性能,而設(shè)計(jì)出來的“特權(quán)對(duì)象。

?特殊行為的對(duì)象:
?除了上面介紹的對(duì)象之外,在固有對(duì)象和原生對(duì)象中,有一些對(duì)象的行為跟正常對(duì)象有很大區(qū)別,
?它們常見的下標(biāo)運(yùn)算(就是使用中括號(hào)或者點(diǎn)來做屬性訪問)或者設(shè)置原型跟普通對(duì)象不同:
??1、Array:Array的length屬性根據(jù)最大的下標(biāo)自動(dòng)發(fā)生變化;
??2、Object.prototype:作為所有正常對(duì)象的默認(rèn)原型,不能再給它設(shè)置原型了;
??3、String:為了支持下標(biāo)運(yùn)算,String的正整數(shù)屬性訪問會(huì)去字符串里查找;
??4、Arguments:arguments的非負(fù)整數(shù)型下標(biāo)屬性跟對(duì)應(yīng)的變量聯(lián)動(dòng);
??5、模塊的namespace對(duì)象:特殊的地方非常多,跟一般對(duì)象完全不一樣,盡量只用于import吧;
??6、類型數(shù)組和數(shù)組緩沖區(qū):跟內(nèi)存塊相關(guān)聯(lián),下標(biāo)運(yùn)算比較特殊;
??7、bind()后的function:跟原來的函數(shù)相關(guān)聯(lián)。
?推薦書籍《ECMAScript 6 入門》