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á)式就是將一個(gè)匿名函數(shù)賦值給一個(gè)變量。函數(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ù)聲明會(huì)函數(shù)提升,在預(yù)解析階段就已創(chuàng)建,聲明前后都可以調(diào)用。
  3. 函數(shù)表達(dá)式類(lèi)似于變量賦值。
  4. 函數(shù)表達(dá)式可以沒(méi)有名字,例如匿名函數(shù)。
  5. 函數(shù)表達(dá)式?jīng)]有變量提升,在執(zhí)行階段創(chuàng)建,必須在表達(dá)式執(zhí)行之后才可以調(diào)用。

下面是一個(gè)根據(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á)式解決上面的問(wèn)題:


  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的語(yǔ)句中,在IE8的瀏覽器中會(huì)出現(xiàn)問(wèn)題,所以為了更好的兼容性我們以后最好用函數(shù)表達(dá)式,不用函數(shù)聲明的方式。

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

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


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

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

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

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


  1. // 所有的函數(shù)實(shí)際上都是Function的構(gòu)造函數(shù)創(chuàng)建出來(lái)的實(shí)例對(duì)象
  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ù)實(shí)際上也是對(duì)象
  6. console.dir(f1);
  7. console.dir(Function);

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

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

  1. // 普通函數(shù)
  2. function f1() {
  3. console.log("我是普通函數(shù)");
  4. }
  5. f1();
  6. // 構(gòu)造函數(shù)---通過(guò)new 來(lái)調(diào)用,創(chuàng)建對(duì)象
  7. function F1() {
  8. console.log("我是構(gòu)造函數(shù)");
  9. }
  10. var f = new F1();
  11. // 對(duì)象的方法
  12. function Person() {
  13. this.play = function() {
  14. console.log("我是對(duì)象中的方法");
  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)用 實(shí)例對(duì)象 原型方法中 this 也是實(shí)例對(duì)象
對(duì)象方法調(diào)用 該方法所屬對(duì)象 緊挨著的對(duì)象
事件綁定方法 綁定事件對(duì)象  
定時(shí)器函數(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. // 對(duì)象的方法
  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. // 定時(shí)器中的this
  23. setInterval(function() {
  24. console.log(this); // window
  25. }, 1000);

4 call、apply、bind

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

4.1 call,apply

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

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

call語(yǔ)法:

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

call參數(shù):

  • thisArg

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

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

apply語(yǔ)法:

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. // 此時(shí)的f1實(shí)際上是當(dāng)成對(duì)象來(lái)使用的,對(duì)象可以調(diào)用方法
  9. // apply和call方法中如果沒(méi)有傳入?yún)?shù),或者是傳入的是null,那么調(diào)用該方法的函數(shù)對(duì)象中的this就是默認(rèn)的window
  10. f1.apply(null, [10, 20]);
  11. f1.call(null, 10, 20);
  12. // apply和call都可以讓函數(shù)或者方法來(lái)調(diào)用,傳入?yún)?shù)和函數(shù)自己調(diào)用的寫(xiě)法不一樣,但是效果是一樣的
  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. // 通過(guò)apply和call改變this的指向
  2. function Person(name, sex) {
  3. this.name = name;
  4. this.sex = sex;
  5. }
  6. //通過(guò)原型添加方法
  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ù)的時(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]); // 此時(shí)f1中的this是window
  8. console.log(r1);
  9. var r2 = f1.call(null, 1, 2); // 此時(shí)f1中的this是window
  10. console.log(r2);
  11. console.log("=============>");
  12. //改變this的指向
  13. var obj = {
  14. sex: "男"
  15. };
  16. // 本來(lái)f1函數(shù)是window對(duì)象的,但是傳入obj之后,f1的this此時(shí)就是obj對(duì)象
  17. var r3 = f1.apply(obj, [1, 2]); //此時(shí)f1中的this是obj
  18. console.log(r3);
  19. var r4 = f1.call(obj, 1, 2); //此時(shí)f1中的this是obj
  20. console.log(r4);


調(diào)用方法的時(shí)候,改變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)前實(shí)例對(duì)象
  7. };
  8. function Student(age) {
  9. this.age = age;
  10. }
  11. var per = new Person(10); // Person實(shí)例對(duì)象
  12. var stu = new Student(100); // Student實(shí)例對(duì)象
  13. // sayHi方法是per實(shí)例對(duì)象的
  14. per.sayHi(10, 20);
  15. per.sayHi.apply(stu, [10, 20]);
  16. per.sayHi.call(stu, 10, 20);

總結(jié)

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

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

4.3 bind

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

語(yǔ)法:

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

參數(shù):

  • thisArg

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

    • 當(dāng)綁定函數(shù)被調(diào)用時(shí),這些參數(shù)將置于實(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ù)制了一個(gè)新的play方法
  14. var ff = per.play.bind(stu);
  15. ff();

示例2:


  1. //通過(guò)對(duì)象,調(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. //改變了定時(shí)器中的this的指向了
  9. window.setTimeout(function() {
  10. //本來(lái)應(yīng)該是window, 現(xiàn)在是實(shí)例對(duì)象了
  11. //顯示隨機(jī)數(shù)
  12. console.log(this.number);
  13. }.bind(this), 1000);
  14. };
  15. //實(shí)例對(duì)象
  16. var sr = new ShowRandom();
  17. //調(diào)用方法,輸出隨機(jī)數(shù)字
  18. sr.show();

4.4 總結(jié)

  • call 和 apply 特性一樣

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

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

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

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

  1. function fn(x, y, z) {
  2. console.log(fn.length) // => 形參的個(gè)數(shù)
  3. console.log(arguments) // 偽數(shù)組實(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ù),那么只傳入命名函數(shù)的名字,沒(méi)有括號(hào)。


  1. function f1(fn) {
  2. console.log("我是函數(shù)f1");
  3. fn(); // fn是一個(gè)函數(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ù)中有兩個(gè)參數(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ù),此時(shí)作為返回值使用");
  5. }
  6. }
  7. var ff = f1();
  8. ff();

作為返回值排序案例: 


  1. // 排序,每個(gè)文件都有名字,大小,時(shí)間,可以按照某個(gè)屬性的值進(jìn)行排序
  2. // 三個(gè)文件,文件有名字,大小,創(chuàng)建時(shí)間
  3. function File(name, size, time) {
  4. this.name = name; // 名字
  5. this.size = size; // 大小
  6. this.time = time; // 創(chuàng)建時(shí)間
  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)建時(shí)間排序:**********");
  41. // 按照創(chuàng)建時(shí)間排序
  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. }

日歷

鏈接

個(gè)人資料

存檔