JavaScript進(jìn)階教程(4)-函數(shù)內(nèi)this指向解惑call(),apply(),bind()的區(qū)別

2020-9-7    前端達(dá)人

目錄

1 函數(shù)的定義方式

1.1 函數(shù)聲明

1.2 函數(shù)表達(dá)式

1.3 函數(shù)聲明與函數(shù)表達(dá)式的區(qū)別

1.4 構(gòu)造函數(shù)Function(了解即可,一般不用)

2 函數(shù)的調(diào)用方式

3 函數(shù)內(nèi) this 的指向

4 call、apply、bind

4.1 call,apply

4.1.1 新的函數(shù)調(diào)用方式apply和call方法

4.1.2 apply和call可以改變this的指向

4.2 call,apply使用

4.3 bind

4.4 總結(jié)

5 函數(shù)的其它成員(了解)

6 高階函數(shù)

6.1 作為參數(shù)

6.2 作為返回值

7 總結(jié)


1 函數(shù)的定義方式

定義函數(shù)的方式有三種:

  1. 函數(shù)聲明
  2. 函數(shù)表達(dá)式
  3. new Function(一般不用)

1.1 函數(shù)聲明


  1. // 函數(shù)的聲明
  2. function fn() {
  3. console.log("我是JS中的一等公民-函數(shù)!!!哈哈");
  4. }
  5. fn();

1.2 函數(shù)表達(dá)式

函數(shù)表達(dá)式就是將一個匿名函數(shù)賦值給一個變量。函數(shù)表達(dá)式必須先聲明,再調(diào)用。


  1. // 函數(shù)表達(dá)式
  2. var fn = function() {
  3. console.log("我是JS中的一等公民-函數(shù)!!!哈哈");
  4. };
  5. fn();

1.3 函數(shù)聲明與函數(shù)表達(dá)式的區(qū)別

  1. 函數(shù)聲明必須有名字。
  2. 函數(shù)聲明會函數(shù)提升,在預(yù)解析階段就已創(chuàng)建,聲明前后都可以調(diào)用。
  3. 函數(shù)表達(dá)式類似于變量賦值。
  4. 函數(shù)表達(dá)式可以沒有名字,例如匿名函數(shù)。
  5. 函數(shù)表達(dá)式?jīng)]有變量提升,在執(zhí)行階段創(chuàng)建,必須在表達(dá)式執(zhí)行之后才可以調(diào)用。

下面是一個根據(jù)條件定義函數(shù)的例子:


  1. if (true) {
  2. function f () {
  3. console.log(1)
  4. }
  5. } else {
  6. function f () {
  7. console.log(2)
  8. }
  9. }

以上代碼執(zhí)行結(jié)果在不同瀏覽器中結(jié)果不一致。我們可以使用函數(shù)表達(dá)式解決上面的問題:


  1. var f
  2. if (true) {
  3. f = function () {
  4. console.log(1)
  5. }
  6. } else {
  7. f = function () {
  8. console.log(2)
  9. }
  10. }

函數(shù)聲明如果放在if-else的語句中,在IE8的瀏覽器中會出現(xiàn)問題,所以為了更好的兼容性我們以后最好用函數(shù)表達(dá)式,不用函數(shù)聲明的方式。

1.4 構(gòu)造函數(shù)Function(了解即可,一般不用)

在前面的學(xué)習(xí)中我們了解到函數(shù)也是對象。注意:函數(shù)是對象,對象不一定是函數(shù),對象中有__proto__原型,函數(shù)中有prototype原型,如果一個東西里面有prototype,又有__proto__,說明它是函數(shù),也是對象。


  1. function F1() {}
  2. console.dir(F1); // F1里面有prototype,又有__proto__,說明是函數(shù),也是對象
  3. console.dir(Math); // Math中有__proto__,但是沒有prorotype,說明Math不是函數(shù)

對象都是由構(gòu)造函數(shù)創(chuàng)建出來的,函數(shù)既然是對象,創(chuàng)建它的構(gòu)造函數(shù)又是什么呢?事實上所有的函數(shù)實際上都是由Function構(gòu)造函數(shù)創(chuàng)建出來的實例對象。

所以我們可以使用Function構(gòu)造函數(shù)創(chuàng)建函數(shù)。

語法:new Function(arg1,arg2,arg3..,body);
arg是任意參數(shù),字符串類型的。body是函數(shù)體。


  1. // 所有的函數(shù)實際上都是Function的構(gòu)造函數(shù)創(chuàng)建出來的實例對象
  2. var f1 = new Function("num1", "num2", "return num1+num2");
  3. console.log(f1(10, 20));
  4. console.log(f1.__proto__ == Function.prototype);
  5. // 所以,函數(shù)實際上也是對象
  6. console.dir(f1);
  7. console.dir(Function);

2 函數(shù)的調(diào)用方式

  1. 普通函數(shù)
  2. 構(gòu)造函數(shù)
  3. 對象方法

  1. // 普通函數(shù)
  2. function f1() {
  3. console.log("我是普通函數(shù)");
  4. }
  5. f1();
  6. // 構(gòu)造函數(shù)---通過new 來調(diào)用,創(chuàng)建對象
  7. function F1() {
  8. console.log("我是構(gòu)造函數(shù)");
  9. }
  10. var f = new F1();
  11. // 對象的方法
  12. function Person() {
  13. this.play = function() {
  14. console.log("我是對象中的方法");
  15. };
  16. }
  17. var per = new Person();
  18. per.play();

3 函數(shù)內(nèi) this 的指向

函數(shù)的調(diào)用方式?jīng)Q定了 this 指向的不同:

調(diào)用方式 非嚴(yán)格模式 備注
普通函數(shù)調(diào)用 window 嚴(yán)格模式下是 undefined
構(gòu)造函數(shù)調(diào)用 實例對象 原型方法中 this 也是實例對象
對象方法調(diào)用 該方法所屬對象 緊挨著的對象
事件綁定方法 綁定事件對象  
定時器函數(shù) window  

  1. // 普通函數(shù)
  2. function f1() {
  3. console.log(this); // window
  4. }
  5. f1();
  6. // 構(gòu)造函數(shù)
  7. function Person() {
  8. console.log(this); // Person
  9. // 對象的方法
  10. this.sayHi = function() {
  11. console.log(this); // Person
  12. };
  13. }
  14. // 原型中的方法
  15. Person.prototype.eat = function() {
  16. console.log(this); // Person
  17. };
  18. var per = new Person();
  19. console.log(per); // Person
  20. per.sayHi();
  21. per.eat();
  22. // 定時器中的this
  23. setInterval(function() {
  24. console.log(this); // window
  25. }, 1000);

4 call、apply、bind

了解了函數(shù) this 的指向之后,我們知道在一些情況下我們?yōu)榱耸褂媚撤N特定環(huán)境的 this 引用,需要采用一些特殊手段來處理,例如我們經(jīng)常在定時器外部備份 this 引用,然后在定時器函數(shù)內(nèi)部使用外部 this 的引用。
然而實際上 JavaScript 內(nèi)部已經(jīng)專門為我們提供了一些函數(shù)方法,用來幫我們更優(yōu)雅的處理函數(shù)內(nèi)部 this 指向問題。這就是接下來我們要學(xué)習(xí)的 call、apply、bind 三個函數(shù)方法。call()、apply()、bind()這三個方法都是是用來改變this的指向的。

4.1 call,apply

call() 方法調(diào)用一個函數(shù), 其具有一個指定的 this 值和分別地提供的參數(shù)(參數(shù)的列表)。
apply() 方法調(diào)用一個函數(shù), 其具有一個指定的 this 值,以及作為一個數(shù)組(或類似數(shù)組的對象)提供的參數(shù)。

注意:call() 和 apply() 方法類似,只有一個區(qū)別,就是 call() 方法接受的是若干個參數(shù)的列表,而 apply() 方法接受的是一個包含多個參數(shù)的數(shù)組。

call語法:

fun.call(thisArg[, arg1[, arg2[, ...]]]) 

call參數(shù):

  • thisArg

    • 在 fun 函數(shù)運行時指定的 this 值
    • 如果指定了 null 或者 undefined 則內(nèi)部 this 指向 window
  • arg1, arg2, ...

    • 指定的參數(shù)列表

apply語法:

fun.apply(thisArg, [argsArray]) 

apply參數(shù):

  • thisArg
  • argsArray

apply() 與 call() 相似,不同之處在于提供參數(shù)的方式。
apply() 使用參數(shù)數(shù)組而不是一組參數(shù)列表。例如:

fun.apply(this, ['eat', 'bananas']) 

