構造函數,就是專門用來生成實例對象的函數。一個構造函數,能夠生成多個實例對象,這些實例對象都有相同的結構。javascript
function Person(name){ this.name = name; }
爲了與普通函數區別,構造函數名字的第一個字母一般大寫。java
構造函數的特色有兩個:瀏覽器
this
關鍵字,表明了所要生成的對象實例。new
命令。new
命令的做用,就是執行構造函數,返回一個實例對象。app
let a = new Person('dora'); a.name // dora
new
命令自己就能夠執行構造函數,因此後面的構造函數能夠帶括號,也能夠不帶括號,但爲了代表是函數調用,推薦使用括號表示更明確的語義。函數
使用 new
命令時,它後面的函數依次執行下面的步驟:this
let o = new Object();
Object.setPrototypeOf(o,Foo.prototype);
Foo.call(o);
若是構造函數內部有 return
語句,並且後面跟着一個對象,則 new
命令會返回 return
語句指定的對象;不然,就會無論 return
語句,返回 this
對象,且不會執行 return
後面的語句。prototype
function Person(name) { this.name = name; if (name == undefined) { return {}; }else if(typeof name != 'string'){ return '姓名有誤'; } console.log(111); } new Person(); // {} new Person(123); // {name: 123} new Person('dora'); // {name:'dora'} // 111
若是當前函數是 new
命令調用的,在函數內部的 new.target
屬性指向當前函數,不然爲 undefined
。code
function F() { console.log(new.target === F); } F() // false new F() // true
使用這個屬性,能夠判斷函數調用時,是否使用了 new
命令。對象
function F() { if (!new.target) { throw new Error('請使用 new 命令調用!'); } } F() // false new F() // true
若是不使用 new
命令執行構造函數就會引起一些意想不到的結果,因此爲了保證構造函數必須與 new
命令一塊兒使用,除了 new.target
以外也能夠有如下兩個解決辦法。繼承
在構造函數內部第一行加上 use strict
。這樣的話,一旦忘了使用 new
命令,直接調用構造函數就會報錯。
function Person(name, age){ 'use strict'; this.name = name; this.age = age; } Person() // TypeError: Cannot set property 'name' of undefined
報錯緣由是由於不加 new
調用構造函數時,this
指向全局對象,而嚴格模式下,this
不能指向全局對象,默認等於 undefined
,給 undefined
添加屬性確定會報錯。
function Person(name, age) { if (!(this instanceof Person)) { return new Person(name, age); } this.name = name; this.age = age; } Person('dora', 18).name // dora (new Person('dora', 18)).age // 18
在構造函數內部判斷 this
是不是構造函數的實例,若是不是,則直接返回一個實例對象。
任何函數都有一個 prototype
屬性,這個屬性稱爲函數的「原型」,屬性值是一個對象。只有函數有原型屬性。
function f(){} typeof f.prototype // "object"
對於普通函數來講,原型沒有什麼用。但對於構造函數來講,經過 new
生成實例的時候,該屬性會自動成爲實例對象的原型對象。
用來定義全部實例對象共用的屬性和方法。
若是將對象的方法寫入構造函數中,則 new
多少個實例,方法將會被複制多少次,雖然複製出來的函數是同樣的,但分別指向不一樣的引用地址,不利於函數的複用。
function Person(){ this.name = function(){ console.log('dora'); } } let p1 = new Person(); let p2 = new Person(); p1.name === p2.name // false
所以,將全部的屬性都定義在構造函數裏,全部的方法都定義在構造函數的原型中。這樣,實例的方法都指向同一個引用地址,內存消耗小不少。
函數原型 prototype
對象的屬性,指向這個原型所在的構造函數,能夠被全部實例對象繼承,指向構造本身的構造函數。
Person.prototype.constructor.name // "Person" p1.constructor.name // "Person"
函數的 name
屬性返回函數名。
Person.prototype.sayHi = function(){ console.log('Hi,I am Dora'); }
Person.prototype = { sayHi: function(){ console.log('Hi,I am Dora'); } }
這樣用對象字面量直接覆蓋,會讓 constructor
與構造函數失聯,能夠手動補上這個屬性。
Person.prototype = { constructor: Person, sayHi: function(){ console.log('Hi,I am Dora'); } }
Object.assign(Person.prototype, { sayHi: function(){} })
定義構造函數原型中的方法時儘可能不要相互嵌套,各方法最好相互獨立。
任何一個對象都有 __proto__
屬性,這個屬性稱爲對象的「原型對象」,一個對象的原型對象就是它的構造函數的 prototype
。
function Person(){} let p1 = new Person(); p1.__proto__ === Person.prototype // true
__proto__
並非語言自己的屬性,這是各大瀏覽器廠商添加的私有屬性,雖然目前不少瀏覽器均可以識別這個屬性,但依舊不建議在生產環境下使用,避免對環境產生依賴。
生產環境下,咱們可使用 Object.getPrototypeOf(obj)
方法來獲取參數對象的原型。
Object.getPrototypeOf(p1) === Person.prototype // true
當訪問對象的屬性時,若是這個對象沒有這個屬性,系統就會查找這個對象的 __proto__
原型對象,原型對象也是個對象,也有本身的 __proto__
原型對象,而後就會按照這個原型鏈依次往上查找,直到原型鏈的終點 Object.prototype
。
Object()
是系統內置的構造函數,用來建立對象的, Object.prototype
是全部對象的原型鏈頂端,而Object.prototype
的原型對象是 null
。
Object.getPrototypeOf(Object.prototype) // null
若是對象自身和它的原型都定義了一個同名屬性,那麼優先讀取對象自身的屬性。
繼承的核心是 子類構造函數的原型是父類構造函數的一個實例對象。
首先繼承父類的屬性
// 1. 父類構造函數 function Super(data){ this.data = data; }; Super.prototype.funName = function(){}; // 2. 子類構造函數 function Sub(){ // 用來繼承父類的參數和屬性 Super.apply(this, arguments); }
其次繼承父類的方法
總體繼承
Sub.prototype = Object.create(Super.prototype); // or Sub.prototype = new Super();
單個方法的繼承
Sub.prototype.funName = function(){ Super.prototype.funName.call(this); // some other code }
最後須要改變 constructor 指向
此時子類實例的 constructor
指向父類構造函數 Super
,需手動改變。
Sub.prototype.constructor = Sub;
ES5 沒有多重繼承功能,即不容許一個對象同時繼承多個對象,但可經過變通方法實現這個功能。
function S(){ M1.call(this); M2.call(this); }; S.prototype = Object.create(M1.prototype); // 繼承 M1 Object.assign(S.prototype,M2.prototype); // 繼承鏈上加入 M2 S.prototype.constructor = S; // 指定構造函數。
instanceof
運算符返回一個布爾值,表示對象是否爲某個構造函數的實例。繼承的子類實例也是父類的實例,所以繼承的也爲 true
。
let d = new Date(); d instanceof Date // true d instanceof Object // true
實例對象可繼承 isProtorypeOf()
方法,用來判斷該對象是否爲參數對象的原型對象。
只要實例對象處在參數對象的原型鏈上,isPrototypeOf()
方法都返回 true
。
let o1 = {}; let o2 = Object.create(o1); let o3 = Object.create(o2); o2.isPrototypeOf(o3) // true o1.isPrototypeOf(o3) // true o2.isPrototypeOf(o2) // false