之前對javascript中的對象總有不明白的地方,在本週也遇到了疑惑,因而藉着機會去深刻的瞭解了一下javascrpit中的對象。javascript
在javascript中,建立對象有兩種方式,一種是使用 new 操做符後跟 Object 構造函數:java
let ob = new Object(); ob.name = 'object';
另外一種是直接使用對象字面值:函數
let ob = { name: 'object' };
這兩種方式是等價的,雖然 Object 構造函數或對象字面量均可以用來建立單個對象,但這些方式有個明顯的缺點:使用同
一個接口建立不少對象,會產生大量的重複代碼。在面向對象的設計語言中,一般經過建立類來解決這一問題。this
咱們能夠經過自定義構造函數來實現javascript中的類:spa
function Student(name) { this.type = 'student'; this.name = name; this.sayName = function() { console.log(this.name); } } let student1 = new Student('zhangsan'); student1.sayName(); // zhangsan let student2 = new Student('lisi'); student2.sayName(); // lisi
要建立 Person 的新實例,必須使用 new 操做符。以這種方式調用構造函數實際上會經歷如下 4個步驟:
(1) 建立一個新對象;
(2) 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象);
(3) 執行構造函數中的代碼(爲這個新對象添加屬性);
(4) 返回新對象。prototype
這樣一來,在建立相同類別的對象時,咱們就不須要寫大量重複的代碼。可是使用構造函數建立對象並不是沒有缺點。使用構造函數的主要問題,就是每一個方法都要在每一個實例上從新建立一遍,而且也沒法作到實例之間共享變量。設計
console.log(student1.sayName === student2.sayName); // false 不是同一個方法實例
爲方法建立兩個實例倒是沒有什麼必要,由於在this的狀況下,根本不須要在執行代碼前就把函數綁定到特定對象上面。好在javascript也有解決的辦法。指針
咱們建立的每一個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。code
不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個 prototype屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個 constructor(構造函數)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指針。對象
修改咱們的代碼,將對象的方法和共享變量放置到原型對象上:
function Student(name) { this.name = name; } Student.prototype.sayName = function() { console.log(this.name); } Student.prototype.type='student'; let student1 = new Student('zhangsan'); student1.sayName(); // zhangsan let student2 = new Student('lisi'); student2.sayName(); // lisi console.log(student1.sayName === student2.sayName); // true console.log(student1.type); // student
當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。ECMA-262 第 5 版中管這個指針叫[[Prototype]]。這個鏈接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。若是在實例中找到了具備給定名字的屬性,則返回該屬性的值;若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性。若是在原型對象中找到了這個屬性,則返回該屬性的值。這樣一來,咱們就能夠經過原型對象來實現實例之間共享方法和變量了。
繼承是面嚮對象語言中十分重要的一個概念。javascript 實現繼承主要是依靠原型鏈來實現的。
ECMAScript 中描述了原型鏈的概念,並將原型鏈做爲實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。簡單回顧一下構造函數、原型和實例的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那麼,假如咱們讓原型對象等於另外一個類型的實例,結果會怎麼樣呢?顯然,此時的原型對象將包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另外一個構造函數的指針。假如另外一個原型又是另外一個類型的實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
function Student(name) { this.name = name; } Student.prototype.sayName = function() { console.log(this.name); } Student.prototype.type='student'; function CollegeStudent() { this.major = '計算機'; Student.call(this,'wangwu'); } CollegeStudent.prototype = new Student(); let collegeStudent = new CollegeStudent(); collegeStudent.sayName(); console.log(collegeStudent);
以上代碼,咱們用大學生繼承了學生,使用Student.call(this,'wangwu')爲CollegeStudent中的this做用域添加上name屬性,再將CollegeStudent的原型對象設爲new Student(),這樣一來,就能夠經過CollegeStudent的原型對象找到Student對象上的方法和屬性了,這就是原型鏈。
雖然經過此方式能夠完成繼承的功能,可是不管什麼狀況下,都會調用兩次Student構造函數。第一次是在使用Student.call()時,第二次是在使用CollegeStudent.prototype = new Student()時。咱們在CollegeStudent原型中並不須要Student的屬性,咱們只須要Student的原型,所以能夠改進爲:
function Student(name) { this.name = name; } Student.prototype.sayName = function() { console.log(this.name); } Student.prototype.type='student'; function CollegeStudent() { this.major = '計算機'; Student.call(this,'wangwu'); } CollegeStudent.prototype = create(Student); let collegeStudent = new CollegeStudent(); collegeStudent.sayName(); function create(father) { function f() { } f.prototype = father.prototype; return new f(); }
惟一的不一樣點就在於CollegeStudent.prototype = create(Student),此時CollegeStudent的原型對象指向的是僅僅包含Student的原型對象而不包含Student的實例屬性(在f函數中並無定義任何的屬性,僅僅將f的原型對象指向Student的原型對象,此時new f()獲得的實例對象僅僅包含對Student原型對象的引用)。
此時的CollegeStudent原型鏈: