【前端面試】原型和原型鏈

1.題目

  • 如何準確判斷一個變量是數組
  • 寫一個原型鏈繼承的例子
  • 繼承實現的其他方式
  • es6 實現繼承的底層原理是什麼
  • 描述new一個對象的過程
  • zepto及其他源碼中如何使用原型鏈

2.知識點

2.1 構造函數

特點:以大寫字母開頭

function Foo(name,age){
    //var obj = {}
    //this = {}
    this.name = name;
    this.age = age;
    this.class = 'class1'
    // return this
}

var f1 = new Foo('liming',19);

擴展

var o = {} 是 var o = new Object() 的語法糖

var a = [] 是 var a = new Array() 的語法糖

function Foo(){} 相當於 var Foo = new Function(){}

2.2 原型規則

五條規則:

1.所有引用類型(對象,數組,函數)都具有對象特性,即可以自由擴展屬性

2.所有引用類型(對象,數組,函數)都具有一個__proto__(隱式原型)屬性,是一個普通對象

3.所有的函數都具有prototype(顯式原型)屬性,也是一個普通對象

4.所有引用類型(對象,數組,函數)__proto__值指向它構造函數的prototype

5.當試圖得到一個對象的屬性時,如果變量本身沒有這個屬性,則會去他的__proto__中去找

for (var key in object) {
    //高級瀏覽器中已經屏蔽了來自原型的屬性
    //建議加上判斷保證程序的健壯性
    if (object.hasOwnProperty(key)) {
        console.log(object[key]);
    }
}

2.3 原型鏈

obj.__ proto . proto . proto __ ...

Object.prototype === null

instanceof 用於判斷引用類型屬於哪個構造函數

obj instanceob Foo

實際意義:判斷 Foo.prototype 在不在 obj的原型鏈上

3.題目解答

3.1 如何準確判斷一個變量是數組

arr instanceof Array

3.2 寫一個原型鏈繼承的例子

封裝dom查詢

function Elem(id){
    this.elem = document.getElementById(id);
};

Elem.prototype.html = function(val){
    var elem = this.elem;
    if (val) {
        elem.innerHTML = val;
        return this;
    }else{
        return elem.innerHTML;
    }
}

Elem.prototype.on = function(type,fun){
    var elem = this.elem;
    elem.addEventListener(type,fun);
    return this;
}

var div1 = new Elem('id1');
div1.html("test").on('click',function(){
    console.log('點擊');
})

3.3 繼承實現的其他方式

3.3.1 原型繼承

        var obj = {
            0:'a',
            1:'b',
            arr:[1]
        }
        
        function Foo(arr2){
            this.arr2 = [1]
        }

        Foo.prototype = obj;

        var foo1 = new Foo();
        var foo2 = new Foo();

        foo1.arr.push(2);
        foo1.arr2.push(2);

        console.log(foo2.arr);  //[1,2]
        console.log(foo2.arr2); //[1]

優點:實現簡單

缺點:

1.無法向父類構造函數傳參

2.同時new兩個對象時改變一個對象的原型中的引用類型的屬性時,另一個對象的該屬性也會修改。因爲來自原型對象的引用屬性是所有實例共享的。

3.3.2 構造繼承

        function Super(b){
            this.b = b;
            this.fun = function(){}
        }
        function Foo(a,b){
            this.a = a;
            Super.call(this,b);
        }

        var foo1 = new Foo(1,2);
        console.log(foo1.b);

優點:可以向父類傳參,子類不會共享父類的引用屬性

缺點:無法實現函數複用,每個子類都有新的fun,太多了就會影響性能,不能繼承父類的原型對象。

3.3.3 組合繼承

function Super(){
    // 只在此處聲明基本屬性和引用屬性
    this.val = 1;
    this.arr = [1];
}
//  在此處聲明函數
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
    Super.call(this);   // 核心
    // ...
}
Sub.prototype = new Super();    

優點:不存在引用屬性共享問題,可傳參,函數可複用

缺點:父類的屬性會被實例化兩次,獲取不到真正實例父類(無法區分實例是父類創建還是父類創建的)

優化:

         function Super(b){
            this.b = b;
            this.fun = function(){}
        }

        Super.prototype.c = function(){console.log(1111)}

        function Foo(a,b){
            this.a = a;
            Super.call(this,b);
        }


        Foo.prototype = Super.prototype;
        //修復構造函數:
        var foo1 = new Foo(1,2);

