【前端 JavaScript 高級】03 - 函數(shù)進(jìn)階 + 函數(shù)閉包 +遞歸

1. 函數(shù)的定義和調(diào)用

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

  1. 方式1 使用 function 關(guān)鍵字 (命名函數(shù))
function fn(){}
  1. 方式2 函數(shù)表達(dá)式(匿名函數(shù))
var fn = function(){}
  1. 方式3 new Function()
ar f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);

var fn = new Function('參數(shù)1','參數(shù)2'..., '函數(shù)體')
注意
/*Function 里面參數(shù)都必須是字符串格式
第三種方式執(zhí)行效率低,也不方便書寫,因此較少使用
所有函數(shù)都是 Function 的實例(對象)  
函數(shù)也屬于對象
*/

1.2 函數(shù)的調(diào)用

 // 1.對象的方法
  let obj = {
    sayHi: function () {
      console.log('hi');
    }
  }

  // 2.普通函數(shù)
  function f() {
    console.log('我是一個函數(shù)');
  }

  // 函數(shù)的調(diào)用方式  直接調(diào)用或者 使用call()調(diào)用
  f.call();

  // 3.構(gòu)造函數(shù)
  function Star(name, age) {
    this.name = name;
    this.age = age;
  }

  // 4.綁定事件函數(shù)
  let btn = document.querySelector('button');
  btn.onclick = function () {
    console.log('你點擊了我');
  }


  // 5. 定時器函數(shù)
  setInterval(() => {
    alert('我是函數(shù)');
  }, 1000);


  // 6. 立即執(zhí)行函數(shù)
  (function () {
    console.log('我是立即執(zhí)行函數(shù)');
  })()

2. this

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

  1. 這些this 的指向,是當(dāng)我們調(diào)用函數(shù)的時候確定的。調(diào)用方式的不同決定了this 的指向不同。

  2. 一般指向我們的調(diào)用者。

函數(shù)內(nèi)部的this指向
// 1.對象的方法 this 指向的是實例對象
  let obj = {
    sayHi: function () {
      console.log('hi');
    }
  }

  // 2.普通函數(shù) this指向的是window
  function f() {
    console.log('我是一個函數(shù)');
  }

  // 函數(shù)的調(diào)用方式  直接調(diào)用或者 使用 call() 調(diào)用
  f.call();

  // 3.構(gòu)造函數(shù) this 指向的是實例對象
  function Star(name, age) {
    this.name = name;
    this.age = age;
  }

  // 4.綁定事件函數(shù) this 指向的是綁定事件的對象
  let btn = document.querySelector('button');
  btn.onclick = function () {
    console.log('你點擊了我');
  };


  // 5. 定時器函數(shù) 里面的this 指向的是 window
  window.setInterval(() => {
    alert('我是函數(shù)');
  }, 1000);


  // 6. 立即執(zhí)行函數(shù) 指向的是window
  (function () {
    console.log('我是立即執(zhí)行函數(shù)');
  })();

2.2 改變函數(shù)內(nèi)部 this 指向

call方法
  1. call()方法調(diào)用一個對象。簡單理解為調(diào)用函數(shù)的方式,但是它可以改變函數(shù)的this 指向。

  2. 應(yīng)用場景: 經(jīng)常做繼承。

/**
   * bind()
   *
   * apply()
   *
   * call()
   */
  let obj = {
    name: 'andy'
  };


  function fun(a,b) {
    console.log(this);
    console.log(a + b);
  }
  // call 可以調(diào)用函數(shù)可以改變this的指向
  fun.call(obj, 123, 123);

  // call 的主要 作用可以實現(xiàn)繼承
  /**
   *
   * @param name
   * @param age
   * @param sex
   * @constructor
   */
  function Father(name,age,sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }


  /**
   *
   * @param name
   * @param age
   * @param sex
   * @constructor
   */
  function Son(name, age, sex) {
    Father.call(this, name, age, sex);
  }
  1. 以上代碼的運行結(jié)果:
