日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

《JavaScript高级程序设计(第四版)》红宝书学习笔记(2)(第四章:变量、作用域与内存)

發布時間:2025/3/17 javascript 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《JavaScript高级程序设计(第四版)》红宝书学习笔记(2)(第四章:变量、作用域与内存) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

個人對第四版紅寶書的學習筆記。不適合小白閱讀。這是part2。持續更新,其他章節筆記看我主頁。

(記 * 的表示是ES6新增的知識點,記 ` 表示包含新知識點)

第四章:變量、作用域與內存

4.1 原始值與引用值

ECMAScript變量可以包含兩種不同類型的數據:原始值引用值。原始值(primitive value)就是最簡單的數據,引用值(reference value)則是由多個值構成的對象。

在把一個值賦給變量時,JavaScript引擎必須確定這個值是原始值還是引用值。上一章討論了6種原始值:Undefined、Nu11、Boolean、Number、String和Symbol。保存原始值的變量是按值(byvalue)訪問的,因為我們操作的就是存儲在變量中的實際值。

引用值是保存在內存中的對象。與其他語言不同,JavaScript不允許直接訪問內存位置,因此也就不能直接操作對象所在的內存空間。在操作對象時,實際上操作的是對該對象的引用(reference)而非實際的對象本身。為此,保存引用值的變量是按引用(by reference)訪問的。

注意:在很多語言中,字符串是使用對象表示的,因此被認為是引用類型。ECMAScript打破了這個慣例。


4.1.1 動態屬性

原始值和引用值的定義方式很類似,都是創建一個變量,然后給它賦一個值。不過,在變量保存了這個值之后,可以對這個值做什么,則大有不同。

  • 對于引用值而言,可以隨時添加、修改和刪除其屬性和方法

  • 原始值不能有屬性,盡管嘗試給原始值添加屬性不會報錯。

注意,原始類型的初始化可以只使用原始字面量形式。如果使用的是new關鍵字,則JavaScript會創建一個Object類型的實例,但其行為類似原始值,下面來看看這兩種初始化方式的差異:

let name1 "Nicholas": let name2 = new String("Matt"); name1.age = 27; name2.age = 26: console.log(name1.age): //-> undefined console.log(name2.age); //-> 26 console.10g(typeof name1); //-> string console.log(typeof name2); // object

4.1.2 復制值

除了存儲方式不同,原始值和引用值在通過變量復制時也有所不同。

1)在通過變量把一個原始值賦到另一個變量時,原始值會被復制到新變量的位置:

let num1 = 5: let num2 = num1;

這里,num1包含數值5。當把num2初始化為num1時,num2也會得到數值5。這個值跟存儲在numl 中的5是完全獨立的,因為它是那個值的副本。這兩個變量可以獨立使用,互不干擾。這個過程如圖4-1所示。

2)在把引用值從一個變量賦給另一個變量時,存儲在變量中的值也會被復制到新變量所在的位置。區別在于,這里復制的值實際上是一個指針,它指向存儲在堆內存中的對象。操作完成后,兩個變量實際上指向同一個對象,因此一個對象上面的變化會在另一個對象上反映出來,如下面的例子所示:

let obj1 = new Object(); let obj2 = obj1: obj1.name = "Nicholas"; console.log(obj2.name); // “Nicholas"

在這個例子中,變量obj1保存了一個新對象的實例。然后,這個值被復制到obj2,此時兩個變量都指向了同一個對象。在給obj1創建屬性name并賦值后,通過obj2也可以訪問這個屬性,因為它們都指向同一個對象。圖4-2展示了變量與堆內存中對象之間的關系:


4.1.3 傳遞參數

ECMAScript中所有函數的參數都是按值傳遞的。這意味著函數外的值會被復制到函數內部的參數中,就像從一個變量復制到另一個變量一樣。如果是原始值,那么就跟原始值變量的復制一樣,如果是引用值,那么就跟引用值變量的復制一樣。

按值傳遞參數時,值會被復制到一個局部變量(即一個命名參數,或者用ECMAScript的話說,就是arguments對象中的一個槽位)。在按引用傳遞參數時,值在內存中的位置會被保存在一個局部變量,這意味著對本地變量的修改會反映到函數外部(這在ECMAScript中是不可能的)。

1)“Javascript中函數按值傳參”這個性質在原始值上可以很明顯的看到:

function addTen(num){num += 10;return num; } let count = 20; let result = addTen(count); console.log(count); //-> 20,沒有變化,原始值沒有受到影響 conaole.log(result); //-> 30

2)而對于引用值來說:

function setName (obj){obj.name = "Nicholas"; } let person = new Object(); setName (person); console.log(person.name); //-> "Nicholas" //看的出來,傳入person對象之后,person對象多了一個name屬性

局部作用域中修改對象,這種變化反映到了全局作用域。但這是不是意味著對象的傳參是按引用傳參呢?再看下一個例子:

function setName(obj){obj.name ="Nicholas";//添加兩條語句:obj = new Object();obj.name = "Greg";//這里試圖將obj重新定義為一個有著不同name屬性的新對象//如果person是按引用傳遞的,那么person應該自動將指針改為指向 name為'Greg'的新對象 } let person = new Object(); setName(person); console.log(person.name); //-> "Nicholas"

person對象的name屬性值仍然是"Nicholas",這意味著:函數中參數的值改變后,原始的引用仍然沒變。當obj在函數內部被重寫時,它變成了一個指向本地對象的指針。而那個對象在函數執行結束時就被銷毀了。

注意:ECMAScript中函數的參數就是局部變量。


4.1.4 確定類型

前一章提到的typeof操作符最適合用來判斷一個變量是否為原始類型。更確切地說,它是判斷一個變量是否為字符串、數值、布爾值或undefined的最好方式。如果值是對象或nu11,那么typeof 會返回"object"。

看的出來,typeof操作符對于引用值的用處不大。所以,ECMAScript提供了一個instanceof操作符:

result = variable instanceof constructor

如果變量是給定引用類型(由其原型鏈決定,將在第8章詳細介紹)的實例,則 instanceof 操作符返回true。如下:

console.log (person instanceof Object); //變量person是Object 嗎? console.log (colors instanceof Array); //變量 colors是Array 嗎? console.log (pattern instanceof RegExp); //變量 pattern是RegExp嗎?

instanceof 檢測任何引用值和Object構造函數都會返回true,因為所有引用值都是Object的實例。當然,對于原始值而言,instanceof則會返回false。

注意:typeof 操作符在用于檢測函數時也會返回“function"。當在 Safari(直到 SafariS)和 Chrome(直到 Chrome 7)中用于檢測正則表達式時,由于實現細節的原因,typeof也會返回“function"。ECMA-262 規定,任何實現內部[[Ca11]]方法的對象都應該在typeof檢測時返回“function"。因為上述瀏覽器中的正則表達式實現了這個方法,所以typeof 對正則表達式也返回“function"。在IE和Firefox中,typeof 對正則表達式返回"object"。


4.2 執行上下文與作用域 `

執行上下文(以下簡稱“上下文”)的概念在JavaScript中是頗為重要的。變量或函數的上下文決定了它們可以訪問哪些數據,以及它們的行為。每個上下文都有一個關聯的變量對象(variable object),而這個上下文中定義的所有變量和函數都存在于這個對象上。雖然無法通過代碼訪問變量對象,但后臺處理數據會用到它。

1)全局上下文是最外層的上下文。根據 ECMAScript實現的宿主環境,表示全局上下文的對象可能不一樣。