4.1.1 新的函數(shù)調(diào)用方式apply和call方法


  1. function f1(x, y) {
  2. console.log("結(jié)果是:" + (x + y) + this);
  3. return "666";
  4. }
  5. f1(10, 20); // 函數(shù)的調(diào)用
  6. console.log("========");
  7. // apply和call方法也是函數(shù)的調(diào)用的方式
  8. // 此時的f1實際上是當(dāng)成對象來使用的,對象可以調(diào)用方法
  9. // apply和call方法中如果沒有傳入?yún)?shù),或者是傳入的是null,那么調(diào)用該方法的函數(shù)對象中的this就是默認(rèn)的window
  10. f1.apply(null, [10, 20]);
  11. f1.call(null, 10, 20);
  12. // apply和call都可以讓函數(shù)或者方法來調(diào)用,傳入?yún)?shù)和函數(shù)自己調(diào)用的寫法不一樣,但是效果是一樣的
  13. var result1 = f1.apply(null, [10, 20]);
  14. var result2 = f1.call(null, 10, 20);
  15. console.log(result1);
  16. console.log(result2);

4.1.2 apply和call可以改變this的指向


  1. // 通過apply和call改變this的指向
  2. function Person(name, sex) {
  3. this.name = name;
  4. this.sex = sex;
  5. }
  6. //通過原型添加方法
  7. Person.prototype.sayHi = function(x, y) {
  8. console.log("您好啊:" + this.name);
  9. return x + y;
  10. };
  11. var per = new Person("小三", "男");
  12. var r1 = per.sayHi(10, 20);
  13. console.log("==============");
  14. function Student(name, age) {
  15. this.name = name;
  16. this.age = age;
  17. }
  18. var stu = new Student("小舞", 18);
  19. var r2 = per.sayHi.apply(stu, [10, 20]);
  20. var r3 = per.sayHi.call(stu, 10, 20);
  21. console.log(r1);
  22. console.log(r2);
  23. console.log(r3);

4.2 call,apply使用

apply和call都可以改變this的指向。調(diào)用函數(shù)的時候,改變this的指向:


  1. // 函數(shù)的調(diào)用,改變this的指向
  2. function f1(x, y) {
  3. console.log((x + y) + ":===>" + this);
  4. return "函數(shù)的返回值";
  5. }
  6. //apply和call調(diào)用
  7. var r1 = f1.apply(null, [1, 2]); // 此時f1中的this是window
  8. console.log(r1);
  9. var r2 = f1.call(null, 1, 2); // 此時f1中的this是window
  10. console.log(r2);
  11. console.log("=============>");
  12. //改變this的指向
  13. var obj = {
  14. sex: "男"
  15. };
  16. // 本來f1函數(shù)是window對象的,但是傳入obj之后,f1的this此時就是obj對象
  17. var r3 = f1.apply(obj, [1, 2]); //此時f1中的this是obj
  18. console.log(r3);
  19. var r4 = f1.call(obj, 1, 2); //此時f1中的this是obj
  20. console.log(r4);


調(diào)用方法的時候,改變this的指向:


  1. //方法改變this的指向
  2. function Person(age) {
  3. this.age = age;
  4. }
  5. Person.prototype.sayHi = function(x, y) {
  6. console.log((x + y) + ":====>" + this.age); //當(dāng)前實例對象
  7. };
  8. function Student(age) {
  9. this.age = age;
  10. }
  11. var per = new Person(10); // Person實例對象
  12. var stu = new Student(100); // Student實例對象
  13. // sayHi方法是per實例對象的
  14. per.sayHi(10, 20);
  15. per.sayHi.apply(stu, [10, 20]);
  16. per.sayHi.call(stu, 10, 20);

總結(jié)

apply的使用語法:
1 函數(shù)名字.apply(對象,[參數(shù)1,參數(shù)2,...]);
2 方法名字.apply(對象,[參數(shù)1,參數(shù)2,...]);
call的使用語法
1 函數(shù)名字.call(對象,參數(shù)1,參數(shù)2,...);
2 方法名字.call(對象,參數(shù)1,參數(shù)2,...);
它們的作用都是改變this的指向,不同的地方是參數(shù)傳遞的方式不一樣。

如果想使用別的對象的方法,并且希望這個方法是當(dāng)前對象的,就可以使用apply或者是call方法改變this的指向。

4.3 bind