call方法
apply方法
  1. apply() 方法調(diào)用一個函數(shù)。簡單理解為調(diào)用函數(shù)的方式,但是它可以改變函數(shù)的 this 指向。

  2. 應(yīng)用場景: 經(jīng)常跟數(shù)組有關(guān)系。

  let o = {
    name: 'andy'
  };

  function fn() {
    console.log(this);
  }

  fn.apply(o, ['pink']);
  // 1. apply也是調(diào)用函數(shù),第二個可以改變函數(shù)內(nèi)部的this指向

  // 2. 但是它的參數(shù)必須是數(shù)組

  // 3. apply的主要應(yīng)用比如說我們可以利用apply借助于數(shù)學(xué)內(nèi)置對象求最大值
  let arr = [1, 2, 3, 512, 456];
  // 使用數(shù)學(xué)內(nèi)置對象求數(shù)組中的最值
  let max = Math.max.apply(Math, arr);
  let min = Math.min.apply(Math, arr);
  console.log(max);
  console.log(min);
apply方法
bind方法
  1. bind() 方法不會調(diào)用函數(shù),但是能改變函數(shù)內(nèi)部this指向,返回的是原函數(shù)改變this之后產(chǎn)生的新函數(shù)。

  2. 如果只是想改變this 指向,并且不想調(diào)用這個函數(shù)的時候,可以使用bind。

  3. 應(yīng)用場景:不調(diào)用函數(shù),但是還想改變this指向。

  // bind 捆綁的意思

  let o = {
    name: 'andy'
  }

  function fn(a, b) {
    console.log(this);
    console.log(a + b);
  }

  // bind 不會調(diào)用原來的函數(shù)但是可以改變原來函數(shù)內(nèi)部的 this 指向
  let f = fn.bind(o, 1, 2);
  f();
  1. 以上代碼運行結(jié)果:
bind方法
bind 方法在實際開發(fā)中的使用:
  1. 讓按鈕在 3秒 后按鈕 變得可用。
 // 1. 如果有的函數(shù)我們不需要立即調(diào)用,但是又想改變這個函數(shù)的內(nèi)部this指向此時用bind

  // 2. 讓按鈕在 3秒后按鈕 變得可用
  let btn = document.querySelector('button');


  /**
   *
   * 使用笨辦法
   * */
 /* btn.onclick = function () {
    this.disabled = true;

    /!**
     * 2 秒之后按鈕恢復(fù)
     *!/
    setTimeout(function () {
      // 原來 定時器里面的 this 是指向的 window
      // 現(xiàn)在改變了指向了
      btn.disabled = false;
    }, 2000)
  };*/

  /**
   *
   * 為按鈕定義一個單擊事件
   * */
  btn.onclick = function () {
    this.disabled = true;

    /**
     * 2 秒之后按鈕恢復(fù)
     */
    setTimeout(function () {
      // 原來 定時器里面的 this 是指向的 window
      // 現(xiàn)在改變了指向了
      this.disabled = false;
    }.bind(this), 2000)
  };
bind 處理多個對象中this的指向問題
  // 獲取所有的按鈕
  let btns = document.querySelectorAll('button');
  for (let i = 0; i < btns.length; i++) {
    btns[i].onclick = function () {
      this.disabled = true;
      setTimeout(function () {
        // btns[i].disabled = false;
        // bind改變了this的指向
        this.disabled = false;
        // 此時的this指向的時btns[i]
      }.bind(this), 2000);
    };
  }

2.3 call、apply、bind三者的異同

共同點 : 都可以改變this指向。
不同點:
  1. callapply 會調(diào)用函數(shù), 并且改變函數(shù)內(nèi)部this指向。

  2. callapply傳遞的參數(shù)不一樣,call傳遞參數(shù)使用逗號隔開,apply使用數(shù)組傳遞。

  3. bind 不會調(diào)用函數(shù), 可以改變函數(shù)內(nèi)部this指向。

應(yīng)用場景
  1. call經(jīng)常做繼承. ;

  2. apply經(jīng)常跟數(shù)組有關(guān)系. 比如借助于數(shù)學(xué)對象實現(xiàn)數(shù)組最大值最小值;

  3. bind不調(diào)用函數(shù),但是還想改變this指向. 比如改變定時器內(nèi)部的this指向。

3. 嚴(yán)格模式

3.1 什么是嚴(yán)格模式

JavaScript 除了提供正常模式外,還提供了嚴(yán)格模式(strict mode)。ES5的嚴(yán)格模式是采用具有限制性 JavaScript變體的一種方式,即在嚴(yán)格的條件下運行JS代碼。
嚴(yán)格模式在IE10 以上版本的瀏覽器中才會被支持,舊版本瀏覽器中會被忽略。
嚴(yán)格模式對正常的JavaScript語義做了一些更改:
  1. 消除了 Javascript語法的一些不合理、不嚴(yán)謹(jǐn)之處,減少了一些怪異行為。

  2. 消除代碼運行的一些不安全之處,保證代碼運行的安全。

  3. 提高編譯器效率,增加運行速度。

  4. 禁用了在 ECMAScript 的未來版本中可能會定義的一些語法,為未來新版本的 Javascript 做好鋪墊。比如一些保留字如:class,enum,export, extends, import, super 不能做變量名。

