1.1 JS continue 語句
定義和用法:
-
continue 用於跳過迴圈中的一個迭代,並繼續執行迴圈中的下一個迭代。
-
continue 與 break 語句的區別是:break 是結束整個迴圈體,continue是結束單次迴圈。
但是,在執行 continue 語句時,表現出了兩種不同型別的迴圈:
-
在 while 迴圈中,會先判斷條件,如果條件為 true,迴圈再執行一次。
-
在for迴圈中,自增長表示式(如:i++)會先計算,然後再判斷條件是否為true,再決定是否執行迭代。
continue 語句可應用於可選的標籤引用。
注意: continue 語句(不帶標籤引用),只能用在迴圈或 switch 中。
迴圈程式碼塊,在 i 的值等於 "3" 時跳過當前迴圈:
2.1 JS break 語句
定義和用法:
-
迴圈程式碼塊,break在變數 i 為 "3" 時退出迴圈:
-
break 語句用於退出 switch 語句或迴圈語句(for, for ... in, while, do ... while)。
-
當 break 語句用於 switch 語句中時,會跳出 switch 程式碼塊,終止執行程式碼。
-
當 break 語句用於迴圈語句時,會終止執行迴圈,並執行迴圈後程式碼(如果有的話)。
-
break 語句同樣可用於可選的標籤引用,用於跳出程式碼塊。
注意: break 語句(不帶標籤引用),只能用在迴圈或 switch 中。
3.1 JS arguments 物件
定義和用法:
arguments是函式裡的 | 可以通過陣列下標的形參訪問函式實參值 |
arguments物件 | 沒有表示引數集合,而是一個偽類陣列 |
arguments物件是 | 所有(非箭頭)函式中都可用的區域性變數 |
此物件包含傳遞給函式的每個引數 | 第一個引數在索引0處。 |
你可以使用arguments物件在函式中引用函式的引數。 |
例如,如果一個函式傳遞了三個引數,你可以如下方式引用他們:
-
arguments[0]
-
arguments[1]
-
arguments[2]
求平均值:
function avg() { var num = 0, j = 0; for (let i = 0; i <= arguments.length; i++) { if (typeof arguments[i] != 'number') continue; num += arguments[i]; j++; } num /= 1; return num; } document.write(avg(1, 2, 3, 4));
3.1.1 callee回撥函式
它如果和callee屬性一起使用的話,利用它可以設計函式迭代操作。使用arguments.callee可以獲取匿名函式。最後比較實參和形參的
個數以檢測使用者傳遞的引數是否符合要求。如果不是匿名函式的話,則arguments.callee等價於函式名(f());
語法:
[function.]arguments.callee
可選項 function 引數是當前正在執行的 Function 物件的名稱。
說明:
callee 屬性的初始值就是正被執行的 Function 物件。
callee 屬性是 arguments 物件的一個成員,是arguments的屬性,該屬性是一個指標,指向擁有arguments物件的函式,這有利於匿
名函式的遞迴或確保函式的封裝性,例如下邊示例的遞迴計算1到n的自然數之和。而該屬性僅當相關函式正在執行時才可用。更有需要注意
的是callee擁有length屬性,這個屬性有時候用於驗證還是比較好的。arguments.length是實參度,arguments.callee.length是形參長
度,由此能夠判斷呼叫時形參長度是否和實參長度一致。
function calleeDemo() { alert(arguments.callee); }
3.1.2 throw new Error()方法:在控制檯裡丟擲錯誤提示。
throw 語句允許我們建立自定義錯誤。正確的技術術語是:建立或丟擲異常(exception)。
如果把 throw 與 try 和 catch 一起使用,那麼您能夠控制程式流,並生成自定義的錯誤訊息。異常可以是 JavaScript 字串、數字、
邏輯值或物件。
throw new Error('傳遞的引數不匹配');
3.1.3 search() 搜尋
search() 方法 | 用於檢索字串中指定的子字串,或檢索與正規表示式相匹配的子字串 |
search() 方法 | 執行正規表示式和 String 物件之間的一個搜尋匹配 |
當你想要知道字串中是否存在某個模式(pattern)時可使用 search(),類似於正規表示式的 test() 方法。當要了解更多匹配資訊
時,可使用 match()(但會更慢一些),該方法類似於正規表示式的 exec() 方法。
語法:
stringObject.search(regexp)
引數 |
描述 |
regexp |
該引數可以是需要在 stringObject 中檢索的子串,也可以是需要檢索的 RegExp 物件。 註釋:要執行忽略大小寫的檢索,請追加標誌 i。 |
返回值
stringObject 中第一個與 regexp 相匹配的子串的起始位置。
註釋:如果沒有找到任何匹配的子串,則返回 -1。
說明:
search() 方法不執行全域性匹配,它將忽略標誌 g。它同時忽略 regexp 的 lastIndex 屬性,並且總是從字串的開始進行檢索,這
意味著它總是返回 stringObject 的第一個匹配的位置。
特別說明:
-
字串中字元的位置與陣列類似,都是從0開始的。
-
searchValue不但可以是普通字串,也可以是正規表示式,
function isEmail() { if (arguments.length > 1) { throw new Error('你只能傳遞一個引數'); } var regexp = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/; if (arguments[0].search(regexp) != -1) { return true; } else { return false; } } var email = '13021946513@qq.com'; if (isEmail(email)) { document.write(email); } else { document.write('請您重新輸入郵箱地址'); }
4.1 使用函式物件
4.1.1 獲取函式形參個數
-
獲取函式的實參個數:使用arguments物件的length屬性,僅能夠在函式體內使用。
-
獲取形參的個數使用函式名.length或者a.callee.length,function物件的length屬性在函式體內外都可以使用.
function check(a) { if (a.length != a.callee.length) { throw new Error('引數不一致'); } }
function f(a, b, c, d) { check(arguments); return (a + b + c + d) } document.write(f(3, 4))
4.2.1 自定義屬性
-
使用者可以通過點語法為函式定義靜態屬性和方法。
-
函式屬性可以在函式體內定義,只能在內部呼叫;函式屬性也可以在函式體外定義,在外部呼叫。
-
函式方法可以在內部或外部定義,不管在內部或外部定義都可以呼叫此函式方法。
-
使用者不妨為函式定義屬性,然後利用函式屬性實現函式每次返回低增值。
function f() { return f.x++; } f.x = 0; for (var i = 0; i < 10; i++) { document.write(f()); }
4.3 call()和apply()函式
call()和apply()是function物件的原型方法。把一個函式當做一個方法繫結到指定物件上並進行呼叫。
具體用法如下:
function.call(thisobj,args...)
function.apply(thisobj,args)
其中引數thisobj表示指定的物件,引數args表示要傳遞給被呼叫函式的引數。而call()方法只能接受多個引數列表,apply()只能接受一個陣列
或偽陣列,陣列元素將作為引數傳遞給被呼叫的函式。
程式碼如下:
function f(x, y) { return x + y; } function o(a, b) { return a * b; } document.write(f.call(o, 3, 4));
使用call()和apply()方法可以把一個函式轉換為指定物件的方法,並在這個物件上臨時繫結呼叫該方法。
提示:它們能夠動態改變函式內this指代的物件,這在物件導向程式設計中是非常有用的。
4.3.1 call()和apply()區別如下:
-
call()和apply()傳遞給引數的方式不同,
-
apply()是以陣列形式傳遞引數,
-
call()方法以多個值的形式傳遞引數。
程式碼如下:
function r(x) { return (x); } function f(x) { x[0] = x[0] + '>'; return x; }
function o() { var temp = r; r = function () { return temp.apply(this, f(arguments)); } }
function a() { o(); document.write(r('=')); } for (var i = 0; i < 10; i++) { a(); }
4.4 使用bind()
函式function增加了一個原型方法bind(),是指用來把函式繫結到指定物件上。
具體用法如下:
function.bind(thisArg[,arg1[,arg2[,argN]);
引數說明如下:
function |
必須引數,一個函式物件 |
thisArg |
必須引數,this關鍵字可在新函式中引用的物件 |
arg1[,arg2[,argN]] |
可選引數,要傳遞到新函式的引數的列表。 |
bind方法將返回與函式相同的新函式不會立即執行,等需要的時候才會執行該新函式。bind()方法也可以多個或兩個傳遞引數值。
var displayArgs = function (val1, val2, val3, val4) { document.write(val1 + '<br> ' + val2 + ' <br>' + val3 + '<br> ' + val4 + '<br> '); }
var emptyObject = {}; var displayArg2 = displayArgs.bind(emptyObject, '姓名:' + '張亮', '年齡:' + 25 + '歲'); displayArg2('身高:' + 180 + 'cm', '民族:' + ' ' + '漢族');
4.4.1 JavaScript trim() 方法
語法:
string.trim()
定義和用法:
trim() |
方法用於刪除字串的頭尾空格。 |
trim() |
方法不會改變原始字串。 |
例項:
去除字串的頭尾空格:
function myTrim(x) { return x.replace(/^\s+|\s+$/gm,''); }
function myFunction() { var str = myTrim(" Runoob "); alert(str); }
5.1 使用this
this的定義:
this是函式體內自帶的一個物件指標,this指向當前呼叫函式的那個物件,或者指向類的當前例項。
this的特點:
-
在function內部被建立
-
指向呼叫時所在函式所繫結的物件(拗口)
-
this 不能被賦值,但可以被 call/apply 改變
this的用途:這裡把它所有用到的地方列出
- this 和構造器
function Tab(nav, content) { this.nav = nav this.content = content } Tab.prototype.getNav = function() { return this.nav; };
2. this 和物件
var tab = { nav: '', content: '', getNav: function() { return this.nav; }, setNav: function(n) { this.nav = n; } }
3. this 和函式
function showMsg() { alert(this.message) } var m1 = { message: '輸入的電話號碼不正確' } var m2 = { message: '輸入的身份證號不正確' } showMsg.call(m1) // '輸入的電話號碼不正確' showMsg.call(m2) // '輸入的身份證號不正確'
4. 全域性環境的 this
var x = 10; function func() { alert(this.x) } var obj = { x: 20, fn: function() { alert(this.x) } } var fn = obj.fn func() // 10 fn() // 10
沒錯,最終輸出的都是全域性的10。永遠記住這一點:判斷 this 指向誰,看執行時而非定義時,只要函式(function)沒有繫結在物件上呼叫,它的
this就是window。
5. this 和 DOM/事件
<div id="nav" onclick="getId()">ddd</div>
<script>
function getId() { alert(this.id) }
</script>
點選 div 後,為什麼 id 是 undefined,不說是指向的當前元素 div 嗎?如果記住了前面提到的一句話,就很清楚為啥是 undefined,把這句話再貼出來。判斷 this 指向誰,看執行時而非定義時,只要函式(function)沒有繫結在物件上呼叫,它的 this 就是 window。
這裡函式getId呼叫時沒有繫結在任何物件上,可以理解成這種結構:
div.onclick = function() { getId() }
getId 所處匿名函式裡的 this 是 div,但 getId 自身內的 this 則不是了。當然 ES5 嚴格模式下還是有個坑。
6. this 可以被 call/apply/bind 改變
var m1 = { message: 'This is A' } var m2 = { message: 'This is B' } function showMsg() { alert(this.message) } showMsg() // undefined showMsg.call(m1) // 'This is A' showMsg.call(m2) // 'This is B'
7. me/self/that/_this 暫存 this
如果採用 OOP 方式寫 JS 程式碼,無可避免的會用到 this,方法內會訪問類的內部屬性(欄位),也可能會呼叫類的另一個方法。當類的方法內又有一個 function 時,比如瀏覽器端開發經常遇見的給 DOM 元素新增事件,這時如果事件處理器(handler)中的想呼叫類的一個方法,此時 handler 內的 this 是 dom 元素而非類的當前物件。這個時候,需要把 this 暫存,開發者發揮著自己的聰明才智留下了幾種經典的命名 me, self, that, _this。
如:一般會在每個方法的第一句就把 this 暫存下來。
8. ES5 中新增的 bind 和 this
var modal = { message: 'This is A' } function showMsg() { alert(this.message) } var otherShowMsg = showMsg.bind(modal) otherShowMsg() // 'This is A'
bind 只是 call/apply 的高階版,其它沒什麼特殊的。
9. ES6 箭頭函式(arrow function) 和 this
var book = { author: 'John Resig', init: function() { document.onclick = ev => { alert(this.author) ; // 這裡的 this 不是 document 了 } } }; book.init()
物件 book 裡有一個屬性 author, 有一個 init 方法, 給 document 新增了一個點選事件,如果是傳統的函式,我們知道 this 指向應該是 document,但箭頭函式會指向當前物件 book。
箭頭函式讓 JS 迴歸自然和簡單,函式定義在哪它 this 就指向哪,定義在物件裡它指向該物件,定義在類的原型上,就指向該類的例項,望文知意這樣更容易理解。
總結:
函式的上下文 this 是 JS 裡不太好理解的,在於 JS 函式自身有多種用途。目的是實現各種語言範型(物件導向,函式式,動態)。this 本質是和麵向物件聯絡的,和寫類,物件關聯一起的, 和“函式式”沒有關係的。如果你採用過程式函式式開發,完全不會用到一個 this。 但在瀏覽器端開發時卻無可避免的會用到 this,這是因為瀏覽器物件模型(DOM)本身採用物件導向方式開發,Tag 實現為一個個的類,類的方法自然會引用類的其它方法,引用方式必然是用 this。當你給DOM物件新增事件時,回撥函式裡引用該物件就只能用 this 了。
六、使用閉包函式
閉包是Js的重要特性之一,在程式中有至關重要的作用。
Js函式能夠記住自己定義時所處的作用域,即使函式不在此作用域執行,依然可以訪問此作用域中的變數。
6.1認識閉包函式
閉包函式就是巢狀結構的函式。
閉包函式需要滿足下面3個條件:
① 在一個函式(含有私有變數或區域性變數)內定義的一個函式。也就是巢狀。
② 內部函式應該訪問外部函式中宣告的私有變數、引數或其他內部函式。
③ 如果在外部函式外呼叫這個內部函式,它就成為了閉包函式。
下面是一個經典閉包結構:
function f(x) { var a = x; var c=1; var b = function () { return a;//形成一個閉包 }; a++; return b; } var c = f(5); document.write(c());
------------------------
如果沒有閉包函式的作用,這種資料寄存和傳遞無法實施:
function f(x) { var a = x; var b = a; a++; return b; } var c = f(5); document.write(c);
當該函式時(f())不管是數字幾賦值給c後進行呼叫,該是多少就是多少這個‘數’,不能實時監測這個變數的變化。
注意:一但函式被呼叫執行,則閉包體也會隨之誕生。
閉包函式被再次執行或者通過某種方法進入函式體時,就可以獲取閉包函式內包含的資訊。
簡單的描述:閉包可以說是函式的資料包、儲存資料。這個資料包在函式執行過程中始終處於啟用狀態,只有呼叫函式閉包才能生效。當函式呼叫返回之後,閉包儲存下來與內部函式關聯的變數的動態聯絡(幫助監測)。
下面是閉包和閉包的常用場景:
-
作用域(受javascript鏈式作用域結構的影響,父級變數中無法訪問到子級變數的值,為了解決這個問題,才使用的閉包。)閉包就是能夠讀取其他函式內部變數的函式。(在JS中,只有函式內部的子函式才能讀取區域性變數,因此可以把閉包簡單理解為”定義在一個函式內部的函式”。無論是在事件中,for迴圈中,還是在函式自呼叫中,只要return出來,便有閉包的應用)。
-
閉包會把函式中變數的值儲存下來,供其他函式使用,這些變數會一直儲存在記憶體當中,這樣佔用大量的記憶體,使用不當很可能造成記憶體洩漏,故要及時清除,清楚方法有兩種,一是標記清除,二便是引用計數清除。
-
閉包的常用場景有一是函式作為返回值,二是函式作為引數來傳遞。不適用於返回閉包的函式是個特別大的函式,很多高階應用都要依靠閉包實現.
-
使用閉包的好處是不會汙染全域性環境,方便進行模組化開發,減少形參個數,延長了形參的生命週期,壞處就是不恰當使用會造成記憶體洩漏。
6.2 使用閉包
下面通過幾個示例介紹閉包的簡單使用,更直觀的理解什麼是閉包以及閉包的函式的作用和用法。
用法1:使用閉包結構能夠跟蹤動態環境中的實時變化,並即時儲存。
function f() { var a = 1; Var b = function () { return a; } a++; Return b; } var c=f(); document.write(c());
用法2:閉包不會因為外部函式環境的登出而消失,而是始終存在。
function f() { var a = 1; b = function () { alert(a); } c = function () { a++; } d = function (x) { a = x; } } <button onclick="f()">按鈕1:(f())</button><br> <button onclick="b()">按鈕2:(b=function(){alert(a);})()</button><br> <button onclick="c()">按鈕3:(c=function(){a++;})()</button><br> <button onclick="d(100)">按鈕4:(d=function(x){a=x;})(100)</button><br>
用法3:如何利用閉包儲存變數所有變化的值。
function f(x) { var a = []; for (var i = 0; i < x.length; i++) { var temp = x[i]; a.push(function () { document.write(temp + ' ' + x[i]) }); } return a; } function e() { var a = f(['a', 'b', 'c']); for (var i = 0; i < a.length; i++) { a[i](); } } e();
6.3 定義閉包儲存器
閉包常見的用法就是為要執行的函式提供引數。
比如:
為事件屬性傳遞動作 |
為定時器函式傳遞行為等 |
這在web前端中非常常見的一種應用。
用法1:
function f(a, b) { return function () { a(b); } } var c = f(alert, '愛前端'); var d = setTimeout(c, 1000);
閉包其實還可以用於建立額外的作用域,通過該作用域就可以設計動態資料管理器。
用法2:下面的例子如何使用閉包作為值來進行傳遞。
function f(a, b) { return function () { a(b); } } var c = f(alert, '我在家學習和鍛鍊!'); window.onload = c;
用法3:下面通過閉包可以使作為緩衝器的陣列與依賴它的函式關聯起來。實施優雅的打包。
var f = function () { var a = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; return function (a1, a2, a3, a4, a5) { a[0] = a1; a[1] = a2; a[2] = a3; a[3] = a4; a[4] = a5; return a.join(','); }; }(); var a = f(12, 14, 16, 18, 20); var b = f(11, 13, 15, 17, 19); document.write(a + '<br>'); document.write(b + '<br>');
6.4 在事件處理中應用閉包
很多時候我們希望引用一個函式後能暫停執行,因為在複雜的環境中不等到執行的時候是很難知道具體引數的,而先前被引用時更是無法預知所要傳遞的引數。
下面用法:希望為頁面中特定的元素或標籤繫結幾個事件,使其能夠在滑鼠經過、滑鼠移開和滑鼠單擊時呈現不同的背景顏色。
<p>p1</p> <p>p2</p> <p>p3</p>
<script>
function f(o, m) { return function (e) { e = e || window.event; return o[m](e, this); } } function g(id) { return function () { var e = document.getElementsByTagName(id); if (e) { for (var i in e) { e[i].onclick = f(g, 'click'); e[i].onmouseover = f(g, 'over'); e[i].onmouseout = f(g, 'out'); } } } } g.click = function (event, element) { element.style.backgroundColor = 'red'; } g.over = function (event, element) { element.style.backgroundColor = 'orange'; } g.out = function (event, element) { element.style.backgroundColor = 'green'; } window.onload = g('p');
</script>
七、實戰案例
7.1繫結函式
函式繫結就是為了糾正函式的執行上下文,特別是當函式中帶有this關鍵字的時候,這一點尤其重要,稍微不小心,就會使函式的執行上下文發生跟預期不同的改變,導致程式碼執行上的錯誤。
函式繫結具有3個特徵:
函式繫結要建立一個函式,可以在特定環境中以指定引數呼叫另一個函式。 |
一個簡單的bind()函式接受一個函式和一個環境,返回一個在給定環境中呼叫給定函式的函式 |
並且將所有引數原封不動的傳遞過去。 |
被繫結函式與普通函式相比有更多的開銷,它們需要更多記憶體,同時也因為多重呼叫而稍微 慢一點,所以最好只在必要時使用。 |
var handler = { messge: '學習js語言', handlerClick: function (event) { document.write(this.messge); } }; var btn = document.getElementsByTagName('my-btn'); EventUtil.addHandler(btn, 'clck', bind(handler.handlerClick, handler));
7.2 鏈式語法
因為 jQuery 庫的緣故,(Jquery最大的亮點就是它的鏈式語法)鏈式語法在前端界變得非常流行。實際上這是一種非常容易實現的模式。基本上,你只需要讓每個函式返回 'this',這樣其他函式就可以立即被呼叫,它也是點語法。
下面用法:
Function.prototype.method = function (name, func) { if (!this.prototype[name]) { this.prototype[name] = func; return this; } };
String.method('trom', function () { return this.replace(/^\s+|\s+$/g, ''); }); String.method('writeln', function () { document.write(this); return this; }); String.method('alert', function () { console.log((this)); return this; }); var str = '黃靜遠'; str.trim().writeln().alert();
7.3 函式節流
函式節流的設計思想就是讓某些程式碼可以在間斷情況下連續重複執行。實現的方法是使用定時器對函式進行節流。
下面用法:
function throttle(method, context) { clearTimeout(method.tId); method.tId = setTimeout(function () { method.call(context); }, 100); }
需要注意的一點:函式的節流是通過減少實際邏輯處理過程的執行來提高事件處理函式執行效能的手段,並沒有實質上減少事件的觸發次數。
7.4 柯里化
柯里化又稱為部分求值,柯里化是把接受多個引數的函式變換成接收一個單一的引數的函式。並且返回一個新函式。而這個函式新函式能夠接收原函式的引數。
下面用法:
function add(a) { // 瘋狂的回撥 return function(b) { return function(c) { return function(d) { // return a + b + c + d; return [a, b, c, d].reduce(function(v1, v2) { return v1 + v2; }); } } } } console.log(add(1)(2)(3)(4)); // 10
柯里化有3個常見作用:
-
引數複用
-
提前返回
-
延遲計算/執行
7.5 遞迴
-
一個過程或函式在其定義或說明中有直接或間接呼叫自身的一種方法,它通常把一個大型複雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,
-
遞迴策略只需少量的程式就可描述出解題過程所需要的多次重複計算,大大地減少了程式的程式碼量。
-
一般來說,遞迴需要有邊界條件、遞迴前進段和遞迴返回段。當邊界條件不滿足時,遞迴前進;當邊界條件滿足時,遞迴返回。
遞迴演算法解決問題的特點:
-
遞迴就是方法裡呼叫自身。
-
在使用遞增歸策略時,必須有一個明確的遞迴結束條件,稱為遞迴出口。
-
遞迴演算法解題通常顯得很簡潔,但遞迴演算法解題的執行效率較低。所以一般不提倡用遞迴演算法設計程式。
-
在遞迴呼叫的過程當中系統為每一層的返回點、區域性量等開闢了棧來儲存。遞迴次數過多容易造成棧溢位等,所以一般不提倡用遞迴演算法設計程式。
任何有意義的遞迴總是兩部分組成的:
遞迴呼叫 |
遞迴終止條件 |
7.5.1遞迴運算
遞迴運算在無限制的情況下,會無休止地自身呼叫。顯然,程式不應該出現這種無休止的遞迴呼叫,而只應出現有限次數的有終止的呼叫。
為此一般在遞迴運算中結合if語句來進行控制。
遞迴演算法的特點:
-
在函式過程中呼叫自身。
-
在遞迴過程中,必須有一個明確的條件判斷遞迴的結束,既遞迴出口。
-
遞迴演算法簡潔但效率低,通常不作為推薦演算法。
用法1 :看電影不知道在第幾排,看電影時不清楚自己在第幾排,可以通過問前一排的人來得知,進行加1即可。
function fn = (n) { if(n< = 0) return '座位不存在' if(n>1) { return fn(n-1)+1 } else { return 1 } }
用法2 :走格子有多少走法,一共有n格,每步可以走1格或者2格,問一共有多少走法。
首先分解問題是第n格可以是前面n-1格走的,也可能是n-2格走的。所以fn(n) = f(n-1) + f(n-2)。
也要知道終止條件是隻有1步,那麼只有一步的可能情況是還有1格,也可能是還有2格。
function fn = (n){ if(n>2){ return fn(n-1) + fn(n-2) } else if(n==2) { return 2 } else { return 1 } }