P4 面向?qū)ο蟮某绦蛟O(shè)計(jì)
4.1 理解對象
// 創(chuàng)建實(shí)例
var person = new Object();
person.name = 'hqy';
person.age = 21;
person.sayName = function() {
alert(this.name);
}
// 對象字面量
var person = {
name: 'hqy',
age: 21,
sayName; function() {
alert(this.name);
}
}
數(shù)據(jù)屬性
-
Configurable:能否通過delete刪除屬性,能否修改屬性的特性,能否把屬性修改為訪問器屬性,默認(rèn)true -
Enumerable:能否通過for-in循環(huán)返回屬性,默認(rèn)true -
Writable:能否修改屬性的值,默認(rèn)true -
Value:這個屬性的數(shù)據(jù)值,默認(rèn)undefined
使用ES5的 Object.defineProperty() 修改屬性默認(rèn)的特性
// 參數(shù):屬性所在對象,屬性名,一個描述符對象
var person = {};
Object.defineProperty(person, 'name', {
writable: false, // 設(shè)置成只讀
value: 'hqy'
})
alert(person.name) // hqy
person.name = 'sss'; // 嚴(yán)格模式下報錯
alert(person.name) // hqy
// 將 configurable 設(shè)置成 false,delete 無效,嚴(yán)格模式報錯
// 一旦把屬性定義為不可配置的,就不能再把它變回可配置了,修改除 writable 之外的特性都會導(dǎo)致錯誤
訪問器屬性
-
Configurable:能否通過delete刪除屬性,能否修改屬性的特性,能否把屬性修改為訪問器屬性,默認(rèn)true -
Enumerable:能否通過for-in循環(huán)返回屬性,默認(rèn)true -
Get:讀取屬性時調(diào)用的函數(shù),默認(rèn)undefined -
Set:寫入屬性時調(diào)用的函數(shù),默認(rèn)undefined
訪問器屬性不能直接定義,必須使用 Object.defineProperty() 來定義
var book = {
_year: 2004, // 下劃線常用來表示只能通過對象方法訪問的屬性
edition: 1
};
Object.defineProperty(book, 'year', {
get: function() {
return this._year;
},
set: function(val) {
if(val > 2004) {
this._year = val;
this.edition += val - 2004;
}
}
})
book.year = 2005;
alert(book.edition) // 2
讀取屬性的特性
使用 Object.getOwnPropertyDescriptor(對象,屬性名) 獲得給定屬性的描述符
4.2 創(chuàng)建對象-工廠模式
function createPerson(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
alert(this.name);
}
return o;
}
缺點(diǎn):沒有解決對象識別的問題,即怎么知道一個對象的類型
4.2 創(chuàng)建對象-構(gòu)造函數(shù)模式
// 構(gòu)造函數(shù)始終都應(yīng)該以一個大寫字母開頭
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
alert(this.name);
}
}
var person1 = new Person('hqy', 21);
var person2 = new Person('sss', 22);
與工廠模式相比:1. 沒有顯式創(chuàng)建對象, 2. 直接將屬性和方法賦給this對象,3. 沒有return
要創(chuàng)建Person的新實(shí)例,必須使用new
- 創(chuàng)建一個新對象
- 將構(gòu)造函數(shù)的作用域賦給新對象(this指向這個新對象)
- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
- 返回新對象
最后,person1 和 person2 分別報錯著 Person 的一個不同的實(shí)例,每個對象都有一個 constructor 屬性,指向 Person。
alert(person1.constructor == Person) // true
alert(person2.constructor == Person) // true
// 檢測對象類型
alert(person1 instanceof Person) // true
alert(person1 instanceof Object) // true
將構(gòu)造函數(shù)當(dāng)作函數(shù)
// 當(dāng)構(gòu)造函數(shù)
var person = new Person('hqy', 21);
person.sayName() // hqy
// 當(dāng)普通函數(shù)
Person('hqy', 21); // 添加到 window
window.sayName(); // 'hqy'
// 在另一個對象的作用域中調(diào)用
var o = new Object();
Person.call(o, 'sss', 25);
o.sayName() // sss
缺點(diǎn)
每個方法都要在每個實(shí)例上重新創(chuàng)建一遍,即不同實(shí)例上的同名函數(shù)是不相等的
alert(person1.sayName == person.sayName) // false
解決辦法
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName() {
alert(this.name);
}
但這樣如果對象需要定義很多方法,那么就要定義這么多的全局函數(shù)。。。
4.2 創(chuàng)建對象-原型模式 ★
理解原型對象
- 每個函數(shù)都有
prototype,指向函數(shù)的原型對象 - 每個原型對象都有
constructor(構(gòu)造函數(shù)),指向prototype屬性所在函數(shù)的指針 -
Person.prototype.constructor指向Person - 每個對象可以用
__proto__訪問Prototype - 這個連接存在與實(shí)例與構(gòu)造函數(shù)的原型對象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間