3.2 開啟嚴(yán)格模式

嚴(yán)格模式可以應(yīng)用到整個腳本或個別函數(shù)中。因此在使用時,我們可以將嚴(yán)格模式分為為腳本開啟嚴(yán)格模式和為函數(shù)開啟嚴(yán)格模式兩種情況。
  1. 情況一 :為腳本開啟嚴(yán)格模式:有的script 腳本是嚴(yán)格模式,有的 script腳本是正常模式,這樣不利于文件合并,所以可以將整個腳本文件放在一個立即執(zhí)行的匿名函數(shù)之中。這樣獨立創(chuàng)建一個作用域而不影響其他script腳本文件。
 // 開啟嚴(yán)格模式 下面的js代碼就會按照嚴(yán)格模式執(zhí)行代碼
  'use strict';
  // 但是嚴(yán)格模式只會在IE10以上才會被支持

  1. 情況二: 為函數(shù)開啟嚴(yán)格模式:要給某個函數(shù)開啟嚴(yán)格模式,需要把“use strict”; (或 'use strict'; ) 聲明放在函數(shù)體所有語句之前。
function fn(){
  "use strict";
  return "123";
} 
//當(dāng)前fn函數(shù)開啟了嚴(yán)格模式

3.4 嚴(yán)格模式中的變化

  1. 嚴(yán)格模式對 Javascript 的語法和行為,都做了一些改變。
 'use strict'
    num = 10 
    console.log(num)//嚴(yán)格模式后使用未聲明的變量
    --------------------------------------------------------------------------------
    var num2 = 1;
    delete num2;//嚴(yán)格模式不允許刪除變量
    --------------------------------------------------------------------------------
    function fn() {
     console.log(this); // 嚴(yán)格模式下全局作用域中函數(shù)中的 this 是 undefined
    }
    fn();  
    ---------------------------------------------------------------------------------
    function Star() {
         this.sex = '男';
    }
    // Star();嚴(yán)格模式下,如果 構(gòu)造函數(shù)不加new調(diào)用, this 指向的是undefined 如果給他賦值則 會報錯.
    var ldh = new Star();
    console.log(ldh.sex);
    ----------------------------------------------------------------------------------
    setTimeout(function() {
      console.log(this); //嚴(yán)格模式下,定時器 this 還是指向 window
    }, 2000);  

  1. 更多嚴(yán)格模式要求參考

4. 高階函數(shù)

  1. 高階函數(shù)是對其他函數(shù)進(jìn)行操作的函數(shù),它接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為返回值輸出。此時fn 就是一個高階函數(shù):

  2. 函數(shù)也是一種數(shù)據(jù)類型,同樣可以作為參數(shù),傳遞給另外一個參數(shù)使用。最典型的就是作為回調(diào)函數(shù)。

  3. 同理函數(shù)也可以作為返回值傳遞回來。

  // 高階函數(shù)是對其他函數(shù)進(jìn)行操作的函數(shù),它是接收函數(shù)作為參數(shù)或者將函數(shù)作為返回值輸出
  function fn(a, b, callback) {
    console.log(a + b);
    callback && callback();
  }

  fn(1, 2, function () {
    console.log('我是回調(diào)函數(shù),在最后調(diào)用');
  });

5. 閉包

5.1 變量的作用域復(fù)習(xí)

變量根據(jù)作用域的不同分為兩種:全局變量和局部變量。
  1. 函數(shù)內(nèi)部可以使用全局變量。

  2. 函數(shù)外部不可以使用局部變量。

  3. 當(dāng)函數(shù)執(zhí)行完畢,本作用域內(nèi)的局部變量會銷毀。

5.2 什么是閉包

  1. 閉包(closure)指有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)。簡單理解就是 ,一個作用域可以訪問另外一個函數(shù)內(nèi)部的局部變量。
  /**
   * 閉包(closure) :指的是有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)
   * 簡單理解:一個作用域 fn 可以訪問另一個函數(shù) fun 內(nèi)部的局部變量
   */
  function fn() {
    // fn 就是一個閉包函數(shù)
    let num = 10;

    function fun() {
      // fun 訪問了 fn 內(nèi)部的 一個局部變量 所以 fn 就是一個局部變量
      console.log(num);
      // fun 作用域中訪問了另一個作用域的num num 變量所在的作用域就是閉包 閉包是一種現(xiàn)象
    }

    fun();

  }

  fn();

