JS基礎總結(3)——做用域和閉包

前言

農曆2019即將過去,趁着年前幾天上班事情少,整理了一下javascript的基礎知識,在此給你們作下分享,喜歡的大佬們能夠給個小贊。本文在github也作了收錄。javascript

本人github: github.com/Michael-lzg前端

JS 做用域

Javascript 變量的做用域無非就是兩種:全局變量和局部變量。Javascript 語言的特殊之處,就在於函數內部能夠直接讀取全局變量。vue

全局做用域(Global Scope)

在代碼中任何地方都能訪問到的對象擁有全局做用域,通常來講一下幾種情形擁有全局做用域:java

  1. 最外層函數和在最外層函數外面定義的變量擁有全局做用域
var name = 'Jack' // 全局定義
function foo() {
  var age = 23 // 局部定義
  function inner() {
    // 局部函數
    console.log(age) //age 23
  }
  inner()
}

console.log(name) // yuan
console.log(age) // Uncaught ReferenceError: age is not defined,在外部沒有這個變量
foo() // 內嵌函數的打印23
inner() // Uncaught ReferenceError: inner is not defined 由於內嵌函數,找不到這個函數
複製代碼
  1. 全部末定義直接賦值的變量自動聲明爲擁有全局做用域
var name = 'yuan'
function foo() {
  age = 23 // 全局定義
  var sex = 'male' // 局部定義
}
foo()
console.log(age) // 23
console.log(sex) // sex is not defined
複製代碼
  1. 全部 window 對象的屬性擁有全局做用域 通常狀況下,window 對象的內置屬性都都擁有全局做用域,例如 window.alert()、window.location、window.top 等等。

做用域鏈

當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(做用域造成的鏈條)webpack

  1. 做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象
  2. 做用域鏈中的下一個對象來自於外部環境,而在下一個變量對象則來自下一個外部環境,一直到全局執行環境
  3. 全局執行環境的變量對象始終都是做用域鏈上的最後一個對象

當在內部函數中,須要訪問一個變量的時候,首先會訪問函數自己的變量對象,是否有這個變量,若是沒有,那麼會繼續沿做用域鏈往上查找,直到全局做用域。若是在某個變量對象中找到則使用該變量對象中的變量值。git

內部環境能夠經過做用域鏈訪問全部外部環境,但外部環境不能訪問內部環境的任何變量和函數。github

變量提高

ES6 以前咱們通常使用 var 來聲明變量,變量提高以下例子:web

function test() {
  console.log(a) //undefined
  var a = 123
}

// 它的實際執行順序以下
function test() {
  var a
  console.log(a)
  a = 123
}
test()
複製代碼

函數提高

javascript 中不只僅是變量聲明有提高的現象,函數的聲明也是同樣。具名函數的聲明有兩種方式:算法

  • 函數聲明式
  • 函數字面量式
//函數聲明式
function bar() {}
//函數字面量式
var foo = function() {}
複製代碼

函數提高是整個代碼塊提高到它所在的做用域的最開始執行vue-cli

console.log(bar)
function bar() {
  console.log(1) //ƒ bar () { console.log(1)}
}

// 實際執行順序
function bar() {
  console.log(1)
}
console.log(bar)
複製代碼

閉包

閉包就是可以讀取其餘函數內部變量的函數,函數沒有被釋放,整條做用域鏈上的局部變量都將獲得保留。因爲在 javascript 語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」

產生一個閉包

建立閉包最多見方式,就是在一個函數內部建立另外一個函數。閉包的做用域鏈包含着它本身的做用域,以及包含它的函數的做用域和全局做用域。

function fn() {
  var a = 1,
    b = 2

  function f1() {
    return a + b
  }
  return f1
}
複製代碼

上面例子中的 f1 就是一個閉包

閉包的應用

  1. 設計私有的方法和變量。 任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數外部訪問這些變量。私有變量包括函數的參數、局部變量和函數內定義的其餘函數。把有權訪問私有變量的公有方法稱爲特權方法(privileged method)。
function Animal() {
  // 私有變量
  var series = '哺乳動物'
  function run() {
    console.log('Run!!!')
  }

  // 特權方法
  this.getSeries = function() {
    return series
  }
}
複製代碼
  1. 匿名函數最大的用途是建立閉包。減小全局變量的使用。從而使用閉包模塊化代碼,減小全局變量的污染。
var objEvent = objEvent || {}
(function() {
  var addEvent = function() {
    // some code
  }
  function removeEvent() {
    // some code
  }

  objEvent.addEvent = addEvent
  objEvent.removeEvent = removeEvent
})()
複製代碼

addEvent 和 removeEvent 都是局部變量,但咱們能夠經過全局變量 objEvent 使用它,這就大大減小了全局變量的使用,加強了網頁的安全性。

  1. 定義模塊,咱們將操做函數暴露給外部,而細節隱藏在模塊內部。
function module() {
	var arr = [];
	function add(val) {
		if (typeof val == 'number') {
			arr.push(val);
		}
	}
	function get(index) {
		if (index < arr.length) {
			return arr[index]
		} else {
			return null;
		}
	}
	return {
		add: add,
		get: get
	}
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add('xxx');
console.log(mod1.get(2));
複製代碼

使用閉包的注意點

  1. 因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在 IE 中可能致使內存泄露。解決方法時,在退出函數以前,將不使用的局部變量所有刪除。
function makeAdder(x) {
  return function(y) {
    return x + y
  }
}

var add5 = makeAdder(5)
var add10 = makeAdder(10)

console.log(add5(2)) // 7
console.log(add10(2)) // 12

// 釋放對閉包的引用
add5 = null
add10 = null
複製代碼

add5 和 add10 都是閉包。它們共享相同的函數定義,可是保存了不一樣的環境。在 add5 的環境中,x 爲 5。而在 add10 中,x 則爲 10。最後經過 null 釋放了 add5 和 add10 對閉包的引用。

  1. 閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法,把內部變量看成它的私有屬性,這時必定要當心,不要隨便改變父函數內部變量的值。

推薦文章

關注的個人公衆號不按期分享前端知識,與您一塊兒進步!

相關文章
相關標籤/搜索