Object-Oriented 面向?qū)ο?/p>
理解對(duì)象
- 對(duì)象屬性分為 【數(shù)據(jù)屬性】 和 【訪問(wèn)器屬性】
- 對(duì)象屬性中的【數(shù)據(jù)屬性】
- configurable 描述該屬性能否通過(guò)delete刪除或被重新定義(為false時(shí)defineProperty也無(wú)法使用了)
- enumerable 能否被for-in循環(huán)返回屬性
- writable 能否修改屬性的值
- value 這個(gè)屬性的數(shù)據(jù)值
-
利用defineProperty來(lái)定義數(shù)據(jù)屬性
因?yàn)閣ritable屬性被設(shè)置為false,我嘗試修改xusheng的值,但是無(wú)效 - 對(duì)象屬性中的的 【訪問(wèn)器屬性】
- configurable 描述該屬性能否通過(guò)delete刪除或被重新定義(為false時(shí)defineProperty也無(wú)法使用了)
- enumerable 能否被for-in循環(huán)返回屬性
- get 在讀取屬性時(shí)調(diào)用的函數(shù)。
- set 在寫入屬性時(shí)調(diào)用的函數(shù)。

_ name 前面 _ 的書寫代表只能通過(guò)對(duì)象方法訪問(wèn)的屬性。
-
定義多個(gè)屬性:defineProperties可以創(chuàng)建多個(gè)對(duì)象屬性
例子創(chuàng)建了_year和edition兩個(gè)數(shù)據(jù)屬性和year訪問(wèn)器屬性 讀取屬性的特性
通過(guò)getOwnPropertyDescriptor獲取到的上面例子的屬性描述符
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");

- 創(chuàng)建對(duì)象方法:
- new Object()
- 對(duì)象字面量{ }
缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。
其他創(chuàng)建對(duì)象的方法
1. 工廠模式
這種模式抽象了創(chuàng)建具體對(duì)象的過(guò)程,用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)。

缺點(diǎn):工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問(wèn)題,但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)。
2. 構(gòu)造函數(shù)模式
構(gòu)造函數(shù)可以用來(lái)創(chuàng)建特定類型的對(duì)象

構(gòu)造函數(shù)開頭字母需要大寫(Person)
-
在new 構(gòu)造函數(shù)時(shí),程序會(huì)執(zhí)行以下步驟
- 創(chuàng)建一個(gè)新對(duì)象
- 將構(gòu)造函數(shù)的作用域賦值給新對(duì)象
- 執(zhí)行構(gòu)造函數(shù)中的代碼
- 返回新對(duì)象
對(duì)象的 constructor 是用來(lái)標(biāo)識(shí)對(duì)象類型的
通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象有一個(gè)constructor屬性,指向原構(gòu)造函數(shù)
-
創(chuàng)建得對(duì)象是構(gòu)造函數(shù)的實(shí)例
實(shí)例和構(gòu)造函數(shù)關(guān)系 以上構(gòu)造函數(shù)帶來(lái)的問(wèn)題,相同作用的函數(shù)被反復(fù)復(fù)制,改進(jìn):


- 新的問(wèn)題:全局下又不可能有多個(gè)類似于sayName函數(shù)的東西,會(huì)很亂
3. 原型模式



回到問(wèn)題本質(zhì),與構(gòu)造函數(shù)的區(qū)別在哪里?
新對(duì)象的這些屬性和方法是由所有實(shí)例共享(不會(huì)再創(chuàng)建相同作用的函數(shù))的。換句話說(shuō),person1 和 person2 訪問(wèn)的都是同一組屬性和同一個(gè) sayName()函數(shù)
- 理解原型對(duì)象