確定對象間是否存在這種關(guān)系
// 通過 isPrototypeOf()
alert(Person.prototype.isPrototypeOf(person1)) // true
// Object.getPrototypeOf()
alert(Object.getPrototypeOf(person1) == Person.prototype) // true
alert(Object.getPrototypeOf(person1).name) // name 設(shè)置在原型上
每當(dāng)代碼讀取某個對象的某個屬性時,都會執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。
- 搜索首先從實(shí)例開始。如果實(shí)例上找到了具有同名的屬性。則返回該屬性的值
- 上面沒有找到,繼續(xù)搜索指針指向的原型對象,找到返回,沒找到返回
undefined

不能通過對象實(shí)例重寫原型中的值,也就是說實(shí)例上的屬性怎么操作都不會影響到原型
function Person() {}
Person.prototype.name = 'hqy';
var person1 = new Person();
var person2 = new Person();
person1.name = 'sss';
alert(person1.name) // 'sss'
alert(person2.name) // 'hqy'
// 只有在實(shí)例上刪除該屬性才能重新訪問到原型中的屬性
delete person1.name;
alert(person1.name) // 'hqy'
hasOwnProperty() 方法檢測一個屬性是存在于實(shí)例上還是原型上,只在給定屬性存在于實(shí)例上才會返回 true。
in 單獨(dú)使用時會在通過對象能夠訪問給頂屬性時返回 true,無論在實(shí)例上還是原型中。
// 判斷屬性到底是存在于對象中還是實(shí)例上
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
枚舉屬性 Object.keys(),Object.getOwnPropertyName()
function Person() {}
Person.prototype.name = 'hqy';
Person.prototype.age = 21;
Person.prototype.job = 'software engineer';
var key = Object.keys(Person.prototype);
alert(key) // name,age,job
var person = new Person();
person.name = 'bob';
var key = Object.keys(person);
alert(key) // name
var key = Object.getOwnPropertyName(Person.prototype);
alert(key) // constructor,name,age,job
更簡單的原型語法
function Person() {}
Person.prototype = {
constructor: Person
// 以字面量創(chuàng)建,constructor 不再指向 Person
name: 'hqy',
age: 21,
job: 'software engineer'
}
原型動態(tài)性
- 重寫整個原型對象,會切斷構(gòu)造函數(shù)與最初原型之間的聯(lián)系
- 實(shí)例中的指針僅指向原型,不指向構(gòu)造函數(shù)
function Person() {}
var friend = new Person();
// 重寫整個原型對象,會切斷構(gòu)造函數(shù)與最初原型之間的聯(lián)系
Person.prototype = {
constructor: Person
// 以字面量創(chuàng)建,constructor 不再指向 Person
name: 'hqy',
age: 21,
job: 'software engineer',
sayName: function() {
alert(this.name);
}
}
friend.sayName(); // error

