目錄
- 入門篇
- js介紹
- 歷史
- 基本語法
- 資料型別
- 概述
- null 和 undefined
- 數值
- 字串
- 物件
- 函式
- 陣列
本系列基于阮一峰老師的《JavaScrip語言入門教程》或《JavaScript教程》記錄整理,教程采用知識共享 署名-相同方式共享 3.0協議,這幾乎是學習js最好的教程之一(去掉之一都不過分)
最好的教程而阮一峰老師又采用開源方式共享出來,之所以重新記錄一遍,一是強迫自己重新認真讀一遍學一遍;二是對其中知識點有個自己的記錄,加深自己的理解;三是感謝這么好的教程,希望更多人閱讀了解
入門篇
js介紹
- JavaScript 是一種輕量級的腳本語言和嵌入式(embedded)語言,其只能通過宿主環境(host,瀏覽器或node環境)提供I/O操作
- 語法角度,JS是一種"物件模型"語言,支持函式式編程、"面向物件"編程、程序式編程等
- js核心語法包括:基本的語法構造(比如運算子、控制結構、陳述句)和標準庫(就是一系列具有各種功能的物件比如
Array、Date、Math等),然后就是宿主提供的API(比如瀏覽器提供的BOM、DOM和Web互聯網相關的類;Node環境提供檔案操作API、網路通信API等) - JavaScript 的所有值都是物件
- js可以采用事件驅動(
event-driven)和非阻塞式(non-blocking)設計,實作高并發、多任務處理
歷史
- 1995年,Brendan Eich 只用了10天,完成了js的第一版,其設計借鑒了C、java、Scheme、Awk、Self、Perl、Python等多種語言,同時也留下了各種設計缺陷(導致常常需要學習各種解決問題的模式)
- JavaScript與Java是兩種不同的語言,兩者的關系僅僅是js的基本語法和物件體系最開始是想要模仿Java,而后又與當時Java的公司Sun有合作,也借助Java的聲勢,從而后來改名叫JavaScript
- ECMAScript 和 JavaScript 的關系是,前者是后者的規格,后者是前者的一種實作,ECMAScript 只用來標準化 JavaScript 的基本語法結構,但其他標準如 DOM 的標準就是由 W3C組織(
World Wide Web Consortium)制定的 - 2007年,ECMAScript 4.0版草案發布,但是過于激進,導致后面中止了 ECMAScript 4.0 ,將其中一小部分功能發布為ECMAScript 3.1,之后又將其改名為 ECMAScript 5,
- 2015年6月,ECMAScript 6 正式發布,且更名為“ECMAScript 2015”,之后每年發布一個ECMAScript版本
- 周邊大事記
基本語法
- js執行單位是行(line),一行一行地執行,一般,一行就是一個陳述句
- 陳述句(statement)執行某種操作、運算式(expression)用于得到回傳值,凡是 JavaScript 語言中預期為值的地方,都可以使用運算式(這一點使js某些使用很靈活)
- 陳述句以分號結尾,一個分號表示一個陳述句結束,多個陳述句可以寫在一行內,分號前無內容,表示空陳述句,運算式不需要分號結尾
- 變數是對“值”的具名參考,即為"值"取名,變數的名字就是變數名,如下使用var宣告一個變數a,并賦值1
var a=1;
- 只宣告變數而不賦值,則該變數的值是
undefined,表示"無定義",同時變數賦值時不寫var也有效,但不建議,變數必須先宣告再使用,否則會報錯not defined - 一條var命令可以宣告多個變數,js是動態型別語言,變數型別可以隨時更改,
var a = 1;
a = 'hello';
- 使用
var重新宣告一個已存在的變數,是無效的,重新宣告時賦值,相當于重新賦值 - 變數提升:js的執行方式是,先決議代碼,獲取所有被宣告的變數,然后再一行一行地運行,這樣就會導致所有變數的宣告陳述句,會被提升到代碼的頭部,這叫做變數提升(
hoisting) - 識別符號(
identifier)指的是識別各種值的合法名稱,比如變數名、函式名,js識別符號大小寫敏感,識別符號的命名規則是:只能以字母、下劃線(_)和美元符號($)開頭,從第二個字符開始還可以使用數字, - js識別符號中的字母指的是
Unicode字母,因此中文識別符號也可以使用 - js中的保留字不能用于識別符號,保留字是指js中用來表示特定含義的字符,如return、class、true、false、function等
- 注釋:注釋用來對代碼進行解釋,js引擎執行時會忽略注釋部分,
//表示單行注釋,/* */表示多行注釋 - js使用大括號表示"區塊"(block),對于
var命令,js的區塊不構成單獨的作用域(scope) - 條件陳述句:
-
if和if...else...結構,根據條件的布林值判斷執行, -
switch結構判斷運算式的值是否與case相符并執行,如果都不符則執行最后的default,case中break不能少,否則當前case代碼塊執行完會接著執行下一個case,switch陳述句部分和case陳述句部分,都可以使用運算式,這就是js中可以為值的地方,都可以使用運算式的體現,如下:
switch (1 + 3) {
case 2 + 2:
f();
break;
default:
neverHappens();
}
switch陳述句的值和case陳述句的值,比較時采用的是嚴格相等運算子(===)
?:三元運算子:如下,條件true,執行"運算式1",否則執行"運算式2",然后獲取對應回傳值
(條件) ? 運算式1 : 運算式2
- 回圈陳述句:重復執行某個操作
-
while回圈:回圈條件和代碼塊,條件為真,就回圈執行代碼塊 -
for回圈:可以指定回圈的起點、終點和終止條件,分為初始化運算式(initialize)、條件運算式(test)、遞增運算式(increment)
for (初始化運算式; 條件; 遞增運算式) {
陳述句
}
所有for回圈,都可以改寫成while回圈
for陳述句的無線回圈表示:
for ( ; ; ){
console.log('Hello World');
}
do...while回圈:先執行一次回圈體,再判斷條件break陳述句用于跳出代碼塊或回圈,continue陳述句用于立即終止本輪回圈,并開始下一輪回圈
- js陳述句的前面可以添加標簽(label),相當于定位符,用于跳轉到程式的任意位置
label:
陳述句
資料型別
概述
- JavaScript的資料型別有六種(ES6新增了 Symbol 型別)
- 數值(number):整數和小數(比如1和3.14)
- 字串(strin):文本(比如"Hello World"),
- 布林值(boolean):表示真偽的兩個特殊值,即
true(真)和false(假) undefined:表示“未定義”或不存在,即由于目前沒有定義,所以此處暫時沒有任何值null:表示空值,即此處的值為空,- 物件(object):各種值組成的集合,
數值、字串、布林值稱為原始型別(primitive type),是最基本的資料型別,物件稱為合成型別(complex type),undefined和null兩個特殊值,
物件分為:狹義的物件(object)、陣列(array)、函式(function)
- 型別判斷
typeof運算子回傳一個值的資料型別:
- 數值、字串、布林值分別回傳
number、string、boolean - 函式回傳
function,undefined回傳undefined(可以檢測未宣告的變數),物件回傳object,null回傳object
// 檢測未宣告
if (typeof v === "undefined") {
// ...
}
typeof window // "object"
typeof {} // "object"
typeof [] // "object"
typeof null // "object"
instanceof可以區分陣列和物件
[] instanceof Array // false
[] instanceof Array // true
null 和 undefined
- 兩者含義"類似",if陳述句中自動轉為false,相等于運算子(==)兩者比較為true,null表示"空"物件,轉為數值是
0;undefined表示"無定義"的原始值,轉為數值是NaN
Number(null) // 0
Number(undefined) // NaN
- 函式沒有回傳值時,默認回傳 undefined
- 布林值表示
true和false兩個真偽狀態, - 如果 JavaScript 預期某個位置應該是布林值,則會將該位置上現有的值自動轉為布林值,
下面六個值會被轉為false,其他的值都是true:
- undefined
- null
- false
- 0
- NaN
- ""或''(空字串)
空陣列([])和空物件({})對應的布林值,都是true
數值
- js中所有數字都是以64位浮點數存盤,整數也是如此,因此
1===1.0,是同一個數
1 === 1.0 // true
JavaScript 語言的底層根本沒有整數,所有數字都是小數(64位浮點數)
- 浮點數不是精確的值,因此小數的比較和運算要特別注意
0.1 + 0.2 === 0.3 // false
0.3 / 0.1 // 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1) // false
- js浮點數的64個二進制位,從最左邊開始,由如下組成:
- 第1位:符號位,0表示正數,1表示負數,數值正負
- 第2位到第12位(共11位):指數部分,數值的大小
- 第13位到第64位(共52位):小數部分(即有效數字),數值的精度
- 絕對值小于2的53次方的整數,即$-253$到$253$,都可以精確表示
Math.pow(2, 53); // 9007199254740992
Math.pow(2, 53) + 1; // 9007199254740992
Math.pow(2, 53) + 2; // 9007199254740994
Math.pow(2, 53) + 3; // 9007199254740996
Math.pow(2, 53) + 4; // 9007199254740996
- 最大值和最小值:
Number.MAX_VALUE和Number.MIN_VALUE
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
- 數值表示法:
50(十進制)、0xFF(十六進制)、123e3或123e-3(科學計數法) - 當小數點前面的數字多于21位時,或者小數點后的零多于5個時,js會自動將數值轉為科學計數法表示
- 使用字面量(
literal)表示數值時:
- 十進制:沒有前導0的數值
- 八進制:有前綴
0o或0O的數值,或者有前導0、且只用到0-7的八個阿拉伯數字的數值, - 十六進制:有前綴
0x或0X的數值, - 二進制:有前綴
0b或0B的數值,
js會自動將八進制、十六進制、二進制轉為十進制
- js存在2個
0:+0,-0,兩者是等價的,唯一區別是+0或-0當作分母時運算式的回傳值不相等, NaN表示“非數字”(Not a Number),比如字串決議為數字報錯時會回傳NaN,0除以0得到NaN,NaN只是一個特殊值,型別依舊是NumberNaN不等于任何值,包括它本身
NaN === NaN // false
NaN的布林值為false,NaN與任何數(包括它自己)的運算,得到的都是NaN
Infinity表示“無窮”,表示無法表示正無窮(Infinity)和負無窮(-Infinity);非0數除以0,得到Infinity,
Infinity大于一切數值(除了NaN),-Infinity小于一切數值(除了NaN)
NaN的比較運算會回傳false
parseInt()方法將字串轉為整數,字串轉為整數時,會一個個字符依次轉換,如果遇到不能轉為數字的字符,就不再進行下去,回傳已經轉好的部分,轉換失敗回傳NaN,
決議科學計數法的數字時會出現奇怪的結果
第二個引數表示決議的值的進制
parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
parseFloat():將一個字串轉為浮點數,可以對科學計數法字串正確轉換isNaN()判斷一個值是否是NaN,isNaN()只對數值有效,其他型別值會先轉為數值,再判斷,對于空陣列和只有一個數值成員的陣列,isNaN()回傳false
isNaN('Hello') // true
// 相當于
isNaN(Number('Hello')) // true
- 判斷NaN最好的方法是:
NaN是唯一不等于自身的值
function myIsNaN(value) {
return value !== value;
}
如果使用isNaN()判斷,要同時判斷型別:
function myIsNaN(value) {
return typeof value =https://www.cnblogs.com/codemissing/p/=='number' && isNaN(value);
}
isFinite()判斷是否為正常的數值,判斷是引數也會進行型別轉換
字串
- 字串是放在單引號或雙引號中的零個或多個排在一起的字符,字串中使用相同的引號要用
\反斜杠轉義 - 每行的尾部使用反斜杠,可以實作多行字串(
\后面只能有換行符,否則報錯)
var longString = 'Long \
long \
long \
string';
longString // "Long long long string"
也可使用+可以將多個字串行連接
- 反斜杠(
\)用來表示一些特殊字符,稱為轉義,如\n換行符;\r:回車鍵;\t:制表符,如果在字串中需要包含反斜杠,需要\轉義自身,
"你好,反斜杠\\"; // "你好,反斜杠\"
- 字串可以看做字符陣列,但僅能使用陣列的方括號運算子獲取字符,而不能進行其他操作
- length屬性回傳字串長度
'hello'.length // 5
- js使用
Unicode字符集,不僅以Unicode存盤字符,而且可以只用Unicode碼點,如'\u00A9'表示著作權符號
var s = '\u00A9';
s // "?"
每個字符在 JavaScript 內部都是以16位(2個位元組)的 UTF-16 格式儲存,js單位字符長度固定為16位長度
js 對 UTF-16 的支持不完整,只支持兩位元組的字符,無法識別四位元組的字符,比如四位元組字符??,瀏覽器可以正確識別是一個字符,但js無法識別,認為是兩個字符,需要特別注意
'??'.length // 2
Base64編碼:對于ASCII 碼0到31的符號無法列印出來,可以使用Base64編碼轉換為可列印的字符;以文本格式傳遞二進制資料,也可以使用Base64編碼Base64是一種編碼方法,可以將任意值轉成 0~9、A~Z、a-z、+和/這64個字符組成的可列印字符,目的是不出現特殊字符,簡化程式處理,
btoa():任意值轉為Base64編碼atob():Base64編碼轉為原來的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
btoa()和atob()的Base64編碼解碼不使用非ASCII碼的字符,如btoa('你好')就會報錯,處理辦法是加一個URI轉碼,如下
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
物件
- 物件是一組"鍵值對"(key-value)的集合,是一種無序的復合資料集合,
如下,物件用大括號,鍵值對又叫成員,分為"鍵名"和"鍵值",對應"成員的名稱"和"成員的值",鍵名與鍵值用冒號分割,鍵值對逗號分割,
var obj = {
foo: 'Hello',
bar: 'World'
};
- 物件的所有鍵名都是字串(ES6中
Symbol值也可以作為鍵名),鍵名要么符合標識名的規則,要么使用引號包含,鍵名又稱為"屬性"(property),屬性可以動態創建 - "鍵值"可以是任何資料型別,比如
屬性值可以為函式,這個屬性也可以稱為"方法",可以像函式一樣呼叫
屬性的值還是物件,就可以形成鏈式參考 - 物件屬性之間逗號分割,最后一個屬性可以加逗號(
trailing comma),也可不加 - 不同的變數名指向同一個物件,則它們都是這個物件的參考,即指向同一個記憶體地址,參考只局限于物件,原始型別中,兩個變數就是值的拷貝
- 物件采用大括號表示,則就有可能和代碼塊的大括號產生歧義,比如行首是大括號,如果看做運算式,則是一個物件;如果是陳述句,則表示一個代碼區塊,
{ foo: 123 }
遇到大括號的歧義時,無法確定是物件還是代碼塊,JavaScript引擎一律解釋為代碼塊,
可以將大括號放入圓括號中,這樣就是運算式
// eval 對字串求值
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
- 使用點運算子或方括號運算子讀取物件的屬性,同時也可以用來賦值,
方括號運算子的鍵名必須有引號,否則被看做變數
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
數值鍵名不能使用點運算子,使用方括號運算子時數值鍵名會轉換為字串
8. Object.keys(obj)查看一個物件所有屬性或鍵名
9. delete用于洗掉物件本身的屬性,成功后回傳true,且洗掉不存在的key也回傳true,只有屬性存在且不能洗掉時,才回傳false,不能洗掉繼承的屬性
delete obj.p // true
in運算子檢查物件是否包含某個屬性,繼承時屬性也會回傳true,hasOwnProperty(key)方法判斷是否為物件自身的屬性for...in回圈用于遍歷物件的全部屬性
- 遍歷的是物件所有可遍歷(
enumerable)的屬性,會跳過不可遍歷的屬性, - 不僅遍歷物件自身的屬性,還遍歷繼承的屬性,
通常都是遍歷物件自身的屬性,因此可以結合hasOwnProperty方法
var person = { name: '阮一峰老師' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
函式
- 函式是一段可以反復呼叫的代碼塊
- js中宣告函式的三種方法
function命令——函式的宣告(Function Declaration)
如下,function 函式名(引數1,...){函式體}
function print(s) {
console.log(s);
}
- 函式運算式(
Function Expression)
變數賦值的方式,這個匿名函式又稱函式運算式
var print = function(s) {
console.log(s);
};
函式運算式需要在陳述句的結尾加上分號,表示陳述句結束,
函式運算式宣告函式時,function后面不帶有函式名,如果加上函式名也僅在函式體內部有效,可用于在函式內部呼叫函式自身(如遞回等),
Function建構式
如下,使用Function建構式時,可以傳遞任意數量的引數,只有最后一個引數會被當做函式體
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
- 函式的重復宣告:如果同一個函式被多次宣告,后面的就會覆寫前面的宣告,
- 函式圓括號在宣告時用于放入引數;非宣告時函式后緊跟圓括號,表示呼叫函式并傳入引數,
return陳述句表示回傳后面運算式的值,并退出當前函式 - 函式呼叫自身,就是遞回(
recursion) - js將函式看作一種值,凡是可以使用值的地方,就能使用函式,函式是一個可以執行的值,因為函式與其他資料型別地位平等,所有在js中又稱函式為第一等公民
- js將函式名視為變數名,所以同樣有函式名的提升,
function宣告函式時會被提升到代碼頭部,但是賦值陳述句定義函式,在賦值前呼叫會報錯(因為是undefined) - 函式的屬性和方法
- name屬性回傳函式的名字
- length屬性回傳函式預期傳入的引數個數,即函式定義之中的引數個數,
length屬性提供了一種機制,判斷定義時和呼叫時引數的差異,以便實作面向物件編程的“方法多載”(overload) toString()方法回傳字串,內容是函式的原始碼,函式內的注釋也會回傳,
原生函式,toString()方法回傳function (){[native code]}
- 函式作用域:作用域(
scope)指變數存在的范圍,ES5中,JavaScript只有兩種作用域:全域作用域(變數在整個程式中一直存在);函式作用域(變數只在函式內部存在),函式作用域內同樣存在變數提升,ES6新增了塊級作用域 - 對于頂層函式(直接在js檔案中或
<script>標簽中),函式外部宣告的變數就是全域變數(global variable),函式內部定義的變數,外部無法讀取,稱為"區域變數"(local variable),且函式內部定義的區域變數,在當前函式作用內會覆寫同名全域變數 - 使用
var宣告的變數只有在函式內才是區域變數,其他區塊內宣告仍是全域變數(比如if、while等塊內) - 函式本身的作用域:函式本身也是一個值,它的作用域與變數一樣,是其宣告時所在的作用域,與其運行時所在的作用域無關,函式執行時所在的作用域,是定義時的作用域,而不是呼叫時所在的作用域,
var a = 1;
var x = function () {
console.log(a); //宣告時所在作用域的變數a
};
function f() {
var a = 2;
x();
}
f() // 1
同樣,函式體內部宣告的函式,作用域系結函式體內部,
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
如上,fon顳部宣告的函式bar,bar的作用域為foo函式內部,當在foo外部取出bar執行時,bar中使用的變數x指向的是foo內部(宣告時所在作用域)的x,而不是foo外部的x,這就是閉包現象
- 閉包(
closure)就是能夠讀取其他函式內部變數的函式,而js中,只有函式內部的子函式才能讀取內部變數,因此閉包簡單理解就是"定義在一個函式內部的函式",本質上,閉包用于將函式內部和函式外部連接起來,
理解閉包,要先理解變數的作用域,函式內部可以讀取全域變數
var e = "我是全域變數";
function f1() {
console.log(e);
}
f1() // "我是全域變數"
但是,函式外部無法讀取函式內部宣告的變數,
function f1() {
var n = "我是函式內部變數";
}
console.log(n) // Uncaught ReferenceError: n is not defined
但是,正常無法得到函式內的區域變數,如果想要實作必須通過變通方法:在函式的內部,再定義一個函式,這樣函式內部的子函式就可以使用函式內的區域變數,但是函式內無法使用其子函式內的區域變數,這就是js特有的 "鏈式作用域"結構(chain scope),子物件會一級一級地向上尋找所有父物件的變數,
父物件的所有變數,對子物件都是可見的,反之則不成立,
這樣將子函式作為回傳值,就可以在函式外部讀取它的內部變數
function f1() {
var n = "我是函式內部變數";
function f2() {
console.log(n); // "我是函式內部變數"
}
return f2;
}
var result=f1();
result(); // "我是函式內部變數"
如上,獲取f1的回傳值f2函式,而f2可以讀取f1的內部變數,這樣就可以在外部獲取f1內部的變數,閉包就是函式f2
閉包最大的特點,就是它可以"記住"誕生的環境
閉包的用處:
- 讀取函式內部的變數之外
- 讓變數始終保持在記憶體中(即閉包可以使得它的誕生環境一直存在)
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5 // 閉包使得start變數一直在記憶體中
inc() // 6
inc() // 7
閉包使得函式內部環境一直存在,閉包可以看作是函式內部作用域的一個介面
- 封裝物件的私有屬性和私有方法
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('張三');
p1.setAge(25);
p1.getAge(); // 25
p1.name; // "張三"
如上,函式Person的內部變數_age,通過閉包getAge和setAge,變成了回傳物件p1的私有變數,
比如下面,就是閉包使用的典型例子,通常其在處理回圈頁面dom元素并添加事件方法時需要特別注意
var funs=[];
for(var i=0;i<5;i++){
funs[i]=function(){
console.log(i);
}
}
funs.forEach(function(f){
f();
})
// 輸出 5個5,而不是0,1,2,3,4
利用閉包,可實作將變數i的每一個回圈值"保存",供呼叫時輸出
var funs=[];
for(var i=0;i<5;i++){
(function(i){
funs[i]=function(){
console.log(i);
}
})(i);
}
funs.forEach(function(f){
f();
})
// 輸出 0,1,2,3,4
- 不能濫用閉包,容易產生網頁性能問題,外層函式每次運行,都會生成一個新的閉包,而每個閉包都會保留外層函式的內部變數,記憶體消耗很大,
- 函式的引數,通過圓括號傳遞外部資料,js在呼叫時允許省略引數(省略的引數變為
undefined,但只能省略在后面的引數,靠前的引數不能省略) - 引數傳遞方式:函式引數如果是原始型別的值(數值、字串、布林值),引數傳遞就是傳值傳遞(
passes by value),函式引數是復合型別的值(陣列、物件、其他函式),傳遞方式是傳址傳遞(pass by reference),此時在函式內部修改引數,會影響原始值 arguments物件包含了函式運行時的所有引數,arguments[0]是第一個引數,arguments[1]是第二個引數,以此類推,arguments只能在函式體內部使用,
arguments的length屬性可以判斷函式呼叫時的引數個數
arguments是物件,只是很像陣列,如果想使用陣列方法,需要將arguments轉為陣列,下面是轉換為陣列的兩種方法:
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
- 立即呼叫的函式運算式(
IIFE):
圓括號()運算子跟在函式名后面,表示呼叫該函式,如果在定義函式之后,立即呼叫該函式,如下當function出現在行首時,js會將其解釋為陳述句,后面是函式的定義,這是以圓括號結尾就會報錯,
function(){ /* code */ }();
function出現在行首就是陳述句,
// 陳述句
function f() {}
// 運算式
var f = function f() {}
解決辦法就是不要讓function出現在行首,讓js引擎解釋為運算式,這樣就可以在定義函式之后立即運行
比如放圓括號中:
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
這就是"立即呼叫的函式運算式"(Immediately-Invoked Function Expression,IIFE),
IIFE最后的分號是必須的,尤其是兩個IIFE寫在一起時,如果省略分號,連著的兩個IIFE就出出現問題,可能會報錯如下,兩行之間沒有分號,js將它們連在一起解釋,將第二行解釋為第一行的引數
// 報錯 (function(){ /* code */ }()) (function(){ /* code */ }())
任何讓解釋器以運算式來處理函式定義的方法,都能產生IIFE,
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
// 甚至
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
通常,只對匿名函式使用"立即執行的函式運算式",目的:一是不必為函式命名,避免了污染全域變數;二是 IIFE 內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數,
如下,寫法二完全避免了污染全域變數
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
陣列
- 陣列(
array)是按次序排列的一組值,位置編號從0開始,用方括號表示,比如var arr = ['a', 'b', 'c']; - 任何型別的資料都可以放入陣列
var arr = [
{a: 1},
[1, 2, 3],
function() {return true;}
];
arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}
- 陣列本質是一種特殊的物件,
typeof回傳陣列的型別是object
陣列"物件"的鍵名,是按次序排列的整數,
var arr = ['a', 'b', 'c'];
Object.keys(arr); // ["0", "1", "2"]
js中,物件的鍵名一律為字串,陣列的鍵名也是字串,物件中數值的鍵名不能使用點結構(object.key),所以陣列元素不能使用.獲取
- 陣列的
length屬性,回傳陣列元素的個數,即陣列長度
js使用一個32位整數,保存陣列的元素個數,即陣列長度最多只有 4294967295 個($2^32 - 1$)個
length屬性是可寫的,減少length值可用于洗掉陣列元素,length設定為0可用于清空陣列,length屬性的值等于最大的數字鍵加1
var arr = [ 'a', 'b', 'c' ];
arr.length // 3
arr.length = 2;
arr // ["a", "b"]
- 不推薦使用
for...in遍歷陣列(會遍歷非數字鍵),可使用for或while回圈,或forEach方法遍歷
var a = [1, 2, 3];
// for回圈
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}
// while回圈
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}
var l = a.length;
while (l--) {
console.log(a[l]);
}
// forEach方法
a.forEach(function (item) {
console.log(item);
});
- 陣列的某個位置是空元素,即兩個逗號之間沒有任何值,稱該陣列存在空位(
hole)
var a = [1, , 3 , 4];
a.length // 4
a[1] // undefined
delete a[2];
a[2] // undefined
a.length // 4
陣列的空位不影響length屬性,陣列最后一個元素后面有逗號,不會產生空位,空位回傳undefined,delete命令洗掉一個陣列成員會形成空位,且不會影響length
- 類似陣列的物件:如果一個物件的所有鍵名都是正整數或零,并且有
length屬性,則這個物件語法上稱為"類似陣列的物件"(array-like object),
"類似陣列的物件"并不是陣列,它們不具備陣列特有的方法,
如下,obj就是一個類似陣列的物件
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function
"類似陣列的物件"的根本特征,就是具有length屬性,只要有length屬性,就可以認為這個物件類似于陣列,但這種length屬性不是動態值
典型的"類似陣列的物件"是函式的arguments物件,以及大多數DOM元素集,還有字串,
// arguments物件
function args() { return arguments }
var arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false
// 字串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false
陣列的slice方法可以將"類似陣列的物件"變成真正的陣列,
var arr = Array.prototype.slice.call(arrayLike);
除了轉為真正的陣列,還可以通過call()把陣列的方法放到物件上面,從而讓"類似陣列的物件"可以使用陣列的方法,
Array.prototype.forEach.call(arrayLike, function (value, index) {
console.log(index + ' : ' + value);
});
這種方法比直接使用陣列原生的forEach要慢,所以可以先轉為真正的陣列,在呼叫方法
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/25036.html
標籤:JavaScript
