JS學習筆記(第6章)(面向對象之建立對象)

1、工廠模式

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    };
    return 0;
}
var person1 = createPerson("Nicholas",29,"Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

抽象了建立具體對象的過程。工廠模式雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)數組

2、構造函數模式

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    };
}
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

構造函數模式與工廠模式存在如下幾點不一樣:
(1)沒有顯示地建立對象;
(2)直接將屬性和方法賦給了this對象;
(3)沒有return語句
要建立Person的新實例,必須使用new操做符,以這種方式調用構造函數實際上會經歷一下4個步驟:
(1)建立一個新得對象;
(2)將構造函數的做用域賦值給新對象(所以this就指向了這個新對象);
(3)執行構造函數中的代碼(爲這個新對象添加屬性);
(4)返回新對象。
建立自定義的構造函數意味着未來能夠將它的實例標識爲一種特定的類型;而這正是構造函數模式賽過工廠模式的地方。
使用構造函數的主要問題就是每一個方法都要在每一個實例上從新建立一遍。
解決辦法:經過把函數定義轉移到構造函數外部來解決這個問題。函數

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName() {
    alert(this.name);
};
var person1 = Person("Nicholas",29,"Software Engineer");
var person2 = Person("Greg", 27, "Doctor");

clipboard.png

clipboard.png

3、原型模式

咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,這個對象的用途就是包含能夠由特定類型的全部實例共享的屬性和方法。prototype就是經過調用構造函數而建立的那個對象實例的原型對象。使用原型對象的好處是可讓全部對象實例共享他所包含的屬性和方法。this

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
person1.sayName();  //"Nicholas"

var person2 = new Person();
person2.sayName();  //"Nicholas"
alert(person1.sayName == person2.sayName);  //true

一、理解原型對象

(1)只要建立了一個新的函數,就會根據一組特定的規則誒該函數建立一個prototype屬性,這個屬性指向函數的原型對象;
(2)在默認狀況下,全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性是一個指向prototype屬性所在函數的指針;
(3)當調用構造函數建立一個新實例後,該實例內部將包含一個指針(內部屬性),指向構造函數的原型對象。注意:這個鏈接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間。spa

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

下圖展現了上述代碼建立的各個對象之間的關係。
clipboard.png
(1)能夠經過isPrototypeOf()方法來肯定對象之間是否存在[[Prototype]]關係,若是[[Prototype]]指向調用isPrototypeOf()方法的對象(Person.prototype),那麼這個方法就返回true。prototype

alter(Person.prototype.isprototypeOf(person1));  //true

Object.getPrototypeOf(),在全部支持的實現中。這個方法返回[[Prototype]]的值。指針

alert(Object.getPrototypeOf(person1)  == Person.prototype);  //true
alert(Object.getPrototypeOf(person1).name);  //"Nicholas"

(2)每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。若是在實例中找到啦具備給定名字的屬性,則返回屬性的值;若是沒有找到。則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性。若是在原型中找到了這個屬性,則返回該屬性的值。
(3)雖然能夠意經過對象實例訪問保存在原型中的值,但卻不可以經過對象實例重寫原型的值。當爲對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性;也就是說,添加這個屬性只會阻止咱們訪問原型中的那個屬性,但不會修改那個屬性。
(4)使用delete操做符則能夠徹底刪除實例屬性,從而使咱們可以從新訪問原型中的屬性。code

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name); //"Greg"來自實例
alert(person2.name); //"Nicholas"來自原型

delete(person1.name); //徹底刪除實例對象
alert(person1.name); //"Nicholas"來自原型

(5)使用hasOwnProperty()方法能夠檢測一個屬性是存在於實例中,仍是存在於原型中。這個方法只在給定屬性存在於對象實例中時,纔會返回true。對象

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnPrototype("name")); //false

person1.name = "Greg";
alert(person1.name); //"Greg"來自實例
alert(person1.hasOwnPrototype("name")); //true

alert(person2.name); //"Nicholas"來自原型
alert(person1.hasOwnPrototype("name")); //false

delete(person1.name); //徹底刪除實例對象
alert(person1.name); //"Nicholas"來自原型
alert(person1.hasOwnPrototype("name")); //false

二、原型與in操做符

(1)有兩方式使用in操做符:單獨使用和在for-in循環使用。在單獨使用時,in操做符會在經過對象可以訪問給定屬性時返回true,不管屬性存在於實例仍是原型中;在使用for-in循環時,返回的是全部可以經過對象訪問的、可枚舉的屬性,其中既包括存在於實例中的屬性,也包括存在於原型中的屬性。blog

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnPrototype("name")); //false
alert("name" in person1); //true

