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() 會執行我們的函式。