欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于JS中的作用域中的問題思考分享

 更新時間:2022年04月06日 16:42:39   作者:yerikyu  
這篇文章主要介紹了關(guān)于JS中的作用域中的問題思考分享,scope和?closure是?javascript中兩個非常關(guān)鍵的概念,前者JS用多了還比較好理解而且容易體會到,而?closure就不一樣了。這玩意是真的很容易迷糊,需要的朋友可以參考下

作用域

作用域,也就是我們常說的詞法作用域,說簡單點(diǎn)就是你的程序存放變量、變量值和函數(shù)的地方。根據(jù)作用范圍不同可以分為全局作用域和局部作用域,簡單說來就是,花括號 {}括起來的代碼共享一塊作用域,里面的變量都對內(nèi)或者內(nèi)部級聯(lián)的塊級作用域可見,這部分空間就是局部作用域,在 {}之外則是全局作用域。

全局作用域

在JavaScript中,作用域是基于函數(shù)來界定的。也就是說屬于一個函數(shù)內(nèi)部的代碼,函數(shù)內(nèi)部以及內(nèi)部嵌套的代碼都可以訪問函數(shù)的變量。

function test(a){
    var b = a * 2
    function test2(c){
        console.log(a ,b, c)
    }
    test2(b * 3)
}
test(4) // 4 8 24

關(guān)于JS中的作用域中的沉思_作用域

我們不妨嘗試著來為這套代碼劃分一下作用域,上面定義了一個函數(shù)test,里面嵌套了函數(shù) test2。圖中三個不同的顏色,對應(yīng)三個不同的作用域:

  • ①對應(yīng)著全局 scope,這里只有 test2
  • ②是 test2界定的作用域,包含a、b、bar
  • ③是bar界定的作用域,這里只有c這個變量。

在查詢變量并作操作的時候,變量是從當(dāng)前向外查詢的。就上圖來說,就是③用到了a會依次查詢③、②、①。由于在②里查到了a,因此不會繼續(xù)查①了。這個其實(shí)就是作用域鏈的查找方式,詳細(xì)內(nèi)容我們后續(xù)介紹。

作用域中的錯誤

這里順便講講常見的兩種error, ReferenceError TypeError。如上圖,如果在test2里使用了d,那么經(jīng)過查詢③、②、①都沒查到,那么就會報(bào)一個ReferenceError;

關(guān)于JS中的作用域中的沉思_作用域_02

如果bar里使用了b,但是沒有正確引用,如b.abc(),這會導(dǎo)致TypeError

關(guān)于JS中的作用域中的沉思_閉包_03

局部作用域

在局部作用域里面的變量通常是用到 withletconst

with

對于with第一印象可能就是 with關(guān)鍵字的作用在于改變作用域,但并不代表這個關(guān)鍵字不好用,至少面試的時候大概率會可以被卷起來,如果你不常用的話。

with語句的原本用意是為逐級的對象訪問提供命名空間式的速寫方式,也就是說在指定的代碼區(qū)域,直接通過節(jié)點(diǎn)名稱調(diào)用對象。 with通常被當(dāng)做重復(fù)引用同一個對象中的多個屬性的快捷方式,可以不需要重復(fù)引用對象本身。如下面代碼

var obj = {a: 2, b: 2, c: 2};
with (obj) { 
      a = 5;
      b = 5;
      c = 5;  
}
console.log(obj) // {a: 5, b: 5, c: 5}

我們快速的創(chuàng)建了一個 obj對象,為了能快速改變obj的值我們可以通過 with的方式來進(jìn)行修改,當(dāng)然了,我們也可以通過逐行賦值的方式來進(jìn)行,代碼不夠簡潔就是了。話說回來,在這段代碼中,我們使用了 with語句關(guān)聯(lián)了 obj對象,這就意味著在 with代碼塊內(nèi)部,每個變量首先被認(rèn)為是一個局部變量,如果局部變量與 obj對象的某個屬性同名,則這個局部變量會指向 obj對象屬性。

弊端

在上面的例子中,我們可以看到, with可以很好地幫助我們簡化代碼。但生產(chǎn)環(huán)境中卻很少見到,事實(shí)上并不是少見多怪,主要是不推薦使用,為啥嘞?原因如下:

  • 數(shù)據(jù)泄露
  • 性能下降