在瀏覽器中,全局上下文就是我們常說的window對象(第12章會詳細介紹)。因此所有通過 var定義的全局變量和函數都會成為 window 對象的屬性和方法。使用 let 和 const 的頂級聲明不會定義在全局上下文中,但在作用域鏈解析上效果是一樣的。

上下文在其所有代碼都執行完畢后會被銷毀,包括定義在它上面的所有變量和函數(全局上下文在應用程序退出前才會被銷毀,比如關閉網頁或退出瀏覽器)。

2)每個函數調用都有自己的上下文,即函數上下文。當代碼執行流進入函數時,函數的上下文被推到一個上下文棧上,在函數執行完之后,上下文棧會彈出該函數上下文,將控制權返還給之前的執行上下文。ECMAScript程序的執行流就是通過這個上下文棧進行控制的。

3)上下文中的代碼在執行的時候,會創建變量對象的一個作用域鏈(scope chain)。這個作用域鏈決定了各級上下文中的代碼在訪問變量和函數時的順序,代碼正在執行的上下文的變量對象始終位于作用域鏈的最前端。

如果上下文是函數,則其活動對象(activation object)用作變量對象。活動對象最初只有一個定義變量:arguments(全局上下文中沒有這個變量)。作用域鏈中的下一個變量對象來自包含上下文,再下一個對象來自再下一個包含上下文。以此類推直至全局上下文;全局上下文的變量對象始終是作用域鏈的最后一個變量對象。

