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

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

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>

二、閉包

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

// 使用閉包的緣由:不能使用函數回調(調用函數已有固定參數,或不能擁有參數),只能將函數定義到擁有局部變量函數的內部
// 閉包目的:不容許提高變量做用域時,該函數的局部變量須要被其餘函數使用
// 閉包本質:函數的嵌套,內層函數稱之爲閉包
// 閉包的解決案例:①影響局部變量的生命週期,持久化局部變量;②解決循環綁定致使的變量污染
2.一、閉包解決變量的生命週期問題

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

<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.二、閉包解決變量污染問題

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

// 在循環綁定中,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);
     }
 }

利用閉包解決:java

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.總結:

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

2、循環綁定

.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.閉包解決,如:1、2.2 利用閉包解決循環綁定所帶來的變量污染
// 3.對象屬性解決
2.一、經過獲取局部做用域解決變量污染
// 在ES5中沒有塊級做用域,在ES6中有塊級做用域,因此只要將 var 改成 let 便可解決變量污染
let lis = document.querySelectorAll('li');
    for (let i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
            alert(i)
        }
    }
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>

3、面向對象JS

一、屬性與方法

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

二、類字典結構使用

JS中沒有字典(鍵值對存儲數據的方式),但能夠經過對象實現字典方式存儲數據,並使用數據es6

  • 結構
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完成增刪改查

三、構造函數(ES5)

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

四、繼承(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();

五、類及繼承(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)
    }
}

4、定時器

應用場景:完成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.容許清除不存在的定時器編號
相關文章
相關標籤/搜索