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