數(shù)據(jù)泄露

function test3(obj) {
	with (obj) {
		a = 2;
	}
}

var o1 = {
	a: 3
};

var o2 = {
	b: 3
}

foo(o1);
console.log(o1.a)

foo(o2);
console.log(o2.a);	
console.log(a);		

在運(yùn)行的過程中,我們可以看到,對于 o1.a, o2.a的回顯結(jié)果都不奇怪,畢竟對于 o1.a來說a是在作用域中定義的,而 o2.a壓根在o2中未定義,對于這個結(jié)果顯而易見,但為何 a的值會從未定義到已賦值之間的轉(zhuǎn)變呢?這個很危險(xiǎn)的,畢竟這個時候已然出現(xiàn)數(shù)據(jù)泄露

關(guān)于JS中的作用域中的沉思_閉包_04

首先,我們來分析上面的代碼。例子中創(chuàng)建了 o1 o2兩個對象。其中一個有 a屬性,另外一個沒有。 test3(obj)函數(shù)接受一個 obj的形參,該參數(shù)是一個對象引用,并對該對象引用執(zhí)行了 with(obj){...}。在 with 塊內(nèi)部,對 a有一個詞法引用,實(shí)際上是一個 LHS引用,將 2 賦值給了它。

當(dāng)我們將 o1傳遞進(jìn)去, a = 2賦值操作找到了 o1.a并將 2 賦值給它。而當(dāng) o2 傳遞進(jìn)去,o2 并沒有 a 的屬性,因此不會創(chuàng)建這個屬性, o2.a保持 undefined。

但為什么對 o2的操作會導(dǎo)致數(shù)據(jù)的泄漏呢?

要回答這個問題則是需要了解 LHS查詢的機(jī)制,后面有機(jī)會我們再展開來分享,基于LHS查詢的原理分析,當(dāng)我們傳遞 o2 with時, with所聲明的作用域是 o2, 從這個作用域開始對 a 進(jìn)行 LHS查詢,在 o2 的作用域、foo(…) 的作用域和全局作用域中都沒有找到標(biāo)識符 a,因此在非嚴(yán)格模式下,會自動在全局作用域創(chuàng)建一個全局變量,在嚴(yán)格模式下,會拋出 ReferenceError異常。

性能下降

with 會在運(yùn)行時修改或創(chuàng)建新的作用域,以此來欺騙其他在開發(fā)時定義的詞法作用域。with的使用可以令代碼更具有擴(kuò)展性,雖然有數(shù)據(jù)泄漏的可能,但只要稍加注意就可以避免,除此之后,靈活運(yùn)用難道不可以創(chuàng)造出很好地功能嗎?事實(shí)上真的不能,不妨我們考察一下性能特點(diǎn)

function test4() {
	console.time("test4");
	var obj = {
		a: [1, 2, 3]
	};
	for(var i = 0; i < 100000; i++)
	{
		var v = obj.a[0];
	}
	console.timeEnd("test4");
}
test4();

function testWith() {
	console.time("testWith");
	var obj = {
		a: [1, 2, 3]
	};
	with(obj) {
		for(var i = 0; i < 100000; i++) {
			var v = a[0];
		}
	}
	console.timeEnd("testWith");
}

testWith();

關(guān)于JS中的作用域中的沉思_局部變量_05

在處理相同邏輯的代碼中,沒用 with的運(yùn)行時間僅為 1.94 ms。而用 with的運(yùn)用時間長達(dá) 44.13ms。

這是為什么呢?

原因是 JavaScript引擎會在編譯階段進(jìn)行數(shù)項(xiàng)的性能優(yōu)化。其中有些優(yōu)化依賴于能夠根據(jù)代碼的詞法進(jìn)行靜態(tài)分析,并預(yù)先確定所有變量和函數(shù)的定義位置,才能在執(zhí)行過程中快速找到標(biāo)識符。

