1. 程式人生 > >前端(十三)—— JavaScript高級:回調函數、閉包、循環綁定、面向對象、定時器

前端(十三)—— JavaScript高級:回調函數、閉包、循環綁定、面向對象、定時器

set 執行 使用 一次 name屬性 解決案例 就會 請求 事件

回調函數、閉包、循環綁定、面向對象、定時器

一、函數高級

1、函數回調

// 回調函數
function callback(data) {}
// 邏輯函數
function func(callback) {
    // 函數回調,判斷回調函數是否存在
    if (callback) callback(data);
}
func(callback);

// 函數回調的本質:在一個函數中(調用函數),當滿足一定條件,調用參數函數(回調函數)
// 回調函數作為調用函數的參數傳入,滿足一定的條件,調用回調函數,回調函數可以獲取調用函數中的局部變量
// 回調函數目的:通過參數將調用函數內部數據傳出,請求數據 => 數據(return | 函數回調) => 外界,匿名函數的自調用,沒有調用者,所以無法獲取返回值,只能通過回調函數來實現
<!-- 外部要接收數據 -->
<!-- 1.外部給內部提供回調函數(函數名callback) -->
<!-- 2.內部將數據反饋給外部回調函數.外部就可以使用內部數據 -->
<script type="text/javascript">
    var callback = function (data) {
        // 使用數據
        console.log(data);
    }
</script>


<!-- 請求數據 -->
<script type="text/javascript">
    // 利用異常處理捕獲callback未定義的異常,不做處理
    try {
        // 采用匿名函數自調用請求數據,達到頁面一加載就獲取到數據
        (function (callback) {
            console.log("開始請求數據...");
            // ...
            var data = [1, 2, 3, 4, 5];
            console.log("數據請求完畢!!!");
            // 如果回調函數存在,那麽回調對應的函數,並將數據攜帶出去
            if (callback) {
                callback(data);
            }
        })(callback)
        // 請求數據完畢之後,需要讓外界獲取請求的數據:
    } catch (err) {

    }
</script>
回調函數在系統中的使用:
  • 對頁面進行點擊,點擊以後,對外傳送數據,數據包括點擊的位置
  • 系統已經書寫好這種函數的回調,但是沒有回調體(回調函數),回調體由普通開發者提供
  • (事件的綁定)鉤子:滿足系統觸發事件的條件時,系統會自動調用回調函數,傳出必要的數據
<script type="text/javascript">
    // 鉤子:滿足條件情況下被系統回調的函數(方法),稱之為鉤子函數(方法) <=> 回調函數
    document.onclick = function (a, b , c) {
        console.log("點擊事件");
        console.log(a, b , c);
    }
</script>

2、閉包

function outer() {
    var data = {}
    function inner() {
        return data;
    }
    return inner;
}

// 使用閉包的原因:不能使用函數回調(調用函數已有固定參數,或不能擁有參數),只能將函數定義到擁有局部變量函數的內部
// 閉包目的:不允許提升變量作用域時,該函數的局部變量需要被其他函數使用
// 閉包本質:函數的嵌套,內層函數稱之為閉包
// 閉包的解決案例:①影響局部變量的生命周期,持久化局部變量;②解決循環綁定導致的變量汙染
2.1、閉包解決變量的生命周期問題

局部變量的生命周期在函數運行結束時就結束,將局部變量傳到外部函數,實現了延長局部變量聲明周期的效果

<script type="text/javascript">
    function outer() {
        // eg: 請求得到的數據,如何不持久化,方法執行完畢後,數據就會被銷毀
        var data = [1, 2, 3, 4, 5];
        console.log(data);
        // 通過閉包解決該類問題,所以閉包所以代碼均可以隨意自定義
        function inner() {
            return data;
        }
        // 數據被inner操作返回,inner屬於outer,屬於需要outer將inner返回出去(跟外界建立起聯系)
        return inner;
    }
    // 將局部變量生命周期提升於inner函數相同,inner存在,局部變量data就一直存在
    var inner = outer();
    console.log(inner());

</script>
2.2、閉包解決變量汙染問題

變量汙染:前幾次變量的定義,被最後一次定義覆蓋。

