在上一節中我們重點講了對象和函數,得出的結論是:函數也是一種對象,對象都是通過函數建立的。
還記得上一節留下的問題嗎?回顧一下:
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
console.log(typeof obj); //object
console.log(typeof arr); //object
console.log(typeof Object); //function
console.log(typeof Array); //function
我們可以看到,
Object
和
Array
都是函數,說明對象是函數建立的。但是,到底是為什麼呢?
接下來,就是揭秘時刻了:
在 JavaScript 中,每當定義一個對象的時候,對象中都會包含一些預定義的屬性。其中每個函數對象都有一個
prototype
屬性,這個屬性指向函數的原型對象。
那你可能會問了,函數對象和原型對象是什麼?
1. 什麼是函數對象
舉個小例子:
var o1 = {};
var o2 =new Object();
var o3 = new f1();
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
雖然 ”萬物皆對象“,但是,對象也是分為函數對象和普通對象的。
首先,聲明一點,
Object
和
Function
是函數對象,它倆是JS自帶的函數對象。
從上面的例子我們可以知道,o1,o2,o3是普通對象,f1,f2,f3是函數對象。怎麼區分普通對象和函數對象呢?其實,隻要記住一點,那就是通過new Function 建立出來的對象都是函數對象,其他都是普通對象。f1和f2歸根到底也是
Function
建立出來的,當然,
Object
也不例外。
了解了函數對象,我們接着往下說,每個函數都有一個
prototype
屬性,而這個屬性值是一個對象,預設隻有一個
constructor
的屬性,指向這個函數的本身。
怎麼了解呢?
var f = function(){};
console.log(f.prototype);
可以看到我建立了一個函數,在控制台輸出它的原型,結果如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLzQTO1ITO0IjMxITNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
這印證了我們上面所說的,函數預設的隻有
constructor
這一個屬性,而我們可以看到,裡面的constructor預設還指向一個函數,而這個函數就是它本身。這裡你可能會問了,為什麼
constructor
展開後,還有一個
__proto__
屬性,不要着急,接下來會細講這一塊。現在先來看看,為什麼
constructor
指向的是函數本身。
展開之後,我們發現,裡面
constructor
的
prototype
屬性不就是咱們這個函數的原型嗎?是以,答案很明顯了。為了更好的了解呢?看看這張圖:
f 是一個函數,右邊的是它的原型,想必這個指向已經很明确了吧,那函數的原型是這個樣子,對象呢?
說到這裡不得不再啰嗦一句,前面我們已經知道,函數也是對象,那對象既然作為屬性的集合,當然不可能像函數那麼單純啦,先列印出來看看:
結果是啥呢?
相較于函數,确實是多了好幾個屬性。但是,别忘了,對象我們是可以自定義屬性的哦。那前面的函數來舉例,預設是隻有
constructor
一個屬性的。
var Person = function(){};
Person.prototype.name = 'hsl';
Person.prototype.setAge = function() {
return 18;
}
console.log(Person.prototype);
新增了兩個屬性,
name
和
setAge
,看看效果:
這樣做有什麼用呢?當然是為了
new
出來的對象能夠調用裡面的方法啊。接着往下看:
function Person(){};
Person.prototype.name = 'hsl';
Person.prototype.setAge = function() {
return 18;
}
var person = new Person();
console.log(person.name); // hsl
console.log(person.setAge()); // 18
console.log(Person.prototype);
Person
是一個函數,
person
對象是從
Person
函數
new
出來的,這樣的話,
person
對象就可以調用
Person.prototype
的屬性了。
2. 什麼是原型對象
之前提到過函數對象預設的
constructor
屬性指向的函數的原型對象,但是,好像一直也不是很清楚,何為原型對象。原型對象,其實也就是一個普通對象,簡單點來說,上面例子的原型對象就是
Person.prototype
,為啥呢?那咱們把上面的例子改改:
function Person(){};
Person.prototype = {
name: 'hsl',
setAge: function () {
return 18;
}
}
var person = new Person();
console.log(person.name); // hsl
console.log(person.setAge()); // 18
console.log(Person.prototype);
最後,你會發現是一樣的結果,也就相當于是兩種為對象添加屬性的方法吧。
經過上面的一些小例子,我們來總結一下,以上面的代碼為例:
- 一個對象的原型對象就相當于是
Person.prototype
-
Person.prototype.constructor == Person
-
person.constructor == Person
簡單的解釋一下,在預設的情況下,所有的原型對象(
Person.prototype
)都會獲得一個
constructor
屬性,這個屬性指向
prototype
屬性所在的函數(
Person
)。對了,這裡插播一句,每個對象都有
__proto__
屬性,但是隻有函數對象才有prototype屬性。當然啦,
Function.prototype
除外,它是函數對象,但是沒有
prototype
屬性。
對于第三條結論,不知道大家還記得構造函數不,其實和我們上面的例子也是差別不大的。構造函數的目的就是為了執行個體化,在這裡,
person
就相當于是
Person
的執行個體,
person
也有一個
constructor
的屬性,該屬性就是指向
Person
的,看下面的例子:
function Person(name, age) {
this.name = name;
this.setAge = function() {
return age;
}
}
var person = new Person('hsl', 18);
console.log(person.constructor == Person); // true
根據這個例子又可以得出一個小結論啦,那就是執行個體的
constructor
指向構造函數。
說了半天的原型對象,那它是幹啥的?前面提到過調用自定義的屬性,這裡想說的是,主要是為了繼承,其實,意思也是差不多。
var Person = function(name){
this.name = name;
};
Person.prototype.setName = function(){
return this.name;
}
var person = new Person('hsl');
person.setName(); // hsl
我們給
Person
定義了一個
setName
屬性,
person
作為
Person
的執行個體繼承了這個屬性,是以我們可以得到setName`的資料。至于怎麼實作繼承的,就是接下來我們要講的原型鍊了。