person1.name = "Greg";
alert(person1.name); //"Greg"來自實例
alert(person1.hasOwnPrototype("name")); //true
alert("name" in person1); //true

alert(person2.name); //"Nicholas"來自原型
alert(person1.hasOwnPrototype("name")); //false
alert("name" in person2); //true

delete(person1.name); //徹底刪除實例對象
alert(person1.name); //"Nicholas"來自原型
alert(person1.hasOwnPrototype("name")); //false
alert("name" in person1); //true

(2)同時使用hasOwnProperty()方法和in操做符,就能夠肯定該屬性究竟是存在於對象中,仍是存在於原型中。ip

function hasOwnProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
}

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person = new Person();
alert(person1.hasOwnPrototype(person,"name")); //true表示屬性存在於原型中 

person.name = "Greg";
alert(person1.hasOwnPrototype(person,"name")); //false表示屬性存在於實例中

Object.keys()方法:取得對象上全部可枚舉的實例屬性。這個方法接收一個對象做爲參數,返回一個包含全部可枚舉屬性的字符串數組。

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys);  //"name, age, job, sayName"

使用Object.getOwnPropertyName()方法能夠獲得全部的實例屬性,不管它是否可枚舉.

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var keys = Object.getOwnPropertyName(Person.prototype);
alert(keys);  //"constructor,name, age, job, sayName"

三、更簡單的原型語法

clipboard.png

//原型模式
function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

//更簡單的原型語法:用一個包含全部屬性和方法的對象字面量來重寫整個原型對象;
致使的問題:至關於重寫了默認的prototype屬性,constructor屬性再也不指向Person 
function Person() {
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

解決辦法:特地將constructor屬性設置回適當的值

function Person() {
}
Person.prototype = {
    constructor : Person, //將constructor設置爲原來的值
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

clipboard.png
可是,以這種方式重設constructor屬性致使它的[[Enumberable]]特性被設置爲true。默認狀況下,原生的constructor屬性是不可枚舉的。所以能夠嘗試用Object.defineProperty()重設構造函數。

function Person() {
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
//重設構造函數
Object.defineProperty(Person.prototype,"constructor",{
    enumerable : false,
    value : Person
});

四、原型的動態性

function Person() {
}
var friend = new Person();
//重寫整個原型對象
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName();  //error

重寫對象以前

clipboard.png

重寫原型對象以後

clipboard.png
重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫;它們引用的仍然是最初的原型。

五、原型對象的問題

原型模式的最大問題是由其共享的本質所致使的,原型中的全部屬性是被不少實例共享的。可是,實例通常都是要有屬於本身的所有屬性的。

function Person() {
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shebly", "Court"],
    sayName : function () {
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push["Van"];
alert (person1.friends); //"Shebly", "Court","Van"
alert (person2.friends); //"Shebly", "Court","Van"
alert(person1.friends === person2.friends); //true

4、組合使用構造函數模式和原型模式

建立自定義類型的最多見方式,就是組合使用構造函數模式和原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。

//構造函數
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);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
alert (person1.friends); //"Shebly", "Court","Van"
alert (person2.friends); //"Shebly", "Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

5、動態原型模式

能夠經過檢查某個應該存在的方法是否有效來決定是否須要初始化

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);
        };
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

注意:若是在已經建立了實例的狀況下重寫原型,就會切斷現有實例與新原型之間的聯繫。

6、寄生構造函數模式

P160封裝建立對象的代碼,而後再返回新建立的對象

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"

寄生構造函數模式除了使用new操做符並把使用的包裝函數叫作構造函數以外,這個函數模式和工廠模式實際上是如出一轍的,下面附上兩種模式的代碼

clipboard.png

7、穩妥構造函數模式

P161穩妥對象,指的是沒有公共屬性,並且其方法也不引用this的對象。穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:(1)新建立對象的實例方法不引用this;(2)不使用new操做符調用構造函數。

function Person(name, age, job) {
    var o = new Object();  //建立要返回的對象
    //能夠在這裏定義私有變量和函數

    //添加函數
    o.sayName = function() {
        alert(name);      `//注意寄生構造函數中是alert(this.name)`
    };
    //返回對象
    return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");   
`//注意寄生構造函數中是var friend = newPerson("Nicholas", 29, "Software Engineer"); `
friend.sayName();  //"Nicholas"

注意,在以這種模式建立的對象中,除了使用sayName()方法以外,沒有其餘辦法訪問name的值。

相關文章
相關標籤/搜索