js函数、作用域和闭包
JavaScript-作用域、塊級作用域、上下文、執(zhí)行上下文、作用域鏈
一、函數(shù)
1、函數(shù)定義
函數(shù)是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)可以接收輸入的參數(shù),不同的參數(shù)會(huì)返回不同的值
2、函數(shù)的聲明方式
主要講兩種:
2.1 用function命令聲明函數(shù)
function命令后面是函數(shù)名,函數(shù)名后面是一對圓括號,里面是傳入函數(shù)的參數(shù),函數(shù)體放在大括號里面
2.2 用函數(shù)表達(dá)式聲明函數(shù)
把匿名函數(shù)賦值給變量
3、函數(shù)參數(shù)
3.1參數(shù)定義
參數(shù):從外部傳入函數(shù),支撐函數(shù)運(yùn)行的外部數(shù)據(jù)
3.2參數(shù)的傳遞規(guī)則
可以多傳、少傳參數(shù),被省略的參數(shù)就是undefined。傳遞參數(shù)是按照順序來適配的。
3.3 arguments 對象
1)用途:arguments 對象可以在函數(shù)體內(nèi)部讀取所有參數(shù)
2)使用規(guī)則:arguments對象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù),arguments[0]就是第一個(gè)參數(shù),arguments[1]就是第二個(gè)參數(shù),以此類推。這個(gè)對象只有在函數(shù)體內(nèi)部,才可以使用。
3)arugments對象長這樣(下圖是我傳遞了三個(gè)值)
3)通過arguments對象的length屬性,可以判斷函數(shù)調(diào)用時(shí)到底帶幾個(gè)參數(shù)
4) 舉個(gè)例子
4、返回值
4.1 用return實(shí)現(xiàn)返回函數(shù)的操作后的數(shù)值,不寫return語句,函數(shù)默認(rèn)返回undefined
4.2 JavaScript 引擎遇到return語句,就直接返回return后面的那個(gè)表達(dá)式的值,后面即使還有語句,也不會(huì)得到執(zhí)行。
4.3返回值的應(yīng)用
函數(shù)可以調(diào)用自身,這就是遞歸(recursion)。下面就是通過遞歸,計(jì)算斐波那契數(shù)列的代碼。
5、函數(shù)聲明前置
和變量的聲明會(huì)前置一樣,函數(shù)聲明同樣會(huì)前置的。分成兩種情況:
5.1用function聲明的函數(shù)
使用function聲明的函數(shù)整個(gè)函數(shù)都會(huì)提升到代碼頭部。
所以你在聲明函數(shù)前,調(diào)用了函數(shù),都不會(huì)報(bào)錯(cuò)的,如下圖
5.2用函數(shù)表達(dá)式聲明函數(shù)
不會(huì)把整個(gè)函數(shù)提升,只會(huì)把定義的變量提升到頭部。
相當(dāng)于如下代碼。對于sum2就是一個(gè)未賦值的變量,為undefined,是不能作為函數(shù)執(zhí)行的,所以報(bào)錯(cuò)了
var sum2; sum2(3,4); var sum2 =function (a,b){ return a+b ;}6、立刻執(zhí)行的函數(shù)表達(dá)式
對于編輯器來說function (a,b){return a+b ;} 是一個(gè)函數(shù)聲明語句,而不是一個(gè)函數(shù)類型的值,所以function (a,b){return a+b ;}加上()是會(huì)報(bào)錯(cuò)的
正確的寫法是(function (a,b){return a+b ;})(), ()內(nèi)部的東西是一個(gè)值,加上()代表立刻執(zhí)行,整個(gè)語句相當(dāng)于一個(gè)函數(shù)類型的值需要立刻執(zhí)行
7、命名沖突
當(dāng)在同一個(gè)作用域內(nèi)定義了名字相同的變量和方法的話,會(huì)根據(jù)前置順序產(chǎn)生覆蓋
var fn = 3;function fn(){}console.log(fn); // 3相當(dāng)于
var fn function fn(){} //覆蓋上面的fn = 3 //重新賦值 console.log(fn) function fn(fn){console.log(fn);var fn = 3;console.log(fn); }fn(10) //10 3相當(dāng)于
function fn(){ var fn =arguments[0];console.log(fn);var fn = 3;console.log(fn); }fn(10) //10 3二、函數(shù)作用域
1、定義
作用域(scope)指的是變量存在的范圍。
2、分類:
在 ES5 的規(guī)范中,Javascript 只有兩種作用域:
一種是全局作用域,變量在整個(gè)程序中一直存在,所有地方都可以讀取;
另一種是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。
3、全局變量和局部變量
函數(shù)外部聲明的變量就是全局變量(global variable),它可以在函數(shù)內(nèi)部讀取。
在函數(shù)內(nèi)部定義的變量,外部無法讀取,稱為“局部變量”(local variable)
javaScript 語言特有"鏈?zhǔn)阶饔糜?#34;結(jié)構(gòu)(chain scope),子對象會(huì)一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。
4、作用域規(guī)則
- {}不產(chǎn)生一個(gè)作用域,定義函數(shù)才會(huì)產(chǎn)生一個(gè)函數(shù)作用域
- 函數(shù)在執(zhí)行的過程中,先從自己內(nèi)部找變量
- 如果找不到,再從創(chuàng)建當(dāng)前函數(shù)所在的作用域去找, 以此往上
三、閉包
1、定義:
函數(shù)連同它作用域鏈上的要找的這個(gè)變量,共同構(gòu)成閉包
2、特點(diǎn)
閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
3、用處
閉包的最大用處有兩個(gè)
- 可以讀取函數(shù)內(nèi)部的變量
- 暫存數(shù)據(jù)(讓這些變量始終保持在內(nèi)存中,即閉包可以使得它誕生環(huán)境一直存在)
4、舉個(gè)栗子
如果沒有這個(gè)閉包,函數(shù)執(zhí)行后,里面speed變量就會(huì)被清理掉。但我們聲明了fn這個(gè)函數(shù),并把它返回出來賦值給新的變量speedup。因?yàn)閟peedup是全局變量,是一直存在的,故這個(gè)fn函數(shù)就一直存在,speed變量也不會(huì)被清理
function car(){var speed = 0function fn(){speed++console.log(speed)}return fn//重要,如果不return出來,相當(dāng)于閉包的作用就沒有了 }var speedUp = car() speedUp() //1 speedUp() //25、閉包經(jīng)典案例
閉包的經(jīng)典案例是定義一個(gè)變量,一個(gè)函數(shù),一個(gè)return 函數(shù)。如上圖
看一下這個(gè)案例如何改造
原理解析:for循環(huán)每次執(zhí)行,都把function(){ return i} 這個(gè)函數(shù)賦值給fnArr[i],但這個(gè)函數(shù)不執(zhí)行。因?yàn)閒nArr[3] =function(){ return i};故當(dāng)我們調(diào)用fnArr[3]() 時(shí),相當(dāng)于function(){ return i};這個(gè)函數(shù)立刻執(zhí)行,這時(shí)for循環(huán)已經(jīng)完成,i已經(jīng)變成了10。故輸出10
如果要輸出3,需要如下改造
var fnArr = [] for (var i = 0; i < 10; i ++) {(function(i){fnArr[i] = function(){return i} })(i) } console.log( fnArr[3]() ) // 3 var fnArr = [] for (var i = 0; i < 10; i ++) {fnArr[i] = (function(j){return function(){return j} })(i) } console.log( fnArr[3]() ) // 3四、作用域鏈
1、執(zhí)行上下文
2、活動(dòng)對象
Ao有兩種來源,1、來自var定義的變量,2、傳遞的參數(shù)
3、scope屬性
執(zhí)行函數(shù)需要值得時(shí)候,就從活動(dòng)對象AO里面找,找不到就從scope里面去找
4、例子1
1)全局上下文:
globalcontext ={
AO:{
x:10
foo:function
bar:function
},
scope:null
}
2)//聲明foo函數(shù)得過程中,foo新增scope屬性并指向了globalContext的Ao
foo.[[scope]] =globalContext.Ao
//聲明bar函數(shù)得過程中,bar新增scope屬性并指向了globalContext的Ao
bar.[[scope]] =globalContext.Ao
在執(zhí)行上下文的聲明的函數(shù),這個(gè)函數(shù)的[[scope]] 就等于globalContext(執(zhí)行上下文)的Ao
3)當(dāng)調(diào)用bar的時(shí)候,進(jìn)入了bar的執(zhí)行上下文
barcontext ={
AO:{
x:30
},
scope:bar.[[scope]] // globalContext.Ao
}創(chuàng)建bar的過程中,bar新增scope屬性并指向了globalContext的Ao
4)當(dāng)調(diào)用foo的時(shí)候,進(jìn)入了foo的執(zhí)行上下文
foocontext ={
AO:{},
scope:foo.[[scope]] // globalContext.Ao
}
5、例2
var x = 10 function bar(){ var x=30 function foo(){ console.log(x) } foo() } bar()1)全局上下文:
globalcontext ={
AO:{
x:10
bar:function
},
scope:null
}
2)//聲明bar函數(shù)得過程中,bar新增scope屬性并指向了globalContext的Ao
bar.[[scope]] =globalContext.Ao
3)當(dāng)調(diào)用bar的時(shí)候,進(jìn)入了bar的執(zhí)行上下文
barcontext ={
AO:{
x:30
foo:function
},
scope:bar.[[scope]] // globalContext.Ao
}創(chuàng)建foo的過程中,foo新增scope屬性并指向了barcontext的Ao
foo.[[scope]] =balContext.Ao
4)當(dāng)調(diào)用foo的時(shí)候,進(jìn)入了foo的執(zhí)行上下文
foocontext ={
AO:{},
scope:foo.[[scope]] // balContext.Ao
}
所以console.log(x)是30
6、例子3
封裝一個(gè) Car 對象
car對象封裝4個(gè)接口,我們只能通過提供接口來操作數(shù)據(jù),不能直接操作數(shù)據(jù)。
原理:定義一個(gè)car對象,設(shè)置其等于一個(gè)立刻執(zhí)行的函數(shù)表達(dá)式 中return出來的內(nèi)容。
return出來的對象,有四個(gè)屬性(setSpeed,get,speedUp,speedDown),四個(gè)屬性分別對應(yīng)了四個(gè)函數(shù)(setSpeed,get,speedUp,speedDown)。這四個(gè)函數(shù)就用于操作speed的值。這導(dǎo)致car得不到釋放,return的變量也無法釋放,對應(yīng)的所有函數(shù)都沒有辦法釋放,就生成了一個(gè)閉包
7、例4
如下代碼輸出多少?如何連續(xù)輸出 0,1,2,3,4
1)原理:我們設(shè)置了延時(shí)為0的定時(shí)器,每次for循環(huán)一次的時(shí)候,就把函數(shù)的代碼添加到異步隊(duì)列里面一次。當(dāng)for循環(huán)5次循環(huán)完之后,開始執(zhí)行5次的函數(shù),函數(shù)執(zhí)行時(shí)去找i的值,這時(shí)候的i的值已經(jīng)變成5,所以就連續(xù)輸出5個(gè)5
2)改造
for(var i=0; i<5; i++){(function(i){setTimeout(function(){console.log('delayer:' + i )}, 0) })(i) }原理:通過一個(gè)立刻執(zhí)行的函數(shù)表達(dá)式,生成一個(gè)閉包。由于for循環(huán)不會(huì)產(chǎn)生一個(gè)作用域,所以可以不用return。當(dāng)然用return也可以
for(var i=0; i<5; i++){setTimeout((function(j){return function(){console.log('delayer:' + j )}}(i)), 0) }8、例5
function makeCounter() {var count = 0return function() {return count++}; }var counter = makeCounter()//**相當(dāng)于把返回的function() {return count++}這個(gè)函數(shù)賦值counter** var counter2 = makeCounter();//**然后把第二次返回的function() {return count++}這個(gè)函數(shù)賦值counter2**console.log( counter() ) // 0 //**counter() 每執(zhí)行一次,就會(huì)返回一個(gè)數(shù)值加1的counter值** console.log( counter() ) // 1console.log( counter2() ) // 0 console.log( counter2() ) // 1原理:因?yàn)樾纬闪艘粋€(gè)閉包 , counter和counter2 返回的函數(shù)存的不是同一個(gè)地址,所以對于counter和counter2對應(yīng)的活動(dòng)對象是不一樣的
9、例6寫一個(gè) sum 函數(shù),實(shí)現(xiàn)如下調(diào)用方式
console.log( sum(1)(2) ) // 3 console.log( sum(5)(-1) ) // 4解析:sum(1)之后能跟著一個(gè)(),表示sum(1)是一個(gè)還沒有執(zhí)行的函數(shù),等于function sum(){
return function(){}}。
sum(1)后面接了一個(gè)(2)表示返回的函數(shù)要接收一個(gè)參數(shù),本身也要接受一個(gè)參數(shù)。function sum(a){
return function(b){}
}
最后根據(jù)這個(gè)函數(shù)的功能,返回a+b的值
總結(jié):函數(shù)柯里化-只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。
10、補(bǔ)全代碼,實(shí)現(xiàn)數(shù)組按姓名、年紀(jì)、任意字段排序
var users = [{ name: "John", age: 20, company: "Baidu" },{ name: "Pete", age: 18, company: "Alibaba" },{ name: "Ann", age: 19, company: "Tecent" } ]users.sort(byName) users.sort(byAge) users.sort(byField('company'))sort后面必須要接受一個(gè)函數(shù),所以需要返回一個(gè)參數(shù)。
function byName(user1, user2){return user1.name > user2.name }function byAge (user1, user2){return user1.age > user2.age }function byFeild(field){return function(user1, user2){return user1[field] > user2[field]} } users.sort(byField('company'))總結(jié)
以上是生活随笔為你收集整理的js函数、作用域和闭包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql错误总结-ERROR 1067
- 下一篇: ThinkPHP/---合并数组后按时间