但如果引擎在代碼中發(fā)現(xiàn)了 with,它只能簡單地假設(shè)關(guān)于標(biāo)識符位置的判斷都是無效的,因?yàn)闊o法知道傳遞給 with用來創(chuàng)建新詞法作用域的對象的內(nèi)容到底是什么。此時引擎的所有的優(yōu)化努力大概率都是無意義的。因此引擎會采取最簡單的做法就是完全不做任何優(yōu)化。這種情況下,設(shè)想我們代碼大量使用 with或者 eval(),那么運(yùn)行起來一定會變得非常慢。無論引擎多聰明,努力將這些悲觀情況的副作用限制在最小范圍內(nèi),也無法避免代碼會運(yùn)行得更慢的事實(shí)。┑( ̄Д  ̄)┍

let

在局部作用域中,關(guān)鍵字let、const倒是很常見了,先說說說let,其是ES6新增的定義變量的方法,其定義的變量僅存在于最近的{}之內(nèi)。

var test5 = true;
if (test5) {
    let bar = test5 * 2;
    console.log( bar );
}
console.log( bar ); // ReferenceError

關(guān)于JS中的作用域中的沉思_局部變量_06

const

與let一樣,唯一不同的是const定義的變量值不能修改

var test6 = true;
if (test6) {
    var a = 2;
    const b = 3; 
    a = 3;
    b = 4; 
}
console.log( a ); 
console.log( b ); 

對于a來說是全局變量,而對于b的作用范圍僅僅是存在與 if的塊內(nèi),此外從嘗試對b進(jìn)行修改的時候也會出錯,提示不能對其進(jìn)行修改

關(guān)于JS中的作用域中的沉思_局部變量_07

作用域鏈

在局部作用中,引用一個變量后,系統(tǒng)會自動在當(dāng)前作用域中尋找var的聲明語句,如果找到則直接使用,否則繼續(xù)向上一級作用域中去尋找var的聲明語句,如未找到,則繼續(xù)向上級作用域中尋找…直到全局作用域中如還未找到var的聲明語句則自動在全局作用域中聲明該變量。我們把這種鏈?zhǔn)降牟樵冴P(guān)系就稱之為"作用域鏈"。這個尋找的過程也是可以在局部作用域中可以引用全局變量的答案

關(guān)于JS中的作用域中的沉思_全局變量_08

代碼中的 testInner2函數(shù)中沒有對變量a進(jìn)行賦值操作,因此由內(nèi)到外一層層尋找,發(fā)現(xiàn)在 testInner中有 var a的賦值操作,由此返回a的賦值,有興趣的讀者不妨把 testInner里面的賦值操作去掉,可以發(fā)現(xiàn)函數(shù)運(yùn)行返回 a的賦值是 yerik。

其實(shí)作用域鏈本質(zhì)是一個對象列表,其保證了變量對象可以有序的訪問。其開始的地方是當(dāng)前代碼執(zhí)行環(huán)境的變量對象,常被稱之為“活躍對象”(AO),變量的查找會從第一個鏈的對象開始,如果對象中包含變量屬性,那么就停止查找,如果沒有就會繼續(xù)向上級作用域查找,直到找到全局對象中,如果找不到就會報(bào) ReferenceError

閉包

簡單的說就是一個函數(shù)內(nèi)嵌套另一個函數(shù),這就會形成一個閉包。請牢記這句話:“無論函數(shù)是在哪里調(diào)用,也無論函數(shù)是如何調(diào)用的,其確定的詞法作用域永遠(yuǎn)都是在函數(shù)被聲明的時候確定下來的”

function test7() {
    var a = 2;
    function test8() {
        console.log( a ); // 2
    }
    test8();
}
test7();

我們看到上面的函數(shù) test7里嵌套了 test8,這樣 test8就形成了一個閉包。在 test8內(nèi)可以訪問到任何屬于 test7的作用域內(nèi)的變量。

function test7() {
    var a = 2;
    function test8() {
        console.log( a ); // 2
    }
    return test8;
}
var test9 = test7();
test9(); // 2

