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

ConardLi發表於2019-02-16

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)

相關文章