1. 程式人生 > >javascript作用域和閉包,this

javascript作用域和閉包,this

全域性作用域

開啟一個 js 檔案,寫了一行程式碼,這行程式碼所在的位置就會是全域性作用域(global scope)。比如:

var name = 'minigui'

區域性作用域

全域性作用域只有一個,在全域性使用域裡面定義的其它的作用域都被稱為區域性作用域(local scope)。區域性作用域是由函式建立的,每個函式都會建立一個區域性作用域。

下面我建立了一個名字是 greet 的函式,在它裡面聲明瞭一個 name 變數,這個變數是在區域性作用域之內。

// 作用域 A: 全域性作用域

var greet = function() {
  // 作用域 B:區域性作用域
  // 這裡是由 greet 這個函式建立的區域性作用域
var name = '貓咪' }

在這個區域性作用域裡面定義的東西,在這個作用域的外面是訪問不到的。試一下:

var greet = function() {
  var name = '貓咪'
}

console.log(name)
// 返回:undefined

在 greet 函式裡宣告的 name 變數,在外面不能訪問到,因為這個 name 是在 greet 函式建立的區域性作用域之內。

詞法作用域

詞法作用域(Lexical Scope)。如果你在一個函式的內部又建立了一個函式,這個內部函式可以訪問到外部函式的作用域。這就是詞法作用域。

看個例子:

// 作用域 A 
var greet = function() { // 作用域 B:在這裡定義了局部變數 name var name = "小刺蝟" var logger = function() { // 作用域 C:在這裡使用了在爸爸(greet)那裡宣告的變數 name console.log(name) } logger() } greet() // 輸出 “小刺蝟”

greet 函式建立了一個作用域,在 greet 裡面定義的 logger 函式也會建立一個作用域,logger 函式是在 greet 內部定義的,它可以訪問到在它之外的作用域裡的東西。這裡就是我們在 logger 函式裡輸出了在 greet 建立的作用域裡定義的 name 這個變數的值。

下面這個例子,name 變數在 function3,function2,function1 都是可以訪問到的:

var name = '貓咪'
var function1 = function() {
  // 在這裡可以訪問到 name
  var function2 = function() {
    // 在這裡也可以訪問到 name
    var function3 = function() {
    // 在這裡還可以訪問到 name
    }
  }
}

假設你在 function3 裡使用了 name 變數,JavaScript 在它裡面沒找到 name,就會往上一層繼續尋找有沒有 name,還沒找到就會繼續往上一層尋找,真到找到為止,如果最終結果還是沒找到 name,就會報 undefined 。

閉包

閉包(closure)。指的是一種函式,這種函式使用了在它周圍作用域下定義的變數。

MDN 裡的關於閉包的中文文件非常好的解釋了什麼是閉包。閉包是一種特別的物件,也可以說是一種特別的函式,這種物件有兩個部分組成,一部分是函式本身,還有一部分是建立這個函式的時候的這個函式所在的那個環境。也就是閉包是函式 + 環境。

也就是函式在某種特別情況下被建立,就會形成閉包。這個特別情況就是,函式使用了在它自己的區域性作用域以外定義的變數。

看個例子:

function robot() {
  var name = '小貓'
  function greet() {
    console.log(`我是${name},喵 ~`)
  }
  return greet
}

var kitty = robot()
kitty()

robot 函式裡定義了一個區域性變數叫 name,在 robot 函式裡又返回了一個函式,名字是 greet,在這個內部函式裡使用了在 robot 區域性作用域下定義的變數 name。這就形成了一個閉包。也就是 kitty 就是一個閉包,這個閉包是 greet 函式,還有 greet 函式引用的 name 所組成的。

再看一個例子:

function robot(name) {
  function greet(greeting) {
    console.log(`我是${name}, ${greeting},`)
  }
  return greet
}

var kitty = robot('小貓')
kitty('喵 ~ ') // 輸出:我是小貓, 喵 ~

var puppy = robot('小狗')
puppy('汪汪 ~') // 輸出:我是小狗,汪汪 ~

這回 robot 函式帶一個 name 引數,robot 還會返回一個 greet 函式,返回的這個 greet 函式也帶一個引數,就是 greeting,在這個 greet 函式裡我們用到了 robot 的 name 引數,還有 greeting 本身的引數 greeting 。

我們基於 robot 又建立了兩個函式,kitty,還有 puppy,這兩個東西都是閉包 。這兩個函式的主體部分都是一樣的,不一樣的地方是,在建立它們的時候的環境是不一樣的。在建立 kitty 的時候,name 的值是 “小貓”,在建立 puppy 的時候,name 的值是 “小狗”。

this

每個作用域裡面都綁定了一個特別的值叫 this ,具體 this 表示的是什麼要看函式是怎麼被執行的。

this 翻譯成中文就是“這…”,它本身並沒有特殊的意義,它的意義完全取決於你在什麼情景下使用它。比如你在跟朋友討論一部電影:《Brother》,“這部電影是北野武的一部作用”,在這句話裡,這(this),指的就是《Brother》這部電影。如果是在另一種語境下,這個 this 表示的可能是完全不同的意思。

在 JavaScript 裡面,this 表示的東西也是要看所處的語境(context),也可以說上下文,情境,環境 …

情形一

在全域性作用域的下面,this 一般表示的是 window 物件。開啟瀏覽器的控制檯,輸入:

this

會返回 window 物件裡的東西。

Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}