在第8行,我們執(zhí)行完 test7()后按理說垃圾回收器會釋放test7的詞法作用域里的變量,然而沒有,當(dāng)我們運(yùn)行 test9()的時候依然訪問到了 test7中a的值。這是因?yàn)椋m然 test7()執(zhí)行完了,但是其返回了 test8并賦給了 test9, test8依然保持著對 test7形成的作用域的引用。這就是依然可以訪問到 test7中a的值的原因。再想想,“無論函數(shù)是在哪里調(diào)用,也無論函數(shù)是如何調(diào)用的,其確定的詞法作用域永遠(yuǎn)都是在函數(shù)被聲明的時候確定下來的”。

我們再來看另一個例子

function createClosure(){
    var name = "yerik";
    return {
        setStr:function(){
            name = "naug";
        },
        getStr:function(){
            return name + ":hello";
        }
    }
}
var builder = new createClosure();
builder.setStr();
console.log(builder.getStr());

關(guān)于JS中的作用域中的沉思_作用域_09

上面在函數(shù)中返回了兩個閉包,這兩個閉包都維持著對外部作用域的引用,因此不管在哪調(diào)用都是能夠訪問外部函數(shù)中的變量。在一個函數(shù)內(nèi)部定義的函數(shù),閉包中會將外部函數(shù)的自由對象添加到自己的作用域中,所以可以通過內(nèi)部函數(shù)訪問外部函數(shù)的屬性,這就是js模擬私有變量的一種方式。

注意:由于閉包會拓展附帶函數(shù)的作用域(內(nèi)部匿名函數(shù)攜帶外部函數(shù)的作用域),因此,閉包會比其他函數(shù)多占用些內(nèi)存空間,過度使用會導(dǎo)致內(nèi)存占用增加,這個時候如果要對性能進(jìn)行優(yōu)化可能會增加一些難度。

閉包對作用域鏈的影響

由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個值,這引起了一個副作用,如果內(nèi)部函數(shù)在一個循環(huán)中,那么變量的值始終為最后一個值。

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
console.log(data[0])
console.log(data[1])
console.log(data[2])

關(guān)于JS中的作用域中的沉思_局部變量_10

如果我們想要獲取循環(huán)過程的中的結(jié)果,應(yīng)該要怎么做呢?

  • 返回匿名函數(shù)的賦值或者立即執(zhí)行函數(shù)
  • 使用es6的let

匿名函數(shù)的賦值

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (num) {
      return function(){
          console.log(num);
      }
  })(i);
}
console.log(data[0])
console.log(data[1])
console.log(data[2])

無論上是立即執(zhí)行函數(shù)還是返回一個匿名函數(shù)賦值,原理上都是因?yàn)樽兞康陌粗祩鬟f,所以會將變量i的值賦值給實(shí)參num,在匿名函數(shù)的內(nèi)部又創(chuàng)建了一個用于訪問num的匿名函數(shù),這樣每一個函數(shù)都有一個num的副本,互不影響。

關(guān)于JS中的作用域中的沉思_作用域_11

使用let

var data = [];

for (let i = 0; i < 3; i++) {
  data[i] = (function (num) {
      return function(){
          console.log(num);
      }
  })(i);
}
console.log(data[0])
console.log(data[1])
console.log(data[2])

前面我們介紹到let主要是作用域局部變量,由于其的存在,使for中的i存在于局部作用域中,而不是再全局作用域。

關(guān)于JS中的作用域中的沉思_作用域_12

這個函數(shù)表執(zhí)行完畢,其中的變量會被銷毀,但是因?yàn)檫@個代碼塊中存在一個閉包,閉包的作用域鏈中引用著局部作用域,所以在閉包被調(diào)用之前,這個塊級作用域內(nèi)部的變量不會被銷毀。

這個循環(huán)本質(zhì)上就是這樣

var data = [];// 創(chuàng)建一個數(shù)組data;

{ 
	// 進(jìn)入第一次循環(huán)
	let i = 0; // 注意:因?yàn)槭褂胠et使得for循環(huán)為局部作用域
	           // 此次 let i = 0 在這個局部作用域中,而不是在全局環(huán)境中
    data[0] = function() {
    	console.log(i);
	};
}
{ 
    // 進(jìn)入第二次循環(huán)
	let i = 1; // 因?yàn)?let i = 1 和上面的 let i = 0     
	           // 在不同的作用域中,所以不會相互影響
	data[1] = function(){
         console.log(i);
	}; 
}
...