__proto__ 是兩個(gè)下劃線??!
確定實(shí)例和原型對(duì)象關(guān)系可以用:
- isPrototypeOf()判斷對(duì)象是否和原型對(duì)象有關(guān)
- Object.getPrototypeOf()獲得對(duì)象的原型對(duì)象
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf(person1) == Object.getPrototypeOf(person1) //獲得person1對(duì)象指向的原型對(duì)象
當(dāng)調(diào)用person1.sayname時(shí)。解析器會(huì)現(xiàn)在實(shí)例對(duì)象上查找sayname屬性,如果找不到,再在原型對(duì)象上查找sayname屬性。所以實(shí)例上的屬性會(huì)屏蔽掉原型上同名屬性
通過(guò)delate操作符可以刪除實(shí)例上的屬性,從而恢復(fù)原型的訪問(wèn)
利用hasOwnProperty(“屬性名”)可以判斷這個(gè)屬性是來(lái)自于原型對(duì)象還是自己本身

- 與hasOwnProperty不同的是,使用in操作符只要能訪問(wèn)到該屬性都會(huì)返回true(無(wú)論是原型對(duì)象上還是實(shí)例對(duì)象上)
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true name屬性在實(shí)例對(duì)象屬性中
- hasPrototypeProperty(person1,"name") 實(shí)例對(duì)象要能訪問(wèn)到原型上的屬性才能返回true,即使原型對(duì)象上有屬性name,但是實(shí)例對(duì)象上的name將原型對(duì)象上的name屏蔽,所以依然返回false
var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true
person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false 實(shí)例對(duì)象要能訪問(wèn)到原型上的屬性才能返回true
-
for in 枚舉屬性
原則:所有開發(fā)人員定義的屬性都是可以枚舉的屬性,所以對(duì)象實(shí)例屬性和原型對(duì)象屬性都是可以被枚舉的。
圖中紅色框中屬性都可被枚舉,黃色框中不可被枚舉,因?yàn)樗麄儊?lái)自原型鏈中的Object原型屬性 取得對(duì)象上所有可枚舉的實(shí)例屬性:Object.keys()
使用Object.key()取得對(duì)象上可枚舉的屬性組成的字符串?dāng)?shù)組
var person = new Person()
Object.key(person) //"say" person實(shí)例對(duì)象上的屬性,原型對(duì)象上的不會(huì)被
console.log(Object.keys(person.__proto__))// "age,job,name,sayName" 原型上的屬性
- Object.getOwnPropertyNames(object) 可以列出對(duì)象上所有的屬性,即使是不可枚舉的屬性,你可以試著枚舉出繼承的Object對(duì)象上的屬性
console.log(Object.getOwnPropertyNames(person.__proto__.__proto__))
- 換一種方式定義構(gòu)造函數(shù)原型上的屬性
function Person(){
}
Person.prototype = {
constructor : Person, //如果沒(méi)有這句,constructor 屬性就指不到構(gòu)造函數(shù)了
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
此方法帶來(lái)的問(wèn)題:constructor 的[[Enumerable]]值為true可枚舉了
使用Object.defineProperty重設(shè)構(gòu)造函數(shù)
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
}); //此寫法適用于es5
- 原型的動(dòng)態(tài)性:很好理解,實(shí)例對(duì)象(person1)的prototype屬性指針指向原型對(duì)象,所以在原型對(duì)象上多次修改屬性值,在實(shí)例上也同樣能被正確的訪問(wèn)
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"(沒(méi)有問(wèn)題?。?
當(dāng)然,你不能重寫原型對(duì)象,切斷與構(gòu)造函數(shù)的聯(lián)系
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error

