前言:
對於軟件行業學習是無止境的,因為知識更替非常快,能夠快速穩固掌握一門新技術是一個程序員應該具備的基本素質。
了解一門語言,了解它的概念非常重要,但是一些優秀的設計思想需要細心和大量實踐才能慢慢參悟,在這之前需要做的是能夠運用它來開發,那么了解一些基礎特性非常有必要,通常這些特性是需要經驗積累,從各種坑中積累出來,但是還有一種看似很笨卻很有效的學習方法.那就是將別人的經驗記錄下來,有事沒事都拿出來看看,集合開發中的經驗,這會非常有效。
記錄的目的在於以后翻閱,同時能將厚厚的書籍里面核心的概念以及技術點去其糟粕,取其精華同時做一個梳理,也會方便了解自己掌握程度以及針對性的學習未掌握的部分。
編程語言的詞法結構是一套基礎性規則,用來描述如何使用這門語言的編寫程序。作為語法的基礎,它規定了諸如變量是什么樣的、怎么寫注釋,以及程序語句之間如何分隔等規則。
字符集:
區分大小寫
JavaScript是區分大小寫的語言。也就是說,關鍵字、變量、函數名和所有的標識符都必須采用一致的大小寫形式
HTML不區分大小寫但是XHTML區分大小寫
JavaScript程序是用Unicode字符集編寫的。Unicode是ASCII和Latin-1的超集,並支持地球上幾乎所有在用的語言
空格、換行符和格式控制符
JavaScript會忽略程序中標識之間的空格,多數情況下統一回忽略換行符
注釋
在行尾 "//" 之后的文本都會被JavaScript當做注釋忽略掉,此外 "/*" 和 "*/" 之間的文本也會當做注釋,這種注釋可以跨行書寫,但不能有嵌套的注釋
直接量
程序中直接使用的數據值 12(數字) 1.2(小數) "hello world"(字符串) true(布爾值) null 正則表達式 對象 數組
標識符和保留字
標識符就是一個名字。在JS中,標識符用來對變量和函數名進行命名,或用作JS代碼中某些循環語句中的跳轉位置的標記。
- 標識符必須以字母、下划線、美元符開始
- 保留字就是JS把一些標識符拿出來用作自己的關鍵字,因此,就不能再程序中把這些關鍵字用作標識符了
- 可選的分號
-
- 缺少分隔符,一條語句結束就成了下一條語句的開始
- 在JS中,如果語句各自獨占一行,通常可以省略語句之間的分號(“}”也可以省略),JS會在換行處自動填補分號,但並不是所有換行出都會自動填補,只有在可以解析的情況下才會填補
- 在編程語言中,能夠表示並操作的值的類型稱做數據類型,編程語言最基本的特性就是能夠支持多種數據類型。
- 當程序需要將值保存起來以備將來使用時,便將其賦值給一個變量
- 變量是一個值的符號名稱,可以通過名稱來獲得對值的引用。變量的工作機制是編程語言的另一個基本特性
- JS的數據類型分為兩類
-
- 原始數據(primitive type)類型,包括數字、字符串和布爾值
- 對象(object type)類型,JS中除了數字、字符串、布爾值、Null、Undefined之外的就是對象了
-
- 對象(object)是屬性(property)的集合,每個屬性都由“名/值對”
- JS定義了一種特殊的對象——數組(array)
- JS定義了一種特殊的對象——函數
- 數字
-
- JS不區分整數值和浮點數值,JS中所有數字均用浮點數值表示
- 當一個數字直接出現在程序中,我們稱之為數字直接量
- JS支持多種格式的數字直接量
-
- 十進制
- 十六進制
- 數組下標的32位整數
- 浮點型直接量
- JS中的算術運算
-
- JS中的算術運算在溢出、下溢或被零整除時不會報錯。當數字運算結果超過了JS所能表示的數字上限,結果為一個特殊的無窮大值,JS中以Infinity表示
- 下溢是當運算結果無限接近於零並比JS能表達的最小值還小的時候,JS將返回0
- 從技術上講,只有JS對象才能擁有方法。然而數字、字符串和布爾值也可以擁有自己的方法(3.6節進行講解)
- 原始類型/對象類型
- 可變類型/不可變類型
-
- 可變類型:值是可以修改的,數組與對象就是可變類型
- 不可變類型:數字、字符串、布爾值、NULL和Undefined——比如,修改一個數值的內容本身就說不通
- JS可以自由地進行數據類型轉換。比如,程序期望使用字符串的地方使用了數字,JS會自動將數字轉換為字符串,使用布爾值的地方使用非布爾值,也會進行自動轉換
- JS中靈活的類型轉換規則對“判斷相等”的定義有影響。等號運算符“==”
- JS變量是無類型的,變量可被賦值任何類型,同樣一個變量也可重新賦予不同類型的值
- 詞法作用域
- 日期和時間
- 字符串
-
- 字符串是一組由16位值組成的不可變的有序序列,每個字符串通常來自於Unicode字符集
- JS定義的各式字符串操作方法均作用於16位值
- JS中字符串是不可變類型,JS中的字符串是固定不變的,類似replace()的方法都返回新的字符串,原始字符串本身並沒發生改變
- 字符串直接量
- 轉義字符
-
- JS字符串中反斜線(\)有着特殊的用途,反斜線符號后加一個字符,就不再表示它們字面含義了,比如,\n就是一個轉義字符,它表示換行符
- \'轉義符,表示單引號
- 字符串的使用
-
- JS的內置功能之一就是字符串連接。字符串使用加(+)號連接,表示兩數相連
- 匹配模式(正則表達式)
- 布爾值
-
- 布爾值指代真或假、開或關、是或否。這個類型只有兩個值,保留字true和false
- 程序中的比較語句的結果通常都是布爾值
- 布爾值通常用於JS中的控制結構中
- 任意JS的值都可以轉換為布爾值。(undefined、null、0、-0、NaN、“”//空字符串)這些值都會被轉換成false。所有其他的值,包括所有對象(數組)都會轉換成true。可以轉換為false的值有時稱做“假值”,反之稱為“真值”
- 布爾值包含toString()方法,這個不重要的API可以將布爾值轉換為字符串
- “&&”運算符執行了邏輯與(AND)操作。當且僅當兩個操作數都是真值時才返回true
- “||”運算符是布爾或(OR)操作
- “!”一元操作符執行了布爾非(NOT)操作
- null和undefined
-
- null是JS語言的關鍵字,表示一個特殊值,用來描述“空值”。對null執行typeof預算,結果返回字符串“object”,也就是說,可以將null認為是一個特殊的對象值,含義是“非對象”。通常認為null是它自有類型的唯一一個成員,它可以表示數字、字符串和對象是“無值”的。
- undefined不是JS語言的關鍵字,它與null一樣表示空缺。用未定義的值表示更深層次的“空值”。它是變量的一種取值,表明變量沒有初始化。typeof運算符得到undefined的類型,則返回“undefined”,表明這個值是這個類型的唯一成員
- null與undefined是不同的,但是它們都是表示“值的空缺”,兩者往往可以互換。判斷相等運算符“==”認為兩者是相等的(喲啊使用嚴格相等運算符“===”來區分它們)。它們都不包括任何屬性和方法。使用”.“和”[]“來存取這兩個值的成員或方法都會產生一個類型錯誤
- 或許可以認為undefined是系統級的,出乎意料的或類似錯誤的值的空缺,而null是表示程序集的、正常的或在意料之中的值的空缺。如果對他們進行操作,賦值給變量或者屬性,或作為參數傳入函數,最佳選擇是使用null
- 全局對象
-
- 當JS解釋器啟動時或者在任何Web瀏覽器加載新頁面的時候,它將創建一個新的全局對象,並給它一組定義的初始屬性
-
- 全局屬性,比如undefined、Infinity和NaN
- 全局函數,比如isNaN()、parseInt()
- 構造函數,比如Date()、RegExp()、String()、Object()和Array()
- 全局對象,比如Math和JSON
- 全局對象的初始屬性並不是保留字,但它們應該當作保留字來對待
- 客戶端JS來講,Windows對象定義了一些額外的全局屬性
- 在代碼的最頂級——不再任何函數內的JS代碼——可以使用JS關鍵字this來引用全局對象
- 在客戶端JS中,在其表示的瀏覽器窗口中的所有JS代碼中,Window對象充當了全局對象。
- 包裝對象
-
- JS對象是一種復合值,它是屬性或已命名值的集合。通過”.”符號來引用屬性值。當屬性值是一個函數的時候,稱其為方法。通過o.m()來調用對象o中的方法
- 字符串也同樣具有屬性和方法,字符串不是對象,但是有屬性。只要引用了字符串的屬性,JS就會將字符串s值通過調用new String(s)的方式轉換成對象,這個對象繼承了字符串的方法,並被用來處理屬性的引用。一旦屬性引用結束,這個新創建的對象就會銷毀。
- 同字符串一樣,數字和布爾值也具有各自的方法:通過Number()和Boolean()構造函數創建一個臨時對象。null和undefined沒有包裝對象,訪問它們的屬性會造成一個類型錯誤
- “==”等於運算符將原始值和其包裝對象視為相等,當“===”全等運算符將它們視為不相等,使用typeof運算符可以看到原始值和其包裝對象的不同
- 不可變的原始值和可變的對象引用
-
- JS中的原始值(undefined、null、布爾值、數字和字符串)與對象(包括數組和函數)有着本質的區別。
- 原始值是不可更改的,任何方法都無法更改一個原始值
- 對數字和布爾值來說改變數字的值本身就說不通,對字符串來說,不那么明顯,看起來字符串組成像是對各字符的數組,我們期望通過制定索引下標來修改字符串中的字符。實際上JS是禁止這樣做的,字符串所有方法看上去返回了一個修改后的字符串,實際上返回的是一個新的字符串值
- 原始值的比較是值的比較,只有在它們的值相等時它們才會相等
- 如果比較兩個單獨的字符串,當且僅當它們的長度相等且每個索引的字符都相等時,JS才認為他們相等
- 對象和原始值不同,它們是可變的——它們的值也是可修改的
- 對象的比較並非值的比較,即使兩個對象包含同樣的屬性及相同的值,它們也是不相等的,各個索引元素完全相等的兩個數組也不相等
- 通常稱對象為引用類型,以此來和JS基本類型區分開
- 對象值都是引用,對象的比較是引用的比較
- 當且僅當它們引用同一個基對象時,它們才相等
- 將數組或對象賦值給一個變量,僅僅是賦值的引用,對象本身並沒有復制一次
- 類型轉換
-
- JS取值非常靈活,從布爾值可以看出,當JS期望使用一個布爾值的時候,可以提供任意類型值,JS將根據需要自行轉換類型
- 顯示類型轉換
-
- JS可以自動做許多類型轉換,但為了代碼變得清晰易讀而做的顯示轉換
- 顯示類轉換最簡單的方法使用Boolean()、Number()、String()或Object()函數
- 對象轉換為原始值
-
- 所有對象繼承了兩個轉換方法。
- toString()
-
- 第一個是toString(),它的作用是返回一個反映這個對象的字符串
- 很多類定義了更多特定版本的toString()
- 數組類的toString()方法,將每個數組元素轉換為一個字符串,並在元素之間添加都好后合並成結果字符串
- 函數類的toString()方法,返回這個函數的實現定義的表示方式,通常是將用戶定義的函數轉換為JS源代碼字符串
- 日期類的toString()方法,返回一個可讀的日期和時間字符串
- RegExp類的toString()方法,將RegExp對象轉換為表示正則表達式直接量的字符串
- valueOf()
-
- 這個方法任務並未詳細定義,如果存在任意原始值,它就默認將對象轉換為表示它的原始值
- 對象是復合值,而且大多數對象無法真正表示為一個原始值,因此簡單的valueOf()方法簡單的返回對象本身,而不是返回一個原始值
- 重復聲明與遺漏聲明
-
- 使用var語句重復聲明變量是合法且無害的。如果重復聲明帶有初始化器,那么這就跟一條簡單的賦值語句沒什么兩樣
- 如果試圖讀取一個沒有聲明的變量值JS會報錯
- 給一個沒有聲明的變量賦值也會報錯
- 變量作用域
-
- 變量的作用域是程序源代碼中定義這個變量的區域
- 全局變量擁有全局作用域
- 函數體內聲明的變量作用域是局部性的,它們只在函數體內有定義
- 函數作用域和聲明提前
-
- JS沒有塊級作用域,JS取而代之的使用了函數作用域
- JS的函數作用域是指在函數內聲明的所有變量在函數體內始終可見,意味着變量在聲明之前已經可用,JS的這個特性被非正式的成為聲明提前,即JS函數里聲明的所有變量(但不涉及賦值)都被“提前”至函數體的頂部
- 作為屬性的變量
-
- 當聲明一個JS全局變量時,實際上是定義了全局對象的一個屬性
- 當使用var聲明一個變量時,創建的這個屬性是不可配置的,也就是說這個變量無法通過delete運算符刪除
- JS全局變量是全局對象的屬性,這是ECMASscript規范中強制規定的,對於局部變量則沒有如此規定
- ECMAScript 5 規范稱“聲明上下文對象”,JS可以允許使用this關鍵字來引用全局對象,卻沒有方法可以引用局部變量中存放的對象。這種存放局部變量的對象的特有性質,是一種對我們不可見的內部實現。
- 作用域鏈(未完全理解)
-
- JS是基於詞法作用域的語言:通過閱讀包含變量定義在內的數行源碼就能知道變量的作用域。
- 全局變量在程序中始終都是有定義的
- 局部變量在聲明它的函數體內以及其所嵌套的函數內始終是有定義的
- /*
- 如果將一個局部變量看做是自定義實現的對象的屬性的話,那么可以換個角度來解讀變量作用域。
- 每一段JS代碼(全局代碼或函數)都有一個與之關聯的作用域鏈。
- 這個作用域鏈是一個對象列表或者鏈表,這組對象定義了這段代碼“作用域中”的變量。
- 當JS需要查找變量x的值的時候(這個過程稱做變量解析),它會從鏈接中的第一個對象開始查找,如果這個對象有名為x的屬性,則會直接使用這個屬性的值,如果第一個對象中不存在名為x的屬性,JS會繼續查找鏈接的下一個對象。
- 如果第二個對象依然沒有名為x的屬性,則會繼續查找下一個對象,以此類推
- 如果作用域鏈就上沒有任何一個對象含有屬性x,那么就認為這段代碼的作用域鏈接上不存在x,並最終拋出一個引用錯誤異常
- */
- /*
- 在JS的最頂層代碼中(也就是不包含在任何函數定義的代碼),作用域鏈由一個全局對象組成
- 在不包含嵌套的函數體內,作用域鏈上有兩個對象,第一個是定義函數參數和局部變量的對象,第二個是全局對象
- 在一個嵌套的函數體內,作用域鏈上至少有三個對象
- 理解對象鏈的創建規則是非常重要的
- 當定義一個函數時,它實際上保存一個作用域鏈。
- 當調用這個函數時,它創建一個新的對象來存儲它的局部變量,並將這個對象添加至保存的那個作用域鏈上,同時創建一個新的更長的表示函數調用作用域的“鏈”
- 對於嵌套函數來講,每次調用外部函數時,內部函數又會重新定義一遍。因為每次調用外部函數的時候,作用域鏈都是不同的
- 內部函數在每次定義的時候都有微妙的差別,在每次調用外部函數的時候,每部函數的代碼都是相同的,而且關聯這段代碼的作用域也不相同
- 表達式JS中的一個短語,JS解釋器會將其計算出一個結果
- 常量、變量名都是一種簡單的表達式
- 復雜表達式是由簡單表達式組成的
- 簡單表達式組合成復雜表達式最常用的方法就是使用運算符。根據運算符規則返回一個值
- 原始表達式
-
- 最簡單的表達式是“原始表達式”
- 原始表達式是表達式的最小單位,它們不再包含其他表達式
- JS原始表達式包含常量、關鍵字、變量
- 對象和數組的初始化表達式
- 函數定義表達式
- 屬性訪問表達式
- 調用表達式
- 對象創建表達式
- 省略部分。。。。。。
- 表達式在JS中是短語,那么語句就是JS整句或命令
- 語句塊的結尾不需要分號。塊中的原始語句必須以分號結束,但語句塊不需要
- 語句塊中的縮進,這不是必須的,但能增強代碼的可閱讀性
- JS中沒有塊級作用域,在語句塊中聲明的變量並不是語句私有的
- 對象是JS的基本數據類型
- 對象是一種復合值
- 對象也可看做是屬性的無序集合,每個屬性都是一個名/值對
- 屬性名是字符串,因此我們可以把對象看成是對字符串到值的映射
- 這種基本數據類型還有很多叫法,有些我們已然非常熟悉,比如散列、散列表、字典、關聯數組
- 然而對象不僅僅是字符串到值的映射,除了可以保持自有的屬性,JS對象還可以從一個稱為原型的對象繼承屬性
- 對象的方法通常是繼承的屬性,這種“原型式繼承”是JS的核心特征
- JS對象是動態的可以增刪屬性
- 除了字符串、數字、true、false、unll、undefined之外,JS中的值都是對象
- 對象最常見的用法是創建、設置、查找、刪除、檢測、枚舉它的屬性
- 屬性包括名字和值
- 屬性名可以是包含空字符串在內的任意字符串
- 對象中不能存在兩個同名的屬性
- 值可以是任意JS值,或者(在ECMASscript5中)可以是一個getter或setter函數
- 除了名字和值之外,每個屬性還有一些與之相關的值,稱為“屬性特性”
- 屬性特性
-
- 可寫,表明是否餓可以設置該屬性的值
- 可枚舉,表明是否可以通過for/in循環返回該屬性
- 可配置,表明是否可以刪除或修改該屬性
- 對象特性
-
- 對象的原型,指向另為一個對象,本對象的屬性繼承自它的原型對象
- 對象的類,是一個標識對象類型的字符串
- 對象的擴展標記,指明了(在ECMAScript 5中)是否可以向該對象添加新屬性
- 三類JS對象的兩類屬性作區分
-
- 內置對象,是由ECMAScript規范定義的對象或類。例如數組、函數、日期、增則表達式
- 宿主對象,是由JS解釋器所嵌入的宿主環境(比如Web瀏覽器)定義的
- 自定義對象,是由運行中的JS代碼創建的對象
- 自有屬性,是直接在對象中定義的屬性
- 繼承屬性,是在對象的原型對象中定義的屬性
- 創建對象
-
- 對象直接量
- 關鍵字new創建
- Object.create()函數創建
- 對象直接量
-
- 創建對象直接量最簡單的方式就是在JS代碼中使用對象直接量
- 對象直接量是由若干名/值對組成的映射表
- 屬性名可以是JS標識符也可以是字符串直接量(包括空字符串)
- 對象之間量是一個表達式,這個表達式的每次運算都創建並初始化一個新的對象
- 每次計算對象之間量的時候,也都會計算它的每個屬性值
- 重復調用的函數中的循環體內使用了對象直接量,它將創建很多新對象,並且每次創建的對象的屬性值也有可能不同
- 通過new創建對象
-
- new運算符創建並初始化一個新對象
- 關鍵字new后跟隨一個函數調用,這里的函數稱做構造函數
- JS語言核心中的原始類型都包含內置構造函數
- 原型
-
- 每一個JS對象(null除外)都和另一個對象相關聯,就是原型,每一個對象都從原型繼承屬性
- 所有對象直接量創建的對象都具有同一個原型對象,並可以通過JS代碼Object.prototype獲得對原型對象的引用
- 通過new和構造函數調用創建的對象的原型就是狗找函數的prototype屬性的值
- 沒有原型的對象為數不多,Object.prototype就是其中之一
- Object.create()
-
- Object.create()是一個靜態函數,而不是提供給某個對象調用的方法
- 可以傳入參數null創建一個沒有原型的新對象,這種方式創建的對象不會繼承任何東西,甚至不包括基礎方法,比如toString(),也就是說它將不能和“+”運算符一起正常工作
- 屬性的查詢和設置
- 作為關聯數組的對象
-
- 第一種語法使用點運算符和一個標識符運算符,這和C和Java中訪問一個結構體或對象的靜態字段非常類似
- 第二種語法使用方括號和一個字符串,看起來更像數組,這個素組元素是通過字符串索引而不是數字索引,這種數組就是關聯數組,也稱散列、映射、字典
- 繼承(未完全理解)
-
- JS對象具有“自有屬性”,也有一些屬性從原型對象繼承而來
- 假設o對象的屬性x賦值,如果o中已經有屬性x(這個屬性不是繼承來的),那么這個賦值操作只會改變這個已有屬性x的值
- 如果o中不存在屬性x,那么賦值操作給o添加一個新屬性x
- 如果之前o繼承自有屬性x,那么這個繼承的屬性就被新創建的同名屬性覆蓋了
- 屬性賦值操作首先檢查原型鏈,以此判斷是否允許賦值操作
- 如果o繼承自一個只讀屬性x,那么賦值操作是不允許的,如果允許賦值操作,它也總是在原始對象上創建屬性或對已有的屬性賦值,而不會去修改原型鏈
- 在JS中,只有在查詢屬性時才會體會到繼承的存在,而設置屬性則和繼承無關,這是JS的一個重要特性,該特性讓程序員可以有選擇地覆蓋繼承的屬性
- 屬性賦值要么失敗,要么創建一個屬性,要么在原始對象中設置屬性,但有一個例外,如果o繼承自屬性x,而這個屬性是一個具有setter方法的accessor屬性,那么這時將調用setter方法而不是給o創建一個屬性x
- 需要注意的是,setter方法是由對象o調用的,而不是定義這個屬性的原型對象調用的。因此如果setter方法定義任意屬性,這個操作只是針對o本身,並不會修改原型鏈
- 屬性訪問錯誤
-
- 屬性訪問並不總是返回或設置一個值
- 查詢一個不存在的屬性並不會報錯,查找對象自有屬性或繼承的屬性中均為找到屬性,屬性訪問表達式返回undefined
- 如果對象不存在,那么試圖查詢這個不存在對象的屬性就會報錯,因為找到對象返回的是undefined而unll與undefind都沒有屬性
- 給unll和undefined設置屬性也會報類型錯誤
- 對象自有屬性是只讀的,不能給只讀屬性重新賦值(defineProperty()方法中有一個例外,可以對可配置的只讀屬性重新賦值)
- 對象繼承自有屬性,且它是只讀的,不同通過同名自有屬性覆蓋只讀的繼承屬性
- 免去其它不可賦值情況下,對象的可擴展性是false,那么不能定義新屬性
- 刪除屬性
-
- delete運算符可以刪除對象的屬性
- delete運算符只是斷開屬性和宿主對象的聯系,而不會去操作屬性中的屬性
- delete運算符只能刪除自有屬性,不能刪除繼承屬性(要刪除繼承屬性必須從定義這個屬性的原型對象上刪除它,而且這會影響到所有繼承自這個原型的對象)
- 當刪除一個嵌套對象,由於刪除的屬性引用依然存在,可能因為這種不嚴謹的代碼而造成內存泄漏。所有在銷毀對象的時候,要遍歷屬性中的屬性,依次刪除
- 當delete表達式刪除成或沒有副作用(比如刪除不存在的屬性)時,它返回true,如果delete后不是一個屬性訪問表達式(比如 delete 1),同樣返回true
- delete不能刪除那些可配置性為false的屬性(盡管可以刪除不可擴展對象的可配置屬性)。
- 檢測屬性
-
- in——in與那算符左側是屬性名(字符串),右側是對象。如果對象自有屬性或繼承屬性中包含這個屬性則返回true
- hasOwnProperty()——方法用來檢測給定的名字是否是對象的自有屬性。對於繼承屬性它將返回false
- propertyIsEnumerable()——是hasOwnProperty()增強版,只有檢測到是自有屬性且這個屬性的可枚舉性為true時它才返回true。通常由JS代碼創建的屬性都是可枚舉的,除非使用特定方法改變屬性的可枚舉性
- !==——比較值
- 有一種場景必須使用in運算符,當對象值為undefined時
- 枚舉屬性
-
- 許多實用工具庫給Object.prototype添加了新的方法或屬性,這些屬性方法可以被所有對象繼承並使用
- for/in循環可以在循環體中遍歷對象中所有可枚舉的屬性(包括自有屬性和繼承的屬性),把屬性名稱賦值給循環變量。
- 對象繼承的內置方法不可枚舉的,但在代碼中給對象添加的屬性都是可枚舉的(除非轉為成不可枚舉)
- ECMAScript 5中定義了兩個用以枚舉屬性名稱的函數Object.keys()、Object.getOwnPropertyNames()
- 屬性getter和setter
- * 屬性的特性
-
- 除了包含名字和值之外,屬性還包含一些標識它們可寫、可枚舉、可配置的特性
- 可以認為一個屬性包含一個名字和4個特性。數據屬性的4個特性分別是它的值、可寫性、可枚舉性、可配置性
- 存取屬性不具有值特性和可寫性,它們的可寫性是由setter方法存在於否決定的。因此存取器屬性的4個特性是讀取、寫入、可枚舉、可配置
- 為了實現屬性特性的查詢和設置操作,ECMAScript 5中定義了一個名為“屬性描述符”的對象,這個對象代表那4個特性
- 描述對象的屬性和它們所描述的屬性特性是同名的。因此,數據屬性的描述符對象的屬性有value、writable、enumerable、configurable
- 存取器屬性的描述符對象則用get、set屬性代替value和writable,其中writable、enumerable和configurable都是布爾值
- 通過調用Object.getOwnPropertyDescriptor()可以獲得某個對象特定屬性的屬性描述符
- getOwnPropertyDescriptor()只能得到自有屬性的描述符,想要得到繼承屬性的特性需要遍歷原型鏈
- 通過definePeoperty()設置屬性的特性,屬性秒速符對象不必包含所有4個特性。這個方法要么修改已有屬性要么新建自有屬性,但不能修改繼承屬性
- 如果要同時修改或創建多個屬性,則需要使用Object.defineProperties()
- 如果對象是不可擴展的,則可以編輯已有的自有屬性,但不能給它添加新屬性
- 如果屬性是不可配置的,則不能修改它的可配置性和可枚舉性
- 如果存取器屬性是不可配置的,則不能修改其getter和setter方法,也不能將它轉換為數據屬性
- 如果數據屬性是不可配置的,則不能將它轉換為存取器屬性
- 如果數據屬性是不可配置的,則不能將它的可寫性從false修改為true,但可以從true修改為false
- 如果數據屬性是不可配置且不可寫的,則不能修改它的值。然而可配置但不可寫屬性的值是可以修改的(實際上是先將它標記為可寫的,然后修改它的值,最后轉換為不可寫的)
- 對象的三個屬性
-
- 每一個對象都有與之相關的原型、類、可擴展性
- 原型屬性
-
- 對象的原型屬性是用來繼承屬性的
- 原型屬性是在實例對象創建之初就設置好的
- 通過對象之間量創建的對象使用Object.prototype作為它們的原型
- 通過new構造函數創建的對象使用構造函數的prototype屬性作為它們的原型
- 通過object.create()創建的對象使用第一個參數(也可以是null)作為它們的原型
- 在ECMScript 5中,將對象作為參數傳入Object.getPrototypeOf()可以查詢它的原型
- 通過new表達式創建的對象,通常繼承一個constructor屬性,這個屬性指代創建這個對象的構造函數
- 通過直接量與object.creat創建的對象包含一個名為constructor的屬性,這個屬性指代Object構造函,因此,constructor.prototype才是對象直接量的真正原型,但對於object.create()創建的對象則往往不是這樣
- 使用isPrototypeOf()方法,檢測一個對象是否是另一個對象的原型
- isPrototypeOf()函數實現的功能與instanceof運算符非常類似
- _proto_,可設置對象的原型,但是應用不廣泛,不推薦使用
- 類屬性
-
- 對象的類屬性是一個字符串,用以表示對象的類型信息ECMAS 3與5都未提供設置個屬性的方法,並只有一種間接方法可以查詢它。默認的toString()方法
- 很多對象繼承的toString()方法被重寫了,為了能調用正確版本的toString,必須間接的調用Function.call()方法
- 通過內置構造函數創建的對象包含“類屬性”,它與構造函數名稱相匹配
- 通過對象直接量和Object.create創建的對象的類屬性是‘object’,自定義構造函數創建的對象也是一樣,因此對於自定義函數來說,沒辦法通過類屬性類區分對象的類
- 可擴展性
-
- 對象的可擴展性用以表示是否可以給對象添加新屬性,所有內置對象和自定義對象都是顯式可擴展的
- ECMAScript 5 定義了用來查詢和設置對象可擴展性的函數。Object.esExtensible()判斷是否可擴展
- 將對象轉換為不可擴展的,需要調用Object.preventExtensions(),一旦將對象轉換為不可擴展的,就無法再將其轉換回可擴展,並且只影響對象本身的可擴展性
- 給一個不可擴展的原型對象添加屬性,這個不可擴展對象同樣會繼承這些新屬性
- Object.seal()和Object.preventExtensions()類似,除了能夠將對象設置為不可擴展的,還可以將對象的所有自有屬性都設置為不可配置的。也就是說不能給這個對象添加新屬性,而且它已有的屬性也不能刪除或配置,不過它已有的可寫屬性依然可以設置。
- 對於已經封閉的對象是不能解封的。可以使用Object.isSealed()來檢查對象是否封閉
- Object.freeze()將更嚴格的鎖定對象——“凍結”。除了將對象設置不可擴展和不可配置外,還可以將它已有數據屬性設置為只讀(如果對象的存取器屬性具有setter方法,存取器屬性將不受影響,仍可以通過屬性賦值調用它們)。使用Object.isFrozen()來檢測對象是否凍結
- preventExtensions()seal()freeze()都返回傳入的對象,也就是說,可以通過函數嵌套的方式調用它們
- 序列化對象
-
- 序列化對象是指將對象的狀態轉換為字符串,也可將字符串還原為對象
- ECMAScript 5 提供了內置函數JSON.stringify()和JSON.parse()用來序列化和還原JS對象
- JSON語法是JS語法的子集,並不能表示JS所有對象
- 子集
-
- 大多數代碼都會定義它們的子集,用以更安全的方式執行第三方代碼
- 子集的設計目的是在容器或“沙箱”中更安全的運行不可信的第三方JavaScript代碼
- 所有能破壞這個沙箱並影響全局執行環境的語言特性和API在這個安全子集中都是禁止的
- 為了讓代碼靜態的通過JavaScript安全檢查,必須移除一些JavaScript特性
-
- 禁止使用this,因為在(非嚴格模式中)this訪問全局變量
-
- 嚴格模式
-
- 嚴格模式是將更好的的錯誤檢查引入代碼中的方法
- 嚴格模式時,無法使用隱式聲明的變量、將賦值給只讀屬性或將屬性添加到不可擴展的對象等
- 消除JS代碼中一些不合理、不嚴謹之處,減少代碼怪異行為
- 程序或函數開頭使用use strict 來聲明嚴格模式
- with、帶.或[]表達式、function、windows和對Document的引用
- 支持對象、數組、字符串、無窮大數字、true、false和null,並可以序列化和反序列化
- NaN/Infinity和-Infinity序列化的結果是null
- RegExp、Error對象和undefined值不能序列化和還原
- JSON.stringify()只能序列化對象可枚舉的自有屬性
- 對於不能序列化的屬性來說,在序列化后的輸出字符串中會將這個屬性省略掉
- stringify()parse都可以接受第二個可選參數,通過傳入需要序列化或還原的屬性列表來定制自定義的序列化或還原操作
- 對象方法
- toString方法
-
- toString方法沒有參數,返回一個表示調用這個方法的對象值的字符串
- 很多類都帶有自自定義的toString方法
- toLocaleString方法
-
- 除了基本的tostring方法外,對象都包含toLocaleString方法,這個方法返回一個表示這個對象的本地字符串
- Date、Number類對toLocaleString方法做了定制
- valueOf
-
- valueOf與toString非常類似,當JS需要將對象轉換為某種原始值而非字符串的時候才會調用它,尤其轉換為數字的時候
- 在需要使用原始值的地方使用了對象,JS就會自動調用這個方法
- 數組是值的有序集合
- 每個值叫做一個元素,每個元素在數組中有一個位置,以數字表示,稱為索引
- 數組可以是任何類型,並且同一個數組中的不同元素也可能有不同的類型
- 數組的元素甚至也可能是對象或其他數組,這允許創建復雜的數據結構
- JS數組的索引是基於零的32位數值
- JS數組是動態的,根據需要他們會增長或縮減,並且在創建數組時無需聲明一個固定大小或者在數組大小變化時無需重新分配空間
- JS數組可能是稀疏的,數組元素的索引不一定要連續的,它們之間可以有空缺
- 每個JS數組都有一個length屬性。針對非稀疏數組該元素就是數組元素的個數
- 針對稀疏數組,length比所有元素的索引要大
- JS數組是JS對象的特殊形式
- 通常,數組的實現是經過優化的,用數字索引來訪問數組元素一般來說比訪問常規對象屬性要快很多
- 數組繼承自Array.prototype中的屬性,它定義了一套豐富的數組操作方法
- 數組直接量中的值不一定是常量,可以是任意表達式
- 它可以包含對象直接量或其他數組直接量
- 如果省略數組直接量中的某個值,省略的元素將被賦予undefined值
- 數組直接量的語法允許有可選的結尾的逗號
- 調用構造函數 Array()是創建數組的另一種方法,這種形式可以預分配一個數組空間,但數組中沒有存儲值,甚至數組的索引屬性還未定義
- 數組的特別之處在於,當使用小於2的32次方的非負整數作為屬性名時數組會自動維護其length屬性值
- 所有數組都是對象,可以為其創建任意名字的屬性。但如果使用的屬性是數組的索引,數組的特殊行為就是將根據需要更新它們的length屬性值
- 可以使用負數或非整數來索引數組。在這種情況下,數值轉換為字符串,字符串作屬性名來用。既然名字不是非負整數,它就只能當作常規的對象屬性,而非數組的索引
- 使用了是非負整數的字符串,它就當作數組索引,而非對象屬性
- 數組索引僅僅是對象屬性名的一種特殊類型,這意味着JS數組沒有“越界”錯誤的概念
- 當試圖查詢任何對象中不存在的屬性時,不會報錯,只會得到undefined值。類似對象,對於對象同樣存在這種情況
- 既然數組是對象,那么它們可以從原型中繼承元素。在ECMASscript 5中,數組可以定義元素的getter和setter方法
- 如果一個元素確實繼承了元素或使用了元素的getter和setter方法,訪問這種數組的元素的時間會與常規對象屬性的查找時間相近
- 訪問數組
-
- 數組是對象的特殊形式。使用方括號訪問數組元素就像用方括號訪問對象的屬性一樣
- JS將指定的數字索引轉換成字符串——索引值1變成“1”——然后將其作為屬性名來使用
- 對於常規JS對象也可以使用方括號來操作
- 創建數組
-
- 直接量創建
- new Array()構造函數創建
- × 數組元素的添加和刪除
-
- push()方法在數組末尾增加一個或多個元素
- unshift()方法在數組的首部插入一個元素,並且將其他元素依次移到跟高的索引處
- delete 直接刪除數組某個元素,並不影響數組長度
- 數組有pop方法(它和push()一起使用),后者一次使減少長度1並返回被刪除元素的值
- shift方法(它和unshift一起使用),從數組頭部刪除一個元素。和delete不同的是shift方法將所有元素下移到比當前索引低1的地方
- 稀疏數組
-
- 稀疏數組就是包含從0開始的不連續索引的數組
- 通常數組中的length屬性值代表數組中元素的個數。如果數組是稀疏的,length屬性值大於元素的個數
- 可以用delete操作符來生產稀疏數組
- 足夠稀疏的數組通常在實現上比稠密的數組更慢、內存利用率更高,在這一的數組中查找元素的時間與常規對象屬性的查找時間一樣長
- 通過數組刪除 Array.shift() 或 Array.pop() splice() 不會產生稀疏數組
- 數組長度
-
- 每個數組有一個length屬性,就是這個屬性使其區別於常規的JS對象
- 在數組中肯定找不到一個元素的索引值大於或等於它的長度。為了維持此規則不變化,數組有兩個特殊的行為
- 1:當為數組元素賦值,他的索引i大於或等於現有數組長度時,length屬性的值將設置i+1
- 2:設置length屬性為一個小於當前長度的非負整數n時,當前數組中那些索引值大於或等於n的元素將從中刪除
- 如果讓一個數組元素不能配置,就不能刪除它。如果不能刪除它,length屬性不嫩設置為小於不可配置元素的索引值
- 數組的遍歷
-
- for
-
- 這是遍歷數組最常用的方法
- 在嵌套循環或其他性能非常重要的上下文中,可以看到這種基本的數組遍歷需要優化,數組的長度應該只查詢一次而非每次循環都要查詢
- for/in
-
- 自動處理稀疏數組
- 循環每次將一個可枚舉的屬性名(包括數組索引)賦值給循環變量。不存在的所有將不會被遍歷
- ECMAScript規范允許for/in循環以不同的孫旭遍歷對象的屬性。通常數組元素的遍歷實現是升序的,但不能保證一定是這樣。特別地,如果數組同時擁有對象屬性和數組元素,返回的屬性名很可能是按照創建的順序而非數值的大小順序。如果算法依賴於遍歷順序,那么不推薦這種遍歷方法
- forEach
-
- ECMAScript 5定義了一些遍歷數組元素的新方法,按照索引的順序按個傳遞給定義的一個函數。這些方法中最常用的就是forEach
- 多維數組
-
- JS不支持真正的多維數組,但可以用數組的數組來近似。
- × 數組方法
- join
-
- Array.join()方法將數組中所有元素都轉化為字符串並連接在一起,返回最后生成的字符串
- 可以指定一個可選的字符串在生產的字符串中來分割數組的各個元素。如果不指定分割符,默認使用逗號
- join是string.split方法的逆向操作,后者是將字符串分割成若干塊來創建一個數組
- reverse
-
- 將數組中的元素顛倒順序,返回逆序的數組
- 它不通過重新排列的元素創建新的數組,而是在原先的數組中重新排序它們
- sort
-
- Array.sort()方法將數組中的元素排序並返回排序后的數組。當不帶參數調用sort()時,數組元素以字母表順序排序
- 如果數組包含undefined元素,它們會被排到數組的尾部
- concat
-
- Array.concat()創建並返回一個新數組
- slice
-
- Array.slice()返回指定數組的一個片段或子數組
- splice
-
- Array.splice()方法是在數組中插入或刪除元素的通用方法
- push和pop
-
- 允許將數組當作棧來使用
- unshift和shift
-
- 行為非常類似於push和pop,不一樣的是前者在數組的頭部而非尾部進行元素的插入和刪除操作
- toString和toLocalString
-
- 數組和其他JS對象一樣擁有toString方法
- forEach
-
- 從頭至尾遍歷數組,為每個元素調用指定的函數
- map
- filter
- every和some
- reduce和reduceRight
- indexOf和astIndexOf
- 數組類型
- 類數組對象
- 作為數組的字符串
- 函數是這樣一段JS代碼,它只定義一次,但可能被執行或調用任意次
- JS函數是參數化的,函數的定義包括一個稱為形參的標識符列表,這些參數在函數體內像局部變量一樣工作
- 函數調用會為形參提供實參的值
- 函數使用它們實參的值來計算返回值,成為該函數調用表達式的值
- 除了實參之外,每次調用還會擁有另一個值——本次調用的上下文——這就是this關鍵字的值
- 函數掛載在一個對象上,作為對象的一個屬性,就稱它為對象的方法。當通過這個對象來調用函數時,該對象就是此次調用的上下文,也就是該函數的this的值
- 用於初始化一個新創建的對象的函數稱為構造函數
- 在JS里,函數即對象,程序可以隨意操控它們,比如可以把函數賦值給變量,或者作為參數傳遞給其他函數。因為函數就是對象,所有可以給它們設置屬性,甚至調用它們的方法
- JS的函數可以嵌套在其他函數中定義,這樣它們就可以訪問它們被定義是所處的作用域中的任何變量。這意味着JS函數構成了一個閉包。它給JS帶來了非常強勁的編程能力
- 形參和實參的區別,形參相當於函數中定義的變量,實參是在運行時的函數調用時傳入的參數
- 函數定義
-
- 函數使用function關鍵字來定義,它可以用在函數定義表達式或者函數聲明語句里。在兩種形式中,函數定義都從function關鍵字開始,其后跟隨這些組成部分
- 嵌套函數
-
- 有趣之處在於它們的變量作用域規則:它們可以訪問嵌套它們(或多重嵌套)的函數的參數和變量
- 函數調用
-
- 作為函數
- 作為方法
- 作為構造函數
- 通過它們的call()和apply()方法間接調用
- 方法調用
-
- 一個方法無非就是保存在一個對象的屬性里的函數。這里的函數表達式本身就是一個屬性訪問表達式,這意味着該函數被當作一個方法,而不是普通函數
- 方法調用與函數調用有一個重要的區別,即,調用上下文。屬性訪問表達式由兩部分組成,一個對象和屬性名稱。
- 方法和this關鍵字是面向對象編程范例的核心
- 任何函數只要作為方法調用實際上都會傳入一個隱式的實參——這個實參是一個對象,方法調用的母體就是這個對象,方法調用的語法已經清晰表明了函數將基於一個對象進行操作
- this是一個關鍵字,不是變量,也不是屬性名。JS的語法不允許給this賦值
- 和變量不同,this關鍵字沒有作用域的限制,嵌套函數不會從調用它的函數中繼承this
- 如果嵌套函數作為方法調用,其this的值指向調用它的對象。如果嵌套函數作為函數調用,其this值不是全局對象(非嚴格模式下)就是undefined(嚴格模式下)
- 方法鏈
-
- 當方法返回值是一個對象,這個對象還可以再調用它的方法
- 構造函數調用
-
- 如果函數或者方法調用之前帶有關鍵字new,它就構造函數調用。
- 構造函數調用和普通函數調用以及方法調用在實參處理、調用上下文和返回值方面都有不同
- 如果構造函數調用在圓括號內包含一組實參列表,先計算這些實參表達式,然后傳入函數內,這和函數調用和方法調用是一致的。如果構造函數沒有形參調用都可以省略圓括號
- 構造函數調用創建一個新的空對象,這個對象繼承自構造函數的prototype屬性。構造函數試圖初始化這個新創建的對象,並將這個對象用作其調用的上下文,因此構造函數可以使用this關鍵字來引用這個新創建的對象
- 構造函數通常不使用關鍵字return,它們通常初始化新對象,當構造函數的函數體執行完畢時,它會顯示返回
- 如果構造函數使用return語句但沒有指定返回值,或者返回一個原始值,那么這時將忽略返回值,同時使用這個新對象作為調用結果
- 間接調用
-
- JS中的函數也是對象,和其他JS對象沒什么兩樣,函數對象也可以包含方法。其中兩個方法call和apply()可以用來間接調用函數。兩個方法都允許顯示指定調用所需的this值,也就是說任何函數可以作為任何對象的方法來調用,哪怕這個函數不是那個對象的方法。兩個方法都可以指定調用的實參
- 函數的實參和形參
-
- JS中的函數定義並 未指定函數形參的類型,函數調用也未對傳入的實參值做任何類型檢查。
- JS函數調用甚至不檢查傳入形參的個數
- 可選形參
-
- /*optional*/來強調形參是可選的
- 可變長的實參列表:實參對象
-
- 當調用函數的時候傳入的實參個數超過函數定義是的形參個數時,沒有辦法直接獲得未命名值的引用
- 在函數體內,標識符arguments是指向實參對象的引用,實參對象是一個類數組對象
- arguments並不是真正的數組,他是一個實參對象。每個實參對象都包含以數字為索引的一組元素以及length屬性,但並不是真正的數組。可以理解為碰巧具有數字為索引的屬性
- callee和caller屬性
- 非標准的,但是大多瀏覽器都有實現,通過caller屬性可以訪問調用棧
- 將對象屬性用作實參
-
- 當一個函數包含超過三個形參時,對程序員來說,要記住嗲用函數中實參的正確順序非常頭疼。通過傳入對象可以簡化這一步
- 作為函數的值
-
- 函數可以定義,也可調用,這時函數最重要的特性
- 函數定義和調用是JS的詞法特性
- JS中函數不僅是一種語法,也是值,可以將函數賦值給變量,存儲在對象的屬性或數組的元素中,作為參數傳入另外一個函數等
- 自定義函數屬性
-
- JS中的函數並不是原始值,而是一種特殊的對象,也就是說函數可以擁有屬性
- 作為命名空間的函數
-
- 函數作用域的概念:在函數中聲明的變量在函數體內都是可見的(包括在嵌套函數中),在函數的外部是不可見的
- JS中無法聲明只在一個代碼塊內部可見的變量,基於這個原因,我們常常是用函數用作臨時命名空間,在這個命名空間內定義的變量都不會污染到全局命名空間
- × 閉包
-
- JS采用詞法作用域,函數的執行依賴於變量作用域,這個作用域是在函數定義時決定的,而不是函數調用時決定的
- JS函數對象內部不僅包含函數的代碼邏輯,還必須引用當前的作用域鏈
- 函數對象通過作用域鏈互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中稱為“閉包”
- 從技術角度將,所有JS函數都是閉包:它們都是對象,它們都關聯到作用域鏈
- 定義大多數函數時的作用域鏈在調用函數時依然有效,但這並不影響閉包
- 待更新........
- 函數屬性、方法和構造函數
- length屬性
-
- arguments.length表示傳入函數的實參的個數。而函數本身的length屬性是只讀的,它代表函數定義時形參的數量
- prototype屬性
-
- 每一個函數都包含一個prototype屬性,這個屬性指向一個對象的引用,這個對象稱作“原型對象”
- call方法和apply方法
- bind方法
- toString方法
- function構造函數
- 可調用的對象
- 函數式編程
-
- JS並非函數式編程語言
- JS中可以向操作對象一樣操控函數,也就是說可以在JS中應用函數式編程技術
- 使用函數處理數組
-
- 高階函數就是操作函數的函數,它接收一個或多個函數作為參數,並返回一個新函數
- 高階函數
- 不完全函數
- 記憶
- 在JS中可以定義對象的類,讓每個對象都共享某些屬性
- 在JS中類的實現是基於其原型繼承機制的
- 兩個實例都從同一個原型對象繼承了屬性,我們說他們是同一個類的實例
- 如果兩個對象繼承自同一個原型,往往意味着(擔不是絕對)它們是由同一個構造函數創建並初始化
- JS中類的一個重要特性是“動態可繼承”
- 類和原型
-
- 在JS中,類的所有實例對象都從同一個原型對象上繼承屬性,因此,原型對象是類的核心
- 類和構造函數
-
- 構造函數是用來初始化新創建的對象的
- 調用構造函數的一個重要特征是,構造函數的prototype屬性被用做新對象的原型。這意味着同一個構造函數創建的所有對象都繼承自一個相同的對象,因此他們都是同一個類的成員
- 構造函數和類的標識
-
- 原型對象是類的唯一標識,當且僅當兩個對象繼承自同一個原型對象時,它們才是屬於同一個類的實例。而初始化的構造函數則不能作為類的標識,兩個構造函數的prototype屬性可能指向同一個原型對象。那么這兩個構造函數有創建的實例屬於同一個類
- 構造函數是類的“外在表現”。構造函數名字通常用做類名
- instanceof的語法強化了“構造函數是類的公有標識”概念
- constructor屬性
-
- 任何JS函數都可以用作構造函數,並且調用構造函數是需要用到一個prototye屬性的
- 每個JS函數(ECMAScript5中的Function.bind()方法返回的函數除外)都自動擁有一個prototype屬性。這個屬性的值是一個對象,這個對象包含唯一一個不可枚舉屬性constructor。constructor屬性值是一個函數對象
- JS中Java式的類繼承
-
- 構造函數對象
-
- 構造函數(對象)為JS類定義名字。任何添加到這個構造函數對象中的屬性都是類字段和類方法(如果屬性值是函數的話就是類方法)
- 原型對象
-
- 原型對象的屬性被類的所有實例所繼承,如果原型對象的屬性值是函數的話,這個函數就作為類的實例的方法來調用
- 實例對象
-
- 類的每個實例都是一個獨立的對象,直接給這個實例定義屬性是不會為所有實例對象所共享的。定義在實例上的非函數屬性,實際上是實例的字段
- JS中定義類的步驟
-
- 第一步,定義一個構造函數,並設置初始化新對象的實例屬性
- 第二步,給構造函數的prototype對象定義實例的方法
- 第三步,給構造函數定義類字段和類屬性
- 類的擴充
-
- JS中基於原型的繼承機制是動態的,對象從其原型繼承屬性,如果創建對象之后原型的屬性發生變化,也會影響到繼承這個原型的所有實例對象
- 類和類型
-
- JS定義了少量的數據類型:null、undefined、布爾值、數字、字符串、函數和對象
- instanceof運算符
-
- 左操作符是待檢測其類對象,右操作數是定義類的構造函數。返回布爾值,這里檢測繼承可以不是直接繼承