5.3 閉包的作用

  1. 作用:延伸變量的作用范圍。
  /**
   * 我們fn外邊的作用域可以訪問fn內(nèi)部的局部變量
   *
   * 閉包的主要作用 : 延伸了變量的作用范圍
  */
  function fn() {

    let num = 10;

    function fun() {
      console.log(num);
    }

    return fun;
  }

  let f = fn();

  /**
   * 執(zhí)行之后就會產(chǎn)生閉包
   * */
  f();
  /*
    // 類似于
    let f = function fun() {
      console.log(num);
    }
  */

5.4 閉包的案例

  1. 利用閉包的方式得到當(dāng)前 li 的索引號;
for (var i = 0; i < lis.length; i++) {
// 利用for循環(huán)創(chuàng)建了4個立即執(zhí)行函數(shù)
// 立即執(zhí)行函數(shù)也成為小閉包因為立即執(zhí)行函數(shù)里面的任何一個函數(shù)都可以使用它的i這變量
(function(i) {
    lis[i].onclick = function() {
      console.log(i);
    }
 })(i);
}
  1. 閉包應(yīng)用- 3秒鐘之后,打印所有 li 元素的內(nèi)容;
 // 閉包案例:點擊你輸出當(dāng)前l(fā)i的索引號

  var lis = document.querySelectorAll('li');

  // 1. 利用我們動態(tài)添加屬性的方式
  /* var lis = document.querySelectorAll('li');
   for (var i = 0; i < lis.length; i++) {
     lis[i].onclick = function () {
       /!**
        * 點擊事件是異步執(zhí)行的
        *!/
       console.log(i); // 6 這里始終都是 6
     }
   }*/

  // 解決:使用動態(tài)添加屬性的方式

  /*for (var i = 0; i < lis.length; i++) {
    lis[i].index = i; // 先將index 存一份到每一個li項中
    lis[i].onclick = function () {
      /!**
       * 點擊事件是異步執(zhí)行的
       *!/
      console.log(this.index); // 6 這里始終都是 6
    }
  }*/

  // 2. 利用閉包的方式得到當(dāng)前小li的索引號
  for (let i = 0; i < lis.length; i++) {
    (function (i) {
      lis[i].onclick = function () {
        console.log(i);
      }
    })(i)
  }
  1. 閉包應(yīng)用-計算打車價格
/*需求分析
打車起步價13(3公里內(nèi)),  之后每多一公里增加 5塊錢.  用戶輸入公里數(shù)就可以計算打車價格
如果有擁堵情況,總價格多收取10塊錢擁堵費*/

 var car = (function() {
     var start = 13; // 起步價  局部變量
     var total = 0; // 總價  局部變量
     return {
       // 正常的總價
       price: function(n) {
         if (n <= 3) {
           total = start;
         } else {
           total = start + (n - 3) * 5
         }
         return total;
       },
       // 擁堵之后的費用
       yd: function(flag) {
         return flag ? total + 10 : total;
       }
    }
 })();
console.log(car.price(5)); // 23
console.log(car.yd(true)); // 33

5.5 案例:關(guān)于閉包的思考題

 var name = "The Window";
   var object = {
     name: "My Object",
     getNameFunc: function() {
     return function() {
     return this.name;
     };
   }
 };
console.log(object.getNameFunc()())
-----------------------------------------------------------------------------------
var name = "The Window";  
  var object = {    
    name: "My Object",
    getNameFunc: function() {
    var that = this;
    return function() {
    return that.name;
    };
  }
};
console.log(object.getNameFunc()())

6. 遞歸

6.1 什么是遞歸

遞歸:如果一個函數(shù)在內(nèi)部可以調(diào)用其本身,那么這個函數(shù)就是遞歸函數(shù)。簡單理解:函數(shù)內(nèi)部自己調(diào)用自己, 這個函數(shù)就是遞歸函數(shù)。
注意:遞歸函數(shù)的作用和循環(huán)效果一樣,由于遞歸很容易發(fā)生“棧溢出”錯誤(stack overflow),所以必須要加退出條件return

6.2 利用遞歸求1~n的階乘

