天天看點

深入淺出了解JS(2)-- prototype原型

在上一節中我們重點講了對象和函數,得出的結論是:函數也是一種對象,對象都是通過函數建立的。

還記得上一節留下的問題嗎?回顧一下:

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);
           

可以看到我建立了一個函數,在控制台輸出它的原型,結果如下:

深入淺出了解JS(2)-- prototype原型

這印證了我們上面所說的,函數預設的隻有

constructor

這一個屬性,而我們可以看到,裡面的constructor預設還指向一個函數,而這個函數就是它本身。這裡你可能會問了,為什麼

constructor

展開後,還有一個

__proto__

屬性,不要着急,接下來會細講這一塊。現在先來看看,為什麼

constructor

指向的是函數本身。

深入淺出了解JS(2)-- prototype原型

展開之後,我們發現,裡面

constructor

prototype

屬性不就是咱們這個函數的原型嗎?是以,答案很明顯了。為了更好的了解呢?看看這張圖:

深入淺出了解JS(2)-- prototype原型

f 是一個函數,右邊的是它的原型,想必這個指向已經很明确了吧,那函數的原型是這個樣子,對象呢?

說到這裡不得不再啰嗦一句,前面我們已經知道,函數也是對象,那對象既然作為屬性的集合,當然不可能像函數那麼單純啦,先列印出來看看:

結果是啥呢?

深入淺出了解JS(2)-- prototype原型

相較于函數,确實是多了好幾個屬性。但是,别忘了,對象我們是可以自定義屬性的哦。那前面的函數來舉例,預設是隻有

constructor

一個屬性的。

var Person = function(){};
Person.prototype.name = 'hsl';
Person.prototype.setAge = function() {
	return 18;
}
console.log(Person.prototype);
           

新增了兩個屬性,

name

setAge

,看看效果:

深入淺出了解JS(2)-- prototype原型

這樣做有什麼用呢?當然是為了

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);
           

最後,你會發現是一樣的結果,也就相當于是兩種為對象添加屬性的方法吧。

經過上面的一些小例子,我們來總結一下,以上面的代碼為例:

  1. 一個對象的原型對象就相當于是

    Person.prototype

  2. Person.prototype.constructor == Person

  3. 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`的資料。至于怎麼實作繼承的,就是接下來我們要講的原型鍊了。