- 原生對(duì)象的原型:原生(Array,String)構(gòu)造函數(shù)都在其原型(prototype)上定義了方法,所以我們可以通過(guò)例如String.substring()來(lái)操作字符串實(shí)例
String.prototype.startsWith() =function(){
...//你可以給原生對(duì)象添加屬性,讓所有實(shí)例化的字符串都能訪問(wèn),但是不建議這樣做
}
- 原型對(duì)象的問(wèn)題:修改原型對(duì)象上的屬性值會(huì)對(duì)其他實(shí)例對(duì)象造成影響(我們有時(shí)候需要它[所有string的實(shí)例都能使用String.substring()],有時(shí)候又不需要它)
4. 組合使用構(gòu)造函數(shù)和原型模式
- 書中想表達(dá)的意思就是物盡其用,將實(shí)例屬性放到構(gòu)造函數(shù)中定義,將用于共享的屬性和方法放到原型對(duì)象里
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
5. 動(dòng)態(tài)原型模式:在構(gòu)造函數(shù)中使用原型模式,并且避免重復(fù)掛載到原型模式
它把所有信息都封裝在了構(gòu)造函數(shù)中,而通過(guò)在構(gòu)造函數(shù)中初始化原型(僅在必要的情況下),又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)
換句話說(shuō),第一次new Person()的時(shí)候,sayName肯定是不存在的,所以會(huì)掛載sayName和其他方法到原型上,而下一次newPerson的時(shí)候,sayName()已經(jīng)在原型上了[注意if判斷的作用],sayName包括其他if語(yǔ)句中的方法都不會(huì)被重復(fù)掛載!
function Person(name, age, job) {
//屬性
this.name = name;
this.age = age;
this.job = job; //方法
if (typeof this.sayName != "function") {
Person.prototype.sayName = function () {
alert(this.name);
};
Person.prototype.sayAge = function () {
alert(this.age);
};
}
}
6. 寄生構(gòu)造函數(shù)模式
類似于工廠模式,不過(guò)使用new function()的形式創(chuàng)建
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
這個(gè)模式可以在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊
數(shù)組。由于不能直接修改 Array 構(gòu)造函數(shù),因此可以使用這個(gè)模式
注意:返回的對(duì)象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性之間沒(méi)有關(guān)系。 instanceof 不能溯源,所以建議在可以使用其他模式的情況下,不要使用這種模式。
7. 穩(wěn)妥構(gòu)造函數(shù)模式(閉包)
function Person(name, age, job){
//創(chuàng)建要返回的對(duì)象
var o = new Object();
//可以在這里定義私有變量和函數(shù)
//添加方法
o.sayName = function(){
alert(name);
};
//返回對(duì)象
return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
除了使用 sayName()方法之外,沒(méi)有其他辦法訪問(wèn) name 的值(有點(diǎn)用到了閉包,讓匿名函數(shù)返回私有變量)
同樣和寄生構(gòu)造模式一樣不能溯源,不能使用instanceof
繼承
- 函數(shù)簽名(或者類型簽名,抑或方法簽名)定義了 函數(shù)或方法的輸入與輸出。JavaScript 是一種類型松散或動(dòng)態(tài)語(yǔ)言。這意味著您不必提前聲明變量的類型。以下是java的函數(shù)簽名
public static void main(String[] args)
繼承分為接口繼承和實(shí)現(xiàn)繼承
接口繼承:接口是一種特殊的抽象類,即繼承函數(shù)的簽名,故js中沒(méi)有接口繼承
實(shí)現(xiàn)繼承:實(shí)現(xiàn)繼承是繼承函數(shù)實(shí)際的方法子類型超類型
子類型:繼承者
超類型:被繼承者(下文用父類型)
1.原型鏈

注意此時(shí)的 instance 應(yīng)該是指向B的原型,因?yàn)锳的prototype被b的實(shí)例重寫了。
在實(shí)例中搜索該屬性。如果沒(méi)有找到該屬性,則會(huì)繼續(xù)搜索實(shí)例的原型。在通過(guò)原型鏈實(shí)現(xiàn)繼承的情況下,搜索過(guò)程就得以沿著原型鏈繼續(xù)向上
-
所有函數(shù)的默認(rèn)原型都是 Object 的實(shí)例:原型鏈的底層永遠(yuǎn)都是Object。因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針,指向 Object.prototype(也就是繼承于Object原生)。這也是為什么所有自定義類型都會(huì)繼承toString(),valueOf()方法的原因。上圖的B其實(shí)還繼承了原生Object,如下圖。
默認(rèn)原型都會(huì)指向Object.prototype, 確定原型和實(shí)例的關(guān)系的方式:
1.instanceof
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
- isPrototypeOf
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
- 在完成繼承后再添加額外的方法,否則額外的方法會(huì)被繼承覆蓋掉。先.prototype = new Function(),再.prototype.xx=xx.更不能使用.prototype={ }字面量重寫原型
- 原型繼承帶來(lái)的引用類型的問(wèn)題:原型中包含引用類型,A構(gòu)造函數(shù)的原型繼承于B的實(shí)例,基于A創(chuàng)建M實(shí)例,修改引用類型值,那么B的原型中的值也相應(yīng)會(huì)改變,如果再基于A創(chuàng)建N實(shí)例,那么M,N就會(huì)擁有相同的引用。
不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)
2.借用構(gòu)造函數(shù)
- 利用apply或者call在自己的構(gòu)造函數(shù)中調(diào)用別人的構(gòu)造函數(shù)
function SuperType(name){
this.name = name;
}
function SubType(){
//繼承了 SuperType,同時(shí)還傳遞了參數(shù)
SuperType.call(this, "Nicholas");
//實(shí)例屬性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
- 缺點(diǎn)很明顯:方法都在構(gòu)造函數(shù)中定義,因此函數(shù)復(fù)用就無(wú)從談起。而且父類型原型中的方法子類型也用不了
3.組合繼承
- 組合繼承是ji中常用的實(shí)現(xiàn)繼承的方式,將原型繼承和構(gòu)造函數(shù)繼承集合
function f1(name) {
this.name = name
this.colors = ['blue', 'red', 'green'] //將需要被繼承的引用類型定義在構(gòu)造函數(shù)中
}
f1.prototype.sayName = function () {
alert(this.name)
}
function f2(name, age) {
f1.call(this, name) //此時(shí),f2上已經(jīng)繼承f1構(gòu)造函數(shù)中的屬性
this.age = age
}
f2.prototype = new f1(); //此時(shí),f2已經(jīng)繼承f1原型對(duì)象上的屬性
var F2 = new f2()
for (const x in F2) {
console.log(x)//name color age sayname
}
4.原型式繼承
- 將一個(gè)對(duì)象作為子對(duì)象的原型對(duì)象實(shí)現(xiàn)繼承
function object(o) {
function F() { }
F.prototype = o; //person對(duì)象被共享到了通過(guò)object創(chuàng)造出來(lái)的對(duì)象的原型上
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
debugger
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
- Es5規(guī)范化方法:Object.creat()
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
在沒(méi)有必要興師動(dòng)眾地創(chuàng)建構(gòu)造函數(shù),而只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類似的情況下,原型式
繼承是完全可以勝任的.
- 個(gè)人理解:其實(shí)就是原型鏈繼承的縮寫方案,同樣存在原型鏈繼承引用類型被共享的問(wèn)題
5.寄生式繼承
function createAnother(original) {
var clone = Object.create(original); //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
clone.sayHi = function () { //以某種方式來(lái)增強(qiáng)這個(gè)對(duì)象
alert("hi");
};
return clone; //返回這個(gè)對(duì)象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
注意點(diǎn): clone.sayHi 可以給新對(duì)象上添加屬性,和寄生構(gòu)造函數(shù)模式有點(diǎn)像,createAnother存在的意義就是在新的實(shí)例上添加公有屬性
6.寄生組合式繼承
- 還記得組合繼承模式嗎?通過(guò)f2.call(this)和.protype=new f2()分別調(diào)用了兩次父類型,寄生組合式繼承就是為了解決這個(gè)問(wèn)題。
- inheritPrototype接受兩個(gè)參數(shù),分別是子類型的構(gòu)造函數(shù)和夫類型的構(gòu)造函數(shù)
function inheritPrototype(subType, superType){
var prototype = Object.creat(superType.prototype); //創(chuàng)建對(duì)象
prototype.constructor = subType; //增強(qiáng)對(duì)象
subType.prototype = prototype; //指定對(duì)象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType); //SubType.protoype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
};
- 只調(diào)用了一次 SuperType 構(gòu)造函數(shù),并且因此避免了在 SubType. prototype 上面創(chuàng)建不必要的、多余的屬性
- 個(gè)人覺(jué)得是拷貝父類型的prototype到子類型的protype上,inheritPrototype的作用其實(shí)就是SubType.protoype = new SuperType();
ps:終于看完了,真特么累,感覺(jué)迷迷糊糊的,肯定要刷第二遍的~。




