摘要:在函數(shù)內(nèi)部定義的變量,外部無(wú)法讀取,稱(chēng)為局部變量語(yǔ)言特有鏈?zhǔn)阶饔糜蚪Y(jié)構(gòu),子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量。
JavaScript-作用域、塊級(jí)作用域、上下文、執(zhí)行上下文、作用域鏈
一、函數(shù) 1、函數(shù)定義函數(shù)是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)可以接收輸入的參數(shù),不同的參數(shù)會(huì)返回不同的值
2、函數(shù)的聲明方式主要講兩種:
2.1 用function命令聲明函數(shù)
function命令后面是函數(shù)名,函數(shù)名后面是一對(duì)圓括號(hào),里面是傳入函數(shù)的參數(shù),函數(shù)體放在大括號(hào)里面
function print(s) { console.log(s); }
2.2 用函數(shù)表達(dá)式聲明函數(shù)
把匿名函數(shù)賦值給變量
var print = function(s) { console.log(s); };3、函數(shù)參數(shù)
3.1參數(shù)定義
參數(shù):從外部傳入函數(shù),支撐函數(shù)運(yùn)行的外部數(shù)據(jù)
3.2參數(shù)的傳遞規(guī)則
可以多傳、少傳參數(shù),被省略的參數(shù)就是undefined。傳遞參數(shù)是按照順序來(lái)適配的。
function printPersonInfo(name, age, sex){ console.log(name) console.log(age) console.log(sex) } printPersonInfo(sjz,male)//實(shí)際上name為sjz ,age為male,sex為undefined
3.3 arguments 對(duì)象
1)用途:arguments 對(duì)象可以在函數(shù)體內(nèi)部讀取所有參數(shù)
2)使用規(guī)則:arguments對(duì)象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù),arguments[0]就是第一個(gè)參數(shù),arguments[1]就是第二個(gè)參數(shù),以此類(lèi)推。這個(gè)對(duì)象只有在函數(shù)體內(nèi)部,才可以使用。
3)arugments對(duì)象長(zhǎng)這樣(下圖是我傳遞了三個(gè)值)
3)通過(guò)arguments對(duì)象的length屬性,可以判斷函數(shù)調(diào)用時(shí)到底帶幾個(gè)參數(shù)
4) 舉個(gè)例子
function printPersonInfo(name, age, sex){ console.log(name) console.log(age) console.log(sex) console.log(arguments) console.log(arguments[0]) console.log(arguments.length) console.log(arguments[1] === age) }4、返回值
4.1 用return實(shí)現(xiàn)返回函數(shù)的操作后的數(shù)值,不寫(xiě)return語(yǔ)句,函數(shù)默認(rèn)返回undefined
4.2 JavaScript 引擎遇到return語(yǔ)句,就直接返回return后面的那個(gè)表達(dá)式的值,后面即使還有語(yǔ)句,也不會(huì)得到執(zhí)行。
4.3返回值的應(yīng)用
函數(shù)可以調(diào)用自身,這就是遞歸(recursion)。下面就是通過(guò)遞歸,計(jì)算斐波那契數(shù)列的代碼。
function fib(num) { if (num === 0) return 0; if (num === 1) return 1; return fib(num - 2) + fib(num - 1); } fib(6) // 85、函數(shù)聲明前置
和變量的聲明會(huì)前置一樣,函數(shù)聲明同樣會(huì)前置的。分成兩種情況:
5.1用function聲明的函數(shù)
使用function聲明的函數(shù)整個(gè)函數(shù)都會(huì)提升到代碼頭部。
所以你在聲明函數(shù)前,調(diào)用了函數(shù),都不會(huì)報(bào)錯(cuò)的,如下圖
sum(3,4) function sum(a,b){ return a+b; } //7
5.2用函數(shù)表達(dá)式聲明函數(shù)
不會(huì)把整個(gè)函數(shù)提升,只會(huì)把定義的變量提升到頭部。
相當(dāng)于如下代碼。對(duì)于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á)式
對(duì)于編輯器來(lái)說(shuō)function (a,b){return a+b ;} 是一個(gè)函數(shù)聲明語(yǔ)句,而不是一個(gè)函數(shù)類(lèi)型的值,所以function (a,b){return a+b ;}加上()是會(huì)報(bào)錯(cuò)的
正確的寫(xiě)法是(function (a,b){return a+b ;})(), ()內(nèi)部的東西是一個(gè)值,加上()代表立刻執(zhí)行,整個(gè)語(yǔ)句相當(dāng)于一個(gè)函數(shù)類(lèi)型的值需要立刻執(zhí)行
7、命名沖突當(dāng)在同一個(gè)作用域內(nèi)定義了名字相同的變量和方法的話(huà),會(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、分類(lèi):在 ES5 的規(guī)范中,Javascript 只有兩種作用域:
一種是全局作用域,變量在整個(gè)程序中一直存在,所有地方都可以讀取;
另一種是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。
函數(shù)外部聲明的變量就是全局變量(global variable),它可以在函數(shù)內(nèi)部讀取。
在函數(shù)內(nèi)部定義的變量,外部無(wú)法讀取,稱(chēng)為“局部變量”(local variable)
javaScript 語(yǔ)言特有"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope),子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量。所以,父對(duì)象的所有變量,對(duì)子對(duì)象都是可見(jiàn)的,反之則不成立。
{}不產(chǎn)生一個(gè)作用域,定義函數(shù)才會(huì)產(chǎn)生一個(gè)函數(shù)作用域
函數(shù)在執(zhí)行的過(guò)程中,先從自己內(nèi)部找變量
如果找不到,再?gòu)膭?chuàng)建當(dāng)前函數(shù)所在的作用域去找, 以此往上
var a = 1 function fn1(){ function fn2(){ console.log(a) } function fn3(){ var a = 4 fn2() } var a = 2 return fn3 } var fn = fn1() fn() //輸出2
var a = 1 function fn1(){ function fn3(){ var a = 4 fn2() } var a = 2 return fn3 } function fn2(){ console.log(a) } var fn = fn1() fn() //輸出1三、閉包 1、定義:
函數(shù)連同它作用域鏈上的要找的這個(gè)變量,共同構(gòu)成閉包
2、特點(diǎn)閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
3、用處閉包的最大用處有兩個(gè)
可以讀取函數(shù)內(nèi)部的變量
暫存數(shù)據(jù)(讓這些變量始終保持在內(nèi)存中,即閉包可以使得它誕生環(huán)境一直存在)
4、舉個(gè)栗子如果沒(méi)有這個(gè)閉包,函數(shù)執(zhí)行后,里面speed變量就會(huì)被清理掉。但我們聲明了fn這個(gè)函數(shù),并把它返回出來(lái)賦值給新的變量speedup。因?yàn)閟peedup是全局變量,是一直存在的,故這個(gè)fn函數(shù)就一直存在,speed變量也不會(huì)被清理
function car(){ var speed = 0 function fn(){ speed++ console.log(speed) } return fn//重要,如果不return出來(lái),相當(dāng)于閉包的作用就沒(méi)有了 } var speedUp = car() speedUp() //1 speedUp() //25、閉包經(jīng)典案例
閉包的經(jīng)典案例是定義一個(gè)變量,一個(gè)函數(shù),一個(gè)return 函數(shù)。如上圖
看一下這個(gè)案例如何改造
var fnArr = []; for (var i = 0; i < 10; i ++) { fnArr[i] = function(){ return i }; } console.log( fnArr[3]() ) // 10
原理解析: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)對(duì)象
Ao有兩種來(lái)源,1、來(lái)自var定義的變量,2、傳遞的參數(shù)
3、scope屬性
執(zhí)行函數(shù)需要值得時(shí)候,就從活動(dòng)對(duì)象AO里面找,找不到就從scope里面去找
4、例子1
var x = 10 bar() function foo(){ console.log(x) } function bar(){ var x=30 foo() }
1)全局上下文:
globalcontext ={
AO:{
x:10
foo:function
bar:function
},
scope:null
}
2)//聲明foo函數(shù)得過(guò)程中,foo新增scope屬性并指向了globalContext的Ao
foo.[[scope]] =globalContext.Ao
//聲明bar函數(shù)得過(guò)程中,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的過(guò)程中,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ù)得過(guò)程中,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的過(guò)程中,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 對(duì)象
car對(duì)象封裝4個(gè)接口,我們只能通過(guò)提供接口來(lái)操作數(shù)據(jù),不能直接操作數(shù)據(jù)。
原理:定義一個(gè)car對(duì)象,設(shè)置其等于一個(gè)立刻執(zhí)行的函數(shù)表達(dá)式 中return出來(lái)的內(nèi)容。
return出來(lái)的對(duì)象,有四個(gè)屬性(setSpeed,get,speedUp,speedDown),四個(gè)屬性分別對(duì)應(yīng)了四個(gè)函數(shù)(setSpeed,get,speedUp,speedDown)。這四個(gè)函數(shù)就用于操作speed的值。這導(dǎo)致car得不到釋放,return的變量也無(wú)法釋放,對(duì)應(yīng)的所有函數(shù)都沒(méi)有辦法釋放,就生成了一個(gè)閉包
var Car = (function(){ var speed = 0; function set(s){ speed = s } function get(){ return speed } function speedUp(){ speed++ } function speedDown(){ speed-- } return { setSpeed: setSpeed, get: get, speedUp: speedUp, speedDown: speedDown } })() Car.set(30) Car.get() //30 Car.speedUp() Car.get() //31 Car.speedDown() Car.get() //30
7、例4
如下代碼輸出多少?如何連續(xù)輸出 0,1,2,3,4
for(var i=0; i<5; i++){ setTimeout(function(){ console.log("delayer:" + i ) }, 0) }
1)原理:我們?cè)O(shè)置了延時(shí)為0的定時(shí)器,每次for循環(huán)一次的時(shí)候,就把函數(shù)的代碼添加到異步隊(duì)列里面一次。當(dāng)for循環(huán)5次循環(huán)完之后,開(kāi)始執(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) }
原理:通過(guò)一個(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 = 0 return 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() ) // 1 console.log( counter2() ) // 0 console.log( counter2() ) // 1
原理:因?yàn)樾纬闪艘粋€(gè)閉包 , counter和counter2 返回的函數(shù)存的不是同一個(gè)地址,所以對(duì)于counter和counter2對(duì)應(yīng)的活動(dòng)對(duì)象是不一樣的
9、例6寫(xiě)一個(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è)還沒(méi)有執(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的值
function sum(a) { return function(b) { return a + b } }
總結(jié):函數(shù)柯里化-只傳遞給函數(shù)一部分參數(shù)來(lái)調(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"))
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/108704.html
摘要:吐槽一下,閉包這個(gè)詞的翻譯真是有很大的誤解性啊要說(shuō)閉包,要先說(shuō)下詞法作用域。閉包兩個(gè)作用通過(guò)閉包,在外部環(huán)境訪問(wèn)內(nèi)部環(huán)境的變量。閉包使得函數(shù)可以繼續(xù)訪問(wèn)定義時(shí)的詞法作用域。 閉包是真的讓人頭暈啊,看了很久還是覺(jué)得很模糊。只能把目前自己的一些理解先寫(xiě)下來(lái),這其中必定包含著一些錯(cuò)誤,待日后有更深刻的理解時(shí)再作更改。 吐槽一下,閉包這個(gè)詞的翻譯真是有很大的誤解性啊…… 要說(shuō)閉包,要先說(shuō)下詞法...
摘要:大名鼎鼎的作用域和閉包,面試經(jīng)常會(huì)問(wèn)到。聲明理解閉包,先理解函數(shù)的執(zhí)行過(guò)程。閉包的基本結(jié)構(gòu)因?yàn)殚]包不允許外界直接訪問(wèn),所以只能間接訪問(wèn)函數(shù)內(nèi)部的數(shù)據(jù),獲得函數(shù)內(nèi)部數(shù)據(jù)的使用權(quán)。 大名鼎鼎的作用域和閉包,面試經(jīng)常會(huì)問(wèn)到。閉包(closure)是Javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色。 聲明 理解閉包,先理解函數(shù)的執(zhí)行過(guò)程。 代碼在執(zhí)行的過(guò)程中會(huì)有一個(gè)預(yù)解析的過(guò)程,也就是在代碼的...
摘要:寫(xiě)在前面對(duì)于一個(gè)前端開(kāi)發(fā)者,應(yīng)該沒(méi)有不知道作用域的。欺騙詞法作用域有兩個(gè)機(jī)制可以欺騙詞法作用域和。關(guān)于你不知道的的第一部分作用域和閉包已經(jīng)結(jié)束了,但是,更新不會(huì)就此止住未完待續(xù) 這是《你不知道的JavaScript》的第一部分。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 寫(xiě)在前面 對(duì)于一個(gè)前端開(kāi)發(fā)者,應(yīng)該沒(méi)有不知道作用域的。它是一個(gè)既簡(jiǎn)單有復(fù)雜的概念,簡(jiǎn)單到每行代碼都有它的影子...
摘要:作用域和閉包以及自執(zhí)行函數(shù)作用域作用域分為種全局作用域全局作用域就是在的任何位置都能訪問(wèn)過(guò)函數(shù)作用域只能在函數(shù)里面調(diào)用的稱(chēng)之為函數(shù)作用域閉包嵌套在函數(shù)里面的函數(shù)及周邊的變量叫閉包閉包存在的問(wèn)題是周邊變量不會(huì)被釋放,常駐內(nèi)存中閉包的缺點(diǎn)消耗內(nèi) 作用域和閉包以及自執(zhí)行函數(shù) 作用域 作用域分為2種 1、全局作用域 全局作用域就是在js的任何位置都能訪問(wèn)過(guò) 2、函數(shù)作用域 只能在函數(shù)里面調(diào)用的...
摘要:閉包的出現(xiàn)正好結(jié)合了全局變量和局部變量的優(yōu)點(diǎn)。這就是閉包的一個(gè)使用場(chǎng)景保存現(xiàn)場(chǎng)。 前言 什么是閉包,其實(shí)閉包是可以重用一個(gè)對(duì)象,又保護(hù)對(duì)象不被篡改的一種機(jī)制。什么是重用一個(gè)對(duì)象又保護(hù)其不被篡改呢?請(qǐng)看下面的詳解。 作用域和作用域鏈 注意理解作用域和作用域鏈對(duì)理解閉包有非常大的幫助,所以我們先說(shuō)一下作用域和作用域鏈 什么是作用域作用域表示的是一個(gè)變量的可用范圍、其實(shí)它是一個(gè)保存變量的對(duì)象...
摘要:在上面的代碼中,函數(shù)實(shí)際上就是函數(shù)的閉包函數(shù),我們讓其執(zhí)行三次,結(jié)果分別為。這是因?yàn)椋瘮?shù)中的局部變量一直被保存在內(nèi)存中。所以閉包有個(gè)缺點(diǎn),就是內(nèi)存占用較大。自執(zhí)行函數(shù)上面這段函數(shù)也是閉包的一種。我們利用閉包來(lái)做一個(gè)小例子。 變量作用域和閉包 變量作用域 當(dāng)我們寫(xiě) js 文檔的時(shí)候經(jīng)常會(huì)設(shè)置變量,變量的類(lèi)型有兩種: 全局變量 局部變量 這兩種類(lèi)型的變量有者不同的作用范圍,全局變量的...
閱讀 1387·2023-04-25 16:45
閱讀 1926·2021-11-17 09:33
閱讀 2316·2021-09-27 14:04
閱讀 919·2019-08-30 15:44
閱讀 2640·2019-08-30 14:24
閱讀 3423·2019-08-30 13:59
閱讀 1696·2019-08-29 17:00
閱讀 895·2019-08-29 15:33