最近感冒了,身體太疲憊,同時我也發現每天更新一篇確實不能保證我的文章的質量,與其每天寫一些水文,不如靜下心來把一些知識梳理好再呈現給大家。
所以很抱歉的通知大家,這個系列從今天起,更新日期由每日一更改為3~5日一更,實際時間只會更短,不會更長。
同時也很感謝很多小伙伴這麼長時間的陪伴,謝謝大家。
在我們的日常開發中,經常會需要讓某一個效果等待多少秒之後才去觸發,這也就引出了我們今天要學習的內容,定時器。
setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式。
提示: 1000 毫秒 = 1 秒.
setTimeout(code,millisec,lang)
點擊按鈕,在等待 3 秒後彈出 "Hello"。
<script>
function myFunction()
{
setTimeout(function(){alert("Hello")},3000);
}
</script>
清除定時器:cleanTimeout
cleanTimeout(myFunction);
setInterval() 方法可按照指定的周期(以毫秒計)來調用函數或計算表達式。
setInterval() 方法會不停地調用函數,直到 clearInterval() 被調用或窗口被關閉。
由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的參數。
提示: 1000 毫秒= 1 秒。
setInterval(code,millisec,lang)
<script type="text/javascript">
var int=self.setInterval("clock()",1000);
function clock()
{
var d=new Date();
var t=d.toLocaleTimeString();
document.getElementById("clock").value=t;
}
</script>
清除定時器:clearInterval
clearInterval(clock);
在上方的兩個模塊中,大家都看見了,這兩個方法全部都是定時器,但是咱們發現兩個除了名字不同,貌似沒什麼區別,那為什麼還要有兩個方法呢?
因為setTimeout(表達式,延時時間)在執行時,是在載入後延遲指定時間後,去執行一次表達式,記住,次數是一次 。
而setInterval(表達式,交互時間)則不一樣,它從載入後,每隔指定的時間就執行一次表達式 。
所以,完全是不一樣的 。
很多人習慣於將setTimeout包含於被執行函數中,然後在函數外再次使用setTimeout來達到定時執行的目的 。
這樣,函數外的setTimeout在執行函數時再次觸發setTimeout從而形成周而復始的定時效果 。
使用的時候各有各的優勢,使用setInterval,需要手動的停止tick觸發。
而使用方法中嵌套setTimeout,可以根據方法內部本身的邏輯不再調用setTimeout就等於停止了觸發。
其實兩個東西完全可以相互模擬,具體使用那個,看當時的需要而定了。
就像for可以模擬所有的循環包括分支,而還提供了do、while一樣。
JS 中類型轉換分為強制轉換和隱式轉換。
var a = "50"; //字符串 String
var b = 10; //數字 Number
//字符串-->數字
var a = Number(a);
//提示 Number
alert(typeof a);
//數字-->字符串
var b = String(b);
//提示 String
alert(typeof b);
補充:
parsetInt() : 從左到右提取字符串中的整數,遇到非數字結束。(14.2312 取出來就是14) parsetFloat(): 提取字符串中的小數 Number() : 將字符串轉化為數字 String() : 將數字轉換為字符串
//隱式類型轉換
var a = "5"; //字符串類型
var b = 10 ; //數字類型
alert(a-b); //隱式轉換 將字符串“5”轉換為數字 5
函數是由事件驅動的或者當它被調用時執行的可重復使用的代碼塊。
或者說,計算機編程語言中的函數是指可以完成某些功能的一段固定代碼。
函數就是包裹在花括號中的代碼塊,前面使用了關鍵詞 function:
function functionname()
{
這裡是要執行的代碼
}
當調用該函數時,會執行函數內的代碼。
可以在某事件發生時直接調用函數(比如當用戶點擊按鈕時),並且可由 JavaScript 在任何位置進行調用。
提示:JavaScript 對大小寫敏感。關鍵詞 function 必須是小寫的,並且必須以與函數名稱相同的大小寫來調用函數。
var MR_LP = function(x){
if(x > 0){
return x;
}
else{
return -x;
}
};
咱們發現上面的函數,並沒有具體的函數名,但是它將函數賦值給了變量 MR_LP,所以我們的 MR_LP就可以調用該函數。
這兩種定義實際上完全等價,只是需要注意第二種方式按照完整的語法,需要在函數體末尾加一個 ” ; “.表示賦值語句結束。
在調用函數時,您可以向其傳遞值,這些值被稱為參數。
這些參數可以在函數中使用。
您可以發送任意多的參數,由逗號 (,) 分隔:
myFunction(argument1,argument2)
//當您聲明函數時,請把參數作為變量來聲明:
function myFunction(var1,var2)
{
這裡是要執行的代碼
}
變量和參數必須以一致的順序出現。第一個變量就是第一個被傳遞的參數的給定的值,以此類推。
有時,我們會希望函數將值返回調用它的地方。
通過使用 return 語句就可以實現。
在使用 return 語句時,函數會停止執行,並返回指定的值。
function myFunction()
{
var x=5;
return x;
}
上面的函數會返回值 5。
注釋:整個 JavaScript 並不會停止執行,僅僅是函數。JavaScript 將繼續執行代碼,從調用函數的地方。
我們調用函數只需要通過 函數名 + 參數
即可完成調用,注意當函數有參數的時候,一定要按照順序傳入參數。
大家還記得剛才的匿名函數麼?我們可以直接通過調用MR_LP
,就可以直接調用函數,例如這樣:
MR_LP(100); //返回100
MR_LP(-99); //返回99,函數內取反了
而且由於 JavaScript 允許傳入任意個參數而不影響調用,因此傳入的參數比定義的參數多也沒有問題,雖然函數內部並不需要這些參數。
MR_LP(100,'13115235'); //返回100
MR_LP(-99,'asdasd','aqweqvx'); //返回99,函數內取反了
當然,傳入的參數比定義的要少也沒有問題。
MR_LP(); 返回 NaN
此時函數的參數將受到 undefined,計算結果為 NaN (Not a Number)。
所以我們在日常的開發中,一定要進行參數的預判斷操作。
var MR_LP = function(x){
//判斷參數是否是 Number 類型
if(typeof x !== 'number'){
//拋出異常
throw 'Not a number';
}
if(x > 0){
return x;
}
else{
return -x;
}
};
在 JavaScript 引擎中有一個在行末自動添加分號的機制,但是這個機制也有可能會讓你栽一個大跟頭。(參考蘋果公司的那個好多年的 return 大坑)
function lol(){
return{ name : 'lol'};
}
lol(); //{name : 'lol'}
但如果我們如果把 return 語句分成了兩句。
function lol(){
return; //return 的結尾被自動添加上了分號,相當於 return undefined;
{ name : 'lol'}; //永遠不會被執行到
}
所以我們平常書寫的時候,可以采用這種寫法:
function lol(){
return{ //這時候不會自動添加分號,因為 {} 代表語句未結束
name : 'lol'
};
}
JavaScript 還有一個免費贈送的關鍵字 arguments
,它只在函數內部起作用,並且永遠指向當前函數的調用者傳入的所有參數。arguments 類似 Array,但實際並不是。
function foo(x){
alert(x); //10
for(var i = 0 ; i < arguments.length ; i ++){
alert(arguments[i]); //10 , 20 , 30
}
}
foo(10, 20, 30);
利用 arguments,你可以獲得調用者傳入的所有參數。
也就是說函數不定義任何參數,還是可以拿到參數的值。
function MR_LP(){
if(arguments.length === 0 ){
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
MR_LP(0); //0
MR_LP(100); //100
MR_LP(-99); //99
實際上 arguments 最常用於判斷傳入參數的個數。你可能會看見這樣的寫法:
// foo(a,[b],c)
//接收2~3個參數,b 是可選參數,如果只傳入2個參數,b 默認為 null;
function foo(a,b,c){
if(arguments.length === 2){
//實際拿到的參數是 a 和 b,c 為 undefined
c = b; //把 b 賦值給 c
b = null; //b 變為默認值
}
}
要把中間的參數 b 變為“可選”參數,就只能通過 arguments 判斷,然後重新調整參數並賦值。
由於 JavaScript 函數允許接收任意個參數,於是我們就不得不用 arguments 來獲取所有參數:
function foo(a,b){
var i,rest = [];
if(arguments.length > 2){
for(i = 2; i < arguments.length; i++){
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
為了獲取除了已定義參數 a , b 之外的參數,我們不得不用 arguments , 並且循環要從索引2 開始以便排除前兩個參數,這種寫法很別扭,只是為了獲得額外的 rest 參數,那我們有沒有更好的寫法?
ES6 標准引入的 rest 參數,上面的函數可以改寫為:
function foo(a, b ... rest){
console.log('a = ' + a);
console.log('b = ' + b);
sonsole.log(rest);
}
foo(1,2,3,4,5);
//結果:
// a = 1
// b = 2
// Array [ 3, 4, 5]
foo(1);
//結果:
// a = 1
// b = undefined
// Array []
rest 參數只能寫在最後,前面用 ...
標識,從運行結果可知,傳入的參數先綁定 a , b,多余的參數以數組的形式交給變量 rest, 所以不再需要 arguments 我們就獲取了全部參數。
如果傳入的參數連正常定義的參數都沒填完,也不要緊,rest 參數會接收一個空數組(注意:不是 undefined)。
因為 rest 參數是 ES6 新標准,所以我們在使用時需要注意兼容性的問題。
在 JavaScript 中,用 var 聲明的變量實際上是有作用域的。
如果一個變量在你函數體內部申明,則該變量的作用域為整個函數體,在函數體外不可以用該變量。
function foo(){
var x = 1;
x = x + 1;
}
x = x + 2; //referrenceError ! 無法再函數體外引用變量 x
如果兩個不同的函數各自申明了同一個變量,那麼該變量只在各自的函數體內起作用。
換句話說,不同函數內容的同名變量相互獨立,互不影響:
function foo(){
var x = 1;
x = x + 1;
}
function bar(){
var x = 'A';
x = x + 'B';
}
由於 JavaScript 的函數可以嵌套,此時內部函數可以訪問外部函數定義的變量,反過來卻不行。
function foo(){
var x = 1;
function bar (){
var y = x + 1; //bar 可以訪問 foo 的變量 x!
}
var z = y + 1; //ReferenceError! foo 不可以訪問 bar 的變量 y !
}
如果內部函數和外部函數的變量名重名怎麼辦?
function foo(){
var x = 1;
function bar(){
var x = 'A';
alert(' x in bar() = ' + x); //'A'
}
alert('x in foo() = ' + x); // 1
bar();
}
這說明 JavaScript 的函數在查找變量時從自身函數定義開始,從“內”向“外”依次查找。
如果內部函數定義的函數名與外部函數有重名的變量,則內部函數的變量將“屏蔽”外部函數的變量。
JavaScript 的函數定義有個特點,它會先掃描整個函數體中的語句,把所有申明的變量“提升”至函數的頂部;
function foo(){
var x = 'hello, ' + y;
alert(x);
var y = 'larra';
}
foo();
語句 var x = 'hello' + y;
並不會報錯,原因是變量 y
在稍後直接聲明了,但是我們的 alert 會提示 hello, undefined
,這說明我們的變量 y
的值是 undefined。
這正是因為 JavaScript 引擎自動提升了變量 y 的聲明,但是不會提升變量 y 的賦值。
所以我們在函數內部定義變量的時候,請嚴格遵守函數內部首先聲明所有變量的原則,最常見的就是用一個 var 聲明函數內部所有用到的變量。
function foo(){
var
x = 1; //x 初始值為 1
y = x + 1; //y 初始值為 2
z,i; //z 和 i 為 undefined
//其他語句
for(i = 0; i<100; i++){
...
}
}
不在任何函數內定義的變量就具有全局作用域。
實際上,JavaScript 默認有一個全局對象 window (也就是浏覽器對象),全局作用域的變量實際上被綁定到 window 的一個屬性:
var course = 'learn JavaScript';
alert(course);
alert(window.course);
因此直接訪問全局變量 course 和訪問 window.course 是完全一樣的。
由於函數的定義有兩種方式,以變量的方式 var foo = function(){}
定義的函數實際上也是一個全局變量,因此,頂層函數的定義也被視為一個全局變量,並綁定到 window 對象:
'use strict';
function foo(){
alert('foo');
}
foo(); //直接調用 foo();
window.foo(); //通過 window.foo()調用
由此我們也可以做一個更大膽的猜測,我們每次調通的 alert() 函數其實也應該是 window 的一個變量。
注意:
JavaScript 實際上之喲偶一個全局作用域。任何函數(函數也視為變量),如果沒有在當前函數作用域中找到,那麼就會繼續向上層去查找,如果最後在全局中也沒有找到,就直接報 ReferenceError 的錯誤。
由於 JavaScript 的變量作用域實際上是函數內部,,我們在 for 循環等語句塊中是無法定義具有局部作用域的變量。
function foo(){
for(var i = 0;i < 100; i++){
//
}
i +=100; //仍然可以引用變量 i
}
所以為了解決塊級作用域,ES6引入了新的關鍵字 let
,用 let 替代 var 可以聲明一個塊級元素作用域的變量:
function foo(){
var sum = 0;
for(let i = 0; i< 100 ; i++){
sum +=i;
}
i +=1; //SyntaxError
}
一個坑
我是按鈕1 我是按鈕2 我是按鈕3 我是按鈕4
變量最明顯的特征就是裡面儲存的值是可以隨意改變的,常量則與之相反,常量一旦確定,則不能發生改變。
var PI = 3.14;
ES6標准出台之後,我們可以使用新的關鍵字 const 來定義常量,const 與 let 都具有塊級作用域。
const PI = 3.14;
PI = 3; //某些浏覽器不會報錯,但是無實際效果
PI; //3.14
高階函數除了可以接受函數作為參數外,還可以把函數作為返回結果返回。
我們來實現一個對 Array 的求和,通常,求和的函數是這樣定義的:
function sum(arr){
return arr.reduce(function(x,y){
return x + y;
});
}
sum([1,2,3,4,5]); //15
但是如果不需要立刻求和,而是在後面的代碼中,根據需要再計算,那我們可以直接返回求和的函數。
function lazy_sum(arr){
var sum = function(){
return arr.reduce(function (x,y){
return x + y;
});
}
return sum;
}
當我們調用lazy_sum()
返回的並不是求和的結果,而是求和的函數:
var f = lazy_sum([1,2,3,4,5]); //function sum()
調用函數f
的時候才是真正計算求和的結果,而是求和函數:
f(); //15
在這個例子中,我們在函數 lazy_sum 中又定義了函數 sum,並且內部函數 sum 可以引用外部函數 lazy_sum 的參數和局部變量,當 lazy_sum 返回函數 sum 時,相關參數和變量都保存在返回的函數中,這種我們稱之為“閉包”。
需要注意,當我們調用 lazy_sum()時,每次調用都會返回一個新的函數,即使傳入相同的參數
var f1 = lazy_sum([1,2,3,4,5]);
var f2 = lazy_sum([1,2,3,4,5]);
f1===f2; //false
f1()和 f2() 的調用結果互不影響。
注意到返回的函數在其定義內部引用了局部變量 arr,所以,當一個函數返回了一個函數後,其內部的局部變量還被新函數引用,所以,閉包用起來容易,做起來可不容易。
另外需要注意,返回的函數並沒有立即執行,而是知道調用了 f() 才執行,我們再來看另外一個例子:
function count(){
var arr = [];
for(var i = 1; i<=3;i++){
arr.push(function(){
return i * i;
});
}
return arr;
}
var resullts = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中,每次循環都創建了一個新的函數,然後把創建的三個函數都添加到一個 array 循環中並返回了。
你可能認為代用的 f1(),f2(),f3()的結果是 1,4,9.而實際情況是
f1(); //16
f2(); //16
f3(); //16
他們全部都是16,原因在於返回的函數引用了變量 i,但它卻不是立即執行,等到三個函數全部執行完畢的時候,i 的值已經變成了 4 ,所以所有的結果都是 16.
所以在返回閉包的時候一定要注意一點: 返回函數不要引用任何循環變量,或者後續會發生變化的變量。
如果程序中必須使用的時候呢?
我們需要重新創建一個函數,用該函數的參數綁定循環變量當前的值,之後就不用管循環變量會怎麼變,我們已經綁定到函數的參數的值是不會變的。
function count(){
var aarr = [];
for(var i=1;i<=3;i++){
arr.push((function(n){
return function(){
return n*n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); //1
f2(); //4
f3(); //9
注意在上面的程序中,我們創建了一個“匿名函數並立即執行”的方法。
(function(x){
return x*x;
})(3)
理論上講,創建一個匿名函數並立即執行可以這兒寫:
function (x){return x * x;} (3);
但是 JS 語法解析,會認為這個函數是錯誤的,會提示 SyntaxError。因此我們需要用括號吧整個函數定義括起來:
(function(x){return x*x;})(3);
而我們日常工作中,會將這個函數分開來寫
(function(x){
return x*x;
})(3);
那我們之前說閉包功能非常強大,他強大在哪裡呢?
在面向對象的程序設計語言內,如 Java 和 C++ ,要在對象內部封裝一個私有變量,可以用 private 修飾一個成員變量。
在沒有 class 機制,只有函數的語言裡,借助閉包,同樣可以封裝一個私有變量。我們用 JavaScript 創建一個計數器:
function create_counter(initial){
var x = initial || 0;
return{
inc : function(){
x += 1;
return x;
}
}
}
使用的時候:
var c1 = create_counter();
c1.inc(); //1
c1.inc(); //2
c1.inc(); //3
var c2 = create_counter(10);
c2.inc(); //11
c2.inc(); //12
c2.inc(); //13
在返回的對象中,實現了一個閉包,該閉包攜帶了局部變量 x , 並且,從外部代碼根本無法訪問到變量 x。換句話說,閉包就是攜帶狀態的函數,並且它的狀態可以完全對外隱藏起來。
閉包還可以把多參數的函數編程但參數的函數。
例如,要計算 x^y
可以使用 Math.pow(x,y)
函數,不過考慮到經常計算 x^2
或者 x^3
, 我們可以利用閉包創建新的函數 pow2
和 pow3
:
function make_pow(n){
return function (x){
return Math.pow(x,n);
}
}
//創建兩個新的函數
var pow2 = make_pow(2);
var pow3 = make_pow(3);
pow2(5); //25
pow3(7); //343
大家注意到,上面我們使用了一個新的函數,Match.pow(x,y)
,這個函數的作用是:返回底數(x)的指定次(y)冪,Match 本身就是一個龐大的數學函數庫,下面我們再來學習一個新函數 :隨機數 Math.random();
,結果為 0 - 1 之間的一個隨機數 (包括0,不包括1)
例如我們現在來生產一個 1-10之間的隨機數:
<code class=" hljs xml"> </code>
eval();
是 JS 中的函數,可以將字符轉化為可執行的 JS 代碼,下面我們來看幾個例子:
var a = "5 + 10";
alert(eval(a)); //15
var str = "function(){alert('a')}";
//直接變函數 等價 var str = function(){alert('a')};
str = eval("("+str+")");
str();