//利用遞歸函數(shù)求1~n的階乘 1 * 2 * 3 * 4 * ..n
 function fn(n) {
     if (n == 1) { //結(jié)束條件
       return 1;
     }
     return n * fn(n - 1);
 }
 console.log(fn(3)); // 6

6.3 利用遞歸求斐波那契數(shù)列

// 利用遞歸函數(shù)求斐波那契數(shù)列(兔子序列)  1、1、2、3、5、8、13、21...
// 用戶輸入一個數(shù)字 n 就可以求出 這個數(shù)字對應(yīng)的兔子序列值
// 我們只需要知道用戶輸入的n 的前面兩項(n-1 n-2)就可以計算出n 對應(yīng)的序列值
function fb(n) {
  if (n === 1 || n === 2) {
        return 1;
  }
  return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));

6.4 利用遞歸遍歷數(shù)據(jù)(多維數(shù)組)

var data = [{
    id: 1,
    name: '家電',
    goods: [{
      id: 11,
      gname: '冰箱',
      goods: [{
        id: 111,
        gname: '海爾'
      }, {
        id: 112,
        gname: '美的'
      },]
    }, {
      id: 12,
      gname: '洗衣機'
    }]
  }, {
    id: 2,
    name: '服飾'
  }];

  // 當(dāng)我們輸入想要輸入的id好,就可以返回對應(yīng)的數(shù)據(jù)對象 
  function getId(data, id) {
    var object = {};
    // 使用forEach遍歷數(shù)組
    data.forEach(function (item) {
      if (item.id === id) {
        object = item;
        return item;
        /**
         * 使用遞歸遍歷多維數(shù)組
         */
      } else if (item.goods && item.goods.length > 0) {
        object = getId(item.goods, id);
      }
    });
    return object;
  }

  console.log(getId(data, 11));
  console.log(getId(data, 1));
  console.log(getId(data, 111));
  console.log(getId(data, 666));

7. 淺拷貝

  1. 淺拷貝只是拷貝一層,更深層次對象級別的只拷貝引用。 拷貝對象的引用會導(dǎo)致修改拷貝的對象導(dǎo)致原來的對象也被修改。
  // 1. 淺拷貝只是拷貝一層,更深層次對象級別的只拷貝引用

  // 2. 深拷貝拷貝的是多層,每一級的數(shù)據(jù)都會拷貝

  let obj = {
    id: '1',
    name: 'andy',
    msg: {
      age: 18
    }
  };

  // 淺拷貝遇到對象級別的只會拷貝對象的引用
  let target = {};
  // es6 中實現(xiàn)淺拷貝的語法糖
  Object.assign(target, obj);

  // 修改對象級別的屬性
  target.msg.age = 100;
  // 打印拷貝之后的對象
  console.log(target);
  // 打印源對象
  console.log(obj);
  1. 運行結(jié)果:
淺拷貝測試運行

8. 深拷貝

  1. 深拷貝拷貝的是多層,每一級的數(shù)據(jù)都會拷貝。
let obj = {
    id: '1',
    name: '張三',
    msg: {
      title: '今天真開心',
      content: '666',
      format: {
        date: '2020',
        birthday: '1998'
      }
    },
    size: ['big', 'small', 'mini']
  };

  let target = {};

  /**
   * 實現(xiàn)對對象的深拷貝
   * @param targetObj
   * @param oldObj
   */
  function deepCopy(targetObj, oldObj) {
    // 1. 循環(huán)遍歷對象
    for (let key in oldObj) {
      // 判斷我們的屬性屬于哪種數(shù)據(jù)類型

      // 1. 獲取屬性值oldObj[key]
      let item = oldObj[key];
      if (item instanceof Array) {
        // 如果屬性是一個數(shù)組
        targetObj[key] = [];
        // 遞歸進(jìn)行拷貝
        deepCopy(targetObj[key], item);
      } else if (item instanceof Object) {
        // 3. 判斷當(dāng)前的屬性是否為一個對象 Object
        targetObj[key] = {};
        deepCopy(targetObj[key], item);
      } else {
        // 如果屬性是一個普通的屬性
        targetObj[key] = item;
      }
    }

  }

  deepCopy(target, obj);
  console.log(target);


  // 修改賦值之后的對象
  target.msg.title = '李四';
  console.log(target); // 原來對象的屬性被修改了
  console.log(obj); // 拷貝的對象的屬性未被修改
深拷貝測試
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容