代碼執行時的標識符解析是通過沿作用域鏈逐級搜索標識符名稱完成的。搜索過程始終從作用域鏈的最前端開始,然后逐級往后,直到找到標識符。(如果沒有找到標識符,那么通常會報錯)

看一看下面這個例子:

var color = "blue";function changeColor(){if(color === "blue"){color = 'red'; } else{color = 'blue';} } changeColor();

對這個例子而言,函數changeColor()的作用域鏈包含兩個對象:一個是它自己的變量對象(就是定義arguments對象的那個),另一個是全局上下文的變量對象。這個函數內部之所以能夠訪問變量color,就是因為可以在作用域鏈中找到它。

局部作用域中定義的變量可用于在局部上下文中替換全局變量。

內部上下文可以通過作用域鏈訪問外部上下文中的一切,但外部上下文無法訪問內部上下文中的任何東西。上下文之間的連接是線性的、有序的。

注意:函數參數被認為i是當前上下文中的變量,因此也跟上下文中的其他變量遵循相同規則。

PS:上下文還是很好理解的,這里不再多討論。


4.2.1 作用域鏈增強

雖然執行上下文主要有全局上下文和函數上下文兩種(eva1()調用內部存在第三種上下文),但有其他方式來增強作用域鏈。某些語句會導致在作用域鏈前端臨時添加一個上下文,這個上下文在代碼執行后會被刪除。通常在兩種情況下會出現這個現象,即代碼執行到下面任意一種情況時:

  • try/catch 語句的catch塊
  • with語句

這兩種情況下,都會在作用域鏈前端添加一個變量對象。對with語句來說,會向作用域鏈前端添加指定的對象;對catch語句而言,則會創建一個新的變量對象,這個變量對象會包含要拋出的錯誤對象的聲明。看下面的例子:

function buildUrl()(let qs = "?debug=true";with(location){let url = href + qs; //這里引用href實際是引用location.href//而引用qs時引用的其實是buildUrl()中定義的qs變量}return url; //這里url沒有定義,因為let聲明被限制在了塊級作用域 }

這里,with 語句將 location對象作為上下文,因此 location會被添加到作用域鏈前端。bui1dUrl() 函數中定義了一個變量qs。當 with 語句中的代碼引用變量href 時,實際上引用的是location.href,也就是自己變量對象的屬性。在引用qs時,引用的則是定義在 bui1dUrl() 中的那個變量,它定義在函數上下文的變量對象上。而在with語句中使用 var聲明的變量url會成為函數上下文的一部分,可以作為函數的值被返回;但像這里使用let聲明的變量ur1,因為被限制在塊級作用域(稍后介紹),所以在with塊之外沒有定義。


4.2.2 變量聲明 *

ES6之后,JavaScript的變量聲明經歷了翻天覆地的變化。直到ECMAScript5.1,var都是聲明變量的唯一關鍵字。ES6不僅增加了1et和const兩個關鍵字,而且還讓這兩個關鍵字壓倒性地超越var成為首選。

1)使用 var的函數作用域聲明

在使用var聲明變量時,變量會被自動添加到最接近的上下文。在函數中,最接近的上下文就是函數的局部上下文。在with語句中,最接近的上下文也是函數上下文。如果變量未經聲明就被初始化了,那么它就會自動被添加到全局上下文,如下面的例子所示:

function add(num1,num2){var sum = num1 + num2;return sum; } let result = add(10, 20); //-> 30 console.log(sum); //-> 報錯:sum 在這里不是有效變量 這里訪問不到函數內部var聲明的變量

但如果省略上面例子中的關鍵字var,那么sum 在add()被調之后就變成可以訪問的了,如下所示:

function add(num1,num2){sum = num1 + num2;return sum; } let result = add(10, 20); //-> 30 console.log(sum); //-> 30 可以訪問,因為sum未經聲明就被初始化,它被直接添加到了全局上下文

注意:未經聲明而初始化變量是Javascript編程中一個常見的錯誤,會導致很多問題。務必注意。

嚴格模式下,未經聲明而初始化變量會報錯。

PS:變量提升這里不再提及。

2)使用let的塊級作用域聲明

塊級作用域由最近的一對花括號界定。換言之,if塊、while塊、function塊,甚至是單獨的塊(也就是僅僅只有兩個花括號)也是let變量聲明的作用域。

let和var的第二個不同是:在同一作用域不能聲明兩次。重復的var聲明會被忽略,但重復的let聲明會拋出SyntaxError。

嚴格來說,let在Javascript運行時也會被提升,但是由于“暫時性死區”的緣故,(上一章提到過)實際上不能在聲明之前使用let變量。

3)使用const的變量聲明