bind() 函數(shù)會創(chuàng)建一個新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調(diào)函數(shù)(綁定函數(shù)的目標(biāo)函數(shù))具有相同的函數(shù)體(在 ECMAScript 5 規(guī)范中內(nèi)置的call屬性)。當(dāng)目標(biāo)函數(shù)被調(diào)用時 this 值綁定到 bind() 的第一個參數(shù),該參數(shù)不能被重寫。綁定函數(shù)被調(diào)用時,bind() 也可以接受預(yù)設(shè)的參數(shù)提供給原函數(shù)。一個綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。提供的 this 值被忽略,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。
bind方法是復(fù)制的意思,本質(zhì)是復(fù)制一個新函數(shù),參數(shù)可以在復(fù)制的時候傳進(jìn)去,也可以在復(fù)制之后調(diào)用的時候傳入進(jìn)去。apply和call是調(diào)用的時候改變this指向,bind方法,是復(fù)制一份的時候,改變了this的指向。

語法:

fun.bind(thisArg[, arg1[, arg2[, ...]]]) 

參數(shù):

  • thisArg

    • 當(dāng)綁定函數(shù)被調(diào)用時,該參數(shù)會作為原函數(shù)運行時的 this 指向。當(dāng)使用new 操作符調(diào)用綁定函數(shù)時,該參數(shù)無效。
  • arg1, arg2, ...

    • 當(dāng)綁定函數(shù)被調(diào)用時,這些參數(shù)將置于實參之前傳遞給被綁定的方法。

返回值:

返回由指定的this值和初始化參數(shù)改造的原函數(shù)的拷貝。

示例1:


  1. function Person(name) {
  2. this.name = name;
  3. }
  4. Person.prototype.play = function() {
  5. console.log(this + "====>" + this.name);
  6. };
  7. function Student(name) {
  8. this.name = name;
  9. }
  10. var per = new Person("人");
  11. var stu = new Student("學(xué)生");
  12. per.play();
  13. // 復(fù)制了一個新的play方法
  14. var ff = per.play.bind(stu);
  15. ff();

示例2:


  1. //通過對象,調(diào)用方法,產(chǎn)生隨機(jī)數(shù)
  2. function ShowRandom() {
  3. //1-10的隨機(jī)數(shù)
  4. this.number = parseInt(Math.random() * 10 + 1);
  5. }
  6. //添加原型方法
  7. ShowRandom.prototype.show = function() {
  8. //改變了定時器中的this的指向了
  9. window.setTimeout(function() {
  10. //本來應(yīng)該是window, 現(xiàn)在是實例對象了
  11. //顯示隨機(jī)數(shù)
  12. console.log(this.number);
  13. }.bind(this), 1000);
  14. };
  15. //實例對象
  16. var sr = new ShowRandom();
  17. //調(diào)用方法,輸出隨機(jī)數(shù)字
  18. sr.show();

4.4 總結(jié)

  • call 和 apply 特性一樣

    • 都是用來調(diào)用函數(shù),而且是立即調(diào)用
    • 但是可以在調(diào)用函數(shù)的同時,通過第一個參數(shù)指定函數(shù)內(nèi)部 this 的指向
    • call 調(diào)用的時候,參數(shù)必須以參數(shù)列表的形式進(jìn)行傳遞,也就是以逗號分隔的方式依次傳遞即可
    • apply 調(diào)用的時候,參數(shù)必須是一個數(shù)組,然后在執(zhí)行的時候,會將數(shù)組內(nèi)部的元素一個一個拿出來,與形參一一對應(yīng)進(jìn)行傳遞
    • 如果第一個參數(shù)指定了 null 或者 undefined 則內(nèi)部 this 指向 window
  • bind

    • 可以用來指定內(nèi)部 this 的指向,然后生成一個改變了 this 指向的新的函數(shù)
    • 它和 call、apply 最大的區(qū)別是:bind 不會調(diào)用
    • bind 支持傳遞參數(shù),它的傳參方式比較特殊,一共有兩個位置可以傳遞
      • 在 bind 的同時,以參數(shù)列表的形式進(jìn)行傳遞
      • 在調(diào)用的時候,以參數(shù)列表的形式進(jìn)行傳遞 
      • 那到底以 bind 的時候傳遞的參數(shù)為準(zhǔn)呢?還是以調(diào)用的時候傳遞的參數(shù)為準(zhǔn)呢?
      • 兩者合并:bind 的時候傳遞的參數(shù)和調(diào)用的時候傳遞的參數(shù)會合并到一起,傳遞到函數(shù)內(nèi)部。

