<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          JavaScript進階教程(4)-函數內this指向解惑call(),apply(),bind()的區別

          2020-9-7    前端達人

          目錄

          1 函數的定義方式

          1.1 函數聲明

          1.2 函數表達式

          1.3 函數聲明與函數表達式的區別

          1.4 構造函數Function(了解即可,一般不用)

          2 函數的調用方式

          3 函數內 this 的指向

          4 call、apply、bind

          4.1 call,apply

          4.1.1 新的函數調用方式apply和call方法

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

          4.2 call,apply使用

          4.3 bind

          4.4 總結

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

          6 高階函數

          6.1 作為參數

          6.2 作為返回值

          7 總結


          1 函數的定義方式

          定義函數的方式有三種:

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

          1.1 函數聲明

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

          1.2 函數表達式

          函數表達式就是將一個匿名函數賦值給一個變量。函數表達式必須先聲明,再調用。

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

          1.3 函數聲明與函數表達式的區別

          1. 函數聲明必須有名字。
          2. 函數聲明會函數提升,在預解析階段就已創建,聲明前后都可以調用。
          3. 函數表達式類似于變量賦值。
          4. 函數表達式可以沒有名字,例如匿名函數。
          5. 函數表達式沒有變量提升,在執行階段創建,必須在表達式執行之后才可以調用。

          下面是一個根據條件定義函數的例子:

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

          以上代碼執行結果在不同瀏覽器中結果不一致。我們可以使用函數表達式解決上面的問題:

          
          
          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. }

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

          1.4 構造函數Function(了解即可,一般不用)

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

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

          對象都是由構造函數創建出來的,函數既然是對象,創建它的構造函數又是什么呢?事實上所有的函數實際上都是由Function構造函數創建出來的實例對象。

          所以我們可以使用Function構造函數創建函數。

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

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

          2 函數的調用方式

          1. 普通函數
          2. 構造函數
          3. 對象方法
          
          
          1. // 普通函數
          2. function f1() {
          3. console.log("我是普通函數");
          4. }
          5. f1();
          6. // 構造函數---通過new 來調用,創建對象
          7. function F1() {
          8. console.log("我是構造函數");
          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 函數內 this 的指向

          函數的調用方式決定了 this 指向的不同:

          調用方式 非嚴格模式 備注
          普通函數調用 window 嚴格模式下是 undefined
          構造函數調用 實例對象 原型方法中 this 也是實例對象
          對象方法調用 該方法所屬對象 緊挨著的對象
          事件綁定方法 綁定事件對象  
          定時器函數 window  
          
          
          1. // 普通函數
          2. function f1() {
          3. console.log(this); // window
          4. }
          5. f1();
          6. // 構造函數
          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

          了解了函數 this 的指向之后,我們知道在一些情況下我們為了使用某種特定環境的 this 引用,需要采用一些特殊手段來處理,例如我們經常在定時器外部備份 this 引用,然后在定時器函數內部使用外部 this 的引用。
          然而實際上 JavaScript 內部已經專門為我們提供了一些函數方法,用來幫我們更優雅的處理函數內部 this 指向問題。這就是接下來我們要學習的 call、apply、bind 三個函數方法。call()、apply()、bind()這三個方法都是是用來改變this的指向的。

          4.1 call,apply

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

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

          call語法:

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

          call參數:

          • thisArg

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

            • 指定的參數列表

          apply語法:

          fun.apply(thisArg, [argsArray]) 

          apply參數:

          • thisArg
          • argsArray

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

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

          4.1.1 新的函數調用方式apply和call方法

          
          
          1. function f1(x, y) {
          2. console.log("結果是:" + (x + y) + this);
          3. return "666";
          4. }
          5. f1(10, 20); // 函數的調用
          6. console.log("========");
          7. // apply和call方法也是函數的調用的方式
          8. // 此時的f1實際上是當成對象來使用的,對象可以調用方法
          9. // apply和call方法中如果沒有傳入參數,或者是傳入的是null,那么調用該方法的函數對象中的this就是默認的window
          10. f1.apply(null, [10, 20]);
          11. f1.call(null, 10, 20);
          12. // apply和call都可以讓函數或者方法來調用,傳入參數和函數自己調用的寫法不一樣,但是效果是一樣的
          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的指向。調用函數的時候,改變this的指向:

          
          
          1. // 函數的調用,改變this的指向
          2. function f1(x, y) {
          3. console.log((x + y) + ":===>" + this);
          4. return "函數的返回值";
          5. }
          6. //apply和call調用
          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函數是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);


          調用方法的時候,改變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); //當前實例對象
          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);

          總結

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

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

          4.3 bind

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

          語法:

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

          參數:

          • thisArg

            • 當綁定函數被調用時,該參數會作為原函數運行時的 this 指向。當使用new 操作符調用綁定函數時,該參數無效。
          • arg1, arg2, ...

            • 當綁定函數被調用時,這些參數將置于實參之前傳遞給被綁定的方法。

          返回值:

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

          示例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("學生");
          12. per.play();
          13. // 復制了一個新的play方法
          14. var ff = per.play.bind(stu);
          15. ff();

          示例2:

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

          4.4 總結

          • call 和 apply 特性一樣

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

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

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

          • arguments
            • 實參集合
          • caller
            • 函數的調用者
          • length
            • 函數定義的時候形參的個數
          • name
            • 函數的名字,name屬性是只讀的,不能修改
          
          
          1. function fn(x, y, z) {
          2. console.log(fn.length) // => 形參的個數
          3. console.log(arguments) // 偽數組實參參數集合
          4. console.log(arguments.callee === fn) // 函數本身
          5. console.log(fn.caller) // 函數的調用者
          6. console.log(fn.name) // => 函數的名字
          7. }
          8. function f() {
          9. fn(10, 20, 30)
          10. }
          11. f()

          6 高階函數

          函數可以作為參數,也可以作為返回值。

          6.1 作為參數

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

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


          作為參數排序案例:

          
          
          1. var arr = [1, 100, 20, 200, 40, 50, 120, 10];
          2. //排序---函數作為參數使用,匿名函數作為sort方法的參數使用,此時的匿名函數中有兩個參數,
          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("函數f1");
          3. return function() {
          4. console.log("我是函數,此時作為返回值使用");
          5. }
          6. }
          7. var ff = f1();
          8. ff();

          作為返回值排序案例: 

          
          
          1. // 排序,每個文件都有名字,大小,時間,可以按照某個屬性的值進行排序
          2. // 三個文件,文件有名字,大小,創建時間
          3. function File(name, size, time) {
          4. this.name = name; // 名字
          5. this.size = size; // 大小
          6. this.time = time; // 創建時間
          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. // 函數作為返回值
          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. // 函數作為參數
          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. // 函數作為參數
          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("按照創建時間排序:**********");
          41. // 按照創建時間排序
          42. var ff = fn("time");
          43. // 函數作為參數
          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. }

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合