const聲明只應用到頂級原語或者對象。換言之,賦值為對象的const變量不能再被重新賦值為其他引用值,但對象的鍵不受限制。如果想讓整個對象都不能修改,可以使用Object.freeze(),這樣再給屬性賦值時雖然不會報錯,但會靜默失敗:

const o1 = {}; o1 = {}; //TypeError:給常量賦值const o2 = {}; o2.name = 'Jake'; alert(o2.name); //-> Jake //對象的鍵不受限制const o3 = Object.freeze({}); o3.name = 'Jake'; //這一步沒有意義,因為整個對象都不可被修改了 alert(o3.name); //-> undefined

由于const聲明暗示變量的值是單一類型且不可修改,JavaScript運行時編譯器可以將其所有實例都替換成實際的值,而不會通過查詢表進行變量查找。谷歌的V8引擎就執行這種優化。

注意:開發實踐表明,如果開發流程并不會因此而受很大影響,就應該盡可能地多使用const 聲明,除非確實需要一個將來會重新賦值的變量。這樣可以從根本上保證提前發現重新賦值導致的bug。

4)標識符查找

當在特定上下文中為讀取或寫入而引用一個標識符時,必須通過搜索確定這個標識符表示什么。搜索開始于作用域鏈前端,以給定的名稱搜索對應的標識符。如果在局部上下文中找到該標識符,則搜索停止,變量確定;如果沒有找到變量名,則繼續沿作用域鏈搜索。(注意,作用域鏈中的對象也有一個原型鏈,因此搜索可能涉及每個對象的原型鏈。)這個過程一直持續到搜索至全局上下文的變量對象。如果仍然沒有找到標識符,則說明其未聲明。


