Javascript函數執行、new機制以及繼承

JS函數執行

一個JavaScript函數fn,被執行有三種途徑:app

  • fn()
  • new fn()
  • fn.call()或fn.apply()

new機制以及繼承

JavaScript中定義了一種對象,稱之爲ECMAScript對象,其內部實現包括如下:函數

  • __call__: 說明該對象能夠被執行,具備function屬性
  • __construct__: 說明該對象能夠接受new操做,具備構造器屬性
  • __prototype__: 指向對象的原型鏈。對於定義的函數,會指向Function.prototype

注意:__prototype__是原型鏈,對全部對象都有的。prototype是原型,是函數纔有的,就是一個普通的對象而已,目的是爲了接受new後給生成的對象提供原型鏈的。this

執行fn就是調用__call__prototype

執行new fn()會進行如下簡化過程:code

  • 新建一個對象,記做o
  • 把o.__prototype__指向fn.prototype(若是fn.prototype不是一個Object,則指向Object.prototype)
  • 執行fn,並用o做爲this(即內部實現的fn.call(this))。若是fn返回是一個object,則返回object, 不然把o返回

fn.call(obj)或者fn.apply(obj)就是將obj做爲this,執行fn。本質是調用__call__,只是傳入了obj做爲this.對象

//定義一個函數,正常函數會具備__call__, __construct__
    //同時Parent.__proto__指向Function.prototype
    function Parent() {
        this.sayAge = function() {
            console.log("age is: " + this.age);
        }
    }
    //原型上添加一個方法
    Parent.prototype.sayParent = function(){
        console.log("this is Parent Method");
    }
    
    //定義另外一個函數
    function Child(firstname){
    
        //這裏就是調用Parent的__call__, 而且傳入this
        //而這裏的this,是Child接受new時候生成的對象
        //所以,這一步會給生成的Child生成的實例添加一個sayAge屬性
        Parent.call(this);
        
        this.fname = firstname;
        this.age = 40;
        this.saySomething = function() {
            console.log(this.fname);
            this.sayAge();
        }
    }
    
    //這一步就是new的調用,按原理分步來看
    //1. 新建了個對象,記做o
    //2. o.__prototype__ = Parent.prototype, 所以o.sayParent會訪問到o.__prototype__.sayParent(原型鏈查找機制)
    //3. Parent.call(o), 所以o也會有個sayAge屬性(o.sayAge)
    //4. Child.prototype = o, 所以 Child.prototype 經過o.__prototype__ 這個原型鏈具備了o.sayParent屬性,同時經過o.sayAge 具備了sayAge屬性(也就是說Child.prototype上具備sayAge屬性,但沒有sayParent屬性,可是經過原型鏈,也能夠訪問到sayParent屬性)
    Child.prototype = new Parent();
    
    //這也是一步new調用
    //1. 新建對象,記做s
    //2. s.__prototype__ = Child.prototype, 此時s會具備sayAge屬性以及sayParent這個原型鏈上的屬性
    //3. Child.call(s), 執行後, 增長了fname, age, saySomething屬性, 同時因爲跑了Parent.call(s), s還具備sayAge屬性, 這個屬性是s身上的, 上面那個sayAge是Child.prototype上的, 即s.__prototype__上的。
    //4. child = s
    var child = new Child("張")
    
    //child自己屬性就有,執行
    child.saySomething();
    
    //child自己屬性沒有, 去原型鏈上看, child.__prototype__ = s.__prototype__ = Child.prototype = o, 這裏沒找到sayParent, 繼續往上找, o.__prototype__ = Parent.prototype, 這裏找到了, 執行(第二層原型鏈找到)
    child.sayParent();

原理來看寫得有些繁瑣,自己實際上是比較簡單的東西。
重點是new的過程,原型prototype和原型鏈__prototype__
也正是new的原理,致使了原型鏈的繼承,本質是生成的對象的__prototype__指向了函數的原型prototype繼承

更復雜的調用繼承之類的,均可以經過這個原理來理解。說白了,原型鏈繼承就是複用了prototype而已。ip

本例來看,Child中的Parent.call(this)看似沒有必要,但本質上是有區別的。若是去掉這一句,則Child的實例自己將沒有sayAge屬性,而Child.prototype具備sayAge屬性,所以實例的__prototype__具備sayAge屬性,所以還能夠執行。原型鏈

但目的是爲了繼承,所以屬性是須要對象上自己持有,而非是經過原型鏈上來訪問,因此加上這一句是原理上的嚴謹要求。能夠經過下面的例子來檢驗:原型

function Parent() {
        this.sayAge = function() {
            console.log("age is: " + this.age);
        }
    }
    Parent.prototype.sayParent = function(){
        console.log("this is Parent Method");
    }
    
    function Child(firstname){
        Parent.call(this); 
        this.fname = firstname;
        this.age = 40;
        this.saySomething = function() {
            console.log(this.fname);
            this.sayAge();
        }
    }
    
    Child.prototype = new Parent();
    
    var child = new Child("張")
    
    child.saySomething();
    child.sayParent();
    
    console.log(child.hasOwnProperty('sayAge')); // true
    child.sayAge(); //能調用,此時調用的是自身的屬性
    delete child.sayAge; // delete只能刪除自身的屬性,不能刪除原型鏈屬性
    console.log(child.hasOwnProperty('sayAge')); // false,自身沒有這個屬性了
    child.sayAge(); //還能調用,此時調用的是原型鏈上的方法

若是刪掉Parent.call(this), 上面兩句child.hasOwnProperty('sayAge'), 都將返回false

相關文章
相關標籤/搜索