call(),apply(),bind() 函式大家可能都有所瞭解,但是在平時搬磚過程中很可能或者基本沒用過,學過但都淡忘了。
但是在大量第三方的框架(庫),甚至js自己都在 原始碼中大量使用 call,apply 函式。所以今天和大家仔細討論下它們在 開發中的應用場景 。
1 . 它們是啥意思 1.1 作用 他們的作用都是改變函式內部的this。 這三個函式都是函式物件的方法,也就是說只有函式才可以直接呼叫這些方法。 1.2 三者區別 引數: 三個函式的第一個引數都是需要繫結的 this。 call: 可以有n個引數,從第二個引數開始的所有引數都是原函式的引數。
`apply`:只有兩個引數,並且第二個引數必須為陣列,陣列中的所有元素一一對應原函式的引數。
`bind`: 只有一個引數,即要繫結的this。
複製程式碼
call 語法: foo.call(this, arg1,arg2, ... ,argn );
apply 語法: foo.apply(this, [ arg1,arg2, ... ,argn ] );
bind 語法: foo.bind(this);
複製程式碼
呼叫: call,apply: 呼叫後立即執行原函式。
`bind`: 呼叫後返回已經繫結好this的函式。
複製程式碼
小例子一枚:
function foo(a,b){
console.log(a+b);
}
foo.call(null,'海洋','餅乾'); // 海洋餅乾 這裡this指向不重要就寫null了
foo.apply(null, ['海洋','餅乾'] ); // 海洋餅乾
var fun = foo.bind(null);
fun('海洋','餅乾'); // 海洋餅乾
複製程式碼
2 .它們能幹啥事 這是我們今天討論的主題,這三個函式如何應用?什麼情況下使用?能改變this指向又能咋滴?
2 .1 處理偽陣列 (最常用) 先考慮一個問題,如果你使用var arr = document.getElementsByTagName('li')獲取了5個li元素,你現在需要獲取其中的第2,3,4三個元素,你會怎麼做?
這樣arr.slice(1,4);? 啊哦,TypeError -- arr.slice is not a function(slice不是函式),陣列操作在日常搬磚中非常常見,我見過最傻的解決這個問題的方式是使用迴圈,將需要的元素一個個新增到一個新陣列裡0.0,下面我介紹的方法完全可以在實戰中使用,可以給你的程式碼加分哦,非常方便簡潔(中高階前端程式設計師中,算是基本操作了)。
先要介紹一個概念( 偽陣列 ),這也是為什麼我們剛剛slice切割陣列時出錯的原因: (對新手來說算是乾貨了,知道的可以跳過)
什麼是偽陣列?( 字面的意思已經呼之欲出了 )
有length屬性 能按索引儲存資料 能像遍歷陣列一樣來遍歷 不能使用陣列的push()、slice()等方法 簡單來說就是可以像陣列一樣操作的物件,但是沒有陣列的方法。
js中存在大量偽陣列,如 :
1. function的arguments物件。
2. getElementsByName(),getElementsByTagName(),childNodes/children 等方法的返回值。
3. 還有比較常見的jquery,使用它獲取的元素也是偽陣列。
複製程式碼
回到原來的問題,如何擷取偽陣列中的元素:偽陣列沒有這些方法,我們'借用'Array的slice不就行了
[].slice.call(arr,1,4); // 推薦寫法
複製程式碼
不想借用你可以直接給偽陣列新增一個slice函式,如
arr.slice = [].slice;
arr.slice(1,4);
複製程式碼
當然,'借用' 更方便,直接新增會導致偽陣列物件'汙染'。
如果可以隨意改變原物件,可以 直接將其轉成真正的陣列物件。
[].slice.call(arr);
複製程式碼
2 .2 繼承 繼承方式多種多樣,我們現在討論的這種是其中很重要的一種實現方式,用call實現 js 建構函式繼承 。
單繼承
function person(name){
this.name = name
}
function man(name){
this.age = '男';
person.call(this,name); // 繼承 man
}
var me = new man('海洋餅乾');
console.log(me.name,me.age); // '海洋餅乾' '男'
複製程式碼
多繼承
function person(name){
this.name = name
}
function man(name){
this.age = '男';
}
function manProgrammer(name){
this.girlfriend = null;
person.call(this,name); // 繼承 person
man.call(this,name); // 繼承 man
}
var me = new manProgrammer('海洋餅乾');
console.log(me.name,me.age,me.girlfriend); // '海洋餅乾' '男' null
複製程式碼
2 .3 this 硬繫結 --- bind 將一個物件強制且永久性繫結到函式的this上,使用call,apply或者其他的繫結方式都無法改變(除了new繫結,當然,可以手動擼一個new都無法改變的硬繫結)
直接看例子:
var fun ;
var obj = {
a : 1,
foo : function(){
var _this = this; //平時有沒有過這種寫法? 為了防止this指向問題
//將this賦值給一個變數,間接維持了this的安全性
fun = function(){
console.log(_this.a);
}
}
}
obj.foo();
fun(); // 1
var obj1 = { a : 2}
obj.foo.call(obj1); // 直接修改_this所繫結的值,boom了
fun(); // 2
複製程式碼
但是這種方法感覺上是在逃避問題,直接不使用this了 ? 這真的不是什麼好的解決問題的態度。下面使用我們的bind來優化一下:
var fun ;
var obj = {
a : 1,
foo : function(){ // 不使用 _this, 避免無謂的變數宣告
fun = function(){
console.log(this.a);
}.bind(this); // 程式碼很簡潔,很漂亮(b格)
}
}
var obj1 = { a : 2}
obj.foo();
fun(); // 1
fun.call(obj1); // 1 call ,apply等繫結 無法修改
// 這裡和上面call的位置不同是因為this所處於不同的位置
複製程式碼
這樣替代 _this 很規(zhuang)範(b)呢
2 .4 取陣列最大最小值 Math.max和min方法,接收多個引數,比較出極值,這裡用到apply的一個預設功能:展開陣列,傳入一個陣列引數就可以預設將這個陣列轉成一個個引數的形式賦給原函式
var num = [6,9,-3,-5];
console.log(Math.max.apply(Math,num)); // 9 等價 console.log(Math.max(6,9,-3,-5));
console.log(Math.min.apply(Math,num)); // -5 等價 console.log(Math.min(6,9,-3,-5));
複製程式碼
2 .5 合併陣列 合併陣列常見有三種方式,1.迴圈 2.Array的concat() 3. 使用apply()合併
這裡是使用最簡便的apply
var a = [1,2,3];
var b = [4,5,6];
[].push.apply(a,b); // 借用陣列的push方法 等價 a.push(4,5,6);
console.log(a); // [1, 2, 3, 4, 5, 6]
複製程式碼
這裡推薦一下我的前端學習交流群:784783012,裡面都是學習前端的,如果你想製作酷炫的網頁,想學習程式設計。自己整理了一份2018最全面前端學習資料,從最基礎的HTML+CSS+JS【炫酷特效,遊戲,外掛封裝,設計模式】到移動端HTML5的專案實戰的學習資料都有整理,送給每一位前端小夥伴,有想學習web前端的,或是轉行,或是大學生,還有工作中想提升自己能力的,正在學習的小夥伴歡迎加入學習。