在全域性作用域下定義一個變數,你可以在這個 window 物件裡得到。試一下:

var name = '小貓'
this.name // '小貓'
window.name // '小貓'

情形二

再試一下:

var robot = function() {
  console.log(this)
}
robot()

在定義的函式裡面輸出 this ,同樣會得到一個 window 物件。但是如果程式碼使用了 ‘use strict’ ,像這樣:

'use strict';
var robot = function() {
  console.log(this)
}
robot()

返回的會是:undefined

再試一下:

var robot = {
  greet(){
    console.log(this)
  }
}

robot.greet()
// this 表示的是 greet 方法所屬的 robot 物件

robot 是個物件,裡面有個 greet 方法,在這個方法的裡面,this 表示的是方法所屬的物件,這裡 this 表示的就是 robot 這個物件。

情形三

假設頁面上有個元素:

<button id="signup">訂閱</button>

我的程式碼像這樣

var signupButton = document.querySelector('#signup')
var signupLog = function() {
  console.log(this)
}
signupButton.addEventListener('click', signupLog, false)

點選了頁面上的 “訂閱” 按鈕,會在控制檯上輸出在 click 事件處理器,也就是 signupLog 函式裡的 this 所表示的東西,這裡 this 表示的是發生 click 事件的那個 button 元素。

情形四

這次 this 出問題了:

var signupButton = document.querySelector('#signup')
var signupLog = function() {
  console.dir(this) // this 是發生 click 事件的元素
  setTimeout(function () {
    console.log(this) // this 表示的是 window 物件
  }, 1000);
}
signupButton.addEventListener('click', signupLog, false)

這次在 signupLog 裡的 setTimeout 裡的 this 指的是 window 物件。想讓這裡的 this 表示發生 click 事件的元素,可以這樣:

var signupButton = document.querySelector('#signup')
var signupLog = function() {
  var _this = this
  console.dir(_this)
  setTimeout(function () {
    console.log(_this); 
  }, 1000);
}
signupButton.addEventListener('click', signupLog, false)

改變 this 的值

call(),apply(),bind() ,這些方法都可以改變 this 表示的值。

var robot = function() {
 console.log(this)
}

robot.call('hello')
// this 的值是 hello

call() 與 apply() 的用法:

.call(thisArg, arg1, arg2, arg3)
.apply(thisArg, [arg1, arg2])

call() 與 apply() 會執行我們的函式。

參考資料