javascript-函數表達式

函數表達式

  • 定義:函數表達式區別於函數聲明,也是一種定義函數的方式,形似與變量賦值,這個值就是函數體,例如:

    var a = function(){}; // 函數表達式之匿名函數
    var a = function fn(){}; // 函數表達式之具名函數
    (function(){})(); // 匿名函數之立即執行函數
    // 目前知道的是這三種形式, 希望高人補充
  • 特點:
    1 . 區別於函數聲明,和普通變量一樣使用前必須聲明,不聲明在非嚴格模式下被認爲是全局的變量,在嚴格模式下報錯

遞歸

  • 定義:在一個函數中調用自身,遞歸必須要有結束條件階乘

    // fibonacci數列
    function fibonacci(n){
      if(n == 1 || n == 2){ // 結束條件
        return 1;
      }else{
        var num = fibonacci(n-1) + fibonacci(n-2); // 遞歸調用
        return num // 每一層遞歸都返回和
      }
    };
    console.log(fibonacci(6)); // 8
  • 特點:
    1 . 調用匿名函數表達式自身,爲了便於維護,可以通過arguments.callee(指向當前函數的指針)來調用當前函數,這樣做的好處是當遞歸函數換名稱時不用更換內部的函數名稱

    function fibonacci(n){
      if(n == 1){
        return 1;
      }else{
        var num = arguments.callee(n-1) * n ;
        return num
      }
    };
    let a = fibonacci;
    fibonacci = null;
    console.log(a(6)); // 函數內在再次調用fibonacci就會報錯 Uncaught TypeError: fibonacci is not a function

一變

function fibonacci(n){
  if(n == 1){
    return 1;
  }else{
    var num = arguments.callee(n-1) * n ;
    return num
  }
};
let a = fibonacci;
fibonacci = null;
console.log(a(6)); // 720 但是在嚴格模式下回報錯 Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed

二變

'use strict';
let a = (function fibonacci(n){
  if(n == 1){
    return 1;
  }else{
    var num = fibonacci(n-1) * n ;
    return num
  }
});
let b = a;
a=null;
console.log(b(4)); // 24 ,這裏相當於包了一層,如果外邊的變量改變是不會影響函數內部調用的

:在嚴格模式下arguments.callee會報錯,可以通過命名函數表達式來實現

閉包

  • 閉包是指有權訪問另一個作用域中的變量的函數,形式很多,不舉例了,重點在下面
  • 特點:關於閉包的特點都得先理解執行環境、作用域、活動對象的概念

    執行環境: 函數被調用時會創建當前函數的執行環境,可以想象成一個對象,對象包含一個屬性,該屬性指向作用域(作用域鏈表)
    作用域,也可以看成是作用域鏈表,一個list,list的元素是指向活動對象,包括本作用域內的活動對象的指向和對上級的指向,當前函數執行完畢作用域就消失了
    活動對象,包含當前函數內的所有屬性,當沒有作用域鏈引用時活動對象被銷燬

:指向可以理解爲引用,像 a=[], a就指向了內存中的一個數組

{                                              
  scope: scopeList              ----|             
}                                   |
執行環境(context)                  |
[                                   |
  activeObj1: activeObjects1,     --|--|
  activeObj2: activeObjects2,     --|--|--|
  ...                               |  |  |
]                                   |  |  |
作用域鏈(scopeList)           <----|  |  |
{                                      |  |  
  var1: 1,                             |  |
  var2: 1,                             |  |
  var1: [],                            |  |
}                                      |  |
活動對象(activeObjects1)           <--|  |
{                                         |
  _var1: 1,                               |
  _var2: 1,                               |
  _var1: [],                              |
}                                         |
活動對象(activeObjects2)              <--|  

// 可以看出執行環境和作用域鏈是一對一的, 所以當執行完函數後執行環境就沒了,作用域沒有被引用了就也沒了,但是活動對象和作用域鏈是多對多的(途中只展示了一對多) ,所以就算作用域沒了,當前作用域的活動對象也可能被其它作用域引用(例如閉包),所以仍然存在於內存中

閉包與變量

  • 特點:閉包對外層活動對象是引用不是複製(也可以說是複製了引用),這裏寫一個親身經歷的筆試題

    var nAdd = null;
    function f(){
      let n = 99;
      nAdd = () => {
       ++n;
      }
      return () => {
       console.log(n);
      }
    };
    var f1 = f();
    var f2 = f();
    nAdd();
    f1();
    f2();
    nAdd();
    f1();
    f2();

    我認爲這個題挺有意思。這裏不給答案,讀者可以自己先猜一下,然後自己跑一下和自己的猜想對對。
    我認爲這個題目的關鍵在nAdd是在f函數的外層,也就是每次實例化這個f函數都會對nAdd重新賦值,重新賦值後執行環境中n會不同,多次賦值取最後一個,只要能搞清楚執行環境、作用域、活動對象的關係其實不難,不會也沒關係,一開始看到我也懵。

