6跃捣、JavaScript 函數(shù)、作用域夺蛇、閉包

函數(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é)果作為返回值歹篓。
  1. 不可以當(dāng)作構(gòu)造函數(shù)瘫证,也就是說,不可以使用new命令庄撮,否則會(huì)拋出一個(gè)錯(cuò)誤背捌。
  2. 不可以使用arguments對(duì)象,該對(duì)象在函數(shù)體內(nèi)不存在洞斯。如果要用毡庆,可以用 rest 參數(shù)代替。
  3. 不可以使用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ù)編譯
  1. 創(chuàng)建AO對(duì)象(Activation Object义矛,執(zhí)行期上下文)
  2. 找形參和變量聲明,將形參的名和變量聲明的名作為AO對(duì)象的屬性名盟萨,值為undefined
  3. 將形參和實(shí)參相統(tǒng)一
  4. 找函數(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í)行
*/
函數(shù)預(yù)編譯過程
全局預(yù)編譯
  1. 生成一個(gè)GO對(duì)象(Global Object)了讨,這個(gè)Go對(duì)象其實(shí)是上文提到的那個(gè)window對(duì)象
  2. 找變量聲明捻激,將變量的名作為Go對(duì)象的屬性名制轰,值為undefined
  3. 找函數(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ì)使得瀏覽器卡頓侨嘀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捂襟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌欢峰,老刑警劉巖葬荷,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纽帖,居然都是意外死亡宠漩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門懊直,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扒吁,“玉大人,你說我怎么就攤上這事室囊〉癖溃” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵融撞,是天一觀的道長盼铁。 經(jīng)常有香客問我,道長尝偎,這世上最難降的妖魔是什么饶火? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮致扯,結(jié)果婚禮上肤寝,老公的妹妹穿的比我還像新娘。我一直安慰自己抖僵,他們只是感情好鲤看,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裆针,像睡著了一般刨摩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上世吨,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天澡刹,我揣著相機(jī)與錄音,去河邊找鬼耘婚。 笑死罢浇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嚷闭,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼攒岛,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了胞锰?” 一聲冷哼從身側(cè)響起灾锯,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嗅榕,沒想到半個(gè)月后顺饮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凌那,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年兼雄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帽蝶。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赦肋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出励稳,到底是詐尸還是另有隱情佃乘,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布麦锯,位于F島的核電站恕稠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扶欣。R本人自食惡果不足惜鹅巍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望料祠。 院中可真熱鬧骆捧,春花似錦、人聲如沸髓绽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顺呕。三九已至枫攀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間株茶,已是汗流浹背来涨。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留启盛,地道東北人蹦掐。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓技羔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卧抗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子藤滥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355