JS核心知識點梳理——原型、繼承(上)

clipboard.png

引言

最近又攀登了一下JS三座大山中的第二座。爬山過程很酸爽,一路發現了許多以前沒曾注意到的美景。本着獨樂樂不如衆樂樂的原則,這裏和你們分享一下。es6

JS的面試對象

有些人認爲 JavaScript 不是真正的面向對象的語言,好比它沒有像許多面向對象的語言同樣有用於建立class類的聲明 (在 ES2015/ES6 中引入了 class 關鍵字,但那只是語法糖,JavaScript 仍然是基於原型的)。JavaScript 用一種稱爲構建函數的特殊函數來定義對象和它們的特徵。

不像「經典」的面向對象的語言,從構建函數建立的新實例的特徵並不是全盤複製,而是經過一個叫作原形鏈的參考鏈連接過去的。同理,原型鏈也是實現繼承的主要方式(ES6的extends只是語法糖)。面試

原型、原型鏈

一直在猶豫,究竟是先講建立對象的方法仍是先講原型。爲了後面保證講建立對象方法的連貫性,這裏仍是先講講原型吧,
這裏爲了權威,直接就摘抄MDN的定義了segmentfault

JavaScript 常被描述爲一種 基於原型的語言 (prototype-based language)——每一個對象擁有一個 原型對象,對象以其原型爲模板、從原型繼承方法和屬性。原型對象也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推。這種關係常被稱爲 原型鏈 (prototype chain),它解釋了爲什麼一個對象會擁有定義在其餘對象中的屬性和方法。

準確地說,這些屬性和方法定義在Object的構造器函數(constructor functions)之上的prototype屬性上,而非對象實例自己。數組

這個__proto__屬性有什麼用呢?在傳統的 OOP 中,首先定義「類」,此後建立對象實例時,類中定義的全部屬性和方法都被複制到實例中。在 JavaScript 中並不如此複製,而是在對象實例和它的構造器之間創建一個連接(它是__proto__屬性,是從構造函數的prototype屬性派生的),以後經過上溯原型鏈,在構造器中找到這些屬性和方法。瀏覽器

簡單的說,就是實例對象能經過本身的__proto__屬性去訪問「類」原型(prototype)上的方法和屬性,類若是也是個實例,就會不斷往上層類的原型去訪問,直到找到app

補充:
1.「類」的原型有一個屬性叫作constructor指向「類」
2.__proto__已被棄用,提倡使用Object.getPrototypeOf(obj)函數

舉例:性能

var arr = [1,2,3] //arr是一個實例對象(數組類Array的實例)
arr.__proto__ === Array.prototype //true  實例上都有一個__proto__屬性,指向「類」的原型
Array.prototype.__proto__ === Object.prototype //true 「類」的原型也是一個Object實例,那麼就必定有一個__proto__屬性,指向「類」object的原型

這裏補充一個知識點:
瀏覽器在在Array.prototype上內置了pop方法,在Object.prototype上內置了toString方法測試

clipboard.png
上圖是我畫的一個原型鏈圖this

[1,2,3].pop() //3
[1,2,3].toString() //'1,2,3'
[1,2,3].constructor.name //"Array" 
[1,2,3].hehe() //[1,2,3].hehe is not a function

當咱們調用pop()的時候,在實例[1,2,3]上面沒有找到該方法,則沿着原型鏈搜索"類"Array的原型,找到了pop方法並執行,同理調用toString方法的時候,在"類"Array沒有找到則會繼續沿原型鏈向上搜索"類"Object的原型,找到toString並執行。
當執行hehe方法的時候,因爲「類」Object的原型上並無找到,搜索「類」Object的__proto__,因爲執行null,中止搜索,報錯。

注意,[1,2,3].constructor.name顯示‘Array’不是說明實例上有constructor屬性,而是正是由於實例上沒有,因此搜索到的原型上了,找到了constructor

類,建立對象的方法

怎麼建立對象,或者說怎麼模擬類。這裏我就不學高程同樣,給你們介紹7種方法了,只講我以爲必須掌握的。畢竟都es6 es7了,不少方法基本都用不到,有興趣本身看高程。

利用構造函數

const Person = function (name) {
        this.name = name
        this.sayHi = function () {
            alert(this.name)
        }
    }
    const xm = new Person('小明')
    const zs = new Person('張三')
    zs.sayHi() //'張三'
    xm.sayHi() //'小明'

缺點: 每次實例化都須要複製一遍函數到實例裏面。可是不論是哪一個實例,實際上sayHi都是相同的方法,不必每次實例化的時候都複製一遍,增長額外開銷。

寄生構造函數模式

function specialArray() {
        var arr = new Array()
        arr.push.apply(arr,arguments)
        arr.sayHi = function () {
            alert('i am an specialArray')
        }
        return arr
    }
    var arr = new specialArray(1,2,3)

這個和在數組的原型鏈上增長方法有啥區別?原型鏈上增長方法,全部數組均可以用。寄生構造函數模式只有被specialArray類new出來的才能用。