原生對象的原型
alert(typeof Array.prototype.sort) // function
String.prototype.getLength = function() {
return this.length;
}
缺點(diǎn)
function Person() {}
Person.prototype = {
constructor: Person
name: [1, 2], // 該屬性會被實(shí)例共享
sayName: function() {
alert(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.push(3);
alert(person1.sayName); // 1,2,3
alert(person2.sayName); // 1,2,3
alert(person1.name === person2.name) // true
4.2 創(chuàng)建對象-構(gòu)造函數(shù)+原型模式
function Person(name, age) {
this.name = name;
this.age = age;
this.friend = [1, 2];
}
Person.prototype = {
constructor: Person
sayName: function() {
alert(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.push(3);
alert(person1.sayName); // 1,2,3
alert(person2.sayName); // 1,2
alert(person1.name === person2.name) // false
4.2 創(chuàng)建對象-動態(tài)原型模式
通過檢查某個應(yīng)該存在的方法是否有效,來決定是否需要初始化原型
function Person(name) {
// 屬性
this.name = name;
//方法
// 初次調(diào)用構(gòu)造函數(shù)時才會執(zhí)行
if(typeof this.sayName !== 'function') {
Person.prototype.sayName = function() {
return this.name;
}
}
}
4.2 創(chuàng)建對象-寄生構(gòu)造函數(shù)模式
寄生構(gòu)造函數(shù)返回的對象與構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型屬性之間沒有關(guān)系,不能依賴 instanceof 確定對象類型。
// 創(chuàng)建一個具有額外方法的特殊數(shù)組
function SpecialArray() {
var list = new Array();
list.push.apply(list, arguments);
list.toPipedString = function() {
return this.join('|');
}
return list;
}
var color = new SpecialArray(1, 2, 3);
alert(color.toPipedString()) // 1|2|3
4.2 創(chuàng)建對象-穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對象最適合在一些安全的環(huán)境中(這些環(huán)境禁止使用this和new),或者在防止數(shù)據(jù)被其他應(yīng)用程序改動時使用。
- 新創(chuàng)建對象的實(shí)例方法不引用
this - 不使用
new操作符調(diào)用構(gòu)造函數(shù)
function Person(name) {
var o = new Object();
// 可以在這里定義私有變量和函數(shù)
...
// 函數(shù)方法
o.sayName = function() {
alert(name);
}
return o;
}
var person = Person('hqy');
person.sayName() // hqy
除了調(diào)用 sayName(),否則沒其他方法訪問其數(shù)據(jù)成員
4.3 繼承-原型鏈
構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:
每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個指向原型對象的內(nèi)部指針。
假如我們讓原型對象等于另一個類型的實(shí)例:
此時的原型對象將包含一個指向另一個原型的指針,相應(yīng)的,另一個原型也包含著一個指向另一個構(gòu)造函數(shù)的指針。
假如另一個原型有事另一個類型的實(shí)例,那么上面的關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例和原型的鏈條,這就是所謂的原型鏈。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 繼承 SubType
SubType.prototype = new SuperType();
SubType.protptype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue()) // true
instance 指向SubType 的原型,SubType 的原型又指向 SuperType 的原型。
getSuperValue() 方法仍然還在SuperType.protptype中,但property則位于SubType.prototype中。這是因?yàn)?code>property是一個實(shí)例屬性,而getSuperValue()是個原型方法。

確定原型和實(shí)例的關(guān)系
// instacnceof
alert(instance instanceof Object) // true
alert(instance instanceof SuperType) // true
alert(instance instanceof SubType) // true
// isPropertyOf()
alert(Object.prototype.isPropertyOf(instance)) // true
alert(SuperType.prototype.isPropertyOf(instance)) // true
alert(SubType.prototype.isPropertyOf(instance)) // true
謹(jǐn)慎地定義方法
- 給原型添加方法一定能夠要放在替換原型的語句之后
- 在通過原型鏈實(shí)現(xiàn)繼承時,不能使用對象字面量創(chuàng)建原型方法,這樣做會重寫原型鏈,切斷構(gòu)造函數(shù)和原型的聯(lián)系
原型鏈的缺點(diǎn)
包含引用類型值的原型屬性會被所有實(shí)例共享。(4.2 創(chuàng)建對象-構(gòu)造函數(shù)+原型模式 缺點(diǎn)已介紹)
4.3 繼承-借用構(gòu)造函數(shù)
function SuperType(name) {
this.name = name;
}
function SubType(name) {
// 繼承了 SuperType,同時傳遞參數(shù)
SuperType.call(this, name);
// 實(shí)例屬性
this.age = 21;
}
var instance = new SubType('hqy')
alert(instance.name); // hqy
alert(instance.age); // 21
缺點(diǎn)
僅僅是借用構(gòu)造函數(shù),方法都在構(gòu)造函數(shù)中定義,因此函數(shù)復(fù)用就無從談起了。
4.3 繼承-組合繼承(借用構(gòu)造+原型鏈)
使用原型鏈實(shí)現(xiàn)對原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)實(shí)現(xiàn)對實(shí)例屬性的繼承
function SuperType(name) {
this.name = name;
this.colors = [1, 2, 3];
}
SuperType.prototype.sayName = function() {
return this.name;
}
function SubType(name, age) {
// 繼承屬性
SuperType.call(this, name);
this.age = age;
}
// 繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; // ★
SubType.prototype.sayAge = function() {
return this.age;
}
var instance1 = new SubType('hqy', 21);
instance1.colors.push(4);
alert(instance1.colors) // 1,2,3,4
alert(instance.sayName()) // hqy
alert(instance.sayAge()) // 21
var instance2 = new SubType('sss', 25);
instance1.colors.push(4);
alert(instance1.colors) // 1,2,3
alert(instance.sayName()) // sss
alert(instance.sayAge()) // 2251
4.3 繼承-原型式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
在 object 函數(shù)內(nèi)部,先創(chuàng)建了一個臨時性的構(gòu)造函數(shù),然后將傳入額對象作為這個構(gòu)造函數(shù)的原型,最后返回這個臨時類型的一個新實(shí)例。
var person = {
name: 'hqy',
friends: [1, 2, 3, 4]
}
var a = object(person);
// 等價于 var a = Object.create(person);
a.friends.push(5);
var b = object(person);
a.friends.push(6);
alert(person.friends) // 1, 2, 3, 4, 5, 6
- 返回的這個新對象將
person作為原型,所以它的原型中就包含一個基本類型值屬性和一個引用類型值屬性。 - 這意味著
person.friends不僅屬于person所有,而且也會被a和b共享。 - 包含引用類型值的屬性始終都會共享相應(yīng)的值
4.3 繼承-寄生式繼承
該函數(shù)在內(nèi)部以某種方式來增強(qiáng)對象
fucntion createAnother(origin) {
var clone = object(origin); // 創(chuàng)建一個新對象
clone.sayHi = function() { // 以某種方式來增強(qiáng)對象
alert('hi');
}
return clone; // 返回這個對象
}
var person = {
name: 'hqy',
friends: [1, 2, 3, 4]
}
var a = createAnother(person);
a.sayHi(); // hi
4.3 繼承-寄生組合繼承
組合繼承是 JavaScript 最常用的繼承模式,但它最大的問題在無論什么情況下,都會調(diào)用兩次超類型構(gòu)造函數(shù)
- 創(chuàng)建子類型原型時
- 子類型構(gòu)造函數(shù)內(nèi)部
組合繼承的例子
function SuperType(name) {
this.name = name;
this.colors = [1, 2, 3];
}
SuperType.prototype.sayName = function() {
return this.name;
}
function SubType(name, age) {
// 繼承屬性
SuperType.call(this, name); // 第二次調(diào)用 SuperType()
this.age = age;
}
// 繼承方法
SubType.prototype = new SuperType(); // 第一次調(diào)用 SuperType()
SubType.prototype.constructor = SubType; // ★
SubType.prototype.sayAge = function() {
return this.age;
}
寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法。
思路:不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們需要的無非就是超類型原型的一個副本而已
// 參數(shù):子類型構(gòu)造函數(shù),超類型構(gòu)造函數(shù)
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 創(chuàng)建超類型原型的一個副本
prototype.constructor = subType; // 增強(qiáng)對象,為副本添加constructor屬性
subType.protptype = prototype; // 將副本復(fù)制給子類型的原型
}
function SuperType(name) {
this.name = name;
this.colors = [1, 2, 3];
}
SuperType.prototype.sayName = function() {
return this.name;
}
function SubType(name, age) {
// 繼承屬性
SuperType.call(this, name);
this.age = age;
}
// 繼承方法
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
return this.age;
}

P5 函數(shù)表達(dá)式
- 函數(shù)聲明提升,意思是在執(zhí)行代碼之前會項(xiàng)讀取函數(shù)聲明,但函數(shù)表達(dá)式只能當(dāng)代碼執(zhí)行到它那一行后才會被解析。
- 創(chuàng)建一個函數(shù)并把它賦值給變量
functionName,這種情況下創(chuàng)建的函數(shù)叫做匿名函數(shù)(拉姆達(dá)函數(shù))
5.1 遞歸
arguments.callee 是一個指向正在執(zhí)行的函數(shù)的指針
function factorial(num) {
if(num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
// 等價于 num * factorial(num -1),主要是為了防止函數(shù)名改變
}
}
5.2 閉包
閉包:是指有權(quán)訪問另一個函數(shù)作用域的變量的函數(shù)。
創(chuàng)建閉包最常見的方式,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)
function createComparisonFunction(key) {
return function(obj1, obj2) {
var val1 = obj1[key];
var val2 = obj2[key];
if (val1 < val2) {
return -1;
} else if(val1 > val2) {
return 1;
} else {
return 0;
}
}
}
當(dāng)一個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境及相應(yīng)的作用域鏈。然后,使用arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象。但在作用域中,外部函數(shù)的活動對象始終處于第二位,外部函數(shù)的外部函數(shù)的活動對象處于第三位。。。直至作為作用域鏈重點(diǎn)的全局執(zhí)行環(huán)境。
作用域鏈本質(zhì)上是一個指向變量對象的指針列表,它值引用當(dāng)不實(shí)際包含變量對象。
在另一個函數(shù)內(nèi)部定義的函數(shù)會將包含函數(shù)(外部函數(shù))的活動對象添加到它的作用域鏈中。因此,在createComparisonFunction()函數(shù)內(nèi)部定義的匿名函數(shù)的作用域中,實(shí)際上將會包含外部函數(shù)createComparisonFunction()的活動對象。