關於this對象

  • 定義:this是和執行環境綁定的
  • 特點:特點和定義息息相關,this和執行環境綁定,只要執行完畢執行環境不存在了就,例如:

    var name = 'china,hebei';
    var province = {
      name: 'hebei',
      getName: function(){
        return function(){
         return this.name;
        };
      }
    };
    console.log(province.getName()()); // china,hebei

從結果來看this指的是全局的那個this,這個和常規理解不太一樣,按說getName屬於province這個對象,this指向province纔對,想想定義就明白了,province.getName()這個執行了getName函數,並返回了一個函數體,再次執行這個函數體的時候getName()已經執行完了,執行完了執行環境當然就不存在了,this就返回給執行的環境(全局環境),那如何改成指向province呢?

var name = 'china,hebei';
var province = {
  name: 'hebei',
  getName: function(){
    let that = this;
    return function(){
     return that.name;
    };
  }
};
console.log(province.getName()()); // hebei

很容易理解,that在getName執行完畢後並不會消失,因爲它所在的活動對象還被最後返回的函數的作用域鏈引用着,所以最後輸出的就是hebei

塊級作用域

  • 定義:通過創建一個立即執行函數來模仿模塊作用域的效果,普通的{}沒有塊級概念

    for(var i=0; i<2; i++){
      console.log(i);
    }
    console.log(i) // 2

塊級作用域,很簡單,通過函數作用域封裝一層即可,例如

(function(){
  for(var i=0; i<2; i++){
    console.log(i);
  }
})()
console.log(i) // Uncaught ReferenceError: i is not defined

私有變量

  • 定義:在函數定義的變量和方法都可以看成是私有變量,可以通過在函數創建閉包實現在函數外部訪問私有變量,稱之爲共有方法(特權方法),例如:

    function Student(){
      var name = 'jiang';
      this.getName = function(){
        return name;
      };
    };
    var xiaoming = new Student();
    console.log(xiaoming.getName()); // jiang   只有這種特權方法可以訪問到

    特點:私有變量只能通過特權方法在函數外部被訪問
    解決的問題:增強函數的封裝性,函數作用域內得變量只能通過特權方法訪問
    帶來的問題:每個實例都會重新創建一個特權方法

靜態私有變量

  • 定義: 在私有作用域內(立即執行函數)定義函數內的私有變量和全局的(變量沒有聲明就賦值時)匿名函數,爲匿名函數添加原型方法,原型方法內訪問函數內的變量,這樣在函數外部可以可以通過變量名稱直接訪問全局的匿名函數上的原型方法,方法內部可以訪問函數私有變量

    (function(){
      let name = 'jiang';
      student = function(){};
      student.prototype.getName = function(){
        return name;
      };
    })()
    console.log(student.prototype.getName()); // jiang
  • 解決的問題:解決了每個實例都不共享的私有變量和特權方法的問題
  • 帶來的問題:解決的問題也變成了它自身的問題,最好的方案是私有變量和靜態私有變量結合使用

模塊模式

  • 定義:模塊模式就是把私有變量和單例模式結合起來,在JS中通過字面對象來創建對象是最簡單的單例模式,而私有變量是函數作用域被的,方法就是定義一個變量(單例對象),然後創建一個立即執行函數返回一個字面對象,對象內部創建公共的特權方法和屬性,函數內部定義私有變量。

單例模式,例如

var a = {};
var a = {}; // 總是隻有一個a對象
var a = (function(){
  var name = 'jiang'; // 私有變量
  return{ // 單例模式
    getName: function(){
      return name;
    }
  }
})()
console.log(a.getName());
  • 解決的問題:在單例內創建私有變量, 單例模式的應用場景是需要重複使用但不需要同時使用的對象,像錯誤提示彈框
  • 帶來的問題:返回的對象是沒有類型的就是不能通過instanceof確認對象類型

增強版的模塊模式

  • 定義:將函數內返回的對象通過構造函數的方式聲明,然後爲其添加特權方法和屬性,然後將對象返回,這樣的對象就可以通過instanceof確認其類型了

    var a = (function(){
      var name = 'jiang';
      function Student(){};
      var xiaoming = new Student();
      xiaoming.getName = function(){
        return name;
      };
      return xiaoming;
    })()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章