由物件到原型

Guohjia發表於2018-08-02

強大的物件

眾所周知,在面試中我們經常會被面試官問到js的基本型別是什麼,這個問題雖然基礎但也正因為如此從而很考驗求職者的基礎。ok,那如果求職者回答出來了並且不想再考驗他基礎了,那就可以換一個角度問他,js中最牛掰的型別是什麼?沒錯,答案就是除了基本型別之外的複雜型別 => Obejct。那麼接下來我要說明的,無非兩個問題,也許也是你看到這裡心中所想的:

  • 1.到底啥是物件
  • 2.物件為什麼牛掰

啥是物件?

  • 官方回答:無序屬性的集合,其屬性可以包含基本 值、物件或者函式
  • 最簡單的來說:鍵值對,其中值可以是資料或者函式
//一個簡單的例子
var person = new Object()
person.name = 'xiaoMing'
person.say= function(){
    console.log('my name is',this.name)
}
複製程式碼

物件為啥牛掰

迴歸本質,想想程式設計世界,就是由兩大核心資料與程式碼組成。資料本是靜態,而程式設計的世界確實五彩繽紛多樣的,因此程式碼必須具備描述與運算元據的能力。而後基於此,各種各樣的程式設計思想程式設計庫程式設計框架,都是在以自己的方式更好地去提升這種能力(更快地運算元據、更優雅更高效地描述展示資料等)的關係。再回頭看看我上面寫的物件吧,它就是程式碼具備這種能力的最好證明,也是javascript精巧的重要原因,name是'xiaoMing'(描述資料),函式say輸出name(運算元據)。物件一下子就把程式碼的能力給展現出來了,還不牛掰嗎?反正我覺得挺牛掰,如果你不覺得,繼續往下看哈哈哈。。

新的物件建立方式

我們看到上面的程式碼都是new Object的方式建立物件(也就是所謂的工廠模式),那創立了多個物件要如何區分呢?或者說如何分類的?能不能都是“人”的物件我們都寫成,new People,都是“動物”的物件我們都寫成new Animal,而不都是new Object?建構函式應運而生,也就是高程書上講的建立自定義的建構函式意味著將來可以將它的例項標識為一種特定的型別

function Person(){
   this.name = name;
   this.say = function(){
       console.log('my name is',this.name)
   }
}
var xiaoMing = new Person('xiaoMing');
var xiaoHong = new Person('xiaoMing');
//xiaoMing,xiaoHong都是Person
複製程式碼

原型的到來

又有一個問題,我們看到上面的程式碼兩個例項xiaoMing和xiaoHong都有一個相同的say方法,但是通過上面的方式該方法被建立了兩次,有沒有辦法優化呢?有,我們創造一個原型,把這個方法都放到上面去,大家都引用這個方法,那麼不是ok了?你建立多少例項都無所謂啊。原型是一個更為獨立、複用、封裝、可繼承、可多型的強大物件

function Person(){
   this.name = name;
   this.say = function(){
       console.log('my name is',this.name)
   }
}

Person.prototype.say = function(){
       console.log('my name is',this.name)
}
var xiaoMing = new Person('xiaoMing');
var xiaoHong = new Person('xiaoHong');
複製程式碼

那麼在程式碼中用什麼來表示原型,又用什麼來指向原型呢?答案就是Prototype和proto。Person的prototype掛了所有例項共有的方法與屬性,xiaoMing等例項的_proto_.屬性指向原型。Person本事就作為constructor.

注意:我們一般不在原型上掛屬性,因為如果是一個包含引用型別值(諸如陣列)的屬性時,原型上的屬性不會被遮蔽,改動結果會影響到別的例項

function Person(){}
Person.prototype.friends = ['xiaoGang'];

var xiaoMing = new Person();
var xiaoHong = new Person();
xiaoMing.friends.push('xiaoWang')
console.log(xiaoHong.friends) //["xiaoGang", "xiaoWang"]
複製程式碼

原型鏈

因為一個例項可能是有不同的物件慢慢衍生出來的,比如最初是Object,再到建構函式Person,再到某個例項,所以啊每個例項會有一條_proto_鏈(_ proto _ . _ proto _ .toString),這個鏈就是原型鏈了。

原型的繼承

