前瞻
JavaScript
是面對對象編程,但是它又跟其他編程語言不一樣,不同之處是JavaScript
面對對象並不是依賴於抽象類,而是通過原型鏈。在C++
和Java
使用new
命令時,都會調用"類"的構造函數。而在Javascript
語言中,new
命令後面跟的不是類,而是構造函數。但是,用構造函數生成實例對象,有一個缺點,那就是無法共享屬性和方法。爲了解決這個問題,於是在構造函數設置了prototype
屬性,也就是原型。
從一個構造函數說起
用構造函數生成實例對象,有一個缺點,那就是無法共享屬性和方法。從而造成對系統資源的浪費。具體例子如下:
function Cat(name, color) {
this.name = name;
this.color = color;
this.meow = function () {
console.log('喵喵');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
從上面的代碼,可以cat1
和cat2
的meow
方法是獨立的但是實現的功能都是一樣的,這既沒有必要,又浪費系統資源,因爲所有meow
方法都是同樣的行爲,完全應該共享。這個問題的解決方法,就是JavaScript
構造函數中添加prototype
屬性。
prototype是什麼?
只有函數纔有prototype屬性,又稱爲顯式原型。
prototype屬性包含一個對象,所有實例對象需要共享的屬性和方法,都放在這個對象裏面;那些不需要共享的屬性和方法,就放在構造函數裏面。
JavaScript
規定,每個函數都有一個prototype
屬性,指向一個對象。
function f() {}
typeof f.prototype // "object"
對於普通函數來說,該屬性基本無用。但是,對於構造函數來說,生成實例的時候,該屬性會自動成爲實例對象的原型。
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
上面代碼中,構造函數Animal
的prototype
屬性,就是實例對象cat1
和cat2
的原型對象。原型對象上添加一個color
屬性,結果,實例對象都共享了該屬性。
原型對象的屬性不是實例對象自身的屬性。只要修改原型對象,變動就立刻體現在所有實例對象上
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
如果實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。
cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';
總結一下,原型對象的作用,就是定義所有實例對象共享的屬性和方法。這也是它被稱爲原型對象的原因,而實例對象可以視作從原型對象衍生出來的子對象。
注:Object.prototype中Object是一個全局對象,也是一個構造函數,以及其他基本類型的全局對象也都是構造函數。
_proto_又是什麼?
_proto_
是每個對象都有的屬性,又稱爲隱式原型。但是,_proto_
不是一個規範屬性,只是部分瀏覽器實現了此屬性,對應的標準屬性是[[Peototype]]
。
大多情況下,_proto_
可以理解爲‘構造函數的原型’,是實例和它的構造函數之間建立的鏈接,即_proto_ ===constructor.prototype
例子:
// 字面量方式
var a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object {}
//構造器方式
var b = function(){}
console.log(b.prototype); //b {}
console.log(b.__proto__); //function() {}
// Object.create方式
var a1 = {}
var a2 = Object.create(a1)
console.log(b.prototype); //undefined
console.log(b.__proto__); //Object a1
ES6中的_proto_使用建議
本段摘自阮一峯ES6入門,具體解析可以點擊查看。
1、_proto_
屬性沒有寫入ES6
的正文,而是寫入了附錄,
2、原因是__proto__
前後的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API
,只是由於瀏覽器廣泛支持,才被加入了ES6
。
3、標準明確規定,只有瀏覽器必須部署這個屬性,其他運行環境不一定需要部署,而且新的代碼最好認爲這個屬性是不存在的。
4、無論從語義的角度,還是從兼容性的角度,都不要使用這個屬性,而是使用下面的Object.setPrototypeOf()
(寫操作)、Object.getPrototypeOf()
(讀操作)、Object.create()
(生成操作)代替。
原型鏈是什麼?
每個對象都會在其內部初始化一個屬性,就是prototype
(原型),當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那麼他就會去prototype
裏找這個屬性,這個prototype
又會有自己的prototype
,於是就這樣一直找下去,也就是我們平時所說的原型鏈的概念。
原型鏈的終點
所有對象的原型最終都可以上溯到Object.prototype
,Object.prototype
對象有沒有它的原型呢?回答是Object.prototype
的原型是null
。null
沒有任何屬性和方法,也沒有自己的原型。因此,原型鏈的盡頭就是null
。
1、字符串原型鏈終點
let test = 'hello'
// 字符串原型
let stringPrototype = Object.getPrototype(test)
// 字符串的原型是String對象
stringPrototype === String.Prototype // true
// String對象的原型是Object對象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true
2、函數原理鏈的終點
let test = function () {}
let fnPrototype = Object.getPrototype(test)
fnPrototype === Function.prototype // true
Object.getPrototypeOf(Function.prototype) === Object.Prototype // true
原型鏈是用來做什麼?
1、屬性查找
當JS引擎查找對象的屬性時,先查找對象本身是否存在該屬性,如果不存在,會去原型鏈上查找,但不會查找本自身的prototype。
用一個例子來形象說明一下:
let test = 'hello'
// 字符串原型
let stringPrototype = Object.getPrototype(test)
// 字符串的原型是String對象
stringPrototype === String.Prototype // true
// String對象的原型是Object對象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true
2、拒絕查找原型鏈
hasOwnPrototype
:指示對象自身屬性中是否具有指定的屬性。
語法:obj.hasOwnPrototype(prop)
。
參數prop
:查找的屬性。
返回值:Boolean
判斷是否存在。
let test = {'ABC': 'EDF' }
test.hasOwnPrototype('ABC') // true
test.hasOwnPrototype('toString') // false
該方法是在Object.prototype
上,所有對象都可以使用,且會忽略掉那些從原型鏈上繼承的屬性。
擴展
constructor 屬性
prototype
對象有一個constructor
屬性,默認指向prototype
對象所在的構造函數。
function P() {}
P.prototype.constructor === P // true
由於constructor
屬性定義在prototype
對象上面,意味着可以被所有實例對象繼承。
function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
上面代碼中,p
是構造函數P
的實例對象,但是p
自身沒有constructor
屬性,該屬性其實是讀取原型鏈上面的P.prototype.constructor
屬性。
constructor 屬性的作用
constructor
屬性的作用是,可以得知某個實例對象,到底是哪一個構造函數產生的。
舉個小栗子:
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
另一方面,有了constructor
屬性,就可以從一個實例對象新建另一個實例。
我再舉個小栗子:
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
總結
天下無難事,只怕有心人。原型和原型鏈算是前端工程師的開發基礎了,原型雖然不太好理解,但是我相信只要有心學習和實踐,也沒有想象中的那麼難。希望,這篇文章對我自己幫組的同時,童鞋們看完之後也有一定的收穫。