《javascript高級程序設計》函數調用模式 & this深度理解

在上一篇文章(《javascript高級程序設計》筆記:Function類型)中稍微說起了一下函數對象的屬性—this,在這篇文章中有深刻的說明:javascript

函數的三種簡單調用模式

1 函數模式前端

定義的函數,若是單獨調用,不將其與任何對象關聯,那麼就是函數調用模式java

function fn(num1, num2) {
    console.log(this); 
}
// 直接在全局調用
fn();// window

// 在某個函數內部調用
!function(){
    fn(); // window
}()

函數模式this指向windownode

2 方法模式面試

定義的函數, 若是將函數做爲一個對象的成員,那麼利用對象調用它就是方法模式segmentfault

var obj = {
    say: function() {
        console.log(this);
    }
};
// 直接在全局調用
obj.say(); // obj

// 在某個函數內部調用
!function(){
    obj.say(); // obj
}()

方法模式this指向調用方法的對象數組

3 構造器模式app

定義的函數, 使用 new 來調用建立對象就是構造器模式函數

var Person = function(name) {
    this.name = name;// this指向當前實例對象
};
var p1 = new Person("xiong");
var p2 = new Person("lee");

構造器模式this指向new建立的實例對象this

面試題

var age = 38;
var obj = {
    age: 18,
    getAge: function() {
        console.log(this.age);
    }
};

var a = obj.getAge();

分析:函數getAge定義在obj對象中,屬於方法模式,其this指向調用方法的函數obj,結果爲 18;

var age = 38;
var obj = {
    age: 18,
    getAge: function() {
        function foo() {
            console.log(this.age);
        }
        foo();
    }
};

console.log(obj.getAge());

分析:obj.getAge()等同於直接執行

function foo() {
        console.log(this.age);
    }

屬於函數模式,其this指向window,結果爲 38;

var age = 38;
var obj = {
    age: 18,
    getAge: function() {
        console.log(this.age);
    }
};

var f = obj.getAge;
f();

分析:分析函數的this指向要看函數的執行環境執行的,

f = obj.getAge = function(){console.log(this.age)}

一樣的,屬於函數模式,其this指向window,結果爲 38;

var length = 10;
function fn(){
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function (fn) {
        fn();//
        arguments[0]();//
    }
};

obj.method(fn, 123);

分析:坑!坑!坑!
執行fn();時,屬於函數模式,this指向爲window,結果爲 10;

執行arguments[0]();時,arguments[0]也是指向fn,而後fn();屬於函數模式,this指向爲window,結果爲 10 ?

真的是這樣嗎,咱們打印出來發現結果爲 2,爲何呢?
看過上一篇文章的都應該知道,arguments是函數內部的屬性,是一個僞數組(對象),索引0是其屬性

arguments: {
    '0': function fn(){
        console.log(this.length);
    }
}

所以:調用方式屬於方法模式,其this指向調用方法的函數objthis.length爲其實參個數,結果爲 2;

借用方法調用模式

上面的三種函數調用方式中,this的指向都是肯定的,可是若是對象A中包含了某個方法,對象B也想調用一樣的方法,那麼在不定義該方法的前提下有什麼方式可以實現方法的共用呢?借用方法調用模式可以很是優雅的解決這個問題

1 apply()與call()方法

兩個方法均能改變函數運行時的this指向,兩者的做用徹底同樣,只是接受參數的方式不太同樣
apply()接受兩個參數,第一個是其中運行函數的做用域,另外一個是參數數組
call()接受多個參數,第一個是其中運行函數的做用域,其餘的參數都直接傳給數組

fn.call(contextObj, arg1, arg2, arg3...)
fn.apply(contextObj, [arg1, arg2, aeg3...])

// 等效於
contextObj.fn(arg1,arg2,arg3)

網上有一個簡單的等效,幫助理解:
foo.call(this,arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3)

下面有幾個實際應用例子:

(1)僞數組轉換成數組

// arguments
// 返回值爲數組,arguments保持不變
var arg = [].slice.call(arguments);

// nodeList
var nList = [].slice.call(document.getElementsByTagName('li'));

(2) 方法借用

var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];

// max、min 是 Math 中的靜態方法,所以必然是沒有使用上下文的必要的
Math.max.call(null, 34,5,3,6,54,6,-67,5,7,6,-8,687);
Math.max.apply(null, arr);

(3) 判斷變量類型

// 判斷是否爲數組
function isArray(obj){
  return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray(123) // false

(4)實現繼承

function Animal(name){
    this.name = name;
    this.showName = function(){
        alert(this.name);    
    }    
}

function Cat(name){
    Animal.apply(this,[name]);
    // call的用法
    Animal.call(this,name); 
}

var cat = new Cat("咕咕");
cat.showName();

2 bind()方法

bind()方法與上面兩個方法最大的不一樣是:
bind是返回對應函數,便於稍後調用;apply、call則是當即調用

bind()方法會建立一個新函數,稱爲綁定函數,當調用這個綁定函數時,綁定函數會以建立它時傳入 bind()方法的第一個參數做爲 this,傳入 bind() 方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。
var func = function(){
    console.log(this.x);
    console.log(arguments);
}
func();  // undefined, {}

var obj = {
    x: 2
}
var bar = func.bind(obj,1);

bar(); // 2 , {'0':1}

一個有意思的事:

var bar = function() {
    console.log(this.x);
}
var foo = {
    x: 3
}
var sed = {
    x: 4
}
var func = bar.bind(foo).bind(sed);
func(); //3

var fiv = {
    x: 5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //3

分析:在Javascript中,屢次 bind() 是無效的。更深層次的緣由, bind() 的實現,至關於使用函數在內部包了一個 call / apply ,第二次 bind() 至關於再包住第一次 bind() ,故第二次之後的 bind 是沒法生效的

注意:
(1)借用方法調用模式中,若是第一個參數不是對象:number string boolean,此時,會自動的被轉化爲其包裝對象

number -> new Number()
string -> new String()
boolean -> new Boolean()

(2)若是第一個參數爲null,則this指向window(在node環境中則指向global)

say.call(null); //this 指向window
say.call(window); //this一樣指向window

參考:
1 前端基礎:call,apply,bind的的理解
2 call/apply的理解與實例分享

相關文章
相關標籤/搜索