以下是2008年Github創建以來,各種程式語言的JavaScript原型詳解情況
其中JavaScript自2015年後就盤踞第一名,成為github上被使用最多的語言,早期,JS的使用還主要集中於瀏覽器中,但是隨著node.js進軍伺服器開發和React Native逐漸向移動端滲透,一個屬於JS的全端時代就要來臨了。而JS界還流傳一句名言:「所有能用JS開發的應用程序,最終都會用JS來開發」。我就問你怕不怕?
好了,說了這麼多,我並不是想說JS為世界上最好的語言(顯然PHP才是,對吧?←_←),也不是覺得JS會替代誰,我只是覺得,JavaScript將會是一個大家(不只web端)都應該了解和學習的語言工具。
面對物件是大家都很熟悉的程式設計思想,是對真實世界的抽象,目前主要OOP語言用來實現面對物件的基礎是類,透過類別的封裝,繼承來映射真實世界。包括Java,C#,甚至是python等都透過類別的設計來實現面對物件。但細想起來也會覺得有問題,因為真實世界其實沒有類這種概念,只有一個個不同的對象,真實世界中,繼承關係發生在對象和對象之間,而不是類別。就例如孩子是對象,父母也是對象,孩子(對象)繼承自父母(對象)
JS也是面對對象的程式語言,只不過它實現面對對象的思路是基於原型(prototype) ,而不是類。這種想法也叫做物件關聯(Object Link Other Object),即在物件上直接映射那種真實世界的關係(如繼承)。
相關的概念其實我研究了好幾天,除開原型概念本身,與之聯繫的對象的產生,構造函數,proto,prototype的區別,為什麼對象沒有prototype這個指向原型的屬性,而是使用proto來指向原型?
好,我們先來談談原型這個概念。 JS中一切皆對象,而每個物件都有一個原型(Object除外),這個原型,大概就像Java中的父類,所以,基本上你可以認為原型就是這個對象的父對象,也就是每一個對象(Object除外)內部都保存了它自己的父對象,這個父對象就是原型。一般建立的物件如果沒有特別指定原型,那麼它的原型就是Object(這就很類似Java中所有的類別預設繼承自Object類別)。
在JS中,物件建立的方法有很多種,最常見的如下:
//第一种,手动创建 var a={'name':'lala'}; //第二种,构造函数 function A(){ this.name='lala'; } var a=new A(); //第三种,class (ES6标准写法) class A{ constructor(){ super(); this.name='lala'; } } var a=new A() //其实后面两种方法本质上是一种写法
這三種寫法所建立的物件的原型(父物件)都是Object,需要提到的是,ES6透過引入class ,extends等關鍵字,以一種語法糖的形式把建構子包裝成類別的概念,更便於大家理解。是希望開發者不再花精力去關注原型以及原型鏈,也充分說明原型的設計意圖和類別是一樣的。
當物件被建立之後,檢視它們的原型的方法不只一種,以前一般使用物件的proto屬性,ES6推出後,推薦用Object.getPrototypeOf( )方法來取得物件的原型
function A(){ this.name='lala'; } var a=new A(); console.log(a.__proto__) //输出:Object {} //推荐使用这种方式获取对象的原型 console.log(Object.getPrototypeOf(a)) //输出:Object {}
無論物件是如何建立的,預設原型都是Object,在這裡需要提及的比較特殊的一點就是,透過建構函式來建立對象,函數A本身也是一個對象,而A有兩個指向表示原型的屬性,分別是proto和prototype,而且兩個屬性並不相同
function A(){ this.name='lala'; } var a=new A(); console.log(A.prototype) //输出:Object {} console.log(A.__proto__) //输出:function () {} console.log(Object.getPrototypeOf(A)) //输出:function () {}
函數的的prototype屬性只有在當作構造函數創建的時候,把自身的prototype屬性值賦給物件的原型。而實際上,作為函數本身,它的原型應該是function對象,然後function物件的原型才是Object。
總之,建議使用ES6推薦的檢視原型和設定原型的方法。
其實原型和類別的繼承的用法是一致的:當你想用某個物件的屬性時,將目前物件的原型指向該對象,你就擁有了該物件的使用權了。
function A(){ this.name='world '; } function B(){ this.bb="hello" } var a=new A(); var b=new B(); Object.setPrototypeOf(a,b); //将b设置为a的原型,此处有一个问题,即a的constructor也指向了B构造函数,可能需要纠正 a.constructor=A; console.log(a.bb) //输出 hello
(增補)
如果使用ES6來做的話則簡單許多,甚至不涉及到prototype這個屬性
class B{ constructor(){ this.bb='hello' } } class A extends B{ constructor(){ super() this.name='world' } } var a=new A(); console.log(a.bb+" "+a.name); //输出hello world console.log(typeof(A)) //输出 "function"
怎麼樣?是不是已經完全看不到原型的影子了?活脫脫就是類別繼承,但你也看得到實際上類別A 的型別是function,所以說,本質上class在JS中是一種語法糖,JS繼承的本質依然是原型,不過,ES6引進class,extends來掩飾原型的概念也是一個很友善的舉動,對於長期學習那些類別繼承為基礎的面對物件程式語言的程式設計師而言。
我的建議是,盡可能理解原型,盡可能用class這種語法糖。
這個概念其實也變得比較簡單,可以類比類別的繼承鏈條,也就是每個物件的原型往上追溯,一直到Object為止,這組成了一個鏈條,將其中的物件串連起來,當尋找目前物件的屬性時,如果沒找到,就會沿著這個鏈條去查找,一直到Object,如果還沒發現,就會報undefined。那麼也意味著你的原型鏈不能太長,否則會出現效率問題。
對於原型概念的理解
#類比類別的繼承,物件的原型可以理解為物件的父物件
原型的運用
#盡可能使用ES6的標準,使用class,extends ,Object.getPrototype(),Object.setPrototype()等等
#需要注意的點