createComparisonFunction() 函數(shù)執(zhí)行完成后,其執(zhí)行環(huán)境的作用域鏈會被銷毀,但活動對象仍然留在內(nèi)存中,知道匿名函數(shù)被銷毀后,createComparisonFunction() 的活動對象才會被銷毀。
var compareNames = createComparisonFunction('name') ;
var result = compareNames({ name: 'hqy' }, { name: 'sss' });
// 解除對匿名函數(shù)的引用,一遍釋放內(nèi)存
compareNames = null;
閉包和變量
function createFunction() {
var result = [];
for(var i = 0; i < 10; i++) {
result[i] = function() {
return i;
}
}
return result;
}
// 每個函數(shù)都返回 10
// 因?yàn)槊總€函數(shù)的作用域中都保存著createFunction的活動對象,所以它們引用的是同一個 i
// 返回一個立即執(zhí)行的匿名函數(shù)解決問題
function createFunction() {
var result = [];
for(var i = 0; i < 10; i++) {
result[i] = function(num) {
return function() {
return num;
}
}(i);
}
return result;
}
關(guān)于 this 對象
這里只是簡單地介紹了下
var name = 'the window';
var object = {
name: 'the object',
getName: function() {
return function() {
return this.name;
}
}
}
// 內(nèi)部函數(shù)在搜索 this, arguments 時,只會搜索到其活動對象為止
alert(object.getName()()) // 'the window' (非嚴(yán)格模式下)
要想獲取 object 中的 name,這樣做
var name = 'the window';
var object = {
name: 'the object',
getName: function() {
var that = this;
return function() {
return that.name;
}
}
}
alert(object.getName()()) // 'the object'
幾種特殊情況
var name = 'the window';
var object = {
name: 'the object',
getName: function() {
return this.name;
}
}
object.getName() // 'the object'
(object.getName)() // 'the object',和上面的代碼定義是一樣的
(object.getName = object.getName)() // 'the window' (非嚴(yán)格模式下),因?yàn)橄葓?zhí)行的時賦值語句
內(nèi)存泄漏
function handle() {
var element = document.getElementById('someElement');
var id = element.id; // 消除循環(huán)引用
element.onclick = function() {
alert(id);
}
element = null; // 解除對DOM對象的引用,順利減少去引用數(shù)
}
5.3 模塊模式-單例
指只有一個實(shí)例的對象
var singleton = {
name: value,
method: function() {
// 這里是方法的代碼
}
}
模塊模式通過為單例添加私有變量和特權(quán)方法能夠使其得到增強(qiáng)
var singleton = function() {
// 私有變量和私有函數(shù)
var privateVal = 10;
function privateFunc() {
return false;
}
//特權(quán)/公有方法和屬性
return {
publicProperty: true,
publicMethod: function() {
privateVal++;
console.log(privateVal);
return privateFunc();
}
}
}
var a = new singleton();
a.publicMethod(); // 11
a.publicMethod(); // 12
var b = new singleton();
b.publicMethod(); // 11
5.3 模塊模式-增強(qiáng)的模塊模式
var singleton = function() {
// 私有變量和私有函數(shù)
var privateVal = 10;
function privateFunc() {
return false;
}
// 創(chuàng)建對象
var obj = new CustomType();
//添加特權(quán)/公有方法和屬性
obj.publicProperty = true;
obj.publicMethod = function() {
privateVal++;
console.log(privateVal);
return privateFunc();
}
// 返回這個對象
return obj;
}