您的瀏覽器不支援JavaScript功能,若網頁功能無法正常使用時,請開啟瀏覽器JavaScript狀態
Antfire 的生活雜記
Skip
    banner

    JavaScript設計樣式 Part 1

    JavaScript設計樣式 Part 1

    this

    JavaScript的this都是指向一個物件,至於是指向哪一個物件,是在實際執行的時候才會進行綁定,而不是根據這個函式宣告的位置。

    this的指派

    this的指派大致有以下四種

    1. 呼叫物件的方法
    2. 呼叫一般函式
    3. 呼叫建構子
    4. 呼叫Function.prototype.callFunction.prototype.apply

    呼叫物件的方法

    var obj = {
    	a: 1,
    	getA: function(){
    		alert(this === obj);  //output: true
    		alert(this.a);  //output: 1
    	}
    }
    obj.getA();

    呼叫一般函式

    當函式不作為物件的屬性被呼叫的時候,就等同於一般函式,這個時候的this就會是全域物件。
    在瀏覽器的JavaScript,全域物件就是window物件

    window.name = 'globalName';
    var getName = function(){
    	return this.name;
    };
    console.log(getName()); //output: globalName

    再來看這個例子

    window.name = 'globalName';
    var myObject = {
    	name: 'sven',
    	getName: function(){
    		return this.name;
    	}
    }
    
    var getName = myObject.getName;
    console.log(myObject.getName());  // sven
    console.log(getName());  // globalName

    strict模式

    如果宣告ECMAScript 5strict模式的話,this預設將不會指向全域物件

    function func(){
    	"use strict"
    	alert(this);  // output: undefined
    }
    func();

    呼叫建構子

    JavaScript沒有類別,但可以藉由new運算符呼叫建構子建立物件,使它看起來更像類別。
    大部分的函式都可以作為建構子,建構子的外觀和一般函數的外觀一模一樣,只差在呼叫的方式。
    當用new運算符呼叫函式時,這個函式將回傳一個物件,在這個情況下,建構子裡面的this,就是這個函式回傳的物件。

    var MyClass = function(){
    	this.name = 'sven';
    };
    var obj = new MyClass();
    console.log(obj.name);   // output: sven

    再來看下一個例子,如果建構子明確回傳了一個物件,則this將會是這個明確宣告的新物件,而不是上一個例子中的this

    var MyClass = function(){
    	this.name = 'sven';
    	return {
    		name: 'anne';
    	}
    }
    var obj = new MyClass();
    console.log(obj.name);  // output: anne

    如果建構子不回傳資料或是回傳一個非物件類型的資料,就不會產生上面的問題

    var MyClass = function(){
    	this.name = 'sven';
    	return 'anne';
    }
    var obj = new MyClass();
    console.log(obj.name);  // output: sven

    呼叫Function.prototype.callFunction.prototype.apply

    Function.prototype.callFunction.prototype.apply可以動態傳入函式的this

    var obj1 = {
    	name: 'sven',
    	getName: function(){
    		return this.name;
    	}
    }
    var obj2 = {
    	name: 'anne';
    };
    
    console.log(obj1.getName());  // output: sven
    console.log(obj1.getName.call(obj2));  // output: anne
    

    消失的this

    先看以下程式碼

    var obj = {
    	myName: 'sven',
    	getName: function(){
    		return this.myName;
    	}
    };
    
    console.log(obj.getName());  // output: sven
    
    var getName2 = obj.getName;
    console.log(getName2());  // output: undefined

    呼叫obj.getName()時,函式getName是作為物件的屬性呼叫的,所以第一個輸出結果是 sven
    而getName2是宣告了另一個變數,參考了obj.getName,因此呼叫getName2時,被當成是一般函式來使用,這個時候的this是因為是全域物件,而這個全域物件並沒有myName這個變數,因此輸出undefined,心裡想的那個this就這樣消失了。

    最後再來看這個例子

    var getId = document.getElementById;
    getId('div1');

    執行以上的程式碼後,會出現 img-2023-02-21_11-05-01.png

    這是因為宣告一個變數指向document.getElementById函式後,函式裡的this由原來指向document變成window的緣故。

    為了修正這個錯誤,我們可以藉由apply將document作為this帶入getId

    <html>
    	<head>
    	...
    	</head>
    	<body>
    		<div id="div1">test</div>
    	</body>
    </html>
    document.getElementById = (function(func){
    	return function(){
    		return func.apply(document,arguments);
    	}
    })(document.getElementById);
    
    var getId = document.getElementById;
    var div = getId('div1');
    
    console.log(div.id);  // output: div1

    call和apply

    區別

    兩者作用一樣,差別只有參數。
    apply接受兩個參數,第一個是指派this的物件;第二個是要傳遞的參數。

    var func = function(a,b,c){
    	alert([a,b,c]);  // output: [1,2,3]
    }
    
    func.apply(null,[1,2,3]);

    call傳遞的參數數量則不固定,愛傳幾個傳幾個,第一個參數也是要指派的this物件,接著就是參數了。

    var func = function(a,b,c){
    	alert([a,b,c]);  // output: [1,2,3]
    }
    
    func.call(null,1,2,3);

    call和apply的用途

    1. 改變this指派
    2. 鳩佔鵲巢

    改變this指派

    var obj1 = {
    	name: 'sven'
    };
    
    var obj2 = {
    	name: 'anne'
    };
    
    window.name = 'window';
    
    var getName = function(){
    	console.log(this.name);
    }
    
    getName();  //output: window
    getName.call(obj1);  //output: sven
    getName.call(obj2);  //output: anne

    鳩佔鵲巢

    鳩佔鵲巢的使用案例之一是實作一個類似繼承的效果

    var A = function(name){
    	this.name = name;
    };
    
    var B = function(){
    	A.apply(this,arguments);
    };
    
    B.prototype.getName = function(){
    	return this.name;
    }
    
    var b = new B('sven');
    console.log(b.getName());  //output: sven
    

     Comments