1. 程式人生 > >JS面試題—原型和原型鏈

JS面試題—原型和原型鏈

一、 題目

  1. 如何準確判斷一個變數是陣列型別
  2. 寫一個原型鏈繼承的例子
  3. 描述new一個物件的過程

二、知識點

1.建構函式

    function Foo(name, age){
        this.name = name
        this.age = age
        this.class = 'class-1'
        // return this //預設有這一行
    }
    var f = new Foo('zhangsan', '20')
    //var f1 = new Foo('lisi', 22) //可建立多個物件

      特點:預設函式首字母大寫,建構函式並沒有顯示返回任何東西。new 操作符會自動建立給定的型別並返回他們,當呼叫建構函式時,new會自動建立this物件,且型別就是建構函式型別。

2.建構函式—擴充套件

      var a = {} 其實是 var a = new Object()的語法糖
      var a = [] 其實是 var a = new Array()的語法糖
      function Foo(){…} 其實是 var Foo = new Function(){…}
      使用instanceof判斷一個函式是否是建構函式

3.原型規則和示例
原型規則是原型鏈的基礎

  1. 所有引用型別(陣列、物件、函式),都具有物件特性,即可自由擴充套件屬性(除‘null’以外)
  2. 所有引用型別(陣列、物件、函式),都具一個_proto_
    (隱式原型)屬性,屬性值是一個普通物件
  3. 所有函式,都有一個prototype(顯式原型)屬性,屬性值也是一個普通物件
  4. 所有引用型別(陣列、物件、函式),_proto_ (隱式原型)屬性值指向它的建構函式的prototype(顯式原型)屬性值
  5. 當試圖得到一個物件的某個屬性時,如果這個物件本身沒有這個屬性,那麼會去它的_proto_ (即它的建構函式的prototype)中尋找
    var obj = {}; obj.a = 100;
    var arr = []; arr.a = 100;
    function fn(){}
    fn.a = 100;  // 所有引用型別都能拓展屬性

    console
.log(
obj.__proto__) console.log(arr.__proto__) console.log(fn.__proto__) //所有引用型別都有_proto_(隱式原型)屬性 console.log(fn.prototype) //所有函式,都有一個prototype(顯式原型)屬性 console.log(obj.__proto__ === Object.prototype)// true 引用型別_proto_(隱式原型)屬性值指向它的建構函式的prototype(顯式原型)屬性值
    //當試圖得到一個物件的某個屬性時,如果這個物件本身沒有這個屬性,那麼會去它的*_proto_* (即它的建構函式的prototype)中尋找
    //建構函式
    function Foo(name, age){
        this.name = name;
    }
    Foo.prototype.alertName = function(){
        alert(this.name)
    }
    //建立例項
    var f = new Foo('zhangsan');
    f.printName = function(){
        console.log(this.name);
    }
    //測試
    f.printName(); //f自身有printName這個屬性,所以直接可以呼叫
    f.alertName(); //f自身沒有alertName這個屬性,所以會去它的_proto_(即它的建構函式的prototype)中尋找

      tips:迴圈物件自身的屬性時,建議加上hasOwnProperty來保證程式的健壯性

    var item;
    for(item in f){
        //高階瀏覽器已經在for in 中遮蔽了來自原型的屬性
        //但是這裡建議大家還是加上這個判斷,保證程式的健壯性
        if(f.hasOwnProperty(item)){
            console.log(item)
        }
    }

4.原型鏈
還是採用上面建構函式的例子來解釋原型鏈

    //建構函式
    function Foo(name, age){
        this.name = name;
    }
    Foo.prototype.alertName = function(){
        alert(this.name)
    }
    //建立例項
    var f = new Foo('zhangsan');
    f.printName = function(){
        console.log(this.name);
    }
    //測試

    f.toString(); //要去f._proto_._proto_中查詢
    /*
     f自身沒有toString這個屬性, 那麼f會去它的隱式原型_proto_(即它的建構函式的prototype)中尋找,而f的構    造函式也沒有toString這個屬性,那該往哪裡找?
     原型規則中第二、三條提到:所有引用型別(陣列、物件、函式),都具一個_proto_(隱式原型)屬性,屬性值是一個普通物件,意思f._proto_(f.prototype)是個物件
     f._proto_是個物件,那麼該往f._proto_隱式原型中尋找,即f._proto_._proto_  (f._proto_尋找其物件的建構函式也就是Object)
    * */

這裡寫圖片描述

5.instanceof

用於判斷引用型別屬於哪個建構函式的方法

f instanceof Foo的判斷邏輯:
      f的proto一層一層往上,能否找到Foo.prototype
      再試著判斷f instanceof Object

三、解答

1.如何準確判斷一個變數是陣列型別

    var arr = []
    arr instanceof Array //true
    typeof arr // Object typeof是無法判斷是否是陣列的

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, fn){
        var elem = this.elem;
        elem.addEventListener(type, fn);
        return this;
    }
    var div1 = new Elem('div1');
    //console.log(div1.html())
    div1.html('<p>hello world<p>').on('click', function(){
        alert('clicked');
    }).html('<p>javascript<p>')

3.描述new一個物件的過程

  1. 建立空物件;
      var obj = {};
  2. 設定新物件的constructor屬性為建構函式的名稱,設定新物件的_proto_屬性指向建構函式的prototype物件;
      obj._proto_ = ClassA.prototype;

  3. 使用新物件呼叫函式,函式中的this被指向新例項物件:
      ClassA.call(obj);  //{}.建構函式();

  4. 將初始化完畢的新物件地址,儲存到等號左邊的變數中

tips:若建構函式中返回this或返回值是基本型別(number、string、boolean、null、undefined)的值,則返回新例項物件;若返回值是引用型別的值,則實際返回值為這個引用型別。