1. 程式人生 > >淺談call apply bind

淺談call apply bind

初學call apply的時候看的是書,書上講的很淺顯,在網上看了很多部落格講的都很是高深,這裡標題說是淺談,其實本來想寫的是深入,但是寫到最後感覺自己所瞭解的遠遠不夠,這裡的this究竟指的是什麼,與作用域又有什麼關係?因為目前知識水平的限制,我就暫時先總結了一些。更加深層次的東西,我也將繼續挖掘。。

其實,call()和apply()就是改變函式的執行上下文,(也就是函式內部的this的指向)。他們兩個是Function物件的方法,每個函式都能呼叫。他們的第一個引數就是你要指定的執行上下文。

call方法

第一個引數是要繫結給this的值,第二部分引數要一個一個傳。(當第一個引數為nll,nudefined的時候,預設指向window)

這是一個簡單的例子:

//例1
var obj = {
    message: 'This is a people: '
}

function person(name, age) {
    console.log(this.message + name + ' ' + age)  //This is a people: xiaoming 18
}

person.call(obj, 'xiaoming', 18)

瞭解了它的基本語法後,我們來看這樣一段程式碼:

//例2
    function A(a,b) {
        this.a = a;
        this.b = b;
    }
function B() { this.a = 5; this.c = 3; A.call(this,1,2); } var s = new B(); console.log(s.a+","+s.b+","+s.c);
  • 輸出是什麼,是5,2,3 還是 1,2,3? 答案是後者,這是因為給B呼叫了A的call方法,使A的this指標指向了B,雖然我們在前面定義了B的a為5,但是在呼叫call方法之後又將B的a重寫為了1。

上面兩個都懂了 看一下第三個例子:

//例3
function A(){
        this
.show = function(){alert(this.name)} } function B(){ this.name = 'b'; } var C = new A(); var D = new B(); C.show.call(D);//b alert(D.show);//undefined //這段程式碼中,讓C.show中的this指向了D,改變了匿名函式裡面的this.name的值 //但是並沒有給D寫入C.show,所以第一個輸出b第二個輸出undefined

apply

接受兩個引數,第一個引數是要繫結給this的值,所有引數都必須放在一個數組裡面作為第二個引數傳進去。(當第一個引數為null、undefined的時候,預設指向window。)

call 和apply是立即呼叫

bind

類似於call,第一個引數是this的指向,從第二個引數開始是接收的引數列表。(區別在於bind方法返回值是函式以及bind接收的引數列表。)

var obj = {
    name: 'xiaoming'
}

function person() {
    console.log(this.name)
}

var people = person.bind(obj)  
console.log(people)  //ƒ person() {...}
people()             //xiaoming

  • bind是新建立一個函式,然後把它的上下文繫結到bind()括號中的引數上,然後將它返回。所以,bind後函式不會執行,而只是返回一個改變了上下文的函式副本。(一般需要一般變數來接受它的返回值) 而原函式 printName 中的 this 並沒有被改變,依舊指向全域性物件 window。
bind中引數的使用:

來,我們再來看一個好玩的東西:

function fn(a, b, c) {
    console.log(a, b, c);
}
var fn1 = fn.bind(null, 'xiaoming'); //相當於已將fn1中第一個引數的值確定好了

fn('A', 'B', 'C');            // A B C
fn1('A', 'B', 'C');           // Dot A B  //傳入的實參依次向後對應一位
fn1('B', 'C');                // Dot B C
fn.call(null, 'xiaoming');    // Dot undefined undefined

對call 來說它會嚴格遵照將第二個及其以後的引數作為實參傳入fn,而對於bind方法 fn1的實參是在bind中已確定值的引數 基礎上向後排的。

三者的使用區別

  1. 都是用來改變函式的this物件的指向的;(第一個引數都是this要指向的物件) 2.都可以利用後續引數傳參;
  2. bind是返回對應函式,便於稍後呼叫,apply、call是立即呼叫;

看了這麼多,你或許還回小瞧它們,難道應用它們僅僅是為了改變this指向,哈哈,當然不是了!

有何用處?

  • 將類陣列轉化為陣列

將這個之前我們先來簡單回答一下類陣列和陣列的關係:類陣列(1)擁有length屬性 (2)不具有陣列所具有的方法

常見的類陣列有哪些呢? 引數的引數 arguments,DOM 物件列表(比如通過 document.getElementsByTags 得到的列表),jQuery 物件(比如 $(“div”))

原生JS中的方法:
var arr = Array.prototype.slice.call(類陣列);

該方法等價於:
var arr = [].slice.call(類陣列);

剛看到設個方法的時候,自己很是疑惑,為啥這樣就可以。再網上看到一篇文章博主是這樣解釋的,覺得很有道理:

Array.prototype.slice.call(arguments);

它會將一個類陣列形式的變數轉化為真正的陣列。為啥呢,其實書上並沒有說slice還有這樣的用法,也不知道是誰發明的。slice的用法可以順便上網查一下,就能查到。但要更正一點,網上的介紹說slice有兩個引數,第一個引數不能省略。然而我不知道是我理解的問題還是咋地,上面這段程式碼不就是典型的沒傳引數嗎!!!arguments是傳給call的那個上下文,前面講過,不要弄混(由於arguments自己沒有slice方法,這裡屬於借用Array原型的slice方法)。而且經過測試,若果你不給slice傳引數,那就等於傳了個0給它,結果就是返回一個和原來陣列一模一樣的副本。這之後的程式碼就很好理解,返回一個函式,該函式把傳給bind的第一個引數當做執行上下文,由於args已經是一個數組,排除第一項,將之後的部分作為第二部分引數傳給apply。

  • 陣列追加(當然這裡可以用陣列的拼接函式concat)
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]

在這裡增加一個知識: push 新增只能將新增項作為一個子元素,增加到母陣列的後面

var arr1=[1,2,3,4];
    var arr2=[5,6,7,8];
    
    arr1.push(arr2);
    console.log(arr1); //(5) [1, 2, 3, 4, Array(4)]
  • 利用call apply做繼承
function Person(name,age){
    // 這裡的this都指向例項
    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Boy(){
    Person.apply(this,arguments)//將父元素所有方法在這裡執行一遍就繼承了
}
var dcbryant = new Boy('dcbryant',22)