當(dāng)我們執(zhí)行 data[1]()的時候,相當(dāng)于是進(jìn)入了以下的執(zhí)行環(huán)境

{ 
     let i = 1; 
     data[1] = function(){
          console.log(i);
     }; 
}

在上面這個執(zhí)行環(huán)境中,它會首先尋找該執(zhí)行環(huán)境中是否存在i,沒有找到,就沿著作用域鏈繼續(xù)向上找,在其所在的塊級作用域執(zhí)行環(huán)境中,找到i=1,于是輸出1。

到此這篇關(guān)于關(guān)于JS中的作用域中的問題思考分享的文章就介紹到這了,更多相關(guān)JS中的作用域內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 微信小程序中不同頁面?zhèn)鬟f參數(shù)的操作方法

    微信小程序中不同頁面?zhèn)鬟f參數(shù)的操作方法

    這篇文章主要介紹了微信小程序中不同頁面?zhèn)鬟f參數(shù)的操作方法,在開發(fā)項(xiàng)目中,避免不了不同頁面之間傳遞數(shù)據(jù)等,那么就需要進(jìn)行不同頁面之間的一個數(shù)據(jù)傳遞的,需要的朋友可以參考下
    2023-12-12
  • js opener的使用詳解

    js opener的使用詳解

    在JS中,window.opener只是對彈出窗口的母窗口的一個引用。本篇文章主要是對js中opener的使用進(jìn)行了詳細(xì)介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2014-01-01
  • JS 實(shí)現(xiàn)列表與多選框選擇附預(yù)覽動畫

    JS 實(shí)現(xiàn)列表與多選框選擇附預(yù)覽動畫

    本節(jié)為大家介紹的是用JS實(shí)現(xiàn)列表與多選框選擇,并附gif演示動畫,這個例子很詳細(xì),大家可以看看
    2014-10-10
  • 深入理解JavaScript中的并行處理

    深入理解JavaScript中的并行處理

    這篇文章給大家詳細(xì)介紹了JavaScript中的并行處理,一直以來,JavaScript 都并沒有多線程的能力,而單線程在讓開發(fā)者方便的同時,也使它一直不能處理計(jì)算量復(fù)雜的場景,唯一方法就是讓服務(wù)端去做。下來通過這篇文章我們一起來看看詳細(xì)介紹。
    2016-09-09
  • js如何判斷不同系統(tǒng)的瀏覽器類型

    js如何判斷不同系統(tǒng)的瀏覽器類型

    正如標(biāo)題所言使用js如何判斷不同系統(tǒng)的瀏覽器類型,下面有個不錯的示例,感興趣的朋友可以參考下
    2013-10-10
  • 利用jsPDF實(shí)現(xiàn)將圖片轉(zhuǎn)為pdf

    利用jsPDF實(shí)現(xiàn)將圖片轉(zhuǎn)為pdf

    這篇文章主要為大家詳細(xì)介紹了如何利用jsPDF實(shí)現(xiàn)將圖片轉(zhuǎn)為pdf的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-08-08
  • 學(xué)習(xí)jQuey中的return false

    學(xué)習(xí)jQuey中的return false

    這篇文章主要介紹了jQuey中的return false作用,以及解決jquery中的return false不起作用的方法,感興趣的小伙伴們可以參考一下
    2015-12-12
  • 基于Unit PNG Fix.js有時候在ie6下不正常的解決辦法

    基于Unit PNG Fix.js有時候在ie6下不正常的解決辦法

    本篇文章是對Unit PNG Fix.js有時候在ie6下不正常的解決辦法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • webpack打包js文件及部署的實(shí)現(xiàn)方法

    webpack打包js文件及部署的實(shí)現(xiàn)方法

    這篇文章主要介紹了webpack打包js文件的方法及webpack打包后的JS文件如何部署,需要的朋友可以參考下
    2017-12-12
  • 解決layer.confirm選擇完之后消息框不消失的問題

    解決layer.confirm選擇完之后消息框不消失的問題

    今天小編就為大家分享一篇解決layer.confirm選擇完之后消息框不消失的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09

最新評論