基於原型鏈,我們其實可以擴充一個功能,那就是既然_proto_可以指向不同原型,那麼我們 是否可以去指定它指向多個我們所希望的原型呢?這就是繼承。舉個生活中實際的例子,比如你,既繼承於你的爸爸,也繼承於你的媽媽,因此你可以既眼睛長得像爸爸,鼻子長得像媽媽。來看看程式碼中的例項:

    function Person(name){
      this.name = name;
    }
    Person.prototype.say = function(){
      console.log('my name is',this.name)
    }

    function Student(num,name){
      this.studentNum = num;
      Person.call(this,name) //借用建構函式傳遞屬性
    }

    Student.prototype = new Person();
    Student.prototype.learn = function(){
      console.log('I am learning ...')
    }
    //糾正建構函式
    Student.prototype.constructor =Student;
    
    var xiaoMing = new Student(2,'xiaoMing');
    xiaoMing.say() //my name is xiaoMing
    xiaoMing.learn(); //I am learning ...
複製程式碼

上述程式碼中xiaoMing既繼承了Person,又繼承了Student,因此他又可以say又可以Learn。同時值得注意的是,我們 借用了建構函式傳遞了屬性值。如此一來,原型的世界是不是瞬間被放大了很多呢?

  • 其他的原型繼承模式 上述是一種比較經典的原型繼承方式,但發展至今,還有一些其他的方式,但本質都是一樣,建立傳遞一個已經指向所想要繼承的原型,的_proto_屬性
    • 道格拉斯克勞福德(2006):
    function obejct(o){
      function F(){}
      F.prototype = o;
      //也可以在這裡做一些擴充套件
      F.prototype.newFn = function(){}
      return new F();
    }
    Student.prototype = obejct(Person.prototype);
    複製程式碼
    • 上述例子演化而來的ES5 API之Obejct.create
    Student.prototype = Object.create(Person.prototype)
    複製程式碼

ES6-新時代繼承

到了ES6時代,繼承變得更加優雅簡潔了

    class People {
      constructor(name){
        this.name  = name
      }
      say(){
        console.log('my name is',this.name)
      }
    }

    class Student extends People{
      constructor(props){
        super(props)
      }

      learn(){
        console.log('i am  learning...')
      }
    }

    var xiaoMing = new Student('xiaoMing');
    xiaoMing.say()  //my name is xiaoMing
複製程式碼

雖說ES6這個語法有點像個語法糖,但仍有幾點值得說明注意:

  • 1.子類中的super會呼叫父類中的建構函式,這也是為什麼子類可以直接繼承父類中的屬性
  • 2.必須要在子類中呼叫super,才可以在子類中使用this
  • 3.super關鍵字還可以呼叫父類上的方法

此外,其實當一種語法已經‘語法糖’到讓人可以毫無阻礙在開發中使用甚至捨棄之前API,那麼已經不再是‘語法糖’了,更像是一個只是跟之前類似、卻更為成熟的新型API

說完ES6,是時候說說“”了,因為它實在讓人聯絡到“類”,特別是“class”這個關鍵字。我的個人感悟是,像“類”,因為我們之前也提到了 ,建構函式與工廠模式的重要差別就是給不同的物件一個標示,同時也可以進行歸類。但 又不像“類”,在javascript中包括我們本文提到的一切 都是圍繞物件所展開,這是一種基於物件的程式語言,甚至你所聯想到或者所認為的“類”,在javascript的世界裡,這都是物件,因為你class出來的東西都可以用鍵值數來描述,這是一種更為本質與微觀的看法。

總結

本文的描述內容主要針對以下幾方面:

  • js中的物件(具有描述與運算元據的能力)
    • 物件建立
      • 工廠模式
      • 建構函式
  • js中的原型(十分高階的物件)
    • 基本的原型描述
    • 原型鏈
    • 原型繼承

從上述方面依次往下看,我們清晰可以看到javascript語言生命力的不斷旺盛。甚至到後來的原型,已經可以實現物件導向中封裝、多型、繼承的三大概念(本文為了避免大篇幅沒有具體展開,其實用多了你就可以感受到的),同時也具有很強大的靈活性。 如果現在你再切回到更巨集觀更本質的角度來看,基本型別 => 物件 => 原型 => 物件 => 基本型別,是否會覺得javascript的世界清晰了很多呢

相關文章