缺點:無法區分實例是父類創建還是子類創建的

3.3.4 寄生組合繼承

         function Super(b){
            this.b = b;
        }

        Super.prototype.c = function(){console.log(1111)}

        function Foo(a,b){
            this.a = a;
            Super.call(this,b);
        }

        var f = new Function();
        f.prototype = Super.prototype;
        Foo.prototype = new f();
        //等同於 Foo.prototype = Object.create(Super.prototype);
        
        var foo1 = new Foo(1,2);

對父類的prototype進行一次寄生,即包裝成一個空對象的prototype,再把這個對象實例化出來作爲子類的peototype。

缺點:無法區分實例是父類創建還是子類創建的

可以添加以下代碼:

Foo.prototype.constructor = Foo

這種解決方法不能用於上面的組合優化方法,因爲子類父類引用的是同一個原型對象,修改會同時修改。

總結:

繼承主要是實現子類對父類方法,屬性的複用。

來自原型對象的引用屬性是所有實例共享的,所以我們要避免從原型中繼承屬性。

在構造函數中通過call函數可以繼承父類構造函數的屬性和方法,但是通過這種方式實例化出來的實例會將父類方法多次存儲,影響性能。

通過組合繼承我們使用call繼承屬性,使用原型繼承方法,可以解決以上兩個問題,但是通過這種方式實例化出來的對象會存儲兩份父類構造函數中的屬性。

用父類的原型構造一個新對象作爲子類的原型,就解決了多次存儲的問題,所以最終的寄生組合繼承就是最佳繼承方式,它的缺點就是書寫起來比較麻煩。

3.3.6 node源碼中的繼承實現


function inherits(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
}; 

function Stream(){
    //...
}

function OutgoingMessage() {
  Stream.call(this);
  //...
}

inherits(OutgoingMessage, Stream);

OutgoingMessage.prototype.setTimeout = ...

以上是寄生組合繼承的一個實例。

1.在OutgoingMessage構造函數中通過call繼承Stream構造中的屬性。

2.調用inherits方法繼承Stream原型中的屬性。

3.擴展OutgoingMessage自身原型的函數。

inherits方法中使用了Object.create方法,該方法的作用是通過指定的原型對象和屬性創建一個新的對象。

ctor.prototype=Object.create(superCtor.prototype,{.....});

該方法實際上就做了我們上面寄生組合繼承中的工作

var f = new Function();
f.prototype =superCtor.prototype;
return new f();

後面的參數是給原型對象添加屬性,可選屬性(非必填),即把自身作爲新創建對象的構造函數。

value: 表示constructor 的屬性值;
writable: 表示constructor 的屬性值是否可寫;[默認爲: false]
enumerable: 表示屬性constructor 是否可以被枚舉;[默認爲: false]
configurable: 表示屬性constructor 是否可以被配置,例如 對obj.a做 delete操作是否允許;[默認爲: false]

3.4 es6繼承的實現方式

參考我這篇文章:https://segmentfault.com/a/11...

3.5 描述new一個對象的過程

  1. 創建一個對象
  2. {}._proto_ = 構造函數.prototype
  3. this指向這個對象
  4. 執行代碼即對this賦值
  5. 返回this

3.6 zepto及其他源碼中如何使用原型鏈

var Zepto = (function(){

    var $,zepto = {}
    
    // ...省略N行代碼...
    
    $ = function(selector, context){
        return zepto.init(selector, context)
    }

    zepto.init = function(selector, context) {
        var dom
        
        // 針對參數情況,分別對dom賦值
        
        // 最終調用 zepto.Z 返回的數據
        return zepto.Z(dom, selector)
    }    

   fnction Z(dom, selector) {
      var i, len = dom ? dom.length : 0
     for (i = 0; i < len; i++) this[i] = dom[i]
      this.length = len
      this.selector = selector || ''
    }

   zepto.Z = function(dom, selector) {
     return new Z(dom, selector)
   }
  
    $.fn = {
        // 裏面有若干個工具函數
    }
      
  
    zepto.Z.prototype = Z.prototype = $.fn
  
    
    // ...省略N行代碼...
    
    return $
})()

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章