組合使用原型和構造函數

//共有方法掛到原型上
    const Person = function () {
         this.name = name
    }
    Person.prototype.sayHi = function () {
            alert(this.name)
        }
    const xm = new Person('小明')
    const zs = new Person('張三')
    zs.sayHi() //'張三'
    xm.sayHi() //'小明'

缺點:基本沒啥缺點了,建立自定義類最多見的方法,動態原型模式也只是在這種混合模式下加了層封裝,寫到了一個函數裏面,好看一點,對提升性能並無卵用。

es6的類

es6的‘類’class其實就是語法糖

class Person {
   constructor(name) {
       this.name = name
  }
  say() {
       alert(this.name)
  }
}
const xm = new Person('小明')
const zs = new Person('張三')
zs.sayHi() //'張三'
xm.sayHi() //'小明'

在es2015-loose模式下用bable看一下編譯

"use strict";

var Person =
/*#__PURE__*/
function () {
  function Person(name) {
    this.name = name;
  }

  var _proto = Person.prototype;

  _proto.say = function say() {
    alert(this.name);
  };

  return Person;
}();

分析:嚴格模式,高級單例模式封裝了一個類,實質就是組合使用原型和構造函數

JS世界裏的關係圖

clipboard.png

知識點:

  1. Object.getPrototypeOf(Function) === Function.prototype // Function是Function的實例,沒毛病
  2. Object.getPrototypeOf(Object.prototype)
  3. 任何方法上都有prototype屬性以及__proto__屬性
    任何對象上都有__proto__屬性
  4. Function.__proto__.__proto__===Object.prototype
  5. Object.getPrototypeOf(Object)===Function.prototype
  6. 最高級應該就是Function.prototype了,由於5

判斷類型的方法

以前在JS核心知識點梳理——數據篇裏面說了一下判斷判斷類型的四種方法,這裏藉着原型再來分析一下

1. typeof:

只能判斷基礎類型中的非Null,不能判斷引用數據類型(由於所有爲object)它是操做符

2. instanceof:

用於測試構造函數的prototype屬性是否出如今對象的原型鏈中的任何位置 風險的話有兩個

//判斷不惟一
[1,2,3] instanceof Array //true
[1,2,3] instanceof Object //true
//原型鏈能夠被改寫
const a = [1,2,3]
a.__proto__ = null
a instanceof Array //false

仿寫一個instanceof,而且掛在Object.prototype上,讓全部對象都能用

//仿寫一個instance方法
    Object.prototype.instanceof = function (obj) {
        let curproto = this.__proto__
        while (!Object.is(curproto , null)){
            if(curproto === obj.prototype){
                return true
            }
            curproto = curproto.__proto__
        }
        return false
    }
   
[1,2,3].instanceof(Array) //true
[1,2,3].instanceof(Object) //true
[1,2,3].instanceof(Number) //false
[1,2,3].instanceof(Function) //false
1..instanceof(Function) //false
(1).instanceof(Number) //true

3. constructor:

constructor 這玩意已經介紹過了,「類」的原型執行constructor指向「類」
風險的話也是來自原型的改寫

[1,2,3].constructor.name //'Array'

// 注意下面兩種寫法區別
Person.protorype.xxx = function //爲原型添加方法,默認constructor仍是在原型裏
Person.protorype = { //原型都被覆蓋了,沒有constructor了,所要要手動添加,要否則constructor判斷失效
   xxx:function
   constructor:Person
}

4.Object.prototype.toString.call(xxx)

試了下,好像這個方法也不是很準
null 能夠用object.is(xxx,null)代替
Array 能夠用Array.isArray(xxx)代替

Object.prototype.toString.call([1,2,3])   //"[object Array]"
Object.prototype.toString.call(function(){}) //"[object Function]"
Object.prototype.toString.call(1) //"[object Number]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call({}) //"[object Object]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(true) // 特別注意 特別注意 特別注意"[object Object]"
Object.prototype.toString.call('string') //  特別注意 特別注意 特別注意 "[object Undefined]"

in操做符

對於for in 和in 都是沿着原型鏈查找屬性是否存在,能夠利用hasOwnProperty進行相關過濾

// 'in' operation test
class Person {
        constructor (name) {
            this.name = name
        }
        sayHi() {
            console.log('Hi')
        }
    }
    var p1 = new Person('小明')


'name' in p1 //true
'sayHi' in p1 //true 

for (var i in p1) {
        if (p1.hasOwnProperty(i)) {
            console.log('ownProperty:' + i)
        } else {
            console.log('prototypeProperty: ' + i)
        }
    }
//'ownProperty: name'
// prototypeProperty: sayHi

總結

參照各類資料,結合本身的理解,在儘可能不涉及到繼承的狀況下,詳細介紹了原型及其衍生應用。因爲本人技術有限,若是有說得不對的地方,但願在評論區留言。

相關文章
相關標籤/搜索