http://www.cnblogs.com/wangfupeng1988/p/3986420.html
執(zhí)行上下文
什么是執(zhí)行上下文
(也叫做“執(zhí)行上下文環(huán)境”)优床?暫且不下定義盲镶,先看一段代碼:
console.log(a) //ReferenceError: a is not defined
console.log(a) //undefined
var a
console.log(a) //undefined
var a = 100
第一句報(bào)錯(cuò)罕拂,a未定義虾标,很正常淮阐。第二句蔓腐、第三句輸出都是undefined矩乐,說(shuō)明瀏覽器在執(zhí)行console.log(a)時(shí),已經(jīng)知道了a是undefined,但卻不知道a是10(第三句中)散罕。
在一段js代碼拿過(guò)來(lái)真正一句一句運(yùn)行之前分歇,瀏覽器已經(jīng)做了一些準(zhǔn)備工作
,也就是我們所說(shuō)的變量提升欧漱。
不僅如此职抡,需要注意代碼注釋中的兩個(gè)名詞——函數(shù)表達(dá)式和函數(shù)聲明。雖然兩者都很常用误甚,但是這兩者在“準(zhǔn)備工作”時(shí)缚甩,卻是兩種待遇。
console.log(f1) //function f1(){}
function f1(){}
上例是函數(shù)聲明,解析文件的過(guò)程中是會(huì)把聲明先讀取,也就是瀏覽器知道了這個(gè)調(diào)用的函數(shù)是存在的
就算是在js文件中函數(shù)調(diào)用在前窑邦,函數(shù)聲明在后擅威,這也是沒(méi)有問(wèn)題的
console.log(f2); //undefined
var f2 = function(){}
上例是函數(shù)表達(dá)式,實(shí)際上也是一個(gè)var一個(gè)函數(shù)的過(guò)程
解析函數(shù)的時(shí),瀏覽器會(huì)把var 的東西先提到最前面
即var f1 = undefined奕翔,沒(méi)有定義的裕寨,你調(diào)用它的函數(shù)有什么作用呢?
所以執(zhí)行肯定出現(xiàn)了錯(cuò)誤
在script文件中派继,在文件執(zhí)行或者函數(shù)執(zhí)行的最開(kāi)始宾袜,瀏覽器會(huì)最先讀取函數(shù)/變量的聲明,將一些賦值的變量也拿出來(lái)驾窟,也就是我們說(shuō)的聲明提升(只有var 聲明的變量才會(huì)提升庆猫,let,const的不會(huì))
我們要知道,在準(zhǔn)備工作
中完成了哪些工作:
- 變量绅络、函數(shù)表達(dá)式——變量聲明月培,默認(rèn)賦值為undefined;
- this——賦值恩急;
- 函數(shù)聲明——賦值杉畜;
這三種數(shù)據(jù)的準(zhǔn)備情況我們稱(chēng)之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”。
為證明我們的觀點(diǎn)衷恭,在控制臺(tái)寫(xiě)上以下代碼此叠,你會(huì)看到
于是我們就可以理解下面兩種不同的情況
情況1
fn('zhangsan');
function fn(name){
alert(name);
} //運(yùn)行后沒(méi)有錯(cuò)誤,并且網(wǎng)頁(yè)彈出警告框
情況2
f1('zty');
var f1 = function(name ){
alert(name)
}
注意:函數(shù)聲明就是以function開(kāi)頭的随珠,函數(shù)賦值則不是
函數(shù)聲明中的函數(shù)可以是匿名的灭袁,函數(shù)賦值不可以。
讓我們來(lái)看看一道比較經(jīng)典的題:
function yideng() {
console.log(1);
}
(function () {
if (false) {
function yideng() {
console.log(2);
}
}
yideng();
})();
//yideng is not a function
在早期的瀏覽器中窗看,彈出來(lái)的結(jié)果應(yīng)該是2,(因?yàn)樽兞刻嵘灼纾麄€(gè) function yideng() { }會(huì)被提升到整個(gè)函數(shù)作用域的頂端)但是如果彈出來(lái)是2的話,我們寫(xiě)的if語(yǔ)句就完全沒(méi)有意義了显沈。后來(lái)瀏覽器為了修正這個(gè)錯(cuò)誤软瞎,像這種情況下面的函數(shù)提升就只是 var yideng
提升到函數(shù)作用域的頂端,本題中false所以沒(méi)進(jìn)入函數(shù)體,所以yideng()就會(huì)報(bào)錯(cuò)yideng is not a function铜涉。而不是像第一題那樣智玻,整個(gè)函數(shù)體都提到前面。
據(jù)說(shuō)除了if芙代,好像while吊奢,switch民逼,for也一樣丁寄。
關(guān)于this
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
上面這個(gè)是阮一峰老師的關(guān)于一些this的內(nèi)部機(jī)制的理解
在箭頭函數(shù)出來(lái)之前,我們只知道this要在函數(shù)執(zhí)行時(shí)才能知道指的是什么
主要場(chǎng)景
- 作為構(gòu)造函數(shù)執(zhí)行
function Foo(name){
this.name = name;
alert(name);
console.log(this.name);
}
var f1 = Foo('zhangsan'); //控制臺(tái)上面打印出zhangsan
var f2 = Foo('hello'); //打印出hello
在這里菲茬,this的存在就顯得比較有意義了铺呵,很多個(gè)對(duì)象都調(diào)用這個(gè)相同的方法裹驰,this到時(shí)指向很多對(duì)象,可以靈活使用片挂,也就不用寫(xiě)那么多的一樣的函數(shù)了
- 作為對(duì)象屬性執(zhí)行
var obj = {
name:'a',
fn: function(){
console.log(this.name);
}
}
obj.fn(); //a
這時(shí)候的this指向執(zhí)行這個(gè)屬性的對(duì)象 幻林,所以也就是a
想想,如果不是作為obj的屬性來(lái)調(diào)用時(shí)音念,會(huì)是怎么樣的一種情況沪饺?
var obj = {
name:'a',
fn: function(){
console.log(this.name);
console.log('kkk');
}
}
var fn1 = obj.fn;
fn1()//undefined
正如上面的代碼:如果fn函數(shù)被賦值到了另一個(gè)變量中,并沒(méi)有作為obj的一個(gè)屬性被調(diào)用闷愤,那么this的值就是window整葡,this.x為undefined。
- 作為普通函數(shù)執(zhí)行
function fn(){
console.log(this);
}
fn(); // this指向全局對(duì)象
- call bind apply
function fn1(name讥脐,age){
console.log(name);
console.log(this);
}
fn1.call({x:100},'zhangsan'遭居,20);
//執(zhí)行結(jié)果:zhangsan {x:100)}
//也就說(shuō)call能讓this指向它所指定的東西,在例子中 就是{x:100}
//常用的就是call.
apply用法與call相同,但是后面是以數(shù)組形式傳遞的
fn1.call({x:200},['zhangsan',20]);
var f2 = function(name){
console.log(name);
console.log(this);
}.bind({y:100},'lisi')
其實(shí)關(guān)于js中的this一直是前端一個(gè)經(jīng)久不衰的話題旬渠,很多筆試題都在this上面做文章俱萍。
this指向的四種情況我們就大概講到這里,來(lái)做幾道課后題吧告丢!
var obj = {
x: 10,
fn: function(){
function f(){
console.log(this);//Window
}
f()
}
};
obj.fn()
什么鼠次?答案不是obj嗎,不是說(shuō)作為對(duì)象屬性來(lái)執(zhí)行的時(shí)候是指向?qū)ο蟮膯幔?/p>
別急芋齿,且聽(tīng)我慢慢道來(lái)!
函數(shù)f雖然是在obj.fn內(nèi)部定義的成翩,但是它仍然是一個(gè)普通的函數(shù)觅捆,this仍然指向window。作為對(duì)象屬性來(lái)執(zhí)行的時(shí)候是指向?qū)ο蟮倪@句話在這個(gè)函數(shù)里指的是fn而不是f麻敌,fn是在obj里面定義的栅炒,但f是在obj.fn里面定義的。不知道這個(gè)解釋你看懂了嗎?
var x = 3;
var y = 4;
var obj = {
x: 1,
y: 6,
getX: function() {
var x =5;
return function() {
return this.x;
}();
},
getY: function() {
var y =7;
return this.y;
}
}
console.log(obj.getX())
console.log(obj.getY())
先給兩分鐘的時(shí)間思考一下赢赊,這道題的結(jié)果應(yīng)該是:
3 6
讓我們來(lái)探究一下吧乙漓!
obj.getX跟我們上面說(shuō)的題其實(shí)差不多,this所在的function其實(shí)只是定義在obj.getX中的一個(gè)普通函數(shù)释移,this自然是指向Window的叭披,從作用域的角度來(lái)看,他會(huì)先在第一層尋找自己的this.name,找到了玩讳,匿名函數(shù)的執(zhí)行環(huán)境具有全局性涩蜘,而且這個(gè)時(shí)候是指向window的,因?yàn)檫@是一個(gè)普通的函數(shù)熏纯。所以應(yīng)該是3同诫。
obj.getY就很循規(guī)蹈矩了,getY就是obj里面的一個(gè)屬性樟澜,作為obj的屬性執(zhí)行時(shí)指向obj误窖,obj的作用域里面的x就等于6。
我們繼續(xù)秩贰。
var length = 10;
function fn(){
console.log(this.length);
}
var obj = {
length:5,
method:function(fn){
fn();
}
}
obj.method(fn);
答案是10霹俺,一樣的道理。function fn其實(shí)是指向全局的啦萍膛!
this指向也是ES6箭頭函數(shù)的一個(gè)重要的特性吭服,下次再單獨(dú)用一篇文章來(lái)寫(xiě)。
關(guān)于作用域
說(shuō)到JS其中一座大山蝗罗,就是我們大名鼎鼎的閉包了艇棕。
不過(guò)在說(shuō)閉包之前,我們要先說(shuō)說(shuō)作用域串塑!
首先應(yīng)該明確沼琉,Js無(wú)塊級(jí)作用域
#include <stdio.h>
void main() {
int i=2;
i--;
if(i) {
int j=3;
}
printf("%d/n",j);
}
以C語(yǔ)言為例,這個(gè)會(huì)彈出錯(cuò)誤桩匪,因?yàn)閖是在if的塊中定義的打瘪,如果跳出了這個(gè)塊,是訪問(wèn)不到的傻昙。
但是在Js中闺骚,不同塊中的還是可以訪問(wèn)的到的。
if(true){
var i = 7;
}console.log(i);//7
父級(jí)作用域
var a = 100;
function fn1(){
var b = 200;
console.log(a);
console.log(b);
}
fn1的父級(jí)作用域是window
函數(shù)里面定義的數(shù)妆档,在外面是改變不了的僻爽,外面是污染不了里面的數(shù)的〖值耄看下面的題:
var a = 100;
function f1(){
var b = 200;
console.log(a);
console.log(b);
}
var a = 10000;
var b = 20000;
f1();//10000 200;
javascript除了全局作用域之外胸梆,只有函數(shù)可以創(chuàng)建的作用域敦捧。
所以,我們?cè)诼暶髯兞繒r(shí)碰镜,全局代碼要在代碼前端聲明兢卵,函數(shù)中要在函數(shù)體一開(kāi)始就聲明好。
之前就踩了一個(gè)坑绪颖!
let person = {
a:2,
say:()=>{
console.log(this.a)
}
}
因?yàn)槲野逊椒▽?xiě)在了對(duì)象里秽荤,而對(duì)象的括號(hào)是不能封閉作用域的。所以此時(shí)的this還是指向全局對(duì)象菠发,所以不是指向person王滤。
讓我們繼續(xù)說(shuō)一下作用域:
如我們上圖:全局,fn滓鸠,bar都分別有自己的作用域雁乡,作用域有上下級(jí)的關(guān)系,上下級(jí)關(guān)系的確定就看函數(shù)是在哪個(gè)作用域下創(chuàng)建的糜俗。例如踱稍,fn作用域下創(chuàng)建了bar函數(shù),那么“fn作用域”就是“bar作用域”的上級(jí)悠抹。
作用域的作用就是能隔離變量珠月,不同作用域下同名變量不會(huì)有沖突。bar需要a就拿bar下面的a楔敌,fn需要a就拿fn下面的a,全局需要就拿全局的a啤挎。
ES6的let和我們所謂的塊結(jié)合就能達(dá)到我們所期待的塊級(jí)作用域的效果了。
作用域和上下文環(huán)境
閉包
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
閉包使用場(chǎng)景:
- 函數(shù)作用一個(gè)返回值
function f1(){
var a = 100;
return function(){
console.log(a);
}
}
var f2 = f1(); //f2(){console.log(a)}卵凑,執(zhí)行時(shí)候去父作用域中尋找(定義時(shí)候的)
var a = 200;//這里的a 與上面的a 是兩碼事
f2(); //100,
- 函數(shù)作為一個(gè)參數(shù)來(lái)傳遞
function f1(){
var a = 100;
return function(){
console.log(a);
}
}
var f2 = f1();
function f3(fn){
var a = 200;
fn();
}
f3(f2); //100
閉包的特殊之處
先看一下這段代碼:
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
var result = compare(5,10)
上面先定義了compare()函數(shù)庆聘,然后在全局作用域中調(diào)用了他。
當(dāng)?shù)谝淮握{(diào)用compare()的時(shí)候勺卢,會(huì)創(chuàng)建一個(gè)包含this,arguments.value1,value2的活動(dòng)對(duì)象伙判,(位于compare()函數(shù)作用域鏈中的第一位),全局執(zhí)行環(huán)境的變量對(duì)象(this,result,compare)在compare()函數(shù)作用域的第二位黑忱。
后臺(tái)的環(huán)境都有一個(gè)表示變量的對(duì)象——變量對(duì)象宴抚,全局環(huán)境的變量對(duì)象始終存在,像compare()函數(shù)的變量對(duì)象甫煞,是局部變量的菇曲,是只在函數(shù)執(zhí)行過(guò)程中存在。創(chuàng)建compare這個(gè)函數(shù)的時(shí)候抚吠,會(huì)先創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈羊娃,保存在內(nèi)部的scope屬性中,當(dāng)你執(zhí)行compare這個(gè)函數(shù)的時(shí)候埃跷,會(huì)先為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境蕊玷,然后將scope屬性中的對(duì)象復(fù)制進(jìn)行你作用域鏈的構(gòu)建,此后又有一些函數(shù)內(nèi)部的一些變量對(duì)象被推進(jìn)到作用域鏈的前端弥雹,在compare()這個(gè)函數(shù)中垃帅,其作用域就包括全局變量和局部變量。作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表剪勿。
一般來(lái)講贸诚,函數(shù)執(zhí)行完畢,局部變量(活動(dòng)對(duì)象)就會(huì)被銷(xiāo)毀厕吉。內(nèi)存中僅保存全局作用域鏈酱固,但是閉包的情況有所不同。
出現(xiàn)閉包的情況時(shí)头朱,在函數(shù)內(nèi)部定義的函數(shù)會(huì)將外部函數(shù)的活動(dòng)對(duì)象添加到它的作用域中运悲,在下面的代碼中,匿名函數(shù)從f1()中被返回的時(shí)候项钮,他的作用域鏈也被初始化為f1函數(shù)的活動(dòng)對(duì)象的全局變量對(duì)象班眯,匿名函數(shù)可以訪問(wèn)所有在f1中定義的所有變量,更重要的是烁巫,在f1函數(shù)執(zhí)行完畢的時(shí)候署隘,他的活動(dòng)對(duì)象也不會(huì)被銷(xiāo)毀,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動(dòng)對(duì)象亚隙,直到匿名函數(shù)被銷(xiāo)毀之后磁餐,f1的活動(dòng)對(duì)象才會(huì)被銷(xiāo)毀。
function f1(){
var a = 100;
return function(){
console.log(a);
}
}
var f2 = f1() //利用上面這個(gè)函數(shù)來(lái)創(chuàng)建一個(gè)新的函數(shù)
var result = f2()//調(diào)用函數(shù)
f2 = null //解除對(duì)匿名函數(shù)的引用阿弃。
相關(guān)面試題目
說(shuō)一下對(duì)于變量提升的理解
Js中函數(shù)和變量的聲明總是會(huì)被解析器提到方法體的最頂部诊霹。(注意,函數(shù)表達(dá)式不會(huì)哦)看上文的關(guān)于作用域這一小節(jié)
最簡(jiǎn)單的例子:
console.log(f);
var f = 100; //undefined,被提到最前面去的只是var f(l類(lèi)型是undefined)
var e = 100;
console.log(e); //100
fn('zhangsan');
function fn(name){
this.name = name;
alert(this.name);
}//運(yùn)行的時(shí)候彈zhangsan恤浪,因?yàn)楹瘮?shù)已經(jīng)被聲明了畅哑,function fn以及被提到最前面去了
f5('xiaosan');
var f5=function(name){
this.name = name;
alert(this.name);
} //出現(xiàn)錯(cuò)誤,因?yàn)檫@是函數(shù)的賦值而不是聲明
閉包塊級(jí)作用域
一般會(huì)給出這樣的代碼水由,讓你看看可能的運(yùn)行結(jié)果
請(qǐng)寫(xiě)出如下點(diǎn)擊的輸出值荠呐,并用三種辦法正確輸出li里面的數(shù)字。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script type="text/javascript">
var list_li = document.getElementsByTagName("li");
for (var i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() { console.log(i);
}
}
</script>
真的是很經(jīng)典的一道題吧翱汀D嗾拧!>现怠媚创!大家可以閉著眼睛說(shuō)不管點(diǎn)擊哪個(gè)li都只會(huì)輸出6。(跟Js的單線程和異步隊(duì)列有關(guān))
解決方法有三個(gè):
- ES6的let
for (let i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() {
console.log(i+1);
}
}
關(guān)于let 可以看看暫時(shí)性死區(qū)這個(gè)知識(shí)點(diǎn)彤恶。
- 閉包钞钙,立即執(zhí)行函數(shù)
for(var i = 0; i < list_li.length; i++){
(function(i){
list_li[i].onclick = function(){
console.log(i+1)
}
})(i)
}
閉包保存這個(gè)變量鳄橘。
- 最后一種方法是佳哥說(shuō)出來(lái)的時(shí)候恍然大悟,這才是最in的解決方案啊我靠芒炼。這道題考的是this啊瘫怜,不是閉包也不是作用域。本刽。鲸湃。
for (var i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() {
console.log(this.innerHTML);
}
}
補(bǔ)充知識(shí)
立即執(zhí)行函數(shù)會(huì)為每次循環(huán)創(chuàng)建一個(gè)單獨(dú)的作用域
立即執(zhí)行的函數(shù)表達(dá)式,只要定義完成子寓,不需要調(diào)用暗挑,馬上執(zhí)行
(function(){ })(i)
var a = 2;
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2
函數(shù)表達(dá)式后面加上一個(gè)括號(hào)會(huì)立即執(zhí)行。
有一個(gè)非常普遍的進(jìn)階用法斜友,就是把他們當(dāng)作函數(shù)調(diào)用并且傳遞函數(shù)進(jìn)去
var a = 2;
(function IIFE(global){
var a = 3;
console.log(a);//3
})(window);
console.log(a);//2
如何理解作用域
自由變量
作用域鏈炸裆,即自由變量的查找
-
閉包的兩個(gè)場(chǎng)景
var a = 100; function f1(){ console.log(a);//此時(shí)在函數(shù)作用域中找不到a,我們把a(bǔ) 稱(chēng)為自由變量 }
在f1作用域中找不到a ,我們就沿著作用域鏈去上一級(jí)尋找蝙寨,a的父級(jí)作用域就是window(看f1是在哪里定義的)然后我們找到了var a = 100;于是開(kāi)開(kāi)心心的打印出 100
-
場(chǎng)景1:函數(shù)作為參數(shù)被傳遞
場(chǎng)景2:函數(shù)作為一個(gè)返回值晒衩。
一般大家是這么形容閉包的:當(dāng)一個(gè)函數(shù)的返回值是另外一個(gè)函數(shù),而返回的那個(gè)函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值墙歪,如果返回的這個(gè)函數(shù)在外部被執(zhí)行听系,就產(chǎn)生了閉包。
閉包是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域的變量的函數(shù)虹菲。
創(chuàng)建閉包的常見(jiàn)方式靠胜,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另外一個(gè)函數(shù)
例如下面的例子,外部執(zhí)行了這個(gè)函數(shù)毕源,是可以訪問(wèn)的到a 這個(gè)變量的浪漠,之所以能夠訪問(wèn)得到這個(gè)變量,主要還是因?yàn)閮?nèi)部函數(shù)的作用域中包含著外面這個(gè)函數(shù)f1的作用域霎褐。function f1(){ var a = 100; return function(){ console.log(a); } } var f2 = f1(); //f2(){console.log(a)}址愿,執(zhí)行時(shí)候去父作用域中尋找(定義時(shí)候的) var a = 200;//這里的a 與上面的a 是兩碼事 f2(); //100
本質(zhì)上無(wú)論何時(shí)何地,如果將函數(shù)當(dāng)作第一級(jí)的值類(lèi)型并且到處傳遞冻璃,你就會(huì)看到閉包在這些函數(shù)中的應(yīng)用响谓。在定時(shí)器,事件監(jiān)聽(tīng)器省艳,Ajax請(qǐng)求娘纷,跨窗口通信,Web Worker或者其他任務(wù)中跋炕,只要使用了回調(diào)函數(shù)赖晶,實(shí)際上就是在使用閉包。
下面是一個(gè)由于沒(méi)有理解閉包和作用域而可能會(huì)遇到的坑
for(var i = 1;i <=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000)
}//彈出來(lái)了5個(gè)6
與我們的預(yù)期不一樣辐烂,我們預(yù)期是依次彈出1,2,3,4,5
根據(jù)作用域的工作原理遏插,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭
代中分別定義的捂贿,像上面的代碼中,他們都被封閉在一個(gè)共享的全局作用
域中涩堤,每次循環(huán)眷蜓,每個(gè)函數(shù)都保存著全局作用域中的活動(dòng)對(duì)象,其中包括i
值胎围,也就是說(shuō)每個(gè)函數(shù)的作用域鏈中都保存著全局作用域中的i,當(dāng)主線程
中的i++這些操作結(jié)束之后德召,i已經(jīng)變成6了白魂,閉包只能取得包含函數(shù)中任何
變量的最后一個(gè)值也就是6,在時(shí)間執(zhí)行到了timer函數(shù)的時(shí)候上岗,因?yàn)閖s解析
的時(shí)候遇到setTimeout會(huì)把回調(diào)抽出來(lái)福荸,放在一個(gè)單獨(dú)的隊(duì)列里面,等到
當(dāng)前處理機(jī)空閑了肴掷,再去執(zhí)行那個(gè)隊(duì)列里的東西敬锐。處理機(jī)先將for循環(huán)全都
處理完畢,再去執(zhí)行SetTimeout這個(gè)函數(shù)呆瞻。此時(shí)全局變量的這個(gè)i就是6台夺,
因此無(wú)法達(dá)到預(yù)期。
解決方法
for(var i = 1;i<=5;i++)
(function(i){
setTimeout(function timer(){
console.log(i); //i是自由變量痴脾,去父級(jí)作用域里面找颤介,我們找到了立即函數(shù)傳進(jìn)來(lái)的i(每次for循環(huán)后都執(zhí)行一次立即函數(shù))
即函數(shù)為每次循環(huán)都開(kāi)拓一個(gè)單獨(dú)的作用域(塊級(jí)作用域),在ES6中只要用{ }就是一個(gè)塊級(jí)作用域了赞赖,不用怕會(huì)被覆蓋
},i*1000)
})(i);
其實(shí)將i 前面的var 改成let 也是可以得到我們想要的效果滚朵,因?yàn)閘et聲明的也是一個(gè)塊級(jí)作用域。
上面的區(qū)別在于前域,我們加了一個(gè)自執(zhí)行函數(shù)辕近,只要定義,不需要調(diào)用匿垄,而且可以立馬執(zhí)行移宅,還會(huì)產(chǎn)生一個(gè)專(zhuān)門(mén)的塊級(jí)作用域,(function(i))(j)就是把j的值傳給i,產(chǎn)生了這么一個(gè)塊級(jí)作用域年堆。
閉包在實(shí)際開(kāi)發(fā)中的作用
模塊化中經(jīng)常需要用到閉包
function f1(){
var something = "something";
return function(){
console.log(something);
}
}
var foo = f1();
foo(); //外面的根本觸碰不到函數(shù)里面的變量
實(shí)際運(yùn)用2
用于封裝變量吞杭,收斂權(quán)限。
function firstLoad(){
var _list = [];//變量前面帶有_表明變量是私有變量
return function(id){
if(_list.indexOf(id) >= 0){
return false;//這個(gè)Id對(duì)應(yīng)的位置上已經(jīng)有東西
}else{
_list.push(id);//這個(gè)Id對(duì)應(yīng)的位置上壓東西進(jìn)去
return true;
}
}
}
var load = firstLoad();
load(10); //true ,第一次加載.
load(10); //false,因?yàn)樯厦嬉呀?jīng)加載過(guò)了
主要就是只有通過(guò)firstload返回的函數(shù)才能去操作__list這個(gè)數(shù)組变丧,如果你在外面直接去操作__list是不可能的芽狗。因?yàn)開(kāi)_list是沒(méi)有被直接暴露出來(lái)的。
理解了這兩段代碼的結(jié)果痒蓬,也許你就理解了什么是閉包童擎。
var name = "The Window";
var object = {
name:"my object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());//var name = "The Window";
var name = "The Window";
var object = {
name:"my object",
getNameFunc:function(){
var that = this;
return function(){
return that.name;
};
}
};
object.getNameFunc()();
// "my object"
談?wù)勎易约旱睦斫獾尉ⅲ驗(yàn)槲覀兪紫纫磧蓚€(gè)函數(shù)其實(shí)都產(chǎn)生了閉包,但是在return function()中return this.name顾复,我們要注意到這其實(shí)是一個(gè)匿名函數(shù)班挖,從作用域的角度來(lái)看,他會(huì)先在第一層尋找自己的this.name,找到了芯砸,匿名函數(shù)的執(zhí)行環(huán)境具有全局性萧芙,而且這個(gè)時(shí)候是指向window的,因?yàn)檫@是一個(gè)普通的函數(shù)假丧。
第二段代碼中双揪,將getNameFunc作用域里面的this保存在閉包可以訪問(wèn)到的變量里,就可以讓閉包訪問(wèn)這個(gè)對(duì)象包帚。(argument也是同樣一個(gè)道理)
(也就是上面講的作為函數(shù)對(duì)象執(zhí)行)
var obj={name:'hhhh',
func:function(){
return this.name;}}
obj.func()//"hhhh"
//至于為什么是getNameFunc()();
var object = {
name:"my object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc());
// ? (){ return this.name; }
一個(gè)括號(hào)其實(shí)就是得到的return的值是那個(gè)function
再加一個(gè)括號(hào)就是再去執(zhí)行那個(gè)function
閉包為什么會(huì)造成內(nèi)存泄漏
先談?wù)動(dòng)?jì)數(shù)垃圾收集渔期,如果0個(gè)引用指向他,那么該對(duì)象就可以被垃圾收集了渴邦。但是計(jì)數(shù)垃圾收集有一個(gè)嚴(yán)重的問(wèn)題疯趟,就是循環(huán)引用的對(duì)象是不能回收的,IE9之前就是用javascript引擎就是使用標(biāo)記清除策略來(lái)實(shí)現(xiàn)谋梭。
循環(huán)引用(兩個(gè)對(duì)象的相互引用)信峻,在函數(shù)調(diào)用之后他們是無(wú)用的可以釋放,但是計(jì)數(shù)算法認(rèn)為章蚣,由于兩個(gè)對(duì)象的每一個(gè)至少都是被引用一次的站欺,兩者都不能被垃圾回收。例如下面的:
function problem(){
var ObjectA = new Object();
var ObjectB = new Object();
ObjectA.someOtherObject = ObjectB;
ObjectB.someOtherObject = ObjectA;
}
在IE9之前纤垂,BOM和DOM對(duì)象都是使用C++以COM對(duì)象的形式來(lái)實(shí)現(xiàn)的矾策,COM對(duì)象的垃圾收集機(jī)制采用的就是計(jì)數(shù)策略,所以IE中只要涉及到BOM和DOM對(duì)象峭沦,就會(huì)存在循環(huán)引用的問(wèn)題贾虽。
像下面這樣子:
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
閉包實(shí)際上很容易造成JavaScript對(duì)象和DOM對(duì)象的隱蔽循環(huán)引用。也就是說(shuō)吼鱼,只要閉包的作用域鏈中保存著一個(gè)HTML元素蓬豁,那么就意味著這個(gè)元素將無(wú)法被銷(xiāo)毀。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert (element.id);
};
}
注意菇肃,閉包的概念是一個(gè)函數(shù)的返回值是另外一個(gè)函數(shù)地粪,返回的那個(gè)函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值,這是一個(gè)element元素事件處理的一個(gè)閉包琐谤。這個(gè)閉包創(chuàng)建了一個(gè)循環(huán)引用蟆技。
- JavaScript對(duì)象element引用了一個(gè)DOM對(duì)象(其id為“someElement”); JS(element) ----> DOM(someElemet)
- 該DOM對(duì)象的onclick屬性引用了匿名函數(shù)閉包,而閉包可以引用外部函數(shù)assignHandler 的整個(gè)活動(dòng)對(duì)象质礼,包括element 旺聚; DOM(someElement.onclick) ---->JS(element)
匿名函數(shù)一直保存著對(duì)assginHandler()活動(dòng)對(duì)象的引用,它所占的內(nèi)存永遠(yuǎn)不會(huì)被回收眶蕉。
所以可以稍稍改進(jìn):
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert (id);
};
element = null;
}
可以首先通過(guò)引用一個(gè)副本變量消除循環(huán)引用砰粹,但是這個(gè)閉包包含外部函數(shù)的全部活動(dòng)對(duì)象,所以就算不直接引用造挽,匿名函數(shù)一直都包含著對(duì)element的引用碱璃。所以最后需要手動(dòng)設(shè)置element變量為null.
閉包的很大作用是可以讀取函數(shù)內(nèi)部的變量,另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中饭入。
只要變量被任何一個(gè)閉包使用了厘贼,就會(huì)被添到詞法環(huán)境中,被該作用域下所有閉包共享圣拄。
關(guān)于函數(shù)提升
alert(a) 1
a(); 2
var a=3;
function a(){
alert(10)
}
alert(a) 3
a=6;
a(); 4
請(qǐng)寫(xiě)出彈出值,并解釋為什么毁欣?
由于函數(shù)提升庇谆,整個(gè)式子會(huì)變成下面這樣
var a;
function a(){
alert(10)
}
alert(a) 1
a(); 2
a=3;
alert(a); 3
a=6;
a(); 4
很明顯凭疮,會(huì)得到下面的結(jié)果
1. f a(){alert(10)} //變量提升饭耳,并且函數(shù)優(yōu)先級(jí)高于變量,如果相同命名执解,變量會(huì)被覆蓋寞肖。
小知識(shí):
console.log(c)//? c(){console.log('hahha')
function c(){
console.log('hahha')
}
var c = 1;
console.log(c) //? c(){console.log('hahha')
var c = 1;
function c(){
console.log('hahha')
}
- 10 //執(zhí)行了a(){alert(10)}
- 3 //給a賦值了3
- a is not a function 此時(shí)已經(jīng)給a賦值了6,a已經(jīng)不是函數(shù)了,所以會(huì)彈出錯(cuò)誤
關(guān)于函數(shù)變量提升的一些特殊情況
function yideng() {
console.log(1);
}
(function () {
if (false) {
function yideng() {
console.log(2);
}
}
yideng();
})();
//yideng is not a function
在早期的瀏覽器中衰腌,彈出來(lái)的結(jié)果應(yīng)該是2,(因?yàn)樽兞刻嵘麦。麄€(gè) function yideng() { }會(huì)被提升到整個(gè)函數(shù)作用域的頂端)但是如果彈出來(lái)是2的話,我們寫(xiě)的if語(yǔ)句就完全沒(méi)有意義了右蕊。后來(lái)瀏覽器為了修正這個(gè)錯(cuò)誤琼稻,像這種情況下面的函數(shù)提升就只是 `var yideng`提升到函數(shù)作用域的頂端,本題中false所以沒(méi)進(jìn)入函數(shù)體饶囚,所以yideng()就會(huì)報(bào)錯(cuò)yideng is not a function帕翻。而不是像第一題那樣,整個(gè)函數(shù)體都提到前面萝风。
據(jù)說(shuō)除了if嘀掸,好像while,switch规惰,for也一樣睬塌。
補(bǔ)充小知識(shí):
if(false){
var a = 1;
}
console.log(a)//undefined
關(guān)于this
寫(xiě)出以下輸出值,解釋為什么?
this.a = 20;
var test = {
a: 40,
init:()=> {
console.log(this.a);
function go() {
console.log(this.a);
}
go.prototype.a = 50;
return go;
}
};
new(test.init())(); //20 50
箭頭函數(shù)的this綁定父級(jí)的詞法作用域衫仑,所以他的this.a固定了是20梨与。
go本身沒(méi)有a 這個(gè)屬性,所以new出來(lái)的對(duì)象也沒(méi)有這個(gè)屬性文狱,會(huì)到原型鏈上面去找粥鞋。所以是50。
this.a = 20;
var test = {
a: 40,
init:()=> {
console.log(this.a);
function go() {
this.a = 60;
console.log(this.a);
}
go.prototype.a = 50;
return go;
}
};
var p = test.init(); //20
p(); // 60
new(test.init())(); // 60瞄崇,60
剛開(kāi)始我以為輸出的結(jié)果會(huì)是20 60 20 60
var p = test.init(); 此時(shí)test.init()會(huì)執(zhí)行一遍呻粹。此時(shí)的箭頭函數(shù)中的this綁定了最外層的this(test父級(jí)作用域的this),也就是this.a = 20
p() 此時(shí)相當(dāng)于執(zhí)行g(shù)o()苏研,注意:這個(gè)時(shí)候是var p = test.init(),所以執(zhí)行p()時(shí)的this是指向windows等浊,也就是我們現(xiàn)在看到的最外層的this.a = 20的那個(gè)this,此時(shí)會(huì)執(zhí)行this.a = 60摹蘑,將windows的this.a 更改成60,執(zhí)行之后打印出來(lái)的this.a=60.
new(test.init())():test.init()會(huì)取到已經(jīng)綁定了的父級(jí)詞法作用域的this筹燕,不過(guò)此時(shí)的this.a已經(jīng)被改為60,所以打印出來(lái)60。繼續(xù)執(zhí)行衅鹿,打印出60撒踪。
思考一下,如果是下面這種情況的話大渤,會(huì)打印出來(lái)什么呢制妄?
執(zhí)行的時(shí)候20
因?yàn)檫@里是var 不是this.a
另外一個(gè)小知識(shí):
function test(){
console.log(this)
}
var obj = {
f:test
}
(obj.f)() //Cannot read property 'f' of undefined
?泵三?耕捞??烫幕?什么鬼俺抽??
原來(lái)是因?yàn)闆](méi)有加纬霞;在瀏覽器看來(lái)
var obj = {f:test}(obj.f)() 是這樣連在一起的凌埂。那肯定會(huì)報(bào)錯(cuò)啊诗芜!
加上分號(hào)之后瞳抓,我們?cè)賮?lái)看一下這段代碼運(yùn)行的結(jié)果是怎么樣的。
function test(){
console.log(this)
}
var obj = {
f:test
};
(obj.f)()//{f: ?}
很明顯伏恐,指向了obj這個(gè)對(duì)象孩哑,因?yàn)槭莖bj調(diào)用了test()。
那如果我們?cè)龠@樣改一下:
function test(){
console.log(this)
}
var obj = {
f:test
};
(false || obj.f)()
猜猜結(jié)果是什么翠桦?是window
横蜒?胳蛮??丛晌?仅炊?這又是什么鬼?澎蛛?抚垄?
短路語(yǔ)句的結(jié)果不也是obj.f,運(yùn)算的結(jié)果應(yīng)該也是obj才對(duì)啊谋逻。
但是()里面是計(jì)算呆馁,所以里面應(yīng)該是表達(dá)式,也就是說(shuō)里面相當(dāng)于var xx = obj.f毁兆,然后xx(),此時(shí)this肯定是指向全局變量window
關(guān)于閉包塊級(jí)作用域
請(qǐng)寫(xiě)出如下點(diǎn)擊的輸出值浙滤,并用三種辦法正確輸出li里面的數(shù)字。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script type="text/javascript">
var list_li = document.getElementsByTagName("li");
for (var i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() { console.log(i);
}
}
</script>
真的是很經(jīng)典的一道題捌椤7睦啊!>グ拧摹菠!大家可以閉著眼睛說(shuō)不管點(diǎn)擊哪個(gè)li都只會(huì)輸出6。(跟Js的單線程和異步隊(duì)列有關(guān))
解決方法有三個(gè):
-
ES6的let
for (let i = 0; i < list_li.length; i++) { list_li[i].onclick = function() { console.log(i+1); } }
關(guān)于let 可以看看暫時(shí)性死區(qū)這個(gè)知識(shí)點(diǎn)骗爆。
-
閉包,立即執(zhí)行函數(shù)
for(var i = 0; i < list_li.length; i++){ (function(i){ list_li[i].onclick = function(){ console.log(i+1) } })(i) }
閉包保存這個(gè)變量蔽介。怎么去理解閉包保存這個(gè)變量呢摘投,也就是說(shuō)這里每次執(zhí)行立即執(zhí)行函數(shù)的時(shí)候,會(huì)傳遞一個(gè)i(實(shí)參)進(jìn)去函數(shù)級(jí)的作用域虹蓄,在這個(gè)函數(shù)級(jí)作用域里面onclick的回調(diào)函數(shù)使用了這個(gè)i犀呼,也就是我們常說(shuō)的閉包了,還要注意的一點(diǎn)是i在這里的按值傳遞的薇组,所以每個(gè)i都是獨(dú)立的各不影響的外臂。
-
最后一種方法是佳哥說(shuō)出來(lái)的時(shí)候恍然大悟,這才是最in的解決方案啊我靠律胀。這道題考的是this啊宋光,不是閉包也不是作用域。炭菌。罪佳。
for (var i = 0; i < list_li.length; i++) { list_li[i].onclick = function() { console.log(this.innerHTML); } }