函數(shù)的定義與要素
函數(shù)定義
-
具名函數(shù)
function 函數(shù)名(形式參數(shù)1,形式參數(shù)2){
語(yǔ)句
return 返回值
} -
匿名函數(shù)
去掉函數(shù)名就是匿名函數(shù)
聲明一個(gè)變量把它賦值為函數(shù)表達(dá)式邢锯,a只是容納了它的地址
let a = function(x,y){return x+y}
聲明變量=函數(shù)表達(dá)式 -
箭頭函數(shù)
let f1 = x => x乘x //x是輸入右邊是輸出
let f2 = (x,y)=>x*y // 多個(gè)參數(shù)
let f3 = (x,y)=>{console.log('hi')
return x+y}如果有花括號(hào)需要寫return
接受一個(gè)值x 返回一個(gè)對(duì)象
花括號(hào)優(yōu)先被認(rèn)成塊作用域扬蕊,必須加個(gè)小括號(hào)告訴他是一個(gè)對(duì)象
let f4 = x =>({name:x}) -
構(gòu)造函數(shù)(所有函數(shù)都是Function構(gòu)造出來(lái)的包括Object/Array/Function)
let f = new Function('x','y','return x + y') - fn和fn()
fn是函數(shù)自身,
fn()調(diào)用函數(shù)
函數(shù)要素
- 調(diào)用時(shí)機(jī)
- 作用域
- 閉包
- 形式參數(shù)
- 返回值
- 調(diào)用棧
- 函數(shù)提升
- arguments(箭頭函數(shù)除外)
- this(箭頭函數(shù)除外)
- call指定this
- 立即執(zhí)行函數(shù)
調(diào)用時(shí)機(jī)
時(shí)機(jī)不同 結(jié)果不同
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a=2
打印出2
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
打印出6個(gè)6
為什么明明setTimeout的時(shí)間設(shè)置為了0丹擎,還是打印出6個(gè)6厨相?
在 js 中,定時(shí)器是一個(gè)異步任務(wù)鸥鹉。 JS 會(huì)優(yōu)先執(zhí)行當(dāng)前的同步任務(wù)蛮穿,在同步代碼執(zhí)行結(jié)束后才會(huì)去處理任務(wù)隊(duì)列中的異步任務(wù)。這樣毁渗,即便定時(shí)器設(shè)置了0践磅,也是在忙完手頭的事情之后才會(huì)去讀取任務(wù)隊(duì)列。
JS 有同步任務(wù)和異步任務(wù)灸异。
同步任務(wù):在主線程上排隊(duì)執(zhí)行的任務(wù)府适,只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)肺樟;
異步任務(wù)指:不進(jìn)入主線程檐春、而進(jìn)入"任務(wù)隊(duì)列"的任務(wù),只有"任務(wù)隊(duì)列"通知主線程么伯,某個(gè)異步任務(wù)可以執(zhí)行了疟暖,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。
for(let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
});
}JS在for和let一起用的時(shí)候加了一些東西所以
打印出 0 1 2 3 4田柔,
每次循環(huán)會(huì)多創(chuàng)建一個(gè)i
作用域
每個(gè)函數(shù)都會(huì)創(chuàng)建一個(gè)作用域
function fn(){
let a = 1
}
console.log(a)//a 不存在俐巴,訪問(wèn)不到作用域里面的a
let 作用域就在它最近的花括號(hào)里
全局變量:
- 在頂級(jí)作用域聲明的變量是全局變量
- 掛到window上的屬性是全局變量
比如Object可以直接用,也可以自己寫window.c=xxx硬爆。
局部變量:
- 函數(shù)里聲明lety或const就是一個(gè)局部變量欣舵,出了作用域不生效。
作用域規(guī)則:
如果多個(gè)作用域有同名的變量a
- 查找a的聲明時(shí)缀磕,向上取最近的作用域(就近原則)
- 查找a的過(guò)程與函數(shù)執(zhí)行無(wú)關(guān),但a的值與函數(shù)的執(zhí)行有關(guān)缘圈。
靜態(tài)作用域:
- 函數(shù)的作用域在函數(shù)定義時(shí)就已經(jīng)確定了劣光,跟函數(shù)執(zhí)行沒(méi)有關(guān)系。
- 函數(shù)的作用域在運(yùn)行時(shí)才確定糟把,跟函數(shù)執(zhí)行有關(guān)系赎线。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
JavaScript采用靜態(tài)作用域的方式訪問(wèn)變量,所以打印出1.
JavaScript之靜態(tài)作用域 - 簡(jiǎn)書 (jianshu.com)
閉包:JS函數(shù)會(huì)就近尋找最近的變量
function f1(){
let a = 1
function f2(){
let a = 2
function f3(){
console.log(a)
}
a = 22
f3()
}
console.log(a)
a = 100
f2()
}
f1()
如果一個(gè)函數(shù)用到了外部的變量,那么這個(gè)函數(shù)加這個(gè)變量就叫做閉包糊饱。上面f2中的a和f3組成閉包垂寥。
形式參數(shù)
形式參數(shù)的意思就是非實(shí)際參數(shù)
let a = 1
let b =2
function add(x, y){
x+1
y+2
return x+y
}
其中x和y就是形參,因?yàn)椴⒉皇菍?shí)際的參數(shù)
add(a,b)
調(diào)用 add 時(shí)另锋,a 和 b 是實(shí)際參數(shù)滞项,會(huì)被賦值給 x y
- JS傳參
傳值:在上面的代碼中,a和b在賦值給x夭坪、y的時(shí)候文判,會(huì)被復(fù)制一份傳進(jìn)去。函數(shù)中對(duì)形參的修改并不會(huì)影響到實(shí)際參數(shù)本身室梅。函數(shù)執(zhí)行完后還是a =1 b =2戏仓。
傳址:但是傳遞的參數(shù)時(shí)對(duì)象時(shí)。此時(shí)傳進(jìn)去的就是對(duì)象的地址亡鼠,對(duì)對(duì)象做出的修改會(huì)影響到對(duì)象本身赏殃。
其實(shí)無(wú)所謂傳值和傳址。就是把stack內(nèi)存中的東西復(fù)制過(guò)去而已间涵。
形參可認(rèn)為是變量聲明仁热。上面代碼近似等價(jià)于下面代碼。
function add(){
var x=arguments[0]//參數(shù)的第0個(gè)
var y=arguments[1]
return x+y
}
返回值
- 每個(gè)函數(shù)都有返回值
function hi(){console.log('hi') }
hi()
沒(méi)寫return勾哩,所以返回值是undefined
function hi(){ return console.log('hi')}
hi()
返回值為console.log('hi')的值抗蠢,即undefined - 函數(shù)執(zhí)行完了后才會(huì)返回
- 只有函數(shù)有返回值
錯(cuò)誤:1+2返回值為3
正確:1+2值為3
調(diào)用棧
什么是調(diào)用棧
- JS引擎在調(diào)用一個(gè)函數(shù)前
- 需要把函數(shù)所在的環(huán)境push到一個(gè)數(shù)組里
- 這個(gè)數(shù)組叫做調(diào)用棧
- 等函數(shù)執(zhí)行完了,就會(huì)把環(huán)境彈(pop)出來(lái)
- 然后return到之前的環(huán)境思劳,繼續(xù)執(zhí)行后續(xù)代碼
作用:
- 由于我每次進(jìn)入一個(gè)函數(shù)我都得記下來(lái)我等會(huì)回到哪迅矛,所以把回到的地址寫到棧里邊。如果進(jìn)入一個(gè)函數(shù)之后還要進(jìn)入一個(gè)函數(shù)潜叛,就再把這個(gè)地址放到棧里邊秽褒,函數(shù)執(zhí)行完了就彈棧 。
舉例
function first() {
console.log('first')
function second() {
console.log("second")
}
second();
third();
}
function third() {
console.log("third")
}
// 調(diào)用 first
first();
調(diào)用的是函數(shù) first, 此時(shí) first 進(jìn)入函數(shù)棧钠导,接著在 first 中調(diào)用函數(shù) second,second 入棧震嫉,當(dāng) second 執(zhí)行完畢后森瘪,second 出棧牡属,third 入棧,接著 third 執(zhí)行完出棧扼睬,執(zhí)行 first 其他代碼逮栅,直至 first 執(zhí)行完悴势,函數(shù)棧清空。
遞歸函數(shù):
階乘
function f(n){
return n !==1 ? n*f(n-1) : 1
}
f(4)
= 4 * f(3)
= 4 * (3 * f(2))
= 4 * (3 * (2 * f(1)))
= 4 * (3 * (2* (1)))
= 4 * (3 * (2))
= 4 * (6)
=24
先遞進(jìn)措伐,后回歸
- 遞歸的調(diào)用棧
1特纤、進(jìn)入f(4)并壓棧
2、得到4 * f(3) 進(jìn)入f(3)并壓棧(把4也記錄下來(lái))
3侥加、得到3 * f(2) 進(jìn)入f(2)并壓棧(把3也記錄下來(lái))
4捧存、得到2* (1)進(jìn)入f(1)并壓棧(把2也記錄下來(lái))
5、得到1
6担败、彈棧2得到2
7昔穴、彈棧3得到6
8、彈棧4得到24
9提前、彈椔鸹酰回到第一行 - 我們可以看到f(4)壓棧4次
- 所以f(10000)壓棧10000次
爆棧:如果調(diào)用棧中壓入的幀過(guò)多,程序就會(huì)崩潰
棧的長(zhǎng)度:
function computeMaxCallStackSize(){
try{
return 1+ computeMaxCallStackSize()
}catch(e){
//報(bào)錯(cuò)說(shuō)明 stack overflow
return 1
}
}
函數(shù)提升
定義:
不管你把具名函數(shù)function fn(){}聲明在哪里狈网,它都會(huì)跑到第一行宙搬。
這種不行
let fn = function(){}
這是賦值,右邊的匿名函數(shù)聲明不會(huì)提升
let不允許有一個(gè)函數(shù)add的時(shí)候再聲明一個(gè)add,var可以:
let add = 1
function add(){}
報(bào)錯(cuò)
var add = 1
function add(){}
console.log(add)
此時(shí)add是1
arguments和this
每個(gè)函數(shù)都有拓哺,除了箭頭函數(shù)
argements:
- 包含所有參數(shù)的偽數(shù)組
- 通過(guò)array.from 可以把它變?yōu)閿?shù)組
- 調(diào)用fn即可傳arguments
- fn(1,2)那么arguments就是【1勇垛,2】偽數(shù)組
打印出來(lái)看一下
function fn(){console.log(arguments)}
this
- 打印出來(lái)看一下
function fn(){console.log(this)} - 我們發(fā)現(xiàn)如果單純打印this不給任何條件的情況下this默認(rèn)指向window
如何傳this和arguments
- 使用fn.call(1,2,3,4)傳this和arguments
1是this(第一個(gè)參數(shù)) 2,3,4是arguments - xxx如果不是對(duì)象JS會(huì)自動(dòng)把他轉(zhuǎn)化成對(duì)象(JS糟粕)
- 在函數(shù)中加'use strick'即可制止
this要解決的問(wèn)題: - 我們寫一個(gè)函數(shù)的時(shí)候需要得到一個(gè)對(duì)象
- 但是不知道對(duì)象的名字,可能那個(gè)對(duì)象還沒(méi)出生
- 怎樣在不知道一個(gè)對(duì)象的名字情況下拿到對(duì)象的引用
let sayHi = function(){
console.log(person.name)
}
我在寫這個(gè)函數(shù)之前我怎么能確定后面這個(gè)變量是person呢
let person = {
name:'frank',
'sayHi':sayHi
}
如果我person改名字了士鸥,sayHi函數(shù)就掛了
sayHi甚至可能在另一個(gè)文件中
所以我們不希望sayHi函數(shù)出現(xiàn)person的引用
class Person{
constructor(name){
this.name = name//這里this強(qiáng)制指定的
}
sayHi(){
console.log(???)
}
}
這里只有類窥摄,還沒(méi)有創(chuàng)建對(duì)象,故不可能拿到對(duì)象的引用
JS是如何解決問(wèn)題的
- JS在每個(gè)函數(shù)里加了this
- 在任何函數(shù)里面可以用this獲取那個(gè)你目前還不知道名字的對(duì)象
let person = {
name:'frank',
sayHi(){
console.log(this.name)
這里的this就表示以后出現(xiàn)的那個(gè)對(duì)象
}
}
- person.sayHi()相當(dāng)于person.sayHi(person)然后person地址被傳給了this
- person.sayHi()會(huì)隱式地把person作為this傳給sayHI础淤,方便sayHi獲取person對(duì)應(yīng)的對(duì)象
函數(shù)的兩種調(diào)用方法
- person.sayHi()會(huì)自動(dòng)把person傳到函數(shù)里作為this
- person.sayHi.call(person)手動(dòng)把person傳到函數(shù)里作為this(推薦)
call指定this
this寫foreach
默認(rèn)把a(bǔ)rray當(dāng)作this
this就是一個(gè)參數(shù)而已崭放,我傳什么就是什么默認(rèn)就是array
this的兩種使用方法
- 隱式傳遞
fn(1,2)=== fn.call(undefined,1,2)
obj.child.fn(1) ===等價(jià)于obj.child.fn.call(obj,child,1) - 顯示傳遞 call apply
fn.call(undefined,1,2)
fn.apply(undefined,[1,2])
bind綁定this
function f1(p1,p2){
console.log(this,p1,p2)
}
let f2 = f1.bind({name:'frank'})
f2就是f1綁定了this之后的新函數(shù)
f2()===f1.call({name:'frank'})
其他參數(shù)
let f3 = f1.bind({name:'frank'},'hi')
f2()===f1.call({name:'frank'},'hi')
箭頭函數(shù)
沒(méi)有arguments和this
箭頭函數(shù)里面的this就是外面的this加call也沒(méi)用
- console.log(this) //this是window
- let a = ()=> console.log(this)
a() //還是window - a.call(1) //還是window
立即執(zhí)行函數(shù)(沒(méi)啥用)
- 以前只有var的時(shí)候 如果想聲明一個(gè)局部變量必須在一個(gè)函數(shù)里聲明才是局部(所以現(xiàn)在直接let就好了)
- 我就想要一個(gè)局部變量不想要全局函數(shù)
-
采用匿名函數(shù)并直接調(diào)用鸽凶,會(huì)報(bào)錯(cuò)所以前面加個(gè)币砂!