本文為作者JavaScript學(xué)習(xí)筆記,只是加強(qiáng)個人理解和認(rèn)識。大部分概念與代碼參考了阮一峰 js 教程
基本
1. 語句
語句(statement)是為了完成某種任務(wù)而進(jìn)行的操作
語句和表達(dá)式的區(qū)別在于,前者主要為了進(jìn)行某種操作,一般情況下不需要返回值;后者則是為了得到返回值,一定會返回一個值。
2. 變量
變量是對“值”的具名引用。變量就是為“值”起名,然后引用這個名字,就等同于引用這個值。變量的名字就是變量名。
變量的聲明和賦值,是分開的兩個步驟
如果只是聲明變量而沒有賦值,則該變量的值是==undefined==。==undefined==是一個特殊的值,表示“無定義”。
變量提升
JavaScript 引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運(yùn)行。這造成的結(jié)果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升(hoisting)。
8.判斷
switch語句后面的表達(dá)式,與case語句后面的表示式比較運(yùn)行結(jié)果時,采用的是嚴(yán)格相等運(yùn)算符(===),而不是相等運(yùn)算符(==),這意味著比較時不會發(fā)生類型轉(zhuǎn)換。
7.do...while 循環(huán)
do...while循環(huán)與while循環(huán)類似,唯一的區(qū)別就是先運(yùn)行一次循環(huán)體,然后判斷循環(huán)條件。
不管條件是否為真,do...while循環(huán)至少運(yùn)行一次,這是這種結(jié)構(gòu)最大的特點(diǎn)。另外,while語句后面的分號注意不要省略。
數(shù)據(jù)類型概述
1. 簡介
js 有6種數(shù)據(jù)類型, es6 又新增了第7種symbol
布爾值
下列運(yùn)算符會返回布爾值:
前置邏輯運(yùn)算符:
!(Not)相等運(yùn)算符:
===,!==,==,!=-
比較運(yùn)算符:
>,>=,<,<=如果 JavaScript 預(yù)期某個位置應(yīng)該是布爾值,會將該位置上現(xiàn)有的值自動轉(zhuǎn)為布爾值。轉(zhuǎn)換規(guī)則是除了下面六個值被轉(zhuǎn)為
false,其他值都視為true。undefinednullfalse0NaN-
""或''(空字符串)
注意,空數(shù)組([])和空對象({})對應(yīng)的布爾值,都是true。
數(shù)值
NaN不等于任何值,包括它本身。
與數(shù)值相關(guān)的全局方法
parseInt()
parseInt方法用于將字符串轉(zhuǎn)為整數(shù)。
parseInt('123') // 123
如果parseInt的參數(shù)不是字符串,則會先轉(zhuǎn)為字符串再轉(zhuǎn)換。
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
字符串轉(zhuǎn)為整數(shù)的時候,是一個個字符依次轉(zhuǎn)換,如果遇到不能轉(zhuǎn)為數(shù)字的字符,就不再進(jìn)行下去,返回已經(jīng)轉(zhuǎn)好的部分。
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
parseFloat()
parseFloat方法用于將一個字符串轉(zhuǎn)為浮點(diǎn)數(shù)。
isNaN()
isNaN方法可以用來判斷一個值是否為NaN。isNaN只對數(shù)值有效,如果傳入其他值,會被先轉(zhuǎn)成數(shù)值.也就是說,isNaN為true的值,有可能不是NaN,而是一個字符串。
isFinite()
isFinite方法返回一個布爾值,表示某個值是否為正常的數(shù)值。除了Infinity、-Infinity、NaN和undefined這幾個值會返回false,isFinite對于其他的數(shù)值都會返回true。
字符串
如果要在單引號字符串的內(nèi)部,使用單引號,就必須在內(nèi)部的單引號前面加上反斜杠,用來轉(zhuǎn)義。雙引號字符串內(nèi)部使用雙引號,也是如此。
轉(zhuǎn)義
\n表示換行,輸出的時候就分成了兩行。
字符串與數(shù)組
符串可以被視為字符數(shù)組,因此可以使用數(shù)組的方括號運(yùn)算符,無法改變字符串之中的單個字符。
length屬性返回字符串的長度,該屬性也是無法改變的。
Base64 轉(zhuǎn)碼
JavaScript 原生提供兩個 Base64 相關(guān)的方法。
-
btoa():任意值轉(zhuǎn)為 Base64 編碼 -
atob():Base64 編碼轉(zhuǎn)為原來的值
對象
簡單說,對象就是一組“鍵值對”(key-value)的集合,是一種無序的復(fù)合數(shù)據(jù)集合。
對象的每一個鍵名又稱為“屬性”(property),它的“鍵值”可以是任何數(shù)據(jù)類型。如果一個屬性的值為函數(shù),通常把這個屬性稱為“方法”,它可以像函數(shù)那樣調(diào)用。
對象的引用
如果不同的變量名指向同一個對象,那么它們都是這個對象的引用,也就是說指向同一個內(nèi)存地址。修改其中一個變量,會影響到其他所有變量。
如果取消某一個變量對于原對象的引用,不會影響到另一個變量。
這種引用只局限于對象,如果兩個變量指向同一個原始類型的值。那么,變量這時都是值的拷貝。
屬性的讀取
讀取對象的屬性,有兩種方法,一種是使用點(diǎn)運(yùn)算符,還有一種是使用方括號運(yùn)算符。
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
請注意,如果使用方括號運(yùn)算符,鍵名必須放在引號里面,否則會被當(dāng)作變量處理。
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo] // 2
上面代碼中,引用對象obj的foo屬性時,如果使用點(diǎn)運(yùn)算符,foo就是字符串;如果使用方括號運(yùn)算符,但是不使用引號,那么foo就是一個變量,指向字符串bar。
屬性的賦值
點(diǎn)運(yùn)算符和方括號運(yùn)算符,不僅可以用來讀取值,還可以用來賦值。
JavaScript 允許屬性的“后綁定”,也就是說,你可以在任意時刻新增屬性,沒必要在定義對象的時候,就定義好屬性。
屬性的查看
查看一個對象本身的所有屬性,可以使用Object.keys方法。
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
屬性的刪除:delete 命令
delete命令用于刪除對象的屬性,刪除成功后返回true。
注意,刪除一個不存在的屬性,delete不報錯,而且返回true。
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
屬性是否存在:in 運(yùn)算符
in運(yùn)算符用于檢查對象是否包含某個屬性(注意,檢查的是鍵名,不是鍵值),如果包含就返回true,否則返回false。它的左邊是一個字符串,表示屬性名,右邊是一個對象。
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
in運(yùn)算符的一個問題是,它不能識別哪些屬性是對象自身的,哪些屬性是繼承的。就像上面代碼中,對象obj本身并沒有toString屬性,但是in運(yùn)算符會返回true,因?yàn)檫@個屬性是繼承的。
這時,可以使用對象的hasOwnProperty方法判斷一下,是否為對象自身的屬性。
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
屬性的遍歷:for...in 循環(huán)
for...in循環(huán)用來遍歷一個對象的全部屬性。
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('鍵名:', i);
console.log('鍵值:', obj[i]);
}
如果繼承的屬性是可遍歷的,那么就會被for...in循環(huán)遍歷到。但是,一般情況下,都是只想遍歷對象自身的屬性,所以使用for...in的時候,應(yīng)該結(jié)合使用hasOwnProperty方法,在循環(huán)內(nèi)部判斷一下,某個屬性是否為對象自身的屬性。
var person = { name: '老張' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
with 語句
with語句的格式如下:
with (對象) {
語句;
}
它的作用是操作同一個對象的多個屬性時,提供一些書寫的方便。
// 例一
var obj = {
p1: 1,
p2: 2,
};
with (obj) {
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
// 例二
with (document.links[0]){
console.log(href);
console.log(title);
console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);
注意,如果with區(qū)塊內(nèi)部有變量的賦值操作,必須是當(dāng)前對象已經(jīng)存在的屬性,否則會創(chuàng)造一個當(dāng)前作用域的全局變量。 建議不使用with語句
函數(shù)
函數(shù)是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)還能接受輸入的參數(shù),不同的參數(shù)會返回不同的值。
1.概述
1.1 函數(shù)的聲明
JavaScript 有三種聲明函數(shù)的方法。
1)function 命令
function命令聲明的代碼區(qū)塊,就是一個函數(shù)。function命令后面是函數(shù)名,函數(shù)名后面是一對圓括號,里面是傳入函數(shù)的參數(shù)。函數(shù)體放在大括號里面。
function print(s) {
console.log(s);
}
上面的代碼命名了一個print函數(shù),以后使用print()這種形式,就可以調(diào)用相應(yīng)的代碼。這叫做函數(shù)的聲明(Function Declaration)。
(2)函數(shù)表達(dá)式
除了用function命令聲明函數(shù),還可以采用變量賦值的寫法。
var print = function(s) {
console.log(s);
};
這種寫法將一個匿名函數(shù)賦值給變量。這時,這個匿名函數(shù)又稱函數(shù)表達(dá)式(Function Expression),因?yàn)橘x值語句的等號右側(cè)只能放表達(dá)式。
采用函數(shù)表達(dá)式聲明函數(shù)時,function命令后面不帶有函數(shù)名。如果加上函數(shù)名,該函數(shù)名只在函數(shù)體內(nèi)部有效,在函數(shù)體外部無效。
(3)Function 構(gòu)造函數(shù)
第三種聲明函數(shù)的方式是Function構(gòu)造函數(shù)。
總的來說,這種聲明函數(shù)的方式非常不直觀,幾乎無人使用。
1.2 函數(shù)的重復(fù)聲明
如果同一個函數(shù)被多次聲明,后面的聲明就會覆蓋前面的聲明。
function f() {
console.log(1);
}
f() // 2
function f() {
console.log(2);
}
f() // 2
上面代碼中,后一次的函數(shù)聲明覆蓋了前面一次。而且,由于函數(shù)名的提升(參見下文),前一次聲明在任何時候都是無效的,這一點(diǎn)要特別注意。
1.3 圓括號運(yùn)算符,return 語句和遞歸
調(diào)用函數(shù)時,要使用圓括號運(yùn)算符。圓括號之中,可以加入函數(shù)的參數(shù)。
函數(shù)體內(nèi)部的return語句,表示返回。JavaScript 引擎遇到return語句,就直接返回return后面的那個表達(dá)式的值,后面即使還有語句,也不會得到執(zhí)行。也就是說,return語句所帶的那個表達(dá)式,就是函數(shù)的返回值。return語句不是必需的,如果沒有的話,該函數(shù)就不返回任何值,或者說返回undefined。
函數(shù)可以調(diào)用自身,這就是遞歸(recursion)。下面就是通過遞歸,計算斐波那契數(shù)列的代碼。
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
上面代碼中,fib函數(shù)內(nèi)部又調(diào)用了fib,計算得到斐波那契數(shù)列的第6個元素是8。
1.4函數(shù)名的提升
JavaScript 引擎將函數(shù)名視同變量名,所以采用function命令聲明函數(shù)時,整個函數(shù)會像變量聲明一樣,被提升到代碼頭部。
2.函數(shù)的屬性
name 屬性
函數(shù)的name屬性返回函數(shù)的名字。
如果是通過變量賦值定義的函數(shù),那么name屬性返回變量名。
如果變量的值是一個具名函數(shù),那么name屬性返回function關(guān)鍵字之后的那個函數(shù)名。
name屬性的一個用處,就是獲取參數(shù)函數(shù)的名字。
length 屬性
函數(shù)的length屬性返回函數(shù)預(yù)期傳入的參數(shù)個數(shù),即函數(shù)定義之中的參數(shù)個數(shù)。
toString()
函數(shù)的toString方法返回一個字符串,內(nèi)容是函數(shù)的源碼。
3. 函數(shù)的作用域
在 ES5 的規(guī)范中,Javascript 只有兩種作用域:一種是全局作用域,變量在整個程序中一直存在,所有地方都可以讀??;另一種是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。ES6 又新增了塊級作用域
與全局作用域一樣,函數(shù)作用域內(nèi)部也會產(chǎn)生“變量提升”現(xiàn)象。var命令聲明的變量,不管在什么位置,變量聲明都會被提升到函數(shù)體的頭部。
函數(shù)本身的作用域
函數(shù)本身也是一個值,也有自己的作用域。它的作用域與變量一樣,就是其聲明時所在的作用域,與其運(yùn)行時所在的作用域無關(guān)。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代碼中,函數(shù)x是在函數(shù)f的外部聲明的,所以它的作用域綁定外層,內(nèi)部變量a不會到函數(shù)f體內(nèi)取值,所以輸出1,而不是2。
總之,函數(shù)執(zhí)行時所在的作用域,是定義時的作用域,而不是調(diào)用時所在的作用域。
閉包就是創(chuàng)建局部變量, 使得函數(shù)外部可以訪問到這個變量
4.參數(shù)
函數(shù)運(yùn)行的時候,有時需要提供外部數(shù)據(jù),不同的外部數(shù)據(jù)會得到不同的結(jié)果,這種外部數(shù)據(jù)就叫參數(shù)。
函數(shù)參數(shù)不是必需的,Javascript 允許省略參數(shù)。
但是,沒有辦法只省略靠前的參數(shù),而保留靠后的參數(shù)。如果一定要省略靠前的參數(shù),只有顯式傳入undefined。
傳遞方式
函數(shù)參數(shù)如果是原始類型的值(數(shù)值、字符串、布爾值),傳遞方式是傳值傳遞(passes by value)。這意味著,在函數(shù)體內(nèi)修改參數(shù)值,不會影響到函數(shù)外部。
如果函數(shù)參數(shù)是復(fù)合類型的值(數(shù)組、對象、其他函數(shù)),傳遞方式是傳址傳遞(pass by reference)。也就是說,傳入函數(shù)的原始值的地址,因此在函數(shù)內(nèi)部修改參數(shù),將會影響到原始值。
注意,如果函數(shù)內(nèi)部修改的,不是參數(shù)對象的某個屬性,而是替換掉整個參數(shù),這時不會影響到原始值。
arguments 對象
由于 JavaScript 允許函數(shù)有不定數(shù)目的參數(shù),所以需要一種機(jī)制,可以在函數(shù)體內(nèi)部讀取所有參數(shù)。這就是arguments對象的由來。
嚴(yán)格模式下,arguments對象是一個只讀對象,修改它是無效的,但不會報錯。
與數(shù)組的關(guān)系
需要注意的是,雖然arguments很像數(shù)組,但它是一個對象。數(shù)組專有的方法(比如slice和forEach),不能在arguments對象上直接使用。
如果要讓arguments對象使用數(shù)組方法,真正的解決方法是將arguments轉(zhuǎn)為真正的數(shù)組。下面是兩種常用的轉(zhuǎn)換方法:slice方法和逐一填入新數(shù)組。
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
5.函數(shù)的其他知識點(diǎn)
閉包
理解閉包,首先必須理解變量作用域。前面提到,JavaScript 有兩種作用域:全局作用域和函數(shù)作用域。函數(shù)內(nèi)部可以直接讀取全局變量。
由于在 JavaScript 語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量,因此可以把閉包簡單理解成“定義在一個函數(shù)內(nèi)部的函數(shù)”。閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境. 在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
閉包的最大用處有兩個,一個是可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量始終保持在內(nèi)存中,即閉包可以使得它誕生環(huán)境一直存在。
閉包的另一個用處,是封裝對象的私有屬性和私有方法。
立即調(diào)用的函數(shù)表達(dá)式
解決方法就是不要讓function出現(xiàn)在行首,讓引擎將其理解成一個表達(dá)式。最簡單的處理,就是將其放在一個圓括號里面。
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
通常情況下,只對匿名函數(shù)使用這種“立即執(zhí)行的函數(shù)表達(dá)式”。它的目的有兩個:一是不必為函數(shù)命名,避免了污染全局變量;二是 IIFE 內(nèi)部形成了一個單獨(dú)的作用域,可以封裝一些外部無法讀取的私有變量。
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
上面代碼中,寫法二比寫法一更好,因?yàn)橥耆苊饬宋廴救肿兞俊?/p>
數(shù)組
1.定義
數(shù)組(array)是按次序排列的一組值。每個值的位置都有編號(從0開始),整個數(shù)組用方括號表示。
除了在定義時賦值,數(shù)組也可以先定義后賦值。
任何類型的數(shù)據(jù),都可以放入數(shù)組。
2.數(shù)組的本質(zhì)
本質(zhì)上,數(shù)組屬于一種特殊的對象。typeof運(yùn)算符會返回數(shù)組的類型是object。
3.length 屬性
數(shù)組的length屬性,返回數(shù)組的成員數(shù)量。
只要是數(shù)組,就一定有length屬性。該屬性是一個動態(tài)的值,等于鍵名中的最大整數(shù)加上1。
length屬性是可寫的。如果人為設(shè)置一個小于當(dāng)前成員個數(shù)的值,該數(shù)組的成員會自動減少到length設(shè)置的值。
清空數(shù)組的一個有效方法,就是將length屬性設(shè)為0。
如果人為設(shè)置length大于當(dāng)前元素個數(shù),則數(shù)組的成員數(shù)量會增加到這個值,新增的位置都是空位。
4. in 運(yùn)算符
檢查某個鍵名是否存在的運(yùn)算符in,適用于對象,也適用于數(shù)組。
5.for...in 循環(huán)和數(shù)組的遍歷
for...in循環(huán)不僅可以遍歷對象,也可以遍歷數(shù)組,畢竟數(shù)組只是一種特殊對象。但是,for...in不僅會遍歷數(shù)組所有的數(shù)字鍵,還會遍歷非數(shù)字鍵。所以,不推薦使用for...in遍歷數(shù)組。
數(shù)組的遍歷可以考慮使用for循環(huán)或while循環(huán)。
數(shù)組的forEach方法,也可以用來遍歷數(shù)組,詳見《標(biāo)準(zhǔn)庫》的 Array 對象一章。
數(shù)組的空位
當(dāng)數(shù)組的某個位置是空元素,即兩個逗號之間沒有任何值,我們稱該數(shù)組存在空位(hole)。需要注意的是,如果最后一個元素后面有逗號,并不會產(chǎn)生空位。也就是說,有沒有這個逗號,結(jié)果都是一樣的。
數(shù)組的空位是可以讀取的,返回undefined。
使用delete命令刪除一個數(shù)組成員,會形成空位,并且不會影響length屬性。
類似數(shù)組的對象
如果一個對象的所有鍵名都是正整數(shù)或零,并且有length屬性,那么這個對象就很像數(shù)組,語法上稱為“類似數(shù)組的對象”(array-like object)。
典型的“類似數(shù)組的對象”是函數(shù)的arguments對象,以及大多數(shù) DOM 元素集,還有字符串。
數(shù)組的slice方法可以將“類似數(shù)組的對象”變成真正的數(shù)組。
var arr = Array.prototype.slice.call(arrayLike);
除了轉(zhuǎn)為真正的數(shù)組,“類似數(shù)組的對象”還有一個辦法可以使用數(shù)組的方法,就是通過call()把數(shù)組的方法放到對象上面。
function print(value, index) {
console.log(index + ' : ' + value);
}
Array.prototype.forEach.call(arrayLike, print);
注意,這種方法比直接使用數(shù)組原生的forEach要慢,所以最好還是先將“類似數(shù)組的對象”轉(zhuǎn)為真正的數(shù)組,然后再直接調(diào)用數(shù)組的forEach方法。