函數(shù)
聲明式函數(shù)
function GetHello(){
console.log("hello world");
}
表達(dá)式函數(shù)
var GetHello = function(){
console.log("hello world");
}
/*此表達(dá)式右邊為一個(gè)匿名函數(shù)疚漆,即沒有名字的函數(shù)*/
立即執(zhí)行函數(shù)
此類函數(shù)沒有聲明,在一次執(zhí)行后即釋放刁赦,瀏覽器再也找不到這個(gè)函數(shù)的引用娶聘,適合做初始化工作。
var num = (function Test(a, b, c){
return a + b + 2*c;
}(1, 2, 3))
/*----------------------------------------------------------------------------------*/
(function(){}())//w3c推薦
(function(){})()//另一種寫法
()是執(zhí)行符號(hào)甚脉,可以執(zhí)行表達(dá)式丸升。
表達(dá)式函數(shù)和聲明式區(qū)別
Fn1();//執(zhí)行
Fn2();//報(bào)錯(cuò)
function Fn1(){
console.log("聲明式")
}
var Fn2 = function(){
console.log("表達(dá)式")
}
js的解析過程分為兩個(gè)階段,預(yù)處理和執(zhí)行期牺氨。Fn1()會(huì)被提升到文檔最上面狡耻,F(xiàn)n2()只提升了var Fn2,所以Fn2()時(shí)就會(huì)報(bào)錯(cuò)波闹。
箭頭函數(shù)
使用場(chǎng)景:一個(gè)函數(shù)以另一個(gè)函數(shù)為參數(shù)酝豪。setTimeout(function(){},100);
const fun = (a,b) => { console.log(a*b) }
//函數(shù)名-----參數(shù)列表--------函數(shù)體
const fun1 = a => { console.log(a*a) }
//如果只有一個(gè)參數(shù)可把參數(shù)列表的括號(hào)去掉,沒有參數(shù)時(shí)要寫空括號(hào)精堕。
const fun2 = a => a*a;
//如果函數(shù)體只有一條語句孵淘,可把大括號(hào)去掉,以這條語句的執(zhí)行結(jié)果作為返回值歹篓。
- 不可以當(dāng)作構(gòu)造函數(shù)瘫证,也就是說,不可以使用
new
命令庄撮,否則會(huì)拋出一個(gè)錯(cuò)誤背捌。- 不可以使用
arguments
對(duì)象,該對(duì)象在函數(shù)體內(nèi)不存在洞斯。如果要用毡庆,可以用 rest 參數(shù)代替。- 不可以使用
yield
命令烙如,因此箭頭函數(shù)不能用作 Generator 函數(shù)么抗。
箭頭函數(shù)的this
箭頭函數(shù)體內(nèi)的this
對(duì)象,就是定義該==函數(shù)==(非對(duì)象)時(shí)所在的作用域指向的對(duì)象亚铁,而不是使用時(shí)所在的作用域指向的對(duì)象蝇刀。(call迫像、bind也無法改變)
var name = 'window';
var A = {
name: 'A',
sayHello: function(){
//必須使用函數(shù)才能產(chǎn)生作用域袒啼?
var s = () => {
console.log(this.name)
}
return s//返回箭頭函數(shù)s
},
sayHi:()=>{
console.log(this);
},
}
var B = {
name: 'B'
}
let sayHello = A.sayHello();
A.sayHi();//window
sayHello();// 輸出A
sayHello.call(B);// call也改變不了的this闻察,輸出A
函數(shù)里面的argument
function Test(){
for (var i = 0;i<arguments.length;i++){
console.log(arguments[i])
}
}
Test(1,2,3);
函數(shù)沒有定義形參奔誓,調(diào)用的時(shí)候傳遞實(shí)參,函數(shù)內(nèi)部與一個(gè)數(shù)組叫arguments站粟,arguments會(huì)保存調(diào)用函數(shù)時(shí)傳遞的實(shí)參列表黍图,實(shí)現(xiàn)了即使沒有形參也可以使用參數(shù)。
函數(shù)調(diào)用時(shí)有實(shí)參有多少個(gè)卒蘸,arguments元素就有多少個(gè)雌隅。并且對(duì)應(yīng)的實(shí)參和arguments元素具有一個(gè)映射關(guān)系。一個(gè)值變了另一個(gè)之也會(huì)跟著變缸沃。如果對(duì)應(yīng)不上了那么他們的映射關(guān)系就不成立了恰起。比如說:
function Test(a,b){
console.log(argument[0]);//1
console.log(argument[1]);//undefine
//映射關(guān)系
a=2;
console.log(argument[0]);//2
argument[0]=3;
console.log(a);//3
/*a和arguments[0]形成映射關(guān)系,一個(gè)變另一個(gè)也變*/
}
Test(1);
高階函數(shù):一個(gè)函數(shù)以另一個(gè)函數(shù)作為參數(shù)趾牧,或者返回值是一個(gè)函數(shù)检盼,這個(gè)函數(shù)成為高階函數(shù)。
改變this指向
call/apply/bind
作用:改變this指向
區(qū)別:后面?zhèn)鞯膮?shù)不同
call
改變調(diào)用者(函數(shù))的this指向翘单,并且執(zhí)行它吨枉。
function Person(name,age){
this.name = name;
this.age = age;
}
var person = new Person("zou",100);
var obj = {}
//Person.call() 沒參數(shù)等效于 person();
//有參數(shù),改變Person里面的this指向obj
Person.call(obj,"jia",200);//第一個(gè)參數(shù)用于指向新增的屬性在哪個(gè)對(duì)象
call的實(shí)際應(yīng)用 --> 用于繼承
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(name,age,tel){
/*調(diào)用別人的函數(shù)實(shí)現(xiàn)自己的功能*/
/*相當(dāng)于哄芜,工廠可以全部零件都自己生產(chǎn)貌亭,也可以去買半成品(call)再自己加工*/
Person.call(this,name,age);//調(diào)用Person方法,利用call改變Person的this指向Student
this.tel = tel;
}
var stu = new Student("zou",20,130256);
apply
apply作用與call一樣认臊,只是參數(shù)的形式不一樣圃庭。
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(name,age,tel){
Person.apply(this,[name,age]);
/*apply方法中的第一個(gè)參數(shù)后面 有且只有 一個(gè)數(shù)組形式的參數(shù)*/
this.tel = tel;
}
var stu = new Student("zou",20,130256);
apply用于求數(shù)組的最大最小值,很是方便
var arr=[1,4,6,2,7,9,5,3]
var maxNum = Math.max.apply(Math,arr)
bind
改變調(diào)用者(函數(shù))的this指向失晴,不執(zhí)行但返回改變后的函數(shù)剧腻。
<body>
<button>按鈕</button>
</body>
<script>
/*點(diǎn)擊按鈕后禁止,三秒鐘后重新開放*/
var btn = document.querySelector("button");
btn.onclick = function(){
this.disabled = true;
setTimeout(function(){
this.disabled = false
}.bind(this),3000)
}
</script>
預(yù)編譯
初識(shí)預(yù)編譯
console.log(Test());//輸出執(zhí)行
function Test(){
return "執(zhí)行";
}
//預(yù)編譯使函數(shù)的聲明整體提升到最上面
console.log(a); //輸出undefine
var a = 10;
/*相當(dāng)于下面-------------------------------------------------------------------------*/
var a;//把聲明提升
console.log(a); //輸出undefine
a = 10;//賦值留在原地
對(duì)于函數(shù) :函數(shù)聲明整體提升
對(duì)于變量 :變量聲明提升涂屁,賦值沒有提升
暗示全局變量
如果一個(gè)變量未經(jīng)聲明就直接賦值书在,那么這個(gè)變量為全局對(duì)象(window對(duì)象)所有。一切聲明的全局對(duì)象全都是window的屬性拆又。
a = 10;// => window.a = 10
var b = 10;// => window.b = 10
function(){
var c = d = 10;
//d是未經(jīng)聲明就賦值的變量儒旬,所以 => window.d = 10,c不在全局作用域內(nèi)所以不歸window所有。
}
console.log(a);//訪問全局變量a帖族,其實(shí)是訪問window.a
預(yù)編譯
預(yù)編譯發(fā)生在函數(shù)執(zhí)行前的前一刻
函數(shù)預(yù)編譯
- 創(chuàng)建AO對(duì)象(Activation Object义矛,執(zhí)行期上下文)
- 找形參和變量聲明,將形參的名和變量聲明的名作為AO對(duì)象的屬性名盟萨,值為undefined
- 將形參和實(shí)參相統(tǒng)一
- 找函數(shù)里面的函數(shù)聲明,值賦予函數(shù)體
function fn(a){
console.log(a);
var a = 123;
console.log(a);
function a(){};
var b = function(){}
console.log(b);
function d() {};
}
fn(1);//看看打印的都是啥
/*最終的AO
AO={
a:function a(){...},
b:undefined,
d:function(){...}
}
執(zhí)行期才把賦值操作執(zhí)行
*/
全局預(yù)編譯
- 生成一個(gè)GO對(duì)象(Global Object)了讨,這個(gè)Go對(duì)象其實(shí)是上文提到的那個(gè)window對(duì)象
- 找變量聲明捻激,將變量的名作為Go對(duì)象的屬性名制轰,值為undefined
- 找函數(shù)聲明,值賦予函數(shù)體
先全局預(yù)編譯胞谭,后函數(shù)預(yù)編譯垃杖,函數(shù)預(yù)編譯直接忽略if,for等判斷語句丈屹,里面有聲明也要提升.
let a = 10;
console.log(b,c);//undefined undefined
if(a==0){
var b=20;
}else{
var c=30;
}
可見if else語句中的var語句被提升到了外面调俘。事實(shí)上if、else if旺垒、else彩库、for、switch先蒋、while之類(除function外)的都會(huì)這樣骇钦。
作用域
作用域
每一個(gè)javascript函數(shù)都是一個(gè)對(duì)象,對(duì)象中有一些屬性我們可以訪問竞漾,有一些不可以眯搭,這些屬性僅供javascript引擎存取,[[scope]]就是其中一個(gè)业岁。[[scope]]指的就是我們所說的作用域鳞仙,其中儲(chǔ)存了運(yùn)行期上下文的集合。
運(yùn)行期上下文
當(dāng)函數(shù)執(zhí)行時(shí)笔时,會(huì)創(chuàng)建一個(gè)成為執(zhí)行期上下文的內(nèi)部對(duì)象棍好。一個(gè)執(zhí)行期上下文定義了一個(gè)函數(shù)執(zhí)行時(shí)的環(huán)境,函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的執(zhí)行上下文都是不一樣的糊闽,所以多次調(diào)用會(huì)導(dǎo)致創(chuàng)建多個(gè)執(zhí)行上下文梳玫,當(dāng)函數(shù)執(zhí)行完畢他所在的在執(zhí)行上下文就會(huì)被銷毀。
變量查找
一個(gè)函數(shù)在查找變量時(shí)右犹,會(huì)從作用域頂端依次向上查找提澎。
作用域鏈
[[scope]]中所存儲(chǔ)的執(zhí)行其上下文對(duì)象的集合,這個(gè)集合呈鏈?zhǔn)芥溄幽盍矗覀儼堰@種鏈?zhǔn)芥溄咏凶鲎饔糜蜴湣?/p>
例子
function a(){
function b(){
var b = 234;
}
var a = 123;
b();
console.log(a);
}
var glob = 100;
a();
b的[[scope]]里面有三個(gè)對(duì)象盼忌,他自己的AO在最頂端0位置,全局GO在最底端掂墓。當(dāng)它要尋找變量時(shí)谦纱,在0位置的AO找不到,會(huì)去找下一位的AO君编,找到則使用跨嘉,找不到則繼續(xù)下一位。
b中a的AO對(duì)象吃嘿,與a自 身的AO對(duì)象是同一個(gè)對(duì)象祠乃,b只是保存了a的AO對(duì)象的引用梦重,指向那個(gè)對(duì)象。
閉包
內(nèi)部函數(shù)保存到外部亮瓷,必定形成閉包琴拧。閉包會(huì)導(dǎo)致原有的作用域鏈不釋放,造成內(nèi)存泄漏嘱支。
函數(shù)和對(duì)其周圍狀態(tài)(lexical environment詞法環(huán)境)的引用捆綁在一起構(gòu)成閉包(closure)蚓胸。也就是說,閉包可以讓你從內(nèi)部函數(shù)訪問外部函數(shù)作用域除师。在 JavaScript 中沛膳,每當(dāng)函數(shù)被創(chuàng)建,就會(huì)在函數(shù)生成時(shí)生成閉包馍盟。
function a(){
function b(){
//定義b()
var bbb = 234;
console.log(a);
}
var aaa = 123;
return b;//返回b()
}
glob = 100;
var demo = a();
demo();//執(zhí)行b
- a()執(zhí)行時(shí)b()被定義但未執(zhí)行于置,a()執(zhí)行完畢后return b賦值給demo,銷毀他自己的執(zhí)行期上下文AO贞岭。
- 由于b()未執(zhí)行所以并沒有生成他自己的執(zhí)行其上下文八毯,所以只有a的AO和GO。
- 即使a()執(zhí)行完畢后銷毀了瞄桨,但是b有被存起來话速,而b中存起來的跟a未銷毀時(shí)是一樣的,所以此時(shí)demo中可以訪問到a()的屬性aaa芯侥。
例子
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i);
}
}
return arr;
}
var myArr = test();
for(var j = 0;j < 10; j++){
myArr[j]();
}
//輸出10個(gè)10
/*
因?yàn)閙yArr里面存的是個(gè)函數(shù)訪問的是test的AO泊交,test執(zhí)行是i最后賦值為10,這個(gè)AO里面存的i=10柱查,所以會(huì)打印10
每一個(gè)函數(shù)都與test形成閉包廓俭,但是test里面的i已經(jīng)固定為10呢,所以 console.log(i)一定打印10
*/
但我非要輸出0-9呢唉工?
解決(important!)-->使用立即執(zhí)行函數(shù)
//使用立即執(zhí)行函數(shù)
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
(function(j){
arr[j] = function(){
console.log(j);
}
}(i));
//把每次循環(huán)的i值賦值給立即執(zhí)行函數(shù)研乒,由于立即執(zhí)行函數(shù)在讀取的時(shí)候就會(huì)執(zhí)行(其他函數(shù)讀取時(shí)不執(zhí)行),所以i的值可以被保存下來
}
return arr;
}
var myArr = test();
for(var j = 0;j < 10; j++){
myArr[j]();
//此時(shí)數(shù)組里面的函數(shù)打印的是一個(gè)真實(shí)的數(shù)淋硝,而不是test的OA對(duì)象的屬性
}
閉包的作用
1雹熬、實(shí)現(xiàn)公有變量:函數(shù)計(jì)數(shù)器
function Count(){
var count = 0;
function add(){
count++;
console.log(count);
}
function sub(){
count--;
console.log(count);
}
return [add,sub];
}
var arr = new Count();
var add=arr[0];
var sub=arr[1];
add();//1
add();//2
add();//3
sub();//2
2、可以做緩存(存儲(chǔ)結(jié)構(gòu)):eater
function eater(){
var food = "";
var obj = {
eat : function(){
console.log("我在吃" + food);
food="";
},
push : function(myFood){
food = myFood;
}
}
return obj;
}
var eater1 = eater();
eater1.push("蘋果");
eater1.eat();
3谣膳、可以實(shí)現(xiàn)封裝竿报,屬性私有化:Person(高級(jí)使用)
function f1(n) {
return function () {
return n++;
};
}
var a1 = f1(1);
a1() // 1
a1() // 2
a1() // 3
var a2 = f1(5);
a2() // 5
a2() // 6
a2() // 7
//這段代碼中,a1 和 a2 是相互獨(dú)立的继谚,各自返回自己的私有變量烈菌。
4、模塊化開發(fā)、防止污染全局變量(見命名空間--使用閉包)
閉包的危害
無意間形成的閉包會(huì)導(dǎo)致瀏覽器占用內(nèi)存無法釋放僧界,大量的閉包會(huì)使得瀏覽器卡頓侨嘀。