this & 原型鏈 & 繼承

What's this?

由于運行期綁定的特性,JavaScript 中的 this 含義非常多,它可以是全局對象、當前對象或者任意對象,這完全取決于函數的調用方式

隨著函數使用場合的不同,this的值會發生變化。但是有一個總的原則,那就是this指的是,調用函數的那個對象

作為函數調用

  • 在函數被直接調用時this綁定到全局對象。在瀏覽器中,window 就是該全局對象
 var a = 1
    console.log(this) //全局對象window
    function fn(){
        console.log(a) //1
        console.log(this) //window
        console.log(b) //b is nor defined
    }
    fn()

內部函數

  • 函數嵌套產生的內部函數的this不是其父函數,仍然是全局變量
 function fn(){
       function fn1(){
           console.log(this) //window
       }
       fn1()
   }
   fn()

setTimeout、setInterval

  • 這兩個方法執行的函數this也是全局對象
document.addEventListener('click', function(e){
  console.log(this); //document
    setTimeout(function(){
        console.log(this); //window
    }, 200);
}, false);
  • 如果想在setTimeout、setInterval執行的函數中返回的this是document,方法一只需要有一個中間變量,保存this即可。
 document.addEventListener('click',function(e){
       console.log(this) //document
       var _this = this
       setTimeout(function(){
           console.log(_this) //document
       })
   })
  • 如果想在setTimeout、setInterval執行的函數中返回的this是document,方法二,使用bind,使其第一個參數為this
document.addEventListener('click',function(e){
        setTimeout(function(){
            console.log(this) //document
        }.bind(this))
    })

作為構造函數調用

  • 所謂構造函數,就是通過這個函數生成一個新對象(object)。這時,this就指這個新對象

new 運算符接受一個函數 F 及其參數:new F(arguments...)。這一過程分為三步:

  1. 創建類的實例。這步是把一個空的對象的 proto 屬性設置為 F.prototype 。
  2. 初始化實例。函數 F 被傳入參數并調用,關鍵字 this 被設定為該實例。
  3. 返回實例。

具體實例:

function People(name){
       this.name = name
   }
   People.prototype.sayName = function(){
       console.log(this.name)
   }
   var p1 = new People('lili')
  • 作為對象方法調用
    在 JavaScript 中,函數也是對象,因此函數可以作為一個對象的屬性,此時該函數被稱為該對象的方法,在使用這種調用方式時,this 被自然綁定到該對象
var obj = {
       name: 'kasck',
       fn: function(){
           console.log(this) //obj
       }
   }
   obj.fn()
  • 對象方法的嵌套
var obj2 = {
       name: 'lll',
        obj3: {
           fn: function(){
               console.log(this) // obj3
           }
        }
    }
    obj2.obj3.fn()
  • 小陷阱
  var obj = {
       name: 'kasck',
       fn: function(){
           console.log(this) 
       }
   }
   obj.fn()

    var fn2 = obj.fn
    fn2() //window.fn2() this === window

Function.prototype.bind

bind:返回一個函數,并且使函數內部的this為傳入的第一個參數

var obj = {
       name: 'kasck',
       fn: function(){
           console.log(this)
       }
   }
   obj.fn()
    obj3 = {a:3}
    var fn3 = obj.fn.bind(obj3) //fn3函數內部的this為bind傳入的第一個參數
    fn3() // this = obj3

使用call和apply設置this

  • call和apply調用一個函數傳入函數執行上下文參數。
fn.call(context,parma1,parma2...);
fn.apply(context,paramArray);

語法很簡單,第一個參數都是希望設置的this對象,不同之處在于call方法接收參數列表,而apply接收參數數組。

fn2.call(obj1);
fn2.apply(obj2);
  • call的實例
 var value = 100;
   var obj4 = {
       value: 200
   };
    function fn4(a,b){
       console.log(this.value + a + b)
    }
    fn4(3,4) //107
    fn4.call(obj4,3,4) //207
  • apply的實例
//與call對比,僅僅是調用參數的方式不同
    fn4.apply(obj4,[3,4]) 207

更多用法

function joinStr(){
        console.log(Array.prototype.join.call(arguments)) //a,b,c
        console.log(Array.prototype.join.call(arguments,'-')) //a-b-c
        var join = Array.prototype.join.bind(arguments)
        console.log(join('-')) //a-b-c
    }
   joinStr('a','b','c')

得到一個數組的最大值和最小值

    var arr = [1,3,5,9]
    console.log(Math.max.apply(null,arr)) //9
    console.log(Math.max.call(null,arr)) //NAN
    console.log(Math.max.call(null,1,3,5,9)) //9

call與apply的不同之處:

  • apply:
    最多只能有兩個參數:新this對象和一個數組argArray。如果給該方法傳遞多個參數,則把參數都寫進這個數組里面,當然,即使只有一個參數,也要寫進數組里面。如果argArry不是一個有效的數組或者不是arguments對象,那么將導致一個TypeError。如果沒有提供argArry和thisObj任何一個參數,那么Global對象將被用作thisObj,并且無法被傳遞任何參數。
  • call:
    則是直接的參數列表,主要用在JS對象各方法互相調用的時候,使當前this實例指針保持一致或在特殊情況下需要改變this指針。如果沒有提供thisObj參數,那么Global對象將被用作thisObj。