// 在循環綁定中,onclick函數中的變量i指向的內存地址,經過循環之後i變成了5,所以每個元素事件運行的時候變量i都是5
var list = document.getElementById("ulDemo").getElementsByTagName("li");
for (var i = 0; i < list.length; i++) {
     var li = list[i];
     li.onclick= function () {
         alert(i);
     }
 }

利用閉包解決:

var lis = document.querySelectorAll(‘ul li‘);
    // 2.循環綁定
    for (var i = 0; i < lis.length; i++) {
        // 解決的原理:一共產生了5個外層函數,存儲的形參i的值分別是0, 1, 2, 3, 4
        // 內層函數也產生了5個,且和外層函數一一對應,打印的i就是外層函數的形參i

        // 外層函數
        (function (i) {
            // 內層函數:閉包
            lis[i].onclick = function () {
                alert(i)
            }
        })(i)   
    }
    console.log(i);

3.總結:

  • 函數會產生局部作用域, 外部需要使用 -- 返回值 | 函數回調 | 閉包 | 提升作用域(不建議)
  • 在外部另一個函數中使用該局部變量 -- 函數回調 | 閉包

二、循環綁定

.html文件
<ul>
    <li>列表項</li>
    <li>列表項</li>
    <li>列表項</li>
</ul>
.js文件
var lis = document.querySelector(‘li‘);
for (var i = 0; i < lis.length; i++) {
    lis[i].onclick = function () {
        // 打印列表項的索引
        console.log(i);
    }
}
// 循環綁定會導致變量汙染,解決方法如下:
// 1.獲取局部(塊級)作用域解決:在ES5中沒有塊級作用域,而在ES6中有塊級作用域
// 2.閉包解決,如:一、2.2 利用閉包解決循環綁定所帶來的變量汙染
// 3.對象屬性解決
2.1、通過獲取局部作用域解決變量汙染
// 在ES5中沒有塊級作用域,在ES6中有塊級作用域,所以只要將 var 改為 let 即可解決變量汙染
let lis = document.querySelectorAll(‘li‘);
    for (let i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
            alert(i)
        }
    }
2.2、通過對象屬性解決變量汙染
<script type="text/javascript">
    var lis = document.querySelectorAll(‘li‘);
    for (var i = 0; i < lis.length; i++) {
        // lis[i]依次代表五個li頁面元素對象
        // 給每一個li設置一個(臨時)屬性
        lis[i].index = i;  // 第一個li的index屬性存儲的就是索引0,以此類推,最後一個存儲的是索引4

        lis[i].onclick = function () {
            // var temp = lis[i].index; // lis[i]中的i一樣存在變量汙染
            var temp = this.index;  // 當前被點擊的li
            alert(temp)  // temp => this.index(lis[i].index) => i(索引)
        }
    }

</script>

三、面向對象JS

1、屬性與方法

// 創建對象
var obj = {}; | var obj = new Object();  // 前者一般是創建一個對象是用,後者在需要創建多個對象是用
// 屬性
obj.屬性名 = "";
// 方法
obj.func = function () {}
// 刪除屬性與方法
delete obj.prop
delete obj.func

2、類字典結構使用

JS中沒有字典(鍵值對存儲數據的方式),但可以通過對象實現字典方式存儲數據,並使用數據

  • 結構
var dict = {name: "zero", age: 18}
  • 拓展
// key在JS標識符語法支持情況下,可以省略引號,但key為js標識符不支持的語法情況下,必須添加引號
var dict = {"my-name": "zero", fn: function () {}, fun () {}}
  • 使用
dict.name | dict["my-name"] | dict.fn()
  • 增刪改查key與value
// 增
dict.key4 = true;
console.log(dict);

// 刪
delete dict.key4;
console.log(dict);

// 改
dict["my-key3"] = [5, 4, 3, 2, 1];
console.log(dict);

// 查
console.log(dict.key1);
console.log(dict["key1"]);
類字典總結:
  • key全為字符串形式,但存在兩種書寫方式
  • key在JS標識符語法支持情況下,可以省略引號,但key為js標識符不支持的語法情況下,必須添加引號
  • value可以為任意類型
  • 訪問值可以通過字典名(對象名).key語法與["key"]語法來訪問value
  • 可以隨意添加key與value完成增刪改查

