JavaScript 函數創建方式
(1)聲明式:解析器會先讀取函數聲明,並使其在執行任何代碼之前可以訪問;
JavaScript 函數通過 function
關鍵詞進行定義,其後是函數名和括號 ()。
function name(參數 1, 參數 2, 參數 3) {
要執行的代碼
}
(2)函數表達式(匿名式):必須等到解析器執行到它所在的代碼行纔會真正被解釋執行
var name=function(參數 1, 參數 2, 參數 3){
要執行的代碼
}
(3)Function式:這是一個函數表達式。一般不推薦用這種方法定義函數,因爲這種語法會導致解析兩次代碼(第一次是解析常規ECMAScript代碼,第二次是解析傳入構造函數中的字符串),從而影響性能。
var name = new Functuin(參數 1, 參數 2, 參數 3,要執行的代碼)
JavaScript 對象 可以有方法(在對象上執行的動作,以函數定義被存儲在屬性中)
自執行函數
//匿名函數:這時你會發現報錯了
function(){ //Function statements require a function name
return 1;
};
單獨運行一個匿名函數,由於不符合語法要求,會報錯。解決方法:只需要用()把匿名函數包起來即可
//由於括弧()和JS的&&,異或,逗號等操作符是在函數表達式和函數聲明上消除歧義的
(function(){
return 1;
};)
自執行函數:立即調用的函數表達式 定義和調用合爲一體
我們創建了一個匿名的函數,並立即執行它,由於外部無法引用它內部的變量,因此在執行完後很快就會被釋放,關鍵是這種機制不會污染全局對象。
對於函數表達式,在後面加括號即可以讓函數立即執行;例如下面這個函數,至於爲什麼加了括
號就可以立即執行,我們可以這麼理解,就是像f();這樣寫的話,函數可以立即執行是沒問題的
,我們在經常會用到,那麼對於函數表達式來說,f()就是對後面的匿名函數的一個引用,因此在
後面的匿名函數後直接加括號,自然也就可以立即執行
var f = function(){
console.log("ss");
}();
//the same
var f = function(){
console.log("ss");
};
f();
alert(!function () { return 10; } ()) //false
alert(-function () { return 10; } ()) //-10
alert(~function () { return 10; } ()) //-11 01010 10101
arrow
使用function定義的函數,this的指向隨着調用環境的變化而變化,而箭頭函數中的this指向是固定不變的,一直指向定義函數的環境。
//使用function定義的函數
function foo(){
console.log(this); //this的指向隨着調用環境的變化而變化
}
var obj = { aa: foo };
foo(); //Window
obj.aa() //obj { aa: foo }
//使用箭頭函數定義函數
var foo = () => { console.log(this) }; //箭頭函數中的this指向是固定不變的
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window
閉包
function f1(){
n=999; //函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明瞭一個全局變量!
}
f1();
alert(n); // 999
Javascript語言特有的"鏈式作用域"結構(chain scope):子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。
function f1(){
var n=999;
function f2(){ //f2內部函數 可以讀取f1中內部的所有局部變量
alert(n); // 999
}
}
既然f2可以讀取f1中的局部變量,那麼只要把f2作爲返回值,我們不就可以在f1外部讀取它的內部變量了嗎!
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2; //f2函數,就是閉包 :通過閉包能夠讀取該函數內部變量的函數
}
/*下面的代碼就是外部了*/
var result=f1(); //在f1外部讀取f2的內部變量
result(); // 999 //外部訪問alert(n);
可以看出,閉包就是一種特殊的函數 在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。
閉包的作用
- 可以讀取函數內部的變量
- 讓這些變量的值始終保持在內存中。
function f1(){
var n=999;
nAdd=function(){n+=1} //沒有使用var關鍵字,因此nAdd是一個全局變量
function f2(){
alert(n);
}
return f2;
}
var result=f1(); //result就是閉包f2函數 f2被賦給了一個全局變量
result(); // 999
nAdd();
result(); // 1000
//result一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變量n一直保存在內存中,並沒有在f1調用後被自動清除。
//f2被賦給了一個全局變量,這導致f2始終在內存中,而f2的存在依賴於f1,因此f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。
注意點
- 由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。
- 閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //The Window
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //My Object
記住嵌套作用域變量值的函數
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName; //返回一個函數名字
}
var myFunc = makeFunc();
myFunc(); //加上括號後才執行
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5); //function(y)
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
//add5 和 add10 都是閉包。它們共享相同的函數定義,但是保存了不同的詞法環境。在 add5 的環境中,x 爲 5。而在 add10 中,x 則爲 10。
//閉包:記住嵌套作用域變量值的函數
作用域(scope):通常來說,一段程序代碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。
工廠函數,工廠函數定義了一個外部的函數,這個函數簡單的生成並返回一個內嵌的函數,僅僅是返回卻不調用,因此通過調用這個工廠函數,可以得到內嵌函數的一個引用,內嵌函數就是通過調用工廠函數時,運行內部的def語句而創建的
閉包很有用,因爲它允許將函數與其所**操作的某些數據(環境)**關聯起來。
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a> <!--不同尺寸的按鈕-->
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
//每個閉包都是引用自己詞法作用域內的變量 privateCounter 。
//每次調用其中一個計數器時,通過改變這個變量的值,會改變這個閉包的詞法環境。然而在一個閉包內對變量的修改,不會影響到另外一個閉包中的變量。
//以這種方式使用閉包,提供了許多與面向對象編程相關的好處 —— 特別是數據隱藏和封裝。
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
循環中創建閉包
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help); //這裏變量共享
}
}
}
setupHelp();
無論焦點在哪個input
上,顯示的都是關於年齡的信息。
解決:使用更多的閉包
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();
匿名閉包
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
(function() {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})(); // 馬上把當前循環項的item與事件回調相關聯起來
}
}
setupHelp();
回調函數
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
函數a有一個參數,這個參數是個函數b,當函數a執行完以後執行函數b。那麼這個過程就叫回調。
回調函數(b):以參數形式傳給函數a
function a(callback)
{
alert("我是parent函數a!");
alert("調用回調函數");
callback();
}
function b(){
alert("我是回調函數b");
}
function c(){
alert("我是回調函數c");
}
function test(){
a(b);
a(c);
}
一
function say (value) {
alert(value);
}
function execute (someFunction, value) {
someFunction(value);
}
execute(say, 'hi js.'); //將say方法作爲參數傳遞給execute方法
/*這裏的say方法就是回調函數
這裏的execute方法接收了兩個參數。第一個參數是say ,第二個參數是"hijs.'。
這裏的第二個參數其實就是say方法所需要的參數。
這樣寫就叫做將回調函數的參數作爲與回調函數同等級的參數進行傳遞*/
function say (value) {
alert(value.name); //回調函數需要一個參數value ,這個value是一個對象 ,擁有屬性name
}
function execute (someFunction) {
var value = { name: 'hi js.'}; //這裏可以自定義一些方法
someFunction(value);
//這裏execute函數只接收一個參數就是say函數本身, say函數的參數在execute函數內部已經定義過了,這裏直接傳遞也能達到回調函數傳參的目的。
}
execute(say); //execute函數是主函數,參數爲say函數本身,這裏的say也叫做回調函數
二
function execute (someFunction, value) {
someFunction(value);
}
execute(function(value){alert(value);}, 'hi js.');//將匿名函數作爲參數傳遞給execute方法
總結
function say (value) {
alert(value);
}
say;
//直接寫say方法的方法名與下面的匿名函數可以認爲是一個東西
function (value) {
alert(value);
}
JS的全局變量跟局部變量
- js有兩級作用域,全局和局部,局部也叫函數作用域
- 全局作用域的變量局部可以使用,局部作用域的變量只能在函數體內使用
- var和function聲明的變量都聲明提前,賦值留在原地
- 如果局部和全局變量重名,優先使用局部變量
- 第3條和第4條,解釋了全局和局部都有相同變量名的時候,而在函數體內打印的變量是undefined
<script>
var a =1;
function test(){
alert(a); //這時a只有聲明,還沒賦值
var a = 2; //全局變量a變成了局部變量a
alert(a);
}
test();
alert(a);
</script>
/*Output
1 2 1 wrong
undefined 2 1 true
*/
Javascript的變量的scope是根據方法塊來劃分的(也就是說以function的一對大括號{ }來劃分)
<script>
function test2(){
var i; //在函數內部聲明,說明是個局部變量
alert ("before for scope:"+i);
}
test2();
alert(i); //error! 沒錯,是error,原因是變量i未聲明,導致腳本錯誤,程序到此結束!
alert("這行打印還會輸出嗎?"); //未執行
</script>
Javascript在執行前會對整個腳本文件的聲明部分做完整分析(聲明不是賦值)
<script>
var a =1;
function test(){
alert(a);
//a爲undefined! 這個a並不是全局變量,這是因爲在function scope裏已經聲明瞭(函數體倒數第4行)一個重名的局部變量,全局變量跟局部變量重名時,局部變量的scope會覆蓋掉全局變量的scope
//所以全局變量a被覆蓋了,這說明了Javascript在執行前會對整個腳本文件的定義部分做完整分析,所以在函數test()執行前,函數體中的變量a就被指向內部的局部變量.而不是指向外部的全局變量. 但這時a只有聲明,還沒賦值,所以輸出undefined。
a=4
alert(a); //a爲4
var a; //局部變量a在這行聲明 因爲這行導致全局變量a變成了局部變量a
alert(a); //a還是爲4,這是因爲之前已把4賦給a了
}
test();
alert(a); //a爲1,這裏並不在function scope內,a的值爲全局變量的值
</script>
再來個小栗子吧
console.log(i);
var i=1; //如果不加這行,報錯 i is not defined 報錯了後面的代碼就都不會執行了
//加了第二行 第一行會輸出undefined
全局變量遇上局部變量時,怎樣使用全局變量呢?用window.globalVariableName。
<script>
var a =1;
function test(){
alert(window.a); //a爲1,這裏的a是全局變量哦!
var a=2; //局部變量a在這行定義
alert(a); //a爲2,這裏的a是局部變量哦!
}
test();
alert(a); //a爲1,這裏並不在function scope內,a的值爲全局變量的值
</script>
JS中的塊級作用域
<script type="text/javascript">
{
var a = 1;
console.log(a); // 1
}
console.log(a); // 1
// 可見,通過var定義的變量可以跨塊作用域訪問到。
(function A() {
var b = 2;
console.log(b); // 2
})();
// console.log(b); // 報錯,
// 可見,通過var定義的變量不能跨函數作用域訪問到
if(true) {
var c = 3;
}
console.log(c); // 3
for(var i = 0; i < 4; i++) {
var d = 5;
};
console.log(i); // 4 (循環結束i已經是4,所以此處i爲4)
console.log(d); // 5
// if語句和for語句中用var定義的變量可以在外面訪問到,
// 可見,if語句和for語句屬於塊作用域,不屬於函數作用域。
{
var a = 1;
let b = 2;
const c = 3;
{
console.log(a); // 1 子作用域可以訪問到父作用域的變量
console.log(b); // 2 子作用域可以訪問到父作用域的變量
console.log(c); // 3 子作用域可以訪問到父作用域的變量
var aa = 11; //var定義的變量,沒有塊的概念,可以跨塊訪問, 不能跨函數訪問。
let bb = 22; //let定義的變量,只能在塊作用域裏訪問,不能跨塊訪問,也不能跨函數訪問。
const cc = 33;
//定義常量 使用時必須初始化(即必須賦值),只能在塊作用域裏訪問,而且不能修改。
//同一個變量只能使用一種方式聲明,不然會報錯
}
console.log(aa); // 11 // 可以跨塊訪問到子 塊作用域 的變量
// console.log(bb); // 報錯 bb is not defined
// console.log(cc); // 報錯 cc is not defined
}
</script>
var、let、const的區別
- var定義的變量,沒有塊的概念,可以跨塊訪問, 不能跨函數訪問。
- let定義的變量,只能在塊作用域裏訪問,不能跨塊訪問,也不能跨函數訪問。
- const用來定義常量,使用時必須初始化(即必須賦值),只能在塊作用域裏訪問,而且不能修改。
- 同一個變量只能使用一種方式聲明,不然會報錯
if語句和for語句中用var定義的變量可以在外面訪問到,可見,if語句和for語句屬於塊作用域,不屬於函數作用域。
JavaScript同步、異步、回調執行順序分析
同步優先、異步靠邊、回調墊底 同步 => 異步 => 回調
for (var i = 0; i < 5; i++) { //塊作用域
setTimeout(function() {
console.log(i); //for循環先執行,但是不會給setTimeout傳參
//等for循環執行完,就會給setTimeout傳參
//這裏,循環本身及三次 timeout 回調均共享唯一的變量i
}, 1000);
}
console.log(i);
/*
Output:
5 reback.html:6 //外部的console先打印出5是因爲for循環執行完成了
⑤5 reback.html:3
也就是說先執行第6行,再依次執行5次第三行 輸出都是5
*/
- for循環和循環體外部的console是同步的,所以先執行for循環,再執行外部的console.log。
- for循環裏面有一個setTimeout回調,他是墊底的存在,只能最後執行。
then()方法是異步執行。當.then()前的方法執行完後再執行then()內部的程序,這樣就避免了,數據沒獲取到等的問題。