淺談JS做用域和閉包

函數表達式和函數聲明

變量/函數聲明都會提早
console.log(a)
let a =1
那麼打印出來的a爲 undefined,由於會將a提到前面並賦予默認值undefined
函數聲明:函數聲明會將函數提到調用函數變量的前面
fn('里斯')//不會報錯
function fn(name) {
console.log();
}
函數表達式:
fn1();//會保錯,由於fn1爲undefeated
const fn1=function fn(name) {
console.log();
}

執行上下文(變量提高)

  • 範圍:一段<script> 或者一個函數
  • 全局:變量定義、函數聲明 一段<script>
  • 函數:變量定義、函數聲明、this、arguments

this

  • this須要在執行時才能確認值,定義時沒法確認
  • this做爲普通函數執行,指向的是window
  • this做爲對象執行,指向的是調用者對象
  • this做爲構造函數執行,指向的是構造函數對象
  • call apply bind

做用域 

  • 無塊級做用域
 
if(true) {
const name='zhangsan'
}
console.log(name);// 'zhangsan'
 
  • 全局做用域和函數做用域
 
const a=12;// 全局變量,全局均可以訪問,該變量容易被污染
function() {
const b =32; // 函數做用域 函數內部能夠訪問
console.log(a); /// a變量在函數中沒有定義,那麼該變量稱之爲自由變量
console.log(b);
}
 
  • 指向函數對象與函數調用
 
var xiaoming = { 
  name: '小明',
  birth: 1990,
  age: function () {
     var y = new Date().getFullYear(); 
     return y - this.birth;
   } };
xiaoming.age; // 只是一個Func對象 
xiaoming.age(); // Func的執行
 
同時strict模式下(use strict)讓函數的this指向undefined,事實是不管是strict模式,this指向undefined或window,能夠經過apply和call來控制this的指向的,完成this的綁定。
 
  • apply
它接收兩個參數,第一個參數就是須要綁定的this變量,第二個參數是Array,表示函數自己的參數。
用apply修復getAge()調用
 
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var user= { name: '小明', birth: 1990, age: getAge }; user.age(); // 25
 getAge.apply(user, []); // 25, this指向user, 參數爲空 
  • call
apply是經過將參數進行打包成數組Array的方式傳入,call是經過將參數以順序的方式傳入Func
 
Math.max.apply(null, [3, 5, 4]); // 5 
Math.max.call(null, 3, 5, 4); // 5

  

對於普通的函數this的指向通常設置爲null
默認值
function test(x,y='world'){
console.log('');
}
  • bind
bind 經過函數變量的方式調用bind函數綁定this對象指向
const ageFunc=function getAge() { 
var y = new Date().getFullYear();
return y - this.birth;
}.bind({birth:20})
當咱們調用時ageFunc()執行函數時,函數指向{birth:20}這個Object對象
 
  • rest參數
 
function foo(a, b, ...rest) { 
  console.log('a = ' + a);
  console.log('b = ' + b);
  console.log(rest);//3,4,5
}
foo(1, 2, 3, 4, 5);
 
  • This對象的綁定
 
function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
var user= {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 當經過user對象去調用指向age,那麼getAge Func中的this對象指向的是調用者,即詞法做用域
getAge(); // 若是直接getAge()執行,那麼至關this屬於window對象

  

單獨調用函數,好比getAge(),此時,該函數的this指向全局對象,也就是window

閉包 

定義: 內部函數能夠引用外部函數的參數和局部變量,當內部函數返回函數時,相關參數和變量都保存在返回的函數中,這種稱爲「閉包(Closure)」
 
  1. 經過返回一個函數而後延遲執行
  2. 若是裏函數引用了外函數的某個變量,那這個變量就能享受和全局變量同樣的特權,不會被回收!由於該變量一直被Child函數一直訪問着。同時享受全局變量不會被銷燬的特權的閉包變量多到必定數量了,那內存就要撐爆了,一旦超過了計算機能接受的內存閥值,就會致使內存泄漏

函數做爲返回值javascript

函數做爲參數傳遞java

返回函數是根據做用域鏈一層一層往上找,找到便可,不是執行時生效,而是定義時生效數組

  • 函數做爲返回值
function F() {
let a = 10
return function() {
console.log(a) // 自由變量 父級做用域中尋找
}
}
let a =23 let f = new F() f() // 10
  • 函數做爲參數傳遞
function F() {
let a = 10
return function() {
console.log(a) // 自由變量 父級做用域中尋找
}
}

function F1(fn) { let a = 120 fn() } let f = new F() let f1 = new F1(f) f1() // 10
 
自由變量是由定義時的做用域來決定的,不是由執行時的做用域來決定的.

特性

根據閉包相關特性,總結其特色以下:
懶執行 
function lazy_sum(arr) {
    var sum = function () {
        return arr.reduce(function (x, y) {
            return x + y;
        });
    }
    return sum;
}
var results=lazy_sum([1,2,3,4,5]);
當去調用results()時纔會去真正計算求和
私有變量
在面嚮對象語言中,通常在函數內部經過private定義私有變量,而在閉包中則經過內部函數攜帶狀態返回。
function rememberCount(initial ) {
var count = initial || 0;
       return {
inc: function() {
count = count + 1;
return count;
}
}
}
var rc=rememberCount();
rc.inc(); //1
rc.inc(); //2
rc.inc(); //3
在返回的對象中,實現了一個閉包,該閉包攜帶了局部變量count,而且,從外部代碼根本沒法訪問到變量count。換句話說,閉包就是攜帶狀態的函數,而且它的狀態能夠徹底對外隱藏起來

注意 

在閉包中,函數內部定義了數組的變量,當函數返回函數,內部的變量還被新的函數所引用,而是直到調用了f()才執行
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
返回結果並不是是咱們想象的1,4,9
f1(); // 16 f2(); // 16 f3(); // 16
所有都是16!緣由就在於返回的函數引用了變量i,但它並不是馬上執行。等到3個函數都返回時,它們所引用的變量i已經變成了4,所以最終結果爲16。
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
 
經過建立並當即執行,建立一個匿名函數並馬上執行:
(function (x) {
    return x * x;
})(3); // 9

function (x) { return x * x } (3);// 語法會解析錯誤
(function (x) { return x * x }) (3);

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) { //建立了3個function對象,保護了i變量的污染
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

實際開發中的閉包應用

  • 用於封裝變量,收斂權限
function isHasWatchList(){
const _list =[];
return function (id) {
if(_list.indexOf(id) >= 0){
return false;
} else {
_list.push(id);
return true;
}
}
}

// 使用
const hasWatchList = new isHasWatchList();
hasWatchList('10') // true
hasWatchList('10') // false
hasWatchList('10') // false
相關文章
相關標籤/搜索