3、構造函數(ES5)

function People(name, age) {
    this.name = name;
    this.age = age;
    this.eat = function () {
        return ‘eat‘;
    }
}

4、繼承(ES5)

// 父級
function Sup(name) {
    this.name = name;
    this.fn = function () {
        console.log(‘fn class‘);
    }
}
// 原型
console.log(Sup.prototype);
console.log(sup.__proto__);

// 子級
function Sub(name) {
    // 繼承屬性,在繼承函數中由被繼承(父級)函數調用,傳入繼承函數的this與被繼承函數創建屬性對屬性進行賦值的所有需要的數據(name)
    Sup.call(this, name);
}
// 繼承方法,利用prototype原型,將new Sup賦值給Sub.prototype
Sub.prototype = new Sup;
// 創建子級對象
var sub = new Sub("subClass");
// 使用屬性
console.log(sub.name);
// 使用方法
sub.fn();

// 指向自身構造函數
Sub.prototype.constructor = Sub;
// 案例
// 1.完成繼承,必須擁有兩個構造函數
// 2.一個函數要使用另外一個函數的屬性與方法,需要對其進行繼承(屬性與方法的復用)
// 3.屬性的繼承call方法,在繼承函數中由被繼承函數調用,傳入繼承函數的this與被繼承函數創建屬性對屬性進行賦值的所有需要的數據,eg:如Sup有name屬性,那麽可以通過call將Sub的name傳給Sup
// 4.方法的繼承prototype原型,將new Sup賦值給Sub.prototype
function Sup(name) {
    // 屬性存放某個值
    this.name = name;
    // 方法完成某項功能
    this.func = function () {
        console.log("func");
    }
}
var sup = new Sup("supClass");
console.log(sup.name);
sup.func();

// 類似於子級
function Sub(name) {
    // 屬性的繼承
    Sup.call(this, name);
}
// 方法的繼承
Sub.prototype = new Sup;

var sub = new Sub("subClass");
console.log(sub.name);
sub.func();

5、類及繼承(ES6)

// 父類
class People {
    // 構造器
    constructor (name, age) {
        this.name = name;
        this.age = age;
    }
    // 實例方法
    eat () {
        console.log(‘吃吃吃‘);
    }
    // 類方法
    static create () {
        console.log(‘誕生‘);
    }
}
// 子類
class Student extends People {
    constructor (name, age) {
        // super關鍵詞
        super(name, age)
    }
}

四、定時器

應用場景:完成JS自啟動畫;完成CSS無法完成的動畫

  • setInterval:持續型定時器,setInterval(函數, 時間間隔(毫秒數), 函數所需參數(可以省略));
  • setTimeout:一次型定時器,setTimeout(函數, 時間間隔(毫秒數), 函數所需參數(可以省略));
// 一次性定時器
/* 異步執行
    setTimeout(函數, 毫秒數, 函數所需參數(可以省略));
*/
console.log("開始");
setTimeout(function (data) {
    console.log(data);
}, 1000, "數據");
console.log("結束");

// 持續性定時器
/*異步執行
    setInterval(函數, 毫秒數, 函數所需參數(可以省略));
*/
console.log("又開始");
var timer = setInterval(function() {     // timer變量,是定時器編號,是普通的數字類型
    console.log("呵呵");
}, 1000)
console.log(timer);
console.log("又結束");
// 清除定時器
// 1.持續性與一次性定時器通常都應該有清除定時器操作
// 2.清除定時器操作可以混用 clearTimeout() | clearInterval()
// 3.定時器的返回值就是定時器編號,就是普普通通的數字類型
document.onclick = function () {
    console.log("定時器清除了");
    clearInterval(timer);
    // clearTimeout(timer);
}

// 清除所有定時器
// 如果上方創建過n個定時器,那麽timeout值為n+1
var timeout = setTimeout(function(){}, 1);
for (var i = 0; i < timeout; i++) {
    // 循環將編號[0, n]所有定時器都清除一次
    clearTimeout(i)
}

// 1.允許重復清除
// 2.允許清除不存在的定時器編號

前端(十三)—— JavaScript高級:回調函數、閉包、循環綁定、面向對象、定時器