5 函數(shù)的其它成員(了解)

  • arguments
    • 實參集合
  • caller
    • 函數(shù)的調(diào)用者
  • length
    • 函數(shù)定義的時候形參的個數(shù)
  • name
    • 函數(shù)的名字,name屬性是只讀的,不能修改

  1. function fn(x, y, z) {
  2. console.log(fn.length) // => 形參的個數(shù)
  3. console.log(arguments) // 偽數(shù)組實參參數(shù)集合
  4. console.log(arguments.callee === fn) // 函數(shù)本身
  5. console.log(fn.caller) // 函數(shù)的調(diào)用者
  6. console.log(fn.name) // => 函數(shù)的名字
  7. }
  8. function f() {
  9. fn(10, 20, 30)
  10. }
  11. f()

6 高階函數(shù)

函數(shù)可以作為參數(shù),也可以作為返回值。

6.1 作為參數(shù)

函數(shù)是可以作為參數(shù)使用,函數(shù)作為參數(shù)的時候,如果是命名函數(shù),那么只傳入命名函數(shù)的名字,沒有括號。


  1. function f1(fn) {
  2. console.log("我是函數(shù)f1");
  3. fn(); // fn是一個函數(shù)
  4. }
  5. //傳入匿名函數(shù)
  6. f1(function() {
  7. console.log("我是匿名函數(shù)");
  8. });
  9. // 傳入命名函數(shù)
  10. function f2() {
  11. console.log("我是函數(shù)f2");
  12. }
  13. f1(f2);


作為參數(shù)排序案例:


  1. var arr = [1, 100, 20, 200, 40, 50, 120, 10];
  2. //排序---函數(shù)作為參數(shù)使用,匿名函數(shù)作為sort方法的參數(shù)使用,此時的匿名函數(shù)中有兩個參數(shù),
  3. arr.sort(function(obj1, obj2) {
  4. if (obj1 > obj2) {
  5. return -1;
  6. } else if (obj1 == obj2) {
  7. return 0;
  8. } else {
  9. return 1;
  10. }
  11. });
  12. console.log(arr);

6.2 作為返回值


  1. function f1() {
  2. console.log("函數(shù)f1");
  3. return function() {
  4. console.log("我是函數(shù),此時作為返回值使用");
  5. }
  6. }
  7. var ff = f1();
  8. ff();

作為返回值排序案例: 


  1. // 排序,每個文件都有名字,大小,時間,可以按照某個屬性的值進(jìn)行排序
  2. // 三個文件,文件有名字,大小,創(chuàng)建時間
  3. function File(name, size, time) {
  4. this.name = name; // 名字
  5. this.size = size; // 大小
  6. this.time = time; // 創(chuàng)建時間
  7. }
  8. var f1 = new File("jack.avi", "400M", "1999-12-12");
  9. var f2 = new File("rose.avi", "600M", "2020-12-12");
  10. var f3 = new File("albert.avi", "800M", "2010-12-12");
  11. var arr = [f1, f2, f3];
  12. function fn(attr) {
  13. // 函數(shù)作為返回值
  14. return function getSort(obj1, obj2) {
  15. if (obj1[attr] > obj2[attr]) {
  16. return 1;
  17. } else if (obj1[attr] == obj2[attr]) {
  18. return 0;
  19. } else {
  20. return -1;
  21. }
  22. }
  23. }
  24. console.log("按照名字排序:**********");
  25. // 按照名字排序
  26. var ff = fn("name");
  27. // 函數(shù)作為參數(shù)
  28. arr.sort(ff);
  29. for (var i = 0; i < arr.length; i++) {
  30. console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
  31. }
  32. console.log("按照大小排序:**********");
  33. // 按照大小排序
  34. var ff = fn("size");
  35. // 函數(shù)作為參數(shù)
  36. arr.sort(ff);
  37. for (var i = 0; i < arr.length; i++) {
  38. console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
  39. }
  40. console.log("按照創(chuàng)建時間排序:**********");
  41. // 按照創(chuàng)建時間排序
  42. var ff = fn("time");
  43. // 函數(shù)作為參數(shù)
  44. arr.sort(ff);
  45. for (var i = 0; i < arr.length; i++) {
  46. console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
  47. }

日歷

鏈接

個人資料

存檔