前端(十三)—— JavaScript高級:回調函數、閉包、循環綁定、面向對象、定時器
阿新 • • 發佈:2018-10-16
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高級:回調函數、閉包、循環綁定、面向對象、定時器