4.3 垃圾回收 `

JavaScript是使用垃圾回收的語言,也就是說執行環境負責在代碼執行時管理內存。JavaScript通過自動內存管理實現內存分配和閑置資源回收。基本思路很簡單:確定哪個變量不會使用,然后釋放它占用的內存。這個過程是周期性的,即垃圾回收程序每隔一段時間就會自動運行。

瀏覽器發展史上,用到過兩種標記策略:標記清理和引用技術。JavaScript最常用的垃圾回收策略是標記清理

內存管理:

將內存占用量保持在一個較小的值可以讓頁面性能更好。優化內存占用的最佳手段就是保證在執行代碼時只保存必要的數據。如果數據不再必要,就把它設置成null,從而釋放其引用。這也可以叫做釋放引用。這個建議最適合全局變量和全局對象的屬性。局部變量在超出作用域后會被自動解除引用。

不過要注意,解除對一個值得引用并不會導致相關內存被回收。解除引用的關鍵在于確保相關的值已經不在上下文中了,因此它在下次垃圾回收時會被回收

1)通過let和const聲明提升性能

因為const和let都以塊(而非函數)為作用域,所以這兩個關鍵字可能會更早的讓回收程序介入,盡早回收應該回收的內存。

2)隱藏類和刪除操作

根據Javascript所在的運行環境,有時候需要根據瀏覽器使用的JavaScript引擎來采取不同的性能優化策略。截至2017年,Chrome是最流行的瀏覽器,使用V8 JavaScript引擎。V8在將解釋后的JavaScript代碼編譯為實際的機器碼時會利用“隱藏類”。如果你的代碼非常注重性能,那么這一點可能對你很重要。

運行期間,V8 會將創建的對象與隱藏類關聯起來,以跟蹤它們的屬性特征。能夠共享相同隱藏類的對象性能會更好,V8會針對這種情況進行優化,但不一定總能夠做到。比如下面的代碼:

function Article(){this.title = 'Inauguration Ceremony Features Kazoo Band'} let a1 = new Article(); let a2 = new Article();

V8會在后臺配置,讓這兩個類實例共享相同的隱藏類,因為這兩個實例共享同一個構造函數和原型。假設之后又添加了下面這行代碼:

a2.author ='Jake';

此時兩個Article實例就會對應兩個不同的隱藏類。根據這種操作的頻率和隱藏類的大小,這有可能對性能產生明顯影響。

當然,解決方案就是避免JavaScript的“先創建再補充”(ready-fire-aim)式的動態屬性賦值,并在構造函數中一次性聲明所有屬性,如下所示:

function Article(opt_author){this.title = 'Inauguration Cerenony Features Kazoo Band';this.author = opt_author; } let al = new Article(); let a2 = new Article('Jake');

這樣,兩個實例基本上就一樣了(不考慮hasOwnProperty的返回值),因此可以共享一個隱藏類從而帶來潛在的性能提升。不過要記住,使用delete關鍵字會導致生成相同的隱藏類片段。看一下這個例子:

function Article(){this.title= 'Inauguration Ceremony Features Kazoo Band';this.author ='Jake'; } let a1 = new Article(): let a2 = new Article(); delete a1.author;

在代碼結束后,即使兩個實例使用了同一個構造函數,它們也不再共享一個隱藏類。動態刪除屬與動態添加屬性導致的后果一樣。最佳實踐是把不想要的屬性設置為nu11。這樣可以保持隱藏類不和繼續共享,同時也能達到刪除引用值供垃圾

3)內存泄漏

  • 意外聲明全局變量

    也就是局部作用域中聲明變量時缺少聲明操作符,而導致該變量變成了全局變量。加上var / const / let即可。

  • 定時器

  • 定時器的回調通過閉包引用外部變量時,如:

    let name = 'Jake'; setInterval(()=>{console.log(name); },100) //定時器只要一直運行,回調函數中引用的name就會一直占用內存,垃圾回收程序自然不會清理外部變量
  • 使用閉包

    let outer = () =>{let name = 'Jake';return function(){return name;}; }; //上述代碼創建了一個內部閉包,只要outer函數存在就不能清理name,因為閉包一直在引用它。如果name的內容很大,就會出現很大的問題了
  • 4)靜態分配與對象池

    總結

    以上是生活随笔為你收集整理的《JavaScript高级程序设计(第四版)》红宝书学习笔记(2)(第四章:变量、作用域与内存)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 欧美男女啪啪 | 色桃av| 麻豆69xxnxxporn| 亚洲一区视频网站 | 午夜精品福利一区二区蜜股av | 欧美一级色图 | 亚洲成人v | 亚洲免费观看高清在线观看 | 花房姑娘免费观看全集 | www.欧美色 | 日韩av黄色片 | 神马午夜激情 | 欧洲av网站 | 日韩一区二区三区精品视频 | 亚洲精品视频二区 | 在线观看av黄色 | 一本大道一区二区 | 高h奶汁双性受1v1 | 在线黄色免费网站 | 国产黄色在线免费观看 | 亚洲女人18毛片水真多 | 欧美视频久久 | 原创av| 久久久久久亚洲中文字幕无码 | 人人干免费 | 久久久久久久久免费视频 | 国产夜夜操 | 亚洲熟女乱色综合亚洲av | 九色九一| 国产日韩欧美专区 | 国产精品无码久久久久高潮 | 国产真实老熟女无套内射 | 亚洲国产www | 久久99精品国产.久久久久久 | 性视频网 | 免费在线播放av | 日本男女网站 | 国产夫妻精品 | 亚洲精品成人无码毛片 | 伊人网视频在线观看 | 91在现看| 在线观看免费视频国产 | 国产精品久久亚洲7777 | 成人夜夜 | 最好看的2019年中文视频 | 日本裸体视频 | 精品一区二区三区免费毛片爱 | 鲁丝片一区二区三区 | 国产极品视频 | 99热国产 | 性欧美大战久久久久久久免费观看 | 国产精品人妖 | 黄色变态网站 | 91超碰在线观看 | 国产手机在线播放 | 性xxxx另类xxⅹ | 成人在线免费小视频 | 亚洲 欧美 自拍偷拍 | 深夜视频在线观看免费 | 天天干夜夜添 | 森林影视官网在线观看 | 亚洲成av人片在线观看无 | 色婷婷91| 亚洲福利片| 国产女人水真多18毛片18精品 | 午夜av在线播放 | 丝袜ol美脚秘书在线播放 | 正在播放久久 | 华丽的外出在线 | 综合久久一区二区 | 国产美女明星三级做爰 | 琪琪午夜伦理 | 日韩精品亚洲精品 | 性欧美长视频 | 欧美一级高清片 | 中文字幕一区二区久久人妻网站 | 欧美综合激情 | 黄色不卡| 特级毛片爽www免费版 | 嫩草国产| 免费黄色观看 | 一级黄色免费网站 | 精人妻无码一区二区三区 | 欧美日韩在线视频免费 | jizz欧美大片 | 男插女av| 亚洲av无码成人精品区 | 国产精品乱码 | 日本一区二区三区免费电影 | 小泽玛利亚一区二区三区在线观看 | 精品美女一区二区三区 | 毛片a| 日韩一二三四区 | 国产精品自拍在线观看 | 欧美视频久久久 | 伊伊成人 | 黄wwwww| 亚洲成a人片在线www | 刘亦菲毛片一区二区三区 |