caller

在函數A調用函數B時,被調用函數B會自動生成一個caller屬性,指向調用它的函數對象,如果函數當前未被調用,或并非被其他函數調用,則caller為null

    function fn(){
        function fn1(){
            console.log(fn1.caller) //fn
        }
        fn1()
    }
    fn()

arguments

  1. 在函數調用時,會自動在該函數內部生成一個名為 arguments的隱藏對象
  2. 該對象類似于數組,可以使用[]運算符獲取函數調用時傳遞的實參
  3. 只有函數被調用時,arguments對象才會創建,未調用時其值為null
  function fn(name,age){
        console.log(arguments)
        var name = "frank"
        console.log(arguments)
        arguments[1] = 14
        console.log(arguments)
    }
    fn('lili',23)

函數的執行環境

一個函數被執行時,會創建一個執行環境(ExecutionContext),函數的所有的行為均發生在此執行環境中,構建該執行環境時,JavaScript 首先會創建 arguments變量,其中包含調用函數時傳入的參數

接下來創建作用域鏈,然后初始化變量。首先初始化函數的形參表,值為 arguments變量中對應的值,如果 arguments變量中沒有對應值,則該形參初始化為 undefined。

如果該函數中含有內部函數,則初始化這些內部函數。如果沒有,繼續初始化該函數內定義的局部變量,需要注意的是此時這些變量初始化為 undefined,其賦值操作在執行環境(ExecutionContext)創建成功后,函數執行時才會執行,這點對于我們理解JavaScript中的變量作用域非常重要,最后為this變量賦值,會根據函數調用方式的不同,賦給this全局對象,當前對象等

至此函數的執行環境(ExecutionContext)創建成功,函數開始逐行執行,所需變量均從之前構建好的執行環境(ExecutionContext)中讀取

三種變量

  1. 實例變量:(this)類的實例才能訪問到的變量
  2. 靜態變量:(屬性)直接類型對象能訪問到的變量
  3. 私有變量:(局部變量)當前作用域內有效的變量
function fn(){
        var a = 1 //局部變量
        this.b = 3 //實例變量
}
fn.c = 5 //靜態變量

原型鏈

  • 代碼示例:
    function Person(name,age){
        this.name = name;
        this.age = age
    }
    Person.prototype.sayName = function(){
        console.log(this.name)
    }
    var p1 = new Person('lili',1);
    var p2 = new Person('llll',3);
    p1.sayName()
    p2.sayName()
  • 原型圖


    捕獲.PNG
     p1.__proto__.constructor === Person
    Person.prototype.constructor === Person
    p1.constructor === Person
  1. 我們通過函數定義了類Person,類(函數)自動獲取屬性prototype
  2. 每個類的實例都會有一個內部屬性__proto__,指向類的prototype·

如果想看某一個對象xx是由誰創建的,只需要看xx.proto.constructor === y,就由y創建。

原型鏈相關問題

有如下代碼,解釋Person、 prototype、proto、p、constructor之間的關聯。

function Person(name){
    this.name = name;
}
Person.prototype.sayName = function(){
    console.log('My name is :' + this.name);
}
var p = new Person("若愚")
p.sayName();
  • 通過函數定義了類Person,類(函數)自動獲取屬性prototype;
  • 每個類的實例都會有一個內部屬性__proto__,指向類的prototype;
  • P是構造函數Person的一個實例,p的__proto__指向了Person的prototype屬性;
  • prototype是構造函數內部的原型函數,所以擁有prototype和__proto__屬性,其中contructor屬性指向構造函數Person,__proto__指向該對象的原型。

上例中,對對象 p可以這樣調用 p.toString()。toString是哪里來的? 畫出原型圖?并解釋什么是原型鏈。

p.toString()方法是繼承構造函數Object的原型對象里定義的toString()方法,首先p會找到自己的toString()方法,如果沒有找到,會沿著__proto__屬性繼續到構造函數Person的prototype里找toString()方法,如果還是未找到,再繼續往Person.prototype__proto__找。

QQ圖片20171018133901.png

原型鏈:由于原型對象本身也是對象,而每個JavaScript對象都有一個原型對象,每個對象都有一個隱藏的proto屬性,原型對象也有自己的原型,而它自己的原型對象又可以有自己的原型,這樣就組成了一條鏈,這個就是原型鏈。在訪問對象的屬性時,如果在對性本身中沒有找到,則會去原型鏈中查找,如果找到,則返回值,如果整個鏈都沒有找到,則返回undefined。

instanceOf有什么作用?內部邏輯是如何實現的?

instanceOf:判斷一個對象是否為另一個對象的實例

//
    function isInstanceOf(obj,fn){ 
        var oldProto = obj.__proto__; 
        do{ 
            if(oldProto === fn.prototype){ //prototype是小寫的! 
                return true; 
            }else{ 
                oldProto = oldProto.__proto__; 
            } 
        }while(oldProto){ 
            return false; 
        } 
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容