日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

js函数、作用域和闭包

發布時間:2025/3/17 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js函数、作用域和闭包 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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) // 8

5、函數聲明前置

和變量的聲明會前置一樣,函數聲明同樣會前置的。分成兩種情況:
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 只有兩種作用域:
一種是全局作用域,變量在整個程序中一直存在,所有地方都可以讀取;
另一種是函數作用域,變量只在函數內部存在。

3、全局變量和局部變量

函數外部聲明的變量就是全局變量(global variable),它可以在函數內部讀取。
在函數內部定義的變量,外部無法讀取,稱為“局部變量”(local variable)
javaScript 語言特有"鏈式作用域"結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。

4、作用域規則

  • {}不產生一個作用域,定義函數才會產生一個函數作用域
  • 函數在執行的過程中,先從自己內部找變量
  • 如果找不到,再從創建當前函數所在的作用域去找, 以此往上
var a = 1 function fn1(){function fn2(){console.log(a)}function fn3(){var a = 4fn2()}var a = 2return fn3 } var fn = fn1() fn() //輸出2 var a = 1 function fn1(){function fn3(){var a = 4fn2()}var a = 2return 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 = 0function fn(){speed++console.log(speed)}return fn//重要,如果不return出來,相當于閉包的作用就沒有了 }var speedUp = car() speedUp() //1 speedUp() //2

5、閉包經典案例

閉包的經典案例是定義一個變量,一個函數,一個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 = 0return 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() ) // 1console.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'))

總結

以上是生活随笔為你收集整理的js函数、作用域和闭包的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。