一、JavaScript 簡介
JavaScript 誕生於 1995年,是一種專爲與網頁交互而設計的腳本語言。由三種不同部分組成:
- ECMAScript,提供核心語言功能;
- 文檔對象模型(DOM),提供訪問和操作網頁內容的方法和接口;
- 瀏覽器對象模型(BOM),提供和瀏覽器交互的方法和接口
1.1 ECMScript
ECMScript 是這本語言的基礎,規定了這門語言的下列組成部分:
- 語法
- 類型
- 語句
- 關鍵字
- 保留字
- 操作符
- 對象
1.2 文檔對象模型(DOM)
文檔對象模型(DOM, Document Object Model) 是針對XML但經過擴展用於HTML的應用程序編程接口。提供了以下方法和接口的定義:
- DOM 視圖;
- DOM 事件;
- DOM 樣式;
- DOM 遍歷和方法;
- SVG;
- MathML;
- SMIL;
1.3 瀏覽器對象模型(BOM)
瀏覽器對象模型(BOM, Browser Object Model) 用來訪問和操作瀏覽器窗口,有以下的擴展:
- 彈出新瀏覽器窗口的功能;
- 移動、縮放和關閉瀏覽器窗口的功能;
- 提供瀏覽器詳細信息的 navigator 對象;
- 提供瀏覽器所加載頁面的詳細信息的 location 對象;
- 提供用戶顯示器分辨率詳細信息的 screen 對象;
- 對 cookies 的支持;
- 像 XMLHttpRequest 和 IE 的 ActiveXObject 這樣自定義對象;
二、在 HTML 中使用 JavaScript
2.1 內嵌腳本
在 HTML 內部直接寫入 <script>
片斷,這種寫法要注意JavaScript 是單線程的,如果在初始化頁面的時候,你的代碼中存在大量同步運行的代碼,導致 JS 線程一直處於繁忙狀態,這時候用戶在頁面上進行交互時將不會得到任何反應,就像是卡死了一樣。想了解具體原因,參考文章瀏覽器渲染原理
2.2 引入外部腳本
<script src="example.js" src="defer"></script>
- defer屬性讓腳本在文檔完全呈現之後再執行
- async屬性表示當前文本不用等待其他文本,異步執行。
三、基本概念
3.1 變量
ESCAScript 的變量是 鬆散類型 的,就是說可以用來保存任何類型的數據。也就是說一個變量就是一個佔位符。
var a = 'hi';
a = 10;
定義變量可以使用 var 操作符,定義多個變量,每個變量之間用 逗號 分開,如果 省略 var 操作符可以定義 全局變量,未經初始化的變量,會保存一個特殊的值—— undefined
function show() {
var _message = '局部變量',
_name = 'lily';
g_message = '全局變量';
}
show();
console.log(g_message); // 全局變量
console.log(_message); // _message is not defined
在 ESCScript 中變量會被自動 提升
console.log(enhance); // 變量提升
var enhance = "變量提升";
3.2 數據類型
ECMAScript 有6種數據類型,其中5種基本的數據類型: Undefined、Null、Boolean、Number和String,還有1種複雜的數據類型: Object。一般使用 typeof 檢查5種基本的數據類型,使用 instanceof 檢查是哪種複雜的數據類型。
-
typeof
常見的就不說了,說幾個可能容易混或者錯的。值 使用typeof操作符返回 值未初始化 ‘undefined’ 值對象、null ‘Object’ 值是函數 ‘function’ // 未初始化的變量 var uninital; console.log('未初始化的變量:---' +typeof uninital); // undefined // 未定義的變量 console.log('未定義的變量:---' + typeof liy); // undefined // 值爲null var obj = null; console.log('值是null的時候:---' + typeof obj); // object // 值未函數 function add (a, b) { return a + b; } console.log('值是funciton:---' + typeof add); // function
-
Boolean 類型
Boolean 類型是 ECMAScript 中使用最多的一種類型,只有兩個字面量值: true、false;區分大小寫,也就是說 True 不是布爾值。流程控制語句(例如 if 語句)自動執行 轉型函數 Boolean()。
var message = "hello world"; if (message) { console.log('轉換爲 true'); // 執行這句 } else { console.log('轉換爲 false'); }
下表給出了幾種數據類型及其對應的轉換規則
數據類型 轉換爲true 值 轉換爲false值 Boolean true false String 任何非空字符串 ‘’(空字符串) Number 任何非0數字(包括無窮大) 0、NaN Object 任何對象 null Undefined 不適用 undefined -
Number 類型
- 浮點數值計算會產生舍入誤差
- isFinite(值) 用於判斷一個數值是不是位於最小值和最大值之間
var result = Number.MAX_VALUE + Number.MAX_VALUE; console.log('數值是否是有窮的:' + isFinite(result)); // false
- isNan(值) 用於判斷一個數值是不是非數值。isNaN 也適用於對象。在基於對象調用 isNaN() 函數時,會首先調用對象的 valueof() 方,然後確定返回值是否可以轉換爲數值。如果不能,則基於這個返回值再調用 toString() 方法,再測試返回值。
console.log(isNaN(10)); // false console.log(isNaN('10')); // false console.log(NaN == NaN); // false
- 將數值轉換爲數值:Number(值), parseInt(值,幾進制),parseFloat(值,幾進制)
console.log(Number('hello')); // NaN console.log(Number('')); // 0
-
String 類型
-
轉換爲字符串有兩種方式 toString([幾進制]) 和 String(名)
var num = 10; console.log(num.toString()); // '10' console.log(num.toString(2)); // '1010' console.log(String(10)); // '10'
-
-
Object 類型
訪問對象的屬性有兩種方式person['name'] person.name
3.3 操作符
-
布爾操作符
邏輯非和邏輯或都是 短路 操作符-
邏輯與
如果第一個操作數是對象,則返回 第二個 操作數;
如果第二個操作符是對象,則只有第一個操作數的求值結果是 true 的情況下才會返回該 對象;var obj = { name: 'lily', } console.log(obj && true); // true console.log(true && obj); // {name: 'lily'}
-
邏輯與
如果第一個操作數是對象,則返回 第一個 操作數;
如果兩個操作數都是對象,則返回 第一個 操作數;
-
-
關係操作符
-
如果兩個操作數都是字符串,則比較兩個字符串對應的 字符編碼,所以字符串比較的時候一般都轉換爲大寫(或者小寫);
-
任何操作數與 NaN 進行比較,結果都是 false;
console.log('Brink'.toLowerCase() < 'alpha'.toLowerCase()); // false console.log(NaN < 3); // false
-
3.4 語句
-
for 語句
在 ECMAScript 中沒有塊級作用域的概念,因此循環內部定義的變量也可以在外部訪問到。for (var i = 0;i < 10 ; i++) {} console.log(i); //10
其實以上的代碼就相當於
var j; for (j = 0; j < 10; j ++) {} console.log(j);
-
for-in 語句
for-in 語句用來遍歷對象的屬性,但此時的對象不能是null或者undefined。 -
switch 語句
switch 語句在比較值的時候使用的是全等
看一個我覺得很新穎的語句var num = 18; switch (true) { case num < 0: alert('less than 0'); break; case num > 0 && num < 10:$ alert('between 0 and 10'); breka; default: alert('more than 10'); }
-
函數
可以向 ECMAScript 函數傳遞任意數量的參數,並且可以通過arguments對象來訪問這些參數。即便你定義的函數只接收兩個參數,在調用這個函數也未必一定傳遞兩個參數,可以傳遞一個、三個甚至不傳遞參數。如果沒傳遞值的命名參數將自動被賦予undefined值。function add() { if (arguments.length == 1) { alert(arguments[0] + 10); } else { alert(arguments[0] + arguments[1]); } } add(10); // 20
四、變量、作用域和內存問題
4.1 變量
ECMAScript 變量包含兩種不同類型的值:基本類型和引用類型值。
-
基本類型的值
-
基本類型的值在內存中佔據固定大小的空間,因此保存在 棧 內存中
-
如果從一個變量向另一個變量複製基本類型的值,會在變量對象上創建一個新值,然後把值複製到新變量的位置上。
var num1 = 5; var num2 = num1;
-
傳遞參數的時候是按 值 傳遞的,和複製變量一樣。
-
-
引用類型
-
引用類型的值是對象,保存在 堆 內存中;
-
從一個變量向另一個變量複製引用類型的值時,同樣也將存儲在變量對象中的值複製一份到新分配的空間中。不同的是,這個值的副本實際上時一個指針。
var obj1 = new Object(); var obj2 = obj1;
但是當爲對象添加屬性的時候,操作的是實際對象。
obj1.name = "lily"; console.log(obj2.name); // lily
-
傳遞參數的時候是按 值 傳遞的,和引用類型複製一樣。
-
4.2 執行環境和作用域
- 執行環境
執行環境定義了變量或函數有權訪問的其他數據,決定了他們的行爲;
執行環境可以分爲**全局執行環境** 和 **每個函數自己的執行環境**;
- 作用域鏈
代碼在執行環境中執行,會創建對象的一個 **作用域鏈**,這個作用域鏈可以保證對執行環境有權訪問的所有變量和函數的有序訪問。
4.3 內存管理
優化內存佔用的最佳方式,就是爲代碼只保存必要的數據。一旦數據不再用了,最好通過將其值設置爲 null 來釋放引用。
function cretePerson(name) {
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var g_person = cretePerson('lily');
g_person = null;
五、引用類型
在 ECMAScript中,引用類型是一種數據結構,常用的有:Object、Date、RegExp、Function、基本包裝類型、單體內置對象。
5.1 Array 類型
-
插入
- 在末尾插入數據:
arr[length] = 'add data from bottom';
- 在末尾插入數據:
arr.push('add data from bottom');
- 在頭部插入數據:
arr.unshift('add data from head');
- 在任意位置插入數據,返回刪除數據組成的數組:
splice(2,0,'red','green');
,三個參數,第一個是位置,第二個是要刪除的數據個數,第三個數據爲插入的數據;
- 在末尾插入數據:
-
刪除
- 在末尾刪除數據:
arr[length] = arr.length - 1;
- 在末尾刪除數據,返回刪除的數據:
arr.pop();
- 在頭部刪除數據,返回刪除的數據:
arr.shift();
- 任意位置刪除,返回刪除數據的數組:
arr.splice(0,1);
,兩個參數,第一個參數是位置,第二個參數是要刪除的個數;
- 在末尾刪除數據:
-
替換
- 替換任意位置數據,返回被替換的數據組成數組:
arr.splice(2,1,'red')
- 替換任意位置數據,返回被替換的數據組成數組:
-
檢測數組
檢測是否是數組if(Array.isArray(value)){}
-
轉換爲字符串的方法
- join
- toString
- tolocalString
- valueOf
-
排序方法
- reverse
- sort(可以傳入排序規則)
-
合併
- concat
var color1 = ['color1', 'color2']; var color2 = color1.concat('yellow'); // color1,color2,yellow
-
位置方法
- indexof(‘要查找內容’,起始位置),不包含返回 -1
- lastIndexof
-
迭代
- every(): 對數組的每一項運行給定函數,每一項都 true 的時候,返回 true;
- some(): 對數組中的每一項運行給定函數,有任意一項返回 true 時候,纔會返回 true;
- filter(): 對數組中的每一項都運行給定函數,返回該函數返回 true 項組成的數組
- forEach(): 對數組中的每一項運行給定的函數,無返回值
- map(): 對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組。
10.歸併數組
- reduce
- reduceRight
5.1 Function 類型
函數分爲函數聲明和函數表達式,函數表達式可以隨時訪問,因爲函數聲明提升。
- 函數內部屬性
-
arguments 對象
arguments 對象主要用途是保存函數參數,包含一個特殊屬性callee屬性,是個指針,指向擁有這個arguments對象的函數;function factorial (num) { if (num < 1) { return 1; } else { return num * arguments.callee(num - 1); } } console.log(factorial(4)); //24
-
this 對象
this 引用的是函數執行環境對象
window.color = 'red'; var obj = {color: 'obj bule'}; function showColor () { console.log(this.color); } showColor(); // red obj.sayColor = showColor; obj.sayColor(); // obj bule
-
- 非繼承的函數方法
每個函數都包含兩個方法:call() 和 apply()。這兩個方法的用途都是在特定的作用域中調用函數,實際上就是設置函數體內this的值。-
apply(運行函數的作用域, 參數數組)
window.color = 'window color red'; var obj = {color: 'obj color blue'}; function showColor(data) { console.log(this.color); //window color red console.log(`傳入數據${data}`); } showColor(); showColor.apply(obj, ['我用了apply']); // obj color blue 傳入數據我用了apply
-
call(運行函數的作用域,其餘參數)
window.color = 'window color red'; var obj = {color: 'obj color blue'}; function showColor(data) { console.log(this.color); //window color red console.log(`傳入數據${data}`); } showColor.call(obj, '我用了call'); //obj color blue //傳入數據我用了call
-
5.1 String 類型
1. length
2. charAt(位置)
3. charCodeAt(位置):返回字符編碼
4. substring(start, [end])
5. substr(start, [end])
6. slice(start, [end])
7. indexof(要查找內容,位置)
8. trim: 去除空格
9. toUpperCase
10. toLocaleUpperCase
11. toLowerCase
12. toLocalLowerCase
13. split(切割符),返回數組
toString 和 toLocaleString() 的區別
- 數字是4位以上的時候
var num = 1234
num.toString(); // 1234
num.toLocalString(); // 1,234
- 時間格式
var date = new Date();
date.toString(); //Tue Jul 09 2019 21:28:06 GMT+0800 (中國標準時間)
date.toLoacalString(); //"2019/7/9 下午9:28:06"
總結:該字符串與執行環境的地區對應
ps: 歡迎大家添加討論
一元運算符
- 前置++
- 前置–;
- 後置++;
- 後置–;
注意:前置操作符,變量的值會在求值語句之前就改變(這種情況被稱爲副作用)
var age = 27;
var afterAge = ++age + 1; // age 會執行爲++,變爲 28 再加1,結果爲29
console.log(age, afterAge); // 27, 29
var age = 27;
var afterAge = age++ + 1; // 會先用age 進行 +1 操作,然後再進行++操作
console.log(age, afterAge); // 28, 28
位操作符-按位非
- 標識符:
~
- 本質:按位非的本質是- 操作數的負值 - 1;
var num = 25;
var nonNum = ~num; // -26
布爾操作符
-
邏輯非
- NaN, null, undefined 返回的都是
false
; - !! - 實際上會模擬
Boolean()
,其中第一個非,無論什麼操作數返回一個布爾值,而第二個邏輯非操作會對該布爾值求反。
- NaN, null, undefined 返回的都是
-
邏輯與
- 短路操作:如果第一個值能知道結果就不看第二個值了;
- 應用於任何類型,返回值也不一定是布爾值;
- 如果第一個值是 null, undefined, NaN , 則直接返回這幾個值;
{ name: 'lily'} && { age: 27 } // {age: 27}
-
邏輯或
-
短路操作:如果第一個的求值結果爲
true
, 就不會計算對第二個操作數求值;
null, undefined 用Number() 轉換
- Numer(null) // 0
- Number(undefined) // NaN
關係操作符 - 兩個操作數都是字符串,則比較兩個字符串字符編碼值
'a' < 'B' // false
'a'.toLocaleLowerCase() < 'B'.toLocaleLowerCase() // true
- 操作數中一個是布爾值,會將其轉換爲數值,再執行比較
false < true // true
- 任何數與NaN比較,結果都是 false
for-in 語句
- 常用來枚舉對象的屬性
- 循環出的屬性的順序是不確定的【?這個用谷歌的瀏覽器驗證了幾次感覺沒有問題啊】
- null, undefined 會有兼容性【?用谷歌瀏覽器驗證了幾次感覺沒有問題】
break & continue 語句 - break 語句,立即跳出循環;
- continue 語句,跳出本次循環,進入下一次循環;
var num = 0;
for(let i = 1; i < 10; i++) {
if (i % 5 === 0) {
break;
}
num++;
console.log(i);
}
// 1 2 3 4
let num2 = 0;
for(let i = 1; i < 10; i ++) {
if (i % 5 === 0) {
continue;
}
num2++;
console.log(i);
}// 1 2 3 4 6 7 8 9
- 配合
label
語句,比如跳出雙循環
let num3 = 0;
outermost:
for(let i = 0; i < 10; i++) {
for(let j = 0; j < 10; j++) {
if(i === 5 && j == 5) {
break outermost;
}
num3++;
}
}
// num3 55
switch 語句
break
關鍵字會導致代碼執行流跳出switch 語句,如果省略了break關鍵字,就會導致執行完當前的case,繼續執行下一個case, 通常這種情況需要添加註釋;
switch(num) {
case 1:
case 2:
/**合併兩種註釋**/
console.log('條件1 和條件2');
break;
default:
break;
}
- switch(任何數據類型,字符串,對象),case[常量,變量,表達式]
- switch 比較的時候使用的是全等
函數參數
- 定義一個接收兩個參數的函數
function test(param1, param2)
,調用的時候可以傳入0個或者多個參數,參數在內部用一個數組來表示,函數內部接收的是這個數組,可以用arguments對象來訪問這個參數數組 - 建議只讀,不通過 arguments 改
function sum(num1, num2) {
if(arguments.length === 1) {
return 10 + num1;
} else if (arguments.length === 2) {
return num1 + num2;
} else {
return '參數有點多';
}
}
sum() // "參數有點多"
sum(1) // 11
sum(2, 3) // 5
檢測類型
- typeof 檢驗基本類型,null 除外,
typeof null
是Object
,typeof 函數
返回function
- instanceof 檢驗引用類型
基本類型和引用類型的值
-
ECMAScript 包含兩種不同數據類型的值: 基本類型值和引用類型值
-
“基本數據類型是按值訪問的,因爲可以操作保存在變量中實際的值”;“引用類型的值是保存在內存中的對象。與其他語言不同,javascript 不允許直接訪問內存中的位置,也就是不能直接操作對象的內存空間。在操作對象時,實際上是在操作對象的引用而不是實際的對象。爲此,引用類型的值是按引用訪問的。”
以下是對於上面這句話理解的剖析: -
值類型和引用類型在內存上的存儲區域:
-
程序員開發涉及到的內存區域: 棧,堆,靜態存儲區域;
-
堆和棧的概念:
- stack (棧)是有結構的(就像後進先出 從下到上) 每一個區域都按照一定次序存放,可以明確知道每個區塊的大小
- heap(堆)是沒有結構 數據可以任意存放。因此stack的尋址速度要快於heap。
- 所以 數據存放的規則是 只要是局部的 佔用空間確定的數據 一般都存放在stack裏面。否則就放在heap裏面。局部變量一旦運行結束 就會GC回收 而heap的那個對象實例直到系統的GC將這裏的內存回收。因此一般內存泄漏都方生在heap。
-
值和引用類型的區別:
-
值類型的值是存儲在內存的棧當中;引用類型的值是存儲在內存的堆中;
-
傳遞值類型和傳遞引用類型的時候,傳遞方式不一樣。值類型我們稱之爲值傳遞,引用類型我們稱之爲引用傳遞。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gEMof9Gd-1592273613965)(https://user-images.githubusercontent.com/16410254/61796800-c0696b00-ae58-11e9-82e5-ae69385a72fc.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-U35k45es-1592273613984)(https://user-images.githubusercontent.com/16410254/61796880-e0009380-ae58-11e9-9caa-417db777fb05.png)]
-
-
-
javascript中的變量
- 基本類型的值在內存中佔據固定大小的空間,因此保存在棧內存中;
- 引用類型的值是對象,保存在堆中;
-
怎麼理解 javascript不允許直接訪問內存中的位置,不能直接操作對象的內存空間?
js語言本身定義確實不允許,其次,考慮js運行環境,是用在瀏覽器上,瀏覽器一般情況也不能訪問內存,不然,你想像一下,瀏覽器可以訪問讀寫電腦上的內存,那將是多麼恐怖的事兒(隨便寫一個小程序就可以控制你電腦,拿到你電腦上所有的東西了。)。作爲一門高級語言,JS並不像低級語言C/C++那樣擁有對內存的完全掌控。JS中內存的分配和回收都是自動完成,內存在不使用的時候會被垃圾回收器自動回收。
值傳遞和引用傳遞
- 一般把函數中參數叫做“形式參數”,而把調用這個函數時傳入的實際的參數叫做“實際參數”,那麼調用函數時,實參傳遞給形參有兩種方式:值傳遞 和 引用傳遞
- 值傳遞:形參只是得到實參的值,是兩個不同的對象,互不影響;
- 引用傳遞:形參是實參的引用,也就是形參和實參是同一個對象,在函數中的對象修改會影響實參;
- ECMAScript 中所有函數的參數都是按值傳遞的,且形參是局部變量,會在函數執行完立即被銷燬
- 引用類型
function setName(obj) { obj.name = 'lily'; obj = new Object(); obj.name = 'lucy'; } let person = new Object(); setName(person); person.name // lily
如果引用類型是按引用傳遞的話,那麼obj
和 person
是同一個對象,當obj = new Object()
的時候person
指向堆中的對象也會變,輸出的person.name 會是 lucy
執行環境和棧
原文網址:https://juejin.im/entry/5833f18fe696c9004d6da42e
這篇文章我將會深入地討論JavaScript中最根本的一部分——Execution Context(執行上下文)。在文章結束的時候,你應該對解釋器的工作原理有一個比較清晰的理解,對於爲什麼會存在‘變量提升’,它們的值又是如何被真正確定的這些問題有一個正確的答案。
什麼是Executin Context(執行上下文)
當JavaScript代碼執行的時候,執行環境是很重要的,它可能是下面三種情況中的一種:
全局 code(Global code)——代碼第一次執行的默認環境
函數 code(Function code)——執行流進入函數體
Eval code(Eval code)——代碼在eval函數內部執行
在網上你能夠讀到許多關於作用域的資料,這篇文章的目的是讓事情變得簡單些。讓我們來思考下execution context這個詞,它與當前代碼的環境 / 作用域是等價的。好了,說的夠多了,讓我們來看一個包含global和function / local context的例子吧。
image 這裏沒有什麼特別的地方,我們有一個global context被紫色的框框着,還有三個不同的function context,分別被綠色、藍色、橘色的框框着。在你的程序中,有且僅能有一個global context,並且它能夠被任何其他的context訪問到。你能夠擁有任意多個function context,並且每個函數被調用的時候都會生成一個新的context。它會生成一個私有作用域,並且在它內部聲明的任何東西都不能直接在它的外部訪問。就像上面的例子,一個函數可以直接訪問它外面的變量,但是外部的context就不能直接訪問在內部聲明的變量或者函數。爲什麼會這樣?如何準確的理解這段代碼的執行?
以上是文章摘要 閱讀更多請點擊——>右下角的more 以下是餘下全文
Execution Context Stack(執行上下文棧)
瀏覽器中的JavaScript解釋器是單線程的。意思就是說在瀏覽器中,同一時間只能做一件事,其它的行爲和事件都會在Execution Stack中排隊。下面這個圖表就是一個單線程棧的抽象描述:
image
我們已經知道,當瀏覽器第一次加載你的script的時候,默認進入global execution context。如果,你在全局代碼中調用了函數,程序序列流就會進入被調用的函數中,生成一個新的execution context並且把它壓入execution stack的頂部。
如果你在當前函數內調用其他函數,會發生同樣的事情。代碼的執行流會進入內部函數,生成一個新的execution context,並且將它壓入existing stack。瀏覽器總是會執行stack頂部的executin context,當被執行的函數上下文執行完成後,它將會彈出棧頂,然後將控制權返回給當前棧中的下一個對象。下面是一個循環函數執行棧的例子:
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
image
函數foo遞歸調用了3次,每次 i 增長1。函數 foo 每次調用後,都出生成一個新的execution context。當一個context執行完成後,它就會出棧並且把控制權返回給下面的context,直到再次到達global context。
關於execution stack有5個關鍵點需要記住:
單線程
同步執行
1個Global context
無限制的函數context
每個函數調用都會創建新的execution context,即使是自己調用自己。現在我們知道了函數每次被調用的時候,一個新的execution context就會被創建。無論如何,在JavaScript解釋器內部,每次調用執行execution context都有兩個階段:
創建階段【在函數被調用的時候,但是內部代碼執行之前】
創建Scope Chain
創建變量、函數和參數
確定 “this” 的值
激活 / 代碼執行階段
變量賦值、引用函數和解釋 / 執行代碼。
每個execution context在概念上可以用一個對象來表示,這個對象有三個屬性:
executionContextObj = {
‘scopeChain’: { /* variableObject + all parent execution context’s variableObject / },
‘variableObject’: { / function arguments / parameters, inner variable and function declarations */ },
‘this’: {}
}
執行對象 / 變量對象【AO/VO】
這個executionContextObj在函數被調用的時候創建,但是是在真實函數代碼被執行之前。這個就可以理解爲第一階段,創建階段(Creation Stage)。在這裏,解釋器通過搜索函數的形參和傳入的實參、本地函數的聲明和本地變量的聲明來創建executionContextObj。搜索的結果就是executionContextObj對象中的variableOject屬性。
這裏是解釋器執行代碼的一個僞綜述:
找到調用函數的代碼。
在執行函數代碼之前,創建execution context。
進入創建階段:
初始化Scope Chain
創建variable object
創建實參對象(arguments object),檢查context的形參(parameters),初始化參數的名稱和參數值並且創建一份引用的拷貝。
掃描context中的函數聲明:
爲每一個函數在varible object上創建一個屬性,屬性名就是函數名,含有一個指向內存中函數的引用指針。
如果函數名已經存在了,這個引用指針的值將會被重寫。
掃描context中的變量申明:
爲每一個變量在variable object上創建一個屬性, 屬性名就是變量名並且將變量的值初始化爲undefined。
如果變量名在variable object中已經存在,那就什麼都不會發生,並且繼續掃描。
激活 / 代碼執行階段:
運行 / 解釋context中的函數代碼,並且根據代碼一行一行的執行,爲變量賦值。
讓我們來看一個例子:
function foo(i) {
var a = ‘hello’;
var b = function privateB() {
};
function c() {
}
}
foo(22);
當調用foo(22)時,創建階段(creation stage)時,context是下面這個樣子:
fooExecutionContext = {
scopeChain: { … },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { … }
}
因此,你可以看到,在創建階段(creation stage)只負責對屬性名稱(變量名)的定義,但是並沒有給它們賦值,當然這裏有一個例外就是formal arguments / parameters(實參 / 形參)。當創建階段完成以後,執行流進入函數內部,激活執行階段(execution stage),然後代碼完成執行,context是下面這個樣子:
fooExecutionContext = {
scopeChain: { … },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: ‘hello’,
b: pointer to function privateB()
},
this: { … }
}
關於Hoisting(變量提升)
在網上你可以找到很多定義JavaScript中hoisting這個詞的文獻,解釋變量和函數的聲明在它們的作用域中被提前。但是,沒有從細節上解釋爲什麼會發什麼這種現象。通過了解解釋器如何創建activation object,就很容易知道這種現象發生的原因了。看下面這個例子:
(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = ‘hello’,
bar = function() {
return ‘world’;
};
function foo() {
return ‘hello’;
}
}());
現在我們可以回答下面這些問題了:
在foo聲明之前,爲什麼我們可以訪問它?
如果我們來跟蹤creation stage, 我們知道在代碼執行階段之前,變量已經被創建了。因此在函數流開始執行之前,foo已經在activation object中被定義了。
foo 被聲明瞭兩次,爲什麼 foo 最後顯示出來是一個function,並不是undefined或者是string?
儘管 foo 被聲明瞭兩次,我們知道,在創建階段,函數的創建是在變量之前的,並且如果屬性名在activation object中已經存在的話,我們是會簡單的跳過這個聲明的。
因此,對 function foo()的引用在activation object上先被創建了,當解釋器到達 var foo 時,我們會發現屬性名 foo 已經存在了,因此代碼什麼都不會做,繼續向下執行。
爲什麼 bar 是undefined?
bar實際上是一個變量,並且被賦值了一個函數的引用。我們知道變量是在創建階段被創建的,但是它們會被初始化爲undefined,所以bar是undefined。希望現在你對JavaScript解釋器如何執行你的代碼能有一個好的理解了。理解execution context and stack會讓你知道爲什麼你的代碼有時候會輸出和你最初期望不一樣的值。
作用域鏈:
推薦兩篇文章:
http://blog.xieliqun.com/2016/10/06/scope-chain-2/
http://blog.xieliqun.com/2016/10/06/scope-chain/
附一張自己整理的圖片
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tkNg7qGL-1592273641906)(https://user-images.githubusercontent.com/16410254/64535352-b4befe80-d349-11e9-8e4c-e75e6550422f.jpg)]
- 閉包引用包含函數的整個活動對象
function test() {
var ele = document.getElementById('#test');
var id = ele;
ele.onclick = function() {
console.log(id);
}
ele = null;
}
-
模仿塊級作用域
- 使用匿名函數模仿作用域
-
特權方法
-
定義:有權訪問私有變量和私有函數的公有方法;
-
可以使用構造函數模式、原型模式、模塊模式、增強的模塊模式來實現
-構造函數模式
function Test() {
var name = 'lily';
// 特權方法
this.setName = function(val) {
name = val;
}
}
- 原型模式
(function() {
var name = 'lily';
Person = function() {};
// 特權方法
Person.prototype.setName = function(val) {
name = val;
}
})();
- 模塊模式
- 概念:爲單例(只有一個實例對象)創建私有變量和特權方法;
- 使用場景:需要一個對象,這個對象可以用某些數據對它進行初始化,同時還要公開一些可以訪問呢這些私有數據的方法。
- 代碼:
var singleton = function() { var name = 'lily'; function privateSay() { console.log('hello'); } // 返回對象 return { publicPropterty: true, publicMethod: function() { name ='luce'; return privateSay(); } } }();
- 增強的模塊模式
- 使用:單例必須是某種類型的實例,同時必須添加某些屬性或方法
- 代碼
var singleton = function(){
// 私有變量
var components = new Array();
// 初始化
components.push(new BaseCom());
// 創建指定類型的是咧
var app = new BaseCom();
// 公共的接口和方法
app.getComponentCount = function () { return components.length; }
return app;
}();
javascript 內存管理
- 具備垃圾收集機制的語言,開發人員一般不用擔心內存管理的問題。但是,javaScript一個主要問題是分配給Web瀏覽器的可用內存數量通常比分配給桌面應用程序的少,這樣做的目的出於安全考慮,防止運行javascript的網頁耗盡全部系統內存而導致系統崩潰。內存限制問題不僅會影響給變量分配內存,同時還會影響調用棧以及在一個線程中能夠同時執行的語句數量, 因此,確保佔用最少的內存可以讓頁面獲得更好的性能。其中一個方法是解除引用,一般應用於全局變量和全局變量對象的屬性,還有循環引用變量的引用。
function createPerson(name) {
var localPerson = new Ojbect();
localPerson.name = name;
return localPerson;
}
var glPerson = createPerson('lily');
glPerson = null // 手工解除引用
Array 類型
-
length
屬性不是隻讀的,因爲可以在數組的末尾移除元素或者添加元素- 移除
var colors = ['red', 'blue']; colors.length = 1; console.log(colors); // ['red']
- 添加
var colors = ['red', 'blue']; colors[colors.length] = 'green'; console.log(colors); // ['red', 'blue', 'green'];
- 移除
-
檢測數據類型
Array.isArray(val)
判斷value
是不是數組;
-
轉換方法
toString()
: 以,
形式拼接每個值的字符串arr.valueof()
: 返回的是數組本身;arr.join(指定分隔符)
: 用指定分隔符拼接每個值的字符串;
注意:如果某一個項的值是null或undefined, 則 join(),valueof(), toString(), toLocaleString() 方法返回的結果中以空串表示
-
模擬棧
- 棧: last-in-first-out (後進先出)
- 方法:pop() : 彈出數組的最後一個元素,並返回該元素
- 方法:push() : 在數組的末尾添加一個元素,返回添加後的數組的length
- 模擬一個棧
let arr = [1]; arr.push(2); arr.pop();
- 棧: last-in-first-out (後進先出)
-
模擬隊列
- 隊列:first-in-first-out (先進先出)
- shift(): 移除數組第一項,並返回該項;
- unshift(): 在數組的前端添加任意項,並返回該數組的長度;
- 模擬一個隊列:
let arr = [1, 2, 3]; arr.push(4); arr.shift(); // 1
- 隊列:first-in-first-out (先進先出)
-
重排序方法
- reverse() : 反轉數組項的順序
var arr = [1, 2, 3]; arr.reverse(); // [3, 2, 1]
- sort(): 調用每個數組的toString() 方法,然後比較得到的字符串;
- sort() 可以接收一個比較函數作爲參數,用來確定哪個值在哪個值的前面:
function compare(value1, value2) { if(value1 < vlaue2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var values = [0, 5, 1, 10, 15]; values.sort(compare); console.log(values); // [0, 1, 5, 10, 15]
- reverse() : 反轉數組項的順序
-
操作方法
concat()
: 基於當前的數組,返回一個新的數組,不改變原來的數組,接收到的參數追加到原來數組的末尾;
var arr1 = [1, 2]; var arr2 = arr1.concat([5]); // [1, 2, 5]
slice(start, end)
: 返回新數組,不影響原來的數組,截取 start~end-1 的元素,如果第二個參數沒有的話,默認爲到末尾;var sliceArr = [1, 2, 3] var transliceArr = sliceArr.slice(1, 2) // [2]
-```splice(index, delNum, item)``: 會改變原來的數組,f返回刪除的元素,第一個參數爲位置,第二個參數爲刪除幾項,第三個參數爲添加的數據。
- 刪除:
var colors = ["red", "green", "black"]; var removed = colors.splice(0, 1); console.log(removed); // ['red'];
- 插入
removed = colors.splice(1, 0, 'white'); // ["green", "white", "black"] console.log(removed); // []
- 替換
colors.splice(1, 1, 'red', 'yellow'); // ["green", "red", "yellow", "black"]
-
位置方法:
- indexof(item, start): item: 要查找的項, start: 開始查找的位置;有:則返回查找元素的index,沒沒有返回-1;
- lastIndexof(item, start): 從後向前找,但是start也是從前向後定義的位置
-
迭代方法:
- every(item, index, array): 對每一項運行給定函數,如果該函數每一項都返回true, 則返回true
- some(item, index, array): 對每一項運行給定函數,如果有一項返回true,則返回true;
- filter(item, index, array): 對每一項運行給定函數,返回true的項組成數組;
- forEach(item, index, array): 對每一項運行給定函數,沒有返回值;
- map(item, index, array): 對每一項運行給定函數,返回調用結果組成的數組;
var num = [1, 2, 3, 4, 5]; var everyRes = num.every((item) => return item >2); // false var someRes = num.some(item => return item > 2); //true; var filterRes = num.filter(item => return item > 2); // [3, 4, 5]; var mapRes = num.map(item => return item * 2); // [2, 4, 6, 8, 10]
-
歸併方法
reduce(prev, cur, index, array)
和reduceRight(prev, cur, index, array)
這個函數返回任何值,都會作爲第一個參數傳入下一項。
var num = [1, 2, 3]; var sum = num.reduce((prev, cur, index ,arr) => return prev + cur ); // 6
-
Date 類型
- Date.now() : 返回調用這個方法時的日期和時間的毫秒數;
- 使用 “+” 操作符,可以獲取 Date 對象的時間戳;
var start = +new Date(); // do some thing var end = +new Date(); var result = end - start;
- Date 類型的
valueOf()
方法,返回日期的毫秒數,所以可以使用**比較操作符來比較日期值。
RegExp 類型
-
字面量創建正則表達式
var expression = /pattern/ flags;
-
flags 包含3種形式
- g : 表示全,表示應用於所有字符串
- i : 表示忽略大小寫
- m: 表示多行,就是到達文本末尾的時候,還會繼續查找下一行中是否存在與模式匹配的項。
-元字符不要轉義
( ) { } [ ] \ ^ $ | ? * + .
- 實例方法
- test()
pattern.test(text)
目標字符串是否和模式匹配,返回Boolean;
- test()
Function 類型
-
函數實際上是對象,每個函數是 Function 類型的實例;函數名實際上是指向某個函數的指針,不會與某個函數綁定;
-
定義:
- 函數聲明語法定義:
function sum(num1, num2) { return num1 + num2 }
; - 函數表達式定義:
var sum = function(num1, num2) { return num1 + num2 };
;
- 函數聲明語法定義:
-
函數聲明和函數表達式
- 函數聲明:在執行代碼之前,解析器會通過一個函數聲明提升,將函數聲明添加到執行環境中
- 區別
- 函數表達式可以直接調用
var test = function () {} test()
; 函數聲明不能直接調用function test() {} ()
;
- 函數表達式可以直接調用
// 函數聲明 sum(4,5); // 9 function sum(a, b) { console.log(a + b)} // 函數表達式 add(4, 5); var add = function(a, b) { console.log(a + b) }
// 遞歸 var factorial = (function f()num { if( num < 1) { return 1} else { return num * f(num -1)} })
-
函數聲明提升:
JS引擎會在正式執行代碼之前進行一次”預編譯“,預編譯的函數,查找函數聲明,作爲GO屬性,值賦予函數體(函數聲明優先);找變量聲明,作爲GO屬性,值賦予undefined;這樣就是函數聲明和函數表達是式的不同- 參考鏈接: https://juejin.im/post/5afcf1b96fb9a07abd0ddc43
-
函數的內部屬性
- callee : 指向擁有
arguments
對象的函數;
function sum(a, b) { console.log(arguments.callee); // sum 這個函數 }
- caller: 調用當前函數的引用;
function outer() { inner() } function inner() { console.log(arguments.callee.caller) } // outer 這個函數
- this:
- this中值是函數的執行環境;
- this 對象是在運行時基於函數的執行環境綁定的;
function thisDemo() { console.log(this); } thisDemo(); // this 是window var obj = {}; obj.demo = thisDemo; obj.demo(); // this 就是obj
- callee : 指向擁有
-
函數屬性
- length: 函數接收的命名參數的個數
function sum(a, b) { return a + b; } sum.length // 2
- prototype: ----以後會做擴展
- length: 函數接收的命名參數的個數
-
函數的方法
- toString(), valueOf() : 返回函數代碼, 可用於調試過程中
- call(scope, [num1, num2]):
- 用途是在特定的作用域中調用函數;
- 兩個參數,第一個是運行函數的作用域, 第二個參數是參數數組或arguments
var color = 'red'; function sayColor() { console.log(this.color) } var banana = { color: 'yellow' } sayColor(); // red sayColor(banana); // yellow;
- apply(scope, arg1, arg2);
- 用途:在特定的作用域中調用函數;
- 多個參數:第一個是運行函數的作用域,其餘的參數是傳入的參數值
- bind(scope):
- 用途: 返回一個函數實例, 這個函數綁定了特定的this的值
var color = 'red'; var banana = { color: 'yellow' } function sayColor () { console.log(this.color) } var bindSayColor = sayColor.bind(banana); bindSayColor(); // 'yellow', 雖然在全局執行,但是this是banana;
基本包裝類型 - String
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dZsDG3qb-1592273710370)(https://user-images.githubusercontent.com/16410254/63694834-e87a2e80-c849-11e9-91c1-df0dad90df75.png)]
單體內置對象 - Global對象
-
URI 編碼方法
encodeURI(uri)
對整個uri 進行編碼,對本身屬於URI的特殊字符不進行編碼,例如/
;encodeURIComponent(uri)
對uri中的一段進行編碼,對所有非標準字符不進行編碼
例如:
var uri = 'http://www.baidu.com/lily index.html#start' encodeURI(uri); // "http://www.baidu.com/lily%20index.html#start" encodeURIComponent(uri); // "http%3A%2F%2Fwww.baidu.com%2Flily%20index.html%23start"
decodeURI(uri)
decodeURIComponent(uri)
-
在任何環境的情況下返回全局對象的方法
var global = function() { return this }();
單體內置對象- Math對象
- min 和 max 取出一組數據中的最小值/最大值
// 取數組中的最大值
var values = [1, 2, 3, 5, 8];
var max = Math.max.apply(Math, values);
-
舍入方法
- Math.ceil(num): 向上舍入,例如
25.1
向上舍入是26
- Math.floor(num): 向下舍入
- Math.round(num): 四捨五入
- Math.ceil(num): 向上舍入,例如
-
random():生成一個(0<= x < 1)之間的隨機數
- 某個範圍隨機選擇一個值的公式
// 值 =Math.floor(Math.random() * 總數 + 第一個值) function selectFrom(lowerVal, upperVal) { var choices = upperVal - lowerVal + 1; return Math.floor(Math.random() * choices + lowerVal); } // 例如2~9之間的隨機數 var num = Math.floor(Math.random() * 8 + 2 );