構造函數
構造函數,就是專門用來生成實例對象的函數。一個構造函數,可以生成多個實例對象,這些實例對象都有相同的結構。
function Person(name){
this.name = name;
}
爲了與普通函數區別,構造函數名字的第一個字母通常大寫。
構造函數的特點有兩個:
- 函數體內部使用了
this
關鍵字,代表了所要生成的對象實例。 - 生成對象的時候,必須使用
new
命令。
new 命令
基本用法
new
命令的作用,就是執行構造函數,返回一個實例對象。
let a = new Person('dora');
a.name // dora
new
命令本身就可以執行構造函數,所以後面的構造函數可以帶括號,也可以不帶括號,但爲了表明是函數調用,推薦使用括號表示更明確的語義。
new 命令的原理
使用 new
命令時,它後面的函數依次執行下面的步驟:
- 創建一個空對象,作爲將要返回的對象實例。
let o = new Object();
- 將這個空對象的原型,指向構造函數的prototype屬性。
Object.setPrototypeOf(o,Foo.prototype);
- 將構造函數的 this 綁定到新創建的空對象上。
Foo.call(o);
- 始執行構造函數內部的代碼。
如果構造函數內部有 return
語句,而且後面跟着一個對象,則 new
命令會返回 return
語句指定的對象;否則,就會不管 return
語句,返回 this
對象,且不會執行 return
後面的語句。
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.target
如果當前函數是 new
命令調用的,在函數內部的 new.target
屬性指向當前函數,否則爲 undefined
。
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
命令一起使用,除了 new.target
之外也可以有以下兩個解決辦法。
1. 構造函數內部使用嚴格模式
在構造函數內部第一行加上 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
添加屬性肯定會報錯。
2. 在構造函數內部通過 instanceof 判斷是否使用 new 命令
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 原型
任何函數都有一個 prototype
屬性,這個屬性稱爲函數的“原型”,屬性值是一個對象。只有函數有原型屬性。
function f(){}
typeof f.prototype // "object"
對於普通函數來說,原型沒有什麼用。但對於構造函數來說,通過 new
生成實例的時候,該屬性會自動成爲實例對象的原型對象。
prototype 屬性的作用
用來定義所有實例對象共用的屬性和方法。
如果將對象的方法寫入構造函數中,則 new
多少個實例,方法將會被複制多少次,雖然複製出來的函數是一樣的,但分別指向不同的引用地址,不利於函數的複用。
function Person(){
this.name = function(){
console.log('dora');
}
}
let p1 = new Person();
let p2 = new Person();
p1.name === p2.name // false
因此,將所有的屬性都定義在構造函數裏,所有的方法都定義在構造函數的原型中。這樣,實例的方法都指向同一個引用地址,內存消耗小很多。
constructor 屬性
函數原型 prototype
對象的屬性,指向這個原型所在的構造函數,可以被所有實例對象繼承,指向構造自己的構造函數。
Person.prototype.constructor.name // "Person"
p1.constructor.name // "Person"
函數的 name
屬性返回函數名。
給 prototype 添加屬性
1. 點語法追加屬性
Person.prototype.sayHi = function(){
console.log('Hi,I am Dora');
}
2. 覆蓋原型對象
Person.prototype = {
sayHi: function(){
console.log('Hi,I am Dora');
}
}
這樣用對象字面量直接覆蓋,會讓 constructor
與構造函數失聯,可以手動補上這個屬性。
Person.prototype = {
constructor: Person,
sayHi: function(){
console.log('Hi,I am Dora');
}
}
定義構造函數原型中的方法時儘量不要相互嵌套,各方法最好相互獨立。
_proto_ 原型對象
任何一個對象都有 __proto__
屬性,這個屬性稱爲對象的“原型對象”,一個對象的原型對象就是它的構造函數的 prototype
。
function Person(){}
let p1 = new Person();
p1.__proto__ === Person.prototype // true
Object.getPrototypeOf(obj)
__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 運算符
instanceof
運算符返回一個布爾值,表示對象是否爲某個構造函數的實例。繼承的子類實例也是父類的實例,因此繼承的也爲 true
。
let d = new Date();
d instanceof Date // true
d instanceof Object // true
Object.prototype.isPrototypeOf()
實例對象可繼承 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