認識JavaScript的原型

原本打算也寫一個JavaScript學習筆記的系列,不過因爲筆者不太想買大部頭的js數據,和網上的資料也很多,因此js系列就打算寫到了算了了。編程

要理解JavaScript就要理解其原型,首先咱們先區分一下JavaScript中的兩種不一樣方式建立的函數;框架

直接定義獲得的函數

1 function foo1(){}
2 console.log(foo1.prototype);
3 console.log(foo1.__proto__);
4 
5 var foo2 = function() {}
6 console.log(foo2.prototype);
7 console.log(foo2.__proto__);

這裏有兩個屬性prototype和__proto__,咱們來看他們分別指向什麼。函數

  1. prototype:指向函數原型;
  2. __proto__:指向一個匿名的空方法;

咱們在看看foo1.prototype包含的屬性:學習

  1. constructor(指向函數自己)
  2. __proto__(指向父級對象的prototype),能夠看下面的驗證代碼:
1 function foo(){}
2 console.log(foo.prototype.__proto__ === Object.prototype);//true

能夠看見,原型對象的__proto__指向其父級對象的prototype;this

經過new關鍵字建立獲得的對象

1 function foo(){}
2 var a = new foo();
3 console.log(a.prototype);//undefined
4 console.log(a.__proto__);//Object

經過輸出咱們能夠發現,經過new獲得的對象,prototype屬性是空的,而__proto__則被賦予了一個對象,看下面的代碼:spa

1 function foo(){}
2 var a = new foo();
3 console.log(a.__proto__ === foo.prototype);//true

js將foo的原型屬性賦予了用new關鍵字建立的a對象;prototype

咱們看看使用new關鍵字js爲咱們作了什麼操做;code

好比下面的代碼:對象

var a = new foo();

能夠理解爲調用了下面的代碼:blog

1 var a={};//也就是說,初始化一個對象a;
2 a.__proto__=foo.prototype;//賦值原型鏈;
3 foo.call(a);//也就是說構造a,也能夠稱之爲初始化a;

原型繼承

咱們再來看看js的原型繼承,js中的繼承是經過原型鏈來體現的,簡單的說,當咱們調用一個屬性時(方法在js中也是以屬性的形式存在的),會先查找自身是否有該屬性,若是沒有則查找原型上是否存在該屬性,若是仍是沒有,則查找原型上的__proto__(即父類的原型)裏是否有該屬性,若是沒有則一致搜索到最頂級(即Object的原型),找到則返回該屬性的值,找不到則返回undefined。

咱們看下下面的代碼:

 1 var foo = function(){
 2     var a = 0;
 3     this.a = 1;
 4 };
 5 foo.prototype.a = 2;
 6 foo.prototype.b = "b";
 7 console.log(foo.a);//undefined
 8 foo.a = 4;
 9 foo.c = 5;
10 console.log(foo.a);//4
11 
12 var obj = new foo();
13 console.log(obj.a);//1
14 console.log(obj.b);//b
15 console.log(obj.c);//undefined

咱們分析一下:

  1. 函數中的var a = 0;是一個做用域在該函數中的臨時變量,函數外部是不能訪問的;
  2. 函數中的this.a = 1;定義的變量只能在使用new關鍵字建立的對象中才能訪問到;
  3. 直接在foo上面賦值的a,也只能使用foo.a能夠訪問到,使用new關鍵字建立的對象不能訪問到;
  4. 若是沒有在函數內部使用this.b = 1;來建立的屬性,會去其原型對象prototype上尋找,找到則返回了1,相反,a在函數內部指定了,則能夠訪問到;
  5. 在foo上直接定義的c,使用new建立的對象不能訪問到;

若是按面向對象的思惟來看,能夠這麼理解:

 1 //建立類 Foo,按通常的規則類名首字母大寫
 2 var Foo = function(){
 3     this.a = 1;//建立一個公開的屬性a
 4 };
 5 //建立一個公開的方法 add
 6 Foo.prototype.add = function(a, b){
 7     return a + b;
 8 }
 9 //建立一個靜態變量 num
10 Foo.num = 100;
11 //建立一個靜態方法 subtract
12 Foo.subtract = function(a, b){
13     return a - b;
14 }
15 
16 //調用靜態屬性及方法
17 console.log(Foo.subtract(Foo.num, 11));//89
18 //調用對象屬性及方法
19 var obj = new Foo();
20 console.log(obj.add(obj.a, Foo.num));//101

實現類繼承

目前咱們知道了使用js來模擬編寫一個類的方法,即如何封裝咱們知道了,可是面向對象編程還有兩個重要的特性:繼承和多態;

可是因爲js是沒有類型的概念的,因此多態這個特性咱們能夠忽略,那麼js如何使用原型來實現類繼承的效果呢?

實際上,有不少方法能夠實現類繼承的效果,或者複雜,或者簡單,接下來的兩篇文章,我會介紹兩種js實現類繼承的方法:

  1. TypeScript的實現方式,由微軟維護的js的超集,實現了靜態語言的大部分特性,學習TypeScript編譯出的JavaScript的實現方式,微軟都這麼實現了,我們就照搬就好了;
  2. Cocos2dx-js的實現方式,做爲一款成熟的遊戲框架,cocos的實現不一樣於微軟的ts的實現,但也一種值得學習的類實現的方式;

以上方式你們能夠二選一,固然也可使用別的方法;

最後說一下,最新的js標準ES6中,已經支持class關鍵字來定義類了,不過在ES6還沒有普及的這幾年,爲了兼容更多的用戶仍是乖乖使用老的方式吧;

相關文章
相關標籤/搜索