js作用域&閉包
作用域
作用域是負責收集并維護由所有聲明的變量組成的一系列查詢,并且實施一套嚴格的規(guī)則,確定當前執(zhí)行的代碼對這些變量的訪問權限 -- 《你不知道的javascript》
作用域最大的用處就是隔離變量诈乒,不同作用域下同名變量不會有沖突
1 作用域鏈
作用域有上下級的關系葵第,會在當前作用域中尋找變量荣刑,如果找不到會沿著創(chuàng)建時
作用域鏈一直往上找措嵌,直到找到全局作用域与柑。
var a=1;
function f1(){
var b=2;
function f2(){
var c=3;
console.log(a,b,c);
}
f2();
}
f1();//1 ,2 ,3
2 作用域形成時機
作用域是在一個函數(shù)創(chuàng)建時就已經(jīng)形成的谤辜,而不是調用時.
function a () {
let b = 2;
return function() {
console.log(b)
}
}
let b = 222
a(); // 2 而不是222
以上的示例調用a函數(shù)看似是在全局環(huán)境蓄坏,但是其中的b卻沒有使用全局的b。而且去尋找定義時的b丑念,沒有找到則沿著創(chuàng)建時的作用域鏈往上找涡戳。結果是2
3 作用域類別
作用域包含全局作用域、函數(shù)作用域渠欺、和es6中新增的塊級作用域妹蔽。
在es6沒有出來之前。我們避免變量污染全局的方法是使用函數(shù)作用域挠将。
最常見的是使用自執(zhí)行函數(shù)來包裹模塊胳岂,這樣函數(shù)中的變量只能在局部作用域中生效
(function() {
// do something
})()
而在es6中新增的let和const可以將變量綁定到所在的任意作用域中通常是{...},為其聲明的變量隱式的劫持了所在的塊級作用域舔稀。 -- 《你不知道的javascript》
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i) // 10 10 ... 10
}, 500)
}
// es6之前 需要使用自執(zhí)行函數(shù)來創(chuàng)建函數(shù)作用域來隔絕變量
for (var i = 0; i < 10; i++) {
(function(i){
setTimeout(() => {
console.log(i) // 10 10 ... 10
}, 500)
})(i)
}
// 而es6中直接使用let就可以實現(xiàn)隔絕變量的作用
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i) // 0 1 2 ... 9
}, 500)
}
4 提升
代碼被執(zhí)行前會有聲明提升的過程乳丰。只有聲明本身會被提升,任何作用域都會進行提升操作内贮。
{
console.log(a);
var a = 2;
}
實際上被解析成如下:
{
var a;
console.log(a);
a = 2;
}
let和const聲明不會被提升产园,但是不代表這個作用域中不會進行提升操作。
{
console.log(a); // ReferenceError
let a = 2;
}
函數(shù)的提升是優(yōu)先于變量的
foo(); // 1 打印出來的是1 而不是2
var foo;
function foo () {console.log(1)}
foo = function() {console.log(2)}
this
this的優(yōu)先級
- 函數(shù)是否在new中調用夜郁,如果是的話this綁定的是新創(chuàng)建的對象
var bar = new foo();
- 函數(shù)通過call什燕、apply(顯式綁定),或者bind(硬綁定)竞端。this綁定的是指定的對象
var bar = foo.call(obj)
- 函數(shù)是否在某個上下文對象中調用(隱式綁定)如果是則綁定的是那個上下文對象
var bar = obj.foo()
- 如果都不是的話使用默認綁定屎即,嚴格模式下undefined,否則都是全局對象
var bar = foo()
apply事富、call都是執(zhí)行本身的函數(shù)技俐,而bind是返回一個新函數(shù)。而且可以用來做函數(shù)柯里化
function foo(a,b){
console.log("a"+a+",b:""+b);
}
var bar = foo.bind(null, 2);
bar(3) // a:2 b:3
1 構造函數(shù)中this
function Kimi() {
this.name = 'kimi'
console.log(this) // 'kimi'
}
new Kimi
如何直接執(zhí)行Kimi() this指向window
2 函數(shù)作為對象的一個屬性
let kimi = {
name : 'kimi',
say: function() {
console.log(this.name) // 'kimi'
}
}
kimi.say() // 這樣調用打印的是‘kimi’
如果不作為屬性調用
let a = kimi.say;
a(); // 這樣調用打印的是window
3 使用call和apply
let kimi = {
name : 'kimi'
}
let say = function() {
console.log(this.name) // 'kimi'
}
say.call(kimi);
4 全局
全局中的this一直指向window
let name = 'kimi';
let say = function() {
console.log(this); // window
console.log(this.name); // 'kimi'
}
say();
有以下情況:
let kimi = {
name : 'kimi',
say: function() {
function a () {
console.log(this.name) // 'undefined'
}
a();
}
}
kimi.say();
函數(shù)a雖然是在kimi.say內部定義的统台,但是它仍然是一個普通的函數(shù)雕擂,this仍然指向window。如果需要重新指向kimi則需要使用箭頭函數(shù)或者用let that = this;保存引用
5 this的用途
this可以動態(tài)的綁定執(zhí)行的對象贱勃,起到復用代碼的作用
jQuery.extend = jQuery.fn.extend = function() {
target = this;
}
jQuery.extend和jQuery.fn.extend都指向了同一個函數(shù)井赌,但是當執(zhí)行時,函數(shù)中的this是不一樣的贵扰。
執(zhí)行jQuery.extend(…)時族展,this指向jQuery;執(zhí)行jQuery.fn.extend(…)時拔鹰,this指向jQuery.fn仪缸。
閉包
在一個函數(shù)內部定義的另一個函數(shù),當內部函數(shù)在包裹他的函數(shù)之外被執(zhí)行時列肢,就會形成閉包恰画。同時內部函數(shù)仍然可以訪問到包裹函數(shù)中的局部變量與函數(shù)宾茂。
閉包的兩個常見用途
1. 函數(shù)作為返回值
封裝變量 避免全局中被修改 ,并且記錄狀態(tài)拴还。狀態(tài)不會銷毀丟失
function isFirst() {
var _list = [];
return function(id) {
if(_list.includes(id)) {
return false
}
_list.push(id)
return true;
}
}
var first = isFirst()
first(10) // true
first(10) // false
first(20) // true
2. 函數(shù)作為參數(shù)
function wait(message) {
setTimeout(function timer() {
console.log(message);
}, 1000)
}
wait('hello');
timer 函數(shù)傳遞給setTimeout(),
timer就具有了涵蓋wait()作用域的閉包跨晴,
因此保有對變量message的引用,等到1000回調執(zhí)行后片林,wait的內部作用域不會消失
在定時器端盆、事件監(jiān)聽器、Ajax請求费封、跨窗口通信焕妙、webworker、或者其他的異步或者同步的任務中弓摘,只要使用了回調函數(shù)焚鹊,實際上就在使用閉包。