JavaScript學習(1)之JavaScript基礎
由于工作原因,開發語言逐漸以JavaScript為主,所以,抽空學習了下JavaScript語法。等現階段的工作穩定之后,陸續會分享下自己在學習和開發過程中的一些經驗總結。本著"技多不壓身"的原則以及對各種編程語言的熱愛,雖然筆者一直從事游戲開發工作,也愉快而又義無反顧的加入了JavaScript陣營。
1、JavaScript概述以應用范圍
1.1 JavaScript概述
首先,JavaScript是一門動態類型的編程語言。支持面向對象、函數式等編程范式。同時,它也是運行在宿主環境下的輕量級的腳本語言,例如:瀏覽器,JavaScript代碼可嵌入到HTML頁面中。當然,也有應用基于Node.js的服務器環境。可以說它是主流瀏覽器都支持的腳本語言,這也造就了JavaScript在PC、手機、平板等環境處理與網頁交互時的天然優勢。隨著HTML5的推廣與廣泛應用,出現了大量基于JavaScript的跨平臺框架和游戲引擎,是通往全棧開發很值得學習的一門編程語言。正如在編程語言界流行著“世界終將是JS的”的槽點,足以可見JavaScript的強大。
1.2 JavaScript應用范圍
web前端:最近比較火的Vue.js、React、 Angular等等前端框架層出不窮。
手機app:React Native、PhoneGap、Weex等跨平臺移動框架等,以及微信小程序等。
游戲引擎:Cocos2d-js、Unity、egret等引擎都支持JavaScript腳本語言。
服務器:pomelo、Node.js等。
2、基本概念
2.1 語法
JavaScript語法中大量借鑒了C和Java、Perl等語言的語法,因此熟悉這些語言的人再來學習JavaScript會感覺似曾相識。但JavaScript與Java是兩回事,JavaScript完全是蹭了當年Java的熱度,來推動自己。
一個完整的JavaScript實現由下面三個不同的部分組成:
- 核心(ECMAScript)
- 文檔對象模型(DOM)
- 瀏覽器對象模型(BOM)
ECMAScript:
定義了語言的基礎,規定并對該語言在語法、類型、語句、關鍵字、保留字、操作符、對象等方面作了具體描述,并不包含輸入和輸出。
DOM:
文檔對象模型(Document Object Model)是XML和HTML的編程接口。DOM會把整個頁面映射為一個多層節點結構樹,程序可以對結構樹進行添加、刪除、替換或修改任何節點。
BOM:
瀏覽器對象模型(Browser Object Model)支持訪問和操作瀏覽器窗口。
注:以下描述就不區分ECMAScript和JavaScript,也不簡稱js,均統稱JavaScript。
2.2 標識符
這里的標識符,統指變量名、函數名、屬性名以及函數參數的名字。那么,標識符的命名有以下規則:(這些規則跟C、Java等語言類似)
- 第一個字符必須是一個字母、下劃線_或者美元符號$
- 其他字符則可以是字母、數字、下劃線和美元符號。
- 不能使用關鍵字、保留字來定義標識符。例如:if、for等。
注:在JavaScript中,變量名、函數名甚至操作符等是區分大小寫的。即name和Name代表的是兩個不同的變量。
2.3 注釋
單行注釋使用//。例如:
// 這是一個單行注釋
多行注釋使用/***/結構。例如:
/*
* 這是一個多行注釋
*/
2.4 嚴格模式
啟用嚴格模式,會對一些不確定的行為或不安全的操作拋出錯誤。在腳本的頂部添加如下字符串:
"use strict";
也可以添加到函數內部,來指定該函數在嚴格模式下執行。
2.5 語句
JavaScript中每條語句都是以分號結尾,但分號不是必需的。但建議是不要省略分號,來避免如不完整的輸入等問題。語句塊用花括號包含。
2.6 變量
可以使用var
關鍵字來定義變量,JavaScript的變量可以用來保存任何類型的數據。例如:
var name = "AlphaGL";
name = 18;
name = function() {
};
也可以使用逗號來定義多個變量。例如:
var name = "AlphaGL",
age = 18,
work = true;
雖然JavaScript支持這種寫法,一般不推薦。不僅可讀性差,而且易出錯。
在函數內聲明的變量,那么該變量的作用域為該函數內。例如:
function test() {
var name = "AlphaGL";
}
test();
console.log(name); // ReferenceError: name is not defined
雖然可以去掉var聲明來解決這個問題,例如:
function test() {
name = "AlphaGL";
}
test();
console.log(name);
這時,name就成了全局變量。一般不推薦這種做法。全局變量難以維護,而且容易導致結構混亂。
3、數據類型
JavaScript中數據類型主要有:Number、String、Boolean、Object、undefined、null這幾種類型。以及涉及到類型判斷時會用到的操作有:typeof、instanceof、Object.prototype.toString。
3.1 Number
(1)JavaScript中并不區分整型或浮點數,所有數字均統一采用64位浮點數表示。可用Number.MIN_VALUE和Number.MAX_VALUE來獲取Number類型的值的范圍。
console.log(Number.MIN_VALUE); // 5e-324
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
(2)NaN
非數值(Not a Number)是一個特殊的數值。當算術運算返回一個未定義或無法表示的值時,就產生NaN了。
console.log(typeof(NaN)); // number
console.log(0 / 0); // NaN
console.log("hello" / 5); // NaN
console.log(NaN + 1); // NaN
注:0除以0會返回NaN。其它數除以0不會。
判斷一個變量是否是非數值,可以使用isNaN()函數。
console.log(isNaN(NaN)); // true
console.log(NaN == NaN); // false
console.log(isNaN("hello")); // true,"hello"不能轉換成數值
console.log(isNaN(true)); // false
console.log(isNaN(false)); // false
console.log(isNaN(5)); // false
console.log(isNaN("5")); // false
注:NaN與其它所有值都不相等,包括它自己。因此判斷NaN不要用
==
或===
。
(3)Infinity(正無窮)和-Infinity(負無窮)
JavaScript中還有兩個特殊的數值Infinity和-Infinity。
console.log(typeof(Infinity)); // number
console.log(typeof(-Infinity)); // number
console.log(1 / 0); // Infinity
console.log(1 / -0); // -Infinity
判斷一個變量是否是有窮數,可以使用isFinite()函數。
console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity)); // false
console.log(isFinite(NaN)); // false
console.log(isFinite(0)); // true
console.log(isFinite("0")); // true
3.2 String
(1)String類型字符串是由單引號或雙引號包括的零個或多個字符序列。
var key = "hello";
var value = 'world';
(2)字符串可以像數組一樣訪問,但一旦字符串的值確定了,就不能改變。
var foo = "Java";
foo[0] = "S";
console.log(foo); // Java
foo = foo + "Script";
console.log(foo); // JavaScript
(3)toString()與String
var a = 5;
var b = false;
var c = "AlphaGL"
var d = new Date();
console.log(a); // 5
console.log(b); // false
console.log(c); // AlphaGL
console.log(d); // 2017-05-12T05:54:30.547Z
console.log(String(5)); // 5
console.log(String(false)); // false
console.log(null); // null
console.log(undefined); // undefined
還有一些常規的String操作的api,可以查閱JavaScript的String api文檔。
3.3 Boolean
該類型只有兩個字面值:true和false。需要強調的是,true不一定是1,false也不一定是0。其它類型轉換成Boolean類型時,有一下規則:
- Boolean
true轉換成true,false轉換成false。 - String
任何非空字符串轉換成true,空字符串("")轉換成false。 - Number
任何非零數字值(包括無窮大)轉換成true,0和NaN轉換成false。 - Object
任何對象轉換成true,null轉換成false。 - Undefined
undefined轉換成false。
console.log(Boolean("AlphaGL")); // true
console.log(Boolean("false")); // true
console.log(Boolean("")) // false
console.log(Boolean(10)); // true
console.log(Boolean(Infinity)); // true
console.log(Boolean(-Infinity)); // true
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(new Date())); // true
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
綜上:當值為0,null,false,NaN,undefined,空字符串("")轉換成Boolean類型時值都為false,其它任何值都為true。
以下實例:
(1)
var x;
if (x) {
console.log("test1")
}else {
console.log("test2")
}
輸出:test2
(2)
var x = null;
if (x) {
console.log("test1")
}else {
console.log("test2")
}
輸出:test2
(3)
var x = Boolean(false);
if (x) {
console.log("test1")
}else {
console.log("test2")
}
輸出:test2
(4)
var x = false;
if (x) {
console.log("test1")
}else {
console.log("test2")
}
輸出:test2
(5)
var x = new Boolean(false);
if (x) {
console.log("test1")
}else {
console.log("test2")
}
輸出:test1
綜上:任何值不為undefined或者null的對象, 包括值為false的Boolean對象, 在條件語句中,其值都將作為true來判斷。
3.4 Object
對象是JavaScript中重要的數據類型,除數字、true、false、null、undefined和字符串外,所有的值都是對象。在JavaScript中對象是一組無序的鍵值對集合,類似其它語言的HashMap、Dictionary等數據結構。下面會有單獨一小節來專門講述下對象。
3.5 undefined
在使用var聲明變量但未對其初始化時,該變量的值即為:undefined。undefined類型也只有這一個值。
字面值undefined主要用于比較。
var i = undefined;
console.log(i == undefined); // true
未初始化的變量與未聲明的變量
var foo;
console.log(foo); // undefined
console.log(bar); // 報錯
var foo;
console.log(typeof(foo)); // undefined
console.log(typeof(bar)); // undefined
使用typeof操作符來檢測未初始化的變量與未聲明的變量,都返回undefined。
3.6 null
與undefined類似,null類型也只有一個值null。空值null表示一個空的對象指針。
console.log(typeof(null)); // object
null與undefined比較:
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(isNaN(1 + null)); // false
console.log(isNaN(1 + undefined)); // true
console.log(null === undefined); // false
console.log(null == undefined); // true
3.7 typeof、instanceof與Object.prototype.toString
由于JavaScript是動態類型的,因此就需要一種手段來檢測給定的變量的具體數據類型。
(1)typeof
typeof操作符返回的值是以下某個字符串:
- "undefined"
- "boolean"
- "string"
- "number"
- "object"
- "function"
console.log(typeof(123)); // number
console.log(typeof("123")); // string
console.log(typeof(NaN)); // number
console.log(typeof(Infinity)); // number
console.log(typeof(false)); // boolean
console.log(typeof(null)); // object
console.log(typeof([])); // object
console.log(typeof({})); // object
console.log(typeof(function(){})); // function
console.log(typeof(undefined)); // undefined
(2)instanceof:下文會討論。
(3)Object.prototype.toString:下文會討論。
4、流程控制與運算符
4.1 流程控制
流程控制主要有條件語句和循環語句。條件語句又分為:if語句,if-else語句,switch語句等。循環語句分為:while循環,for循環,do-while循環。
4.1.1 條件語句
(1)if語句
if (condition1) {
statement1;
}else if(condition2) {
statement2;
}else {
statement3;
}
該結構,當條件1(condition1為true)滿足時,執行語句1(statement1),否則條件2(condition2為true)滿足時,執行語句2,以上都不滿足則執行語句3(statement3)。
這里的else if
可以有零個或多個,甚至else
都是可選的,根據實際情況來做實際判斷。
if (i > 0) {
console.log("i大于0");
}else if(i < 0) {
console.log("i小于0");
}else {
console.log("i等于0");
}
(2)switch語句
switch是一種多分支結構,與if結構類似,也是一種普遍使用的流程控制語句。
switch(expression) {
case value1:
statement1;
break;
case value2:
statement2;
break;
default:
statement3;
}
當表達式(expression)的值,等于對應case的值(value),則會執行對應的語句塊(statement),break用于跳出該switch結構,若省略break,則會繼續執行下一個case。default用于以上表達式的值都不滿足條件。
表達式(expression)可以使用任何數據類型,包括字符串、對象。case的值(value)也可以是變量、表達式,不一定是常量。這點與其他有的語言不同,只能使用數值。
4.1.2 循環語句
(1)while語句
while循環包含循環條件和循環體,當循環條件滿足條件時,才會執行循環體內的代碼塊。
while(expression) {
statement;
}
while(i < 100) {
i++;
}
(2)for語句
for語句一般包含初始條件、循環條件、循環遞增(遞減)語句三部分。
for(initialization; expression; post-loop-expression) {
statement;
}
for(var i = 0; i < 100; i++) {
console.log(i);
}
(3)for-in語句
該語句類似于其他語言的foreach語句,可以用了遍歷容器結構或對象的屬性。
for (property in expression) {
statement;
}
var o = [1, 2, 3];
for(var i in o) {
console.log(o[i]);
}
(4)do-while語句
do-while語句與while語句類似,不同的時,do-while語句會先執行循環體,再檢測循環條件,這就意味著該循環語句至少會執行一次。
do {
statement;
}while(expression);
(5)break與continue
break和continue都可用于控制循環中代碼的執行。
break:立即退出循環。
continue:跳出當前循環,繼續執行下次循環。
4.2 運算符
JavaScript中運算符大體可分為,算術運算符、關系運算符、邏輯運算符以、位運算符以及其它操作符等,當然也包括運算符重載。
- 算術運算符:
+
,-
,*
,/
,%
,++
,--
,+=
,-=
,*=
,/=
,%=
等。 - 關系運算符:
<
,>
,<=
,>=
,==
,===
,!=
,!==
。 - 邏輯運算符:
&&
,||
,!
- 位運算符:
~
,&
,|
,^
,<<
,>>
,>>>
- 其他操作符:
=
,,
,?=
4.2.1 算術運算符
(1)加法操作符(+)
加減法操作符時JavaScript中最常用得操作符之一,與其他語言不同得是,在JavaScript中有一系列特殊得行為,使用時應當注意。
console.log(1 + 2); // 3
console.log(1 + "2"); // 12
console.log("1" + "2"); // 12
console.log(1 + [2]); // 12
console.log(1 + "2" + 3); // 123
console.log("1" + 2 + 3); // 123
console.log(1 + 2 + "3"); // 33
console.log("1" + 2 + "3"); // 123
console.log(1 + [2, 3]); // 12,3
console.log(1 + ""); // 1
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.05 + 0.25); // 0.3
// boolean
console.log(1 + true); // 2
console.log(1 + false); // 1
// string
console.log("AlphaGL:" + null); // AlphaGL:null
console.log("AlphaGL:" + undefined); // AlphaGL:undefined
console.log("AlphaGL:" + Infinity); // AlphaGL:Infinity
console.log("AlphaGL:" + NaN); // AlphaGL:NaN
console.log("AlphaGL:" + false); // AlphaGL:false
console.log("AlphaGL:" + true); // AlphaGL:true
// infinity
console.log(1 + Infinity); // Infinity
console.log(Infinity + Infinity); // Infinity
console.log(1 + (-Infinity)); // -Infinity
console.log((-Infinity) + (-Infinity)); // -Infinity
console.log(Infinity + (-Infinity)); // NaN
// 0
console.log((+0) + (+0)); // 0
console.log((-0) + (-0)); // -0
console.log(0 + (-0)); // 0
// NaN
console.log(1 + NaN); // NaN
console.log(1 + null); // 1
console.log(1 + undefined); // NaN
console.log(1 + (new Date())); // 1Mon May 25 2017 17:09:08 GMT+0800 (中國標準時間)
console.log(1 + {name: "AlphaGL"}); // 1[object Object]
綜上,使用加法操作符可以總結為如下規則:
- 如果兩個操作數都是數值,則執行常規得加法計算。這里需要注意浮點數的加法。
- 如果一個操作數為字符串,則將另外一個操作數也轉化成字符串類型,再執行字符串的拼接。
- 如果一個操作數是數值,另外一個操作是Infinity,則加法結果為Infinity。如果一個操作數是數值,另外一個操作數是-Infinity,則加法結果為-Infinity。如果是Infinity加-Infinity,則加法結果為NaN。如果一個操作數是數值,另外一個操作數是NaN,則加法結果為NaN。
- 如果一個操作數是數值,另外一個操作數是boolean,null類型,則先將boolean和null類型轉行成原始值,再執行加法運算。
- 如果一個操作數是數值,另外一個操作數是對象,則會先調用對象的
valueOf
方法轉化成原始值,如果對象沒有valueOf
方法,則調用toString
方法。
(2)減法運算符(-)
減法的運算規則與加法類似,這里就不再詳細介紹了。
(3)乘法運算符(*)
console.log(2 * 3); // 6
console.log(-2 * -3); // 6
console.log(2 * -3); // -6
console.log(2 * Number.MAX_VALUE); // Infinity
console.log(-2 * Number.MAX_VALUE); // -Infinity
// NaN
console.log(2 * NaN); // NaN
console.log(-2 * NaN); // NaN
console.log(0 * NaN); // NaN
console.log(NaN * NaN); // NaN
// Infinity
console.log(2 * Infinity); // Infinity
console.log(-2 * Infinity); // -Infinity
console.log(-2 * -Infinity); // Infinity
console.log(0 * Infinity); // NaN
console.log(Infinity * Infinity); // Infinity
console.log(-Infinity * -Infinity); // Infinity
// undefined
console.log(2 * undefined); // NaN
console.log(0 * undefined); // NaN
console.log(undefined * undefined); // NaN
// boolean
console.log(2 * false); // 0
console.log(2 * true); // 2
console.log(2 * "34"); // 68
console.log(2 * "AlphaGL"); // NaN
console.log(2 * [3, 4]); // NaN
console.log(2 * {name:"34"}); // NaN
console.log(2 * new Date()); // 2992421935692
綜上,使用減法操作符可以總結為如下規則:
- 兩個正數或兩個負數相乘,結果為正數。其它有一個操作數為負數,那結果也為負數。如果結果超出數值的表示范圍,則結果為Infinity或-Infinity。
- 如果有一個操作數為NaN或undefined,則結果為NaN。
- 如果一個非0數值與Infinity或-Infinity相乘,則結果為Infinity或-Infinity,符號取決于操作數的符號和Infinity還是-Infinity。0與Infinity或-Infinity,則結果為NaN。
- 如果一個操作數是數值,另外一個操作數是boolean或者字符串,則先將該操作數轉化為原始值,如果轉化后的值不是數值,則結果為NaN,否則執行常規乘法運算。
- 如果一個操作數是數值,另外一個操作數是對象,則結果為NaN。如果是Date對象,則乘以基于當前到1970年1月1日起的毫米數。
(4)除法操作數(/)
除法的運算規則與乘法類似,同樣,這里就不再詳細介紹了。
(5)模(求余)運算(%)
該運算符是求得除法運算后的余數。
console.log(10 % 3); // 1
console.log(-10 % 3); // -1
console.log(10 % -3); // 1
console.log(10 % 3.14); // 0.5799999999999996
綜上,模運算規則如下:
- 模運算的結果的符號,與第一個操作數相同。模運算用于浮點數時,結果會有誤差。
(6)自增(++)與自減(--)
自增和自減有分為前置和后置。
var x = 5;
var y = ++x - 2
/* 等價于
* var x = 5;
* x = x + 1;
* var y = x - 2;
*/
console.log(x); // 6
console.log(y); // 4
var x = 5;
var y = x++ - 2;
/* 等價于
* var x = 5;
* var y = x - 2;
* x = x + 1;
*/
console.log(x); // 6
console.log(y); // 3
前置自增與后置自增的區別是,前置自增先執行自增,再執行后續的運算,后置自增是先執行運算,再執行自增。同理,自減原理也一樣,就不在贅述了。
(7)x op= y操作
這里把+=
,-=
,*=
,/=
,%=
等復合運算統稱為op=
,那么:
x op= y
大多數情況下等價于:
x = x op y
其中,下面這個表達式中x計算了兩次,在x含有副作用的表達式時,二者就不等價了。
var c = [1, 2, 3];
var i = 0;
c[i++] *= 2;
console.log(c)
// [ 2, 2, 3 ]
var d = [1, 2, 3];
var j = 0;
d[j++] = d[j++] * 2;
console.log(d);
// [ 4, 2, 3 ]
4.2.2 關系運算符
用來判斷一個操作數是否大于或小于或等于另外一個操作數。
console.log(2 < 3); // true
console.log("12" < 3); // false
console.log("12" < "3"); // true
console.log("Alpha" < "alpha"); // true
console.log("AlphaGL" < "AlphagL"); // true
console.log(2 < "AlphaGL"); // false
console.log(2 < true); // false
console.log(2 < undefined); // false
console.log(2 < null); // false
console.log(2 < NaN); // false
console.log(false < true); // true
console.log(2 < Infinity); // true
console.log(2 < -Infinity); // false
console.log(Infinity < Infinity); // false
console.log(Infinity < Infinity + 1); // false
console.log(0 <= 0); // true
console.log(0 >= 0); // true
console.log(12 == "12"); // true
console.log(12 === "12"); // false
console.log(12 !== "12"); // true
console.log(undefined == 0); // false
console.log(undefined == null); // true
console.log(undefined == false); // false
console.log(null == false); // false
console.log(null == 0); // false
console.log("" == 0); // true
console.log(undefined == ""); // false
console.log(2 != NaN); // true
console.log(NaN == NaN); // false
console.log(NaN != NaN); // true
console.log(false == 0); // true
console.log(true == 1); // true
綜上,關系運算符返回的都是boolean值,有以下規則:
- 如果比較的兩個操作數都是數值,則執行數值比較。如果只有一個操作數是數值,則將另外一個操作數轉換為數值,再執行數值比較。
- 如果比較的兩個操作數都是字符串,則依次比較字符串每個字符的Unicode值。
- 如果有一個操作數是NaN,則執行結果為false,執行不相等操作時,執行結果為true。
- null和undefined相等。但不能將null和undefined轉化為其它任何值。
- 如果有一個操作數是對象,另外一個操作數不是,則會調用對象的valueOf方法得到原始值,再應用上面的規則。
- 當兩個操作數的值相同,類型也相同,并且都不是NaN時,則兩個操作數全等(===)。當比較的兩個操作數轉換為相同類型后的值相等,則兩個操作數相等(==)。
4.2.3 邏輯運算符
(1)邏輯與(&&)
在boolean環境下當邏輯與的兩個操作數同時為true時,結果才為true,否則為false。
console.log(new Date() && 2); // 2
console.log(2 && new Date()); // 2017-05-31T02:39:51.033Z
console.log(false && new Date()); // false
console.log(new Date() && new Date()); // 2017-05-31T02:39:51.035Z
console.log(false && 0); // false
console.log(true && 0); // 0
console.log(2 && 0); // 0
console.log(2 && ""); // ""
console.log(2 && "AlphaGL"); // AlphaGL
console.log(2 && null); // null
console.log(2 && undefined); // undefined
console.log(2 && NaN); // NaN
console.log(2 && Infinity); // Infinity
綜上,邏輯與的使用規則可以總結如下:
- 如果第一個操作數能轉換成false,則返回第一個操作數,否則返回第二個操作數。在boolean環境中使用時,兩個操作數結果都為true時,返回true,否則返回false。
- 能夠轉換為false的值有,0,"",null,undefined。
短路操作:
在執行邏輯與操作時,當第一個操作數的結果為false時,就不在執行第二個操作數的求值了。因為無論第二個操作數為何值,其結果都不可能為true。
function test(i) {
if(i > 0) {
return i;
}else{
return -i;
}
}
console.log(false && test(2)); // false
console.log(true && test(2)); // 2
(2)邏輯或(||)
在boolean環境下當邏輯或的兩個操作數任意一個為true時,結果都為true。一般,可用來給變量設置默認值。
console.log(new Date() || 2); // 2017-05-31T02:46:51.732Z
console.log(2 || new Date()); // 2
console.log(false || new Date()); // 2017-05-31T02:48:51.732Z
console.log(new Date() || new Date()); // 2017-05-31T02:48:51.732Z
console.log(false || 0); // 0
console.log(true || 0); // true
console.log(2 || 0); // 2
console.log(2 || ""); // 2
console.log(2 || "AlphaGL"); // 2
console.log(2 || null); // 2
console.log(2 || undefined); // 2
console.log(2 || NaN); // 2
console.log(2 || Infinity); // 2
綜上,邏輯或的使用規則可以總結如下:
- 如果第一個操作數能轉換成true,則返回第一個操作數,否則返回第二個操作數。在boolean環境中使用時,兩個操作數任意一個為true時,返回true,否則返回false。
- 能夠轉換為false的值有,0,"",null,undefined。
短路操作:
在執行邏輯或操作時,當第一個操作數的結果為true時,就不在執行第二個操作數的求值了。因為無論第二個操作數為何值,其結果都不可能為false。
function test(i) {
if(i > 0) {
return i;
}else{
return -i;
}
}
console.log(false || test(2)); // 2
console.log(true || test(2)); // true
(3)邏輯非(!)
無論操作數是什么類型的數據,該操作都會返回一個boolean。邏輯非會先將操作數轉換為一個boolean,再對齊求反。
console.log(!0); // true
console.log(!""); // true
console.log(!NaN); // true
console.log(!null); // true
console.log(!undefined); // true
console.log(!Infinity); // false
console.log(!2); // false
console.log(!"AlphaGL"); // false
console.log(!new Date()); // false
綜上,邏輯非的使用規則可以總結如下:
- 如果操作數能轉換為true的話,則返回false,否則返回false。
- 能夠轉換為false的值有,0,"",null,undefined。
4.2.4 位運算符
位運算是比較低層次的運算,按內存中表示數值的位來操作數值。JavaScript中所有的數值都是以64位格式存儲,而位操作符是先將64位的值轉換成32位的整數,然后執行操作,最后再將結果轉換回64位。
對于有符號的整數,32中的前31位表示整數的值,第32位表示數值的符號,用0表示整數,1表示負數,因此第32位也叫符號位。其中,正數是以二進制格式存儲的,負數二進制補碼的形式存儲的。
(1)原碼、反碼和補碼
原碼,是該數值的符號位與絕對值的二進制表示。例如:
2[原碼]: 0000 0000 0000 0000 0000 0000 0000 0010
-2[原碼]: 1000 0000 0000 0000 0000 0000 0000 0010
反碼,正數的反碼是其原碼。負數的反碼,是符號位不變,其余各位取反,即1變成0,0變成1。例如:
2[反碼]: 0000 0000 0000 0000 0000 0000 0000 0010
-2[反碼]:1111 1111 1111 1111 1111 1111 1111 1101
補碼,正數的補碼是其原碼。負數的補碼,是其反碼加1。例如:
2[補碼]: 0000 0000 0000 0000 0000 0000 0000 0010
-2[補碼]:1111 1111 1111 1111 1111 1111 1111 1110
(2)按位與(&)
按位于,是將兩個操作數的二進制位對齊,當兩個數值的位都為1時,結果為1,任意一個為0,則結果為0。
console.log(3 & 5); // 1
3 = 0000 0000 0000 0000 0000 0000 0000 0011
5 = 0000 0000 0000 0000 0000 0000 0000 0101
& = 0000 0000 0000 0000 0000 0000 0000 0001
(3)按位或(|)
按位或,是將兩個操作數的二進制位對齊,當兩個數值的位任意一個為1時,結果為1,兩個都為0,則結果為0。
console.log(3 | 5); // 7
3 = 0000 0000 0000 0000 0000 0000 0000 0011
5 = 0000 0000 0000 0000 0000 0000 0000 0101
| = 0000 0000 0000 0000 0000 0000 0000 0111
(4)按位非(~)
按位非,是得到該數值的反碼。
console.log(~3); // -4
3 = 0000 0000 0000 0000 0000 0000 0000 0011
~ = 1111 1111 1111 1111 1111 1111 1111 1100
(5)按位異或(^)
按位異或,是將兩個操作數的二進制位對齊,當兩個數值的位其中只有一個為1時,結果為1,兩個都為0或都為1,則結果為0。
console.log(3 ^ 5); // 6
3 = 0000 0000 0000 0000 0000 0000 0000 0011
5 = 0000 0000 0000 0000 0000 0000 0000 0101
^ = 0000 0000 0000 0000 0000 0000 0000 0110
(6)左移(<<)
左移,是將操作數的所有位移動指定的位數,右側多出的位用0填充。左移不影響操作數的符號位。
console.log(3 << 2); // 12
console.log(-3 << 2); // -12
3 = 0000 0000 0000 0000 0000 0000 0000 0011
<< 2 = 0000 0000 0000 0000 0000 0000 0000 1100
(7)有符號右移(>>)
有符號右移,是將操作數的所有位移動指定的位數,并保留符號位。左側多出的位用0填充。
console.log(12 >> 2); // 3
console.log(-12 >> 2); // -3
12 = 0000 0000 0000 0000 0000 0000 0000 1100
>> 2 = 0000 0000 0000 0000 0000 0000 0000 0011
(8)無符號右移(>>>)
無符號右移,是將操作數的所有位移動指定的位數。對于正數,無符號右移與有符號右移結果相同,負數會以補碼的形式右移指定的位。
console.log(12 >>> 2); // 3
console.log(-12 >>> 2); // 1073741821
4.2.5 其它運算符
(1)賦值運算符(=)
賦值可以和其他運算符組合使用。例如:
var x = 3;
console.log(x += 5); // 8
(2)逗號運算符(,)
逗號運算符,可以再一條語句中執行多個操作。如果,逗號運算符用于賦值,則返回表達式中的最后一項。
var x = 2, y = 3, z = 5;
var pos = (2, 3, 5);
console.log(z); // 5
console.log(pos); // 5
(3)三目運算符(?=)
三目運算符,格式形如:
variable = boolean_expression ? true_value : false_value
當表達式boolean_expression的值位true時,則返回true_value的值,否則,返回false_value的值。
console.log(1 > 2 ? 1 + 2 : 1 - 2); // -1
5、對象
在介紹數據類型的時候提到過,在JavaScript中對象是一組無序的鍵值對集合,類似其它語言的HashMap、Dictionary等數據結構。除數字、true、false、null、undefined和字符串外,所有的值都是對象。JavaScript內置了Object、Date、Array、Function、RegExp等對象。所有對象繼承Object對象。
5.1 對象的創建
對象的創建分為兩種方式:
(1)使用new操作符,后面緊跟構造函數
var student = new Object(); // 等價于 var student = {};
student.name = "AlphaGL";
student.age = 18;
student.print = function () {
console.log("hello AlphaGL");
}
(2)使用對象字面量表示法。由若干名/值對中間用冒號分割,每對名/值對間用逗號分隔組成的映射表。
var student = {
name : "AlphaGL",
age : 18
print: function () {
console.log("hello AlphaGL");
},
};
5.2 讀取屬性
可以通過點(.)或者中括號([])的方式獲取對象屬性的值。
(1)通過點(.)來獲取
var student = {
name : "AlphaGL",
age : 18
};
console.log("name = " + student.name); // name = AlphaGL
(2)通過中括號訪問屬性的值,中括號內可以是變量且計算結果必須是字符串的表達式。如果屬性名包含回導致語法錯誤的字符,或者屬性名使用的是關鍵字或者保留字,也可以使用中括號表示。
var name = "nick name";
student[name] = "AlphaGL"; // 等價于 student["nick name"] = "AlphaGL";
一般推薦使用點的方式去獲取對象屬性。
5.3 檢測屬性
(1)hasOwnProperty()方法可以檢測給定屬性存在于對象實例中時,則返回true。
function Student() {
}
Student.prototype.work = "game";
var stu = new Student();
stu.name = "AlphaGL";
stu.age = 18;
console.log(stu.hasOwnProperty("name")); // true
console.log(stu.hasOwnProperty("work")) // false
(2)in操作符會訪問對象的給定屬性,無論該屬性是存在于實例中還是原型中都返回true。
function Student() {
}
Student.prototype.work = "game";
var stu = new Student();
stu.name = "AlphaGL";
stu.age = 18;
console.log("name" in stu); // true
console.log("work" in stu) // true
5.4 刪除屬性
delete運算符可以用來刪除對象的自有屬性,不會刪除原型的同名屬性,刪除不存在的屬性在對象上,delete將不會起作用,但仍會返回true。成功刪除的時候會返回true,否則返回false。
function Student() {
};
Student.prototype.name = "hello";
var stu = new Student();
stu.name = "AlphaGL";
stu.age = 18;
console.log(delete stu.name); // true
console.log(delete stu.name); // 什么不做,同樣返回true
console.log(stu.name); // hello
5.5 Array對象
JavaScript中,數組算是最常用的類型。數組的大小可以動態調整,每一項可以保存任何類型的數據,起始項從0開始。還可以實現堆棧,隊列等數據結構。
(1)數組的創建
- 使用Array構造函數創建。
var nums = new Aarray(3); var names = new Array("foo", "bar") var colors = Array("R", "G", "B")
- 使用數組字面量表示法。即使用中括號,并將每個元素用逗號隔開。
var num = [1, 2, 3]; var names = ["foo", "bar"]; var params = [1.2, "ab", true]; var pos = [{x:1, y:2}, {x:3, y:4}];
(2)數組元素的訪問。
var a = ["AlphaGL", 18, true];
console.log(a[0]); // AlphaGL
console.log(a[1]); // 18
console.log(a[2]); // true
//indexOf返回在數組中可以找到一個給定元素的第一個索引,如果不存在,則返回-1。類似的還有lastIndexOf方法。
console.log(a.indexOf("AlphaGL")); // 0
console.log(a.indexOf(true)); // 2
console.log(a.indexOf(18)); // 1
console.log(a.indexOf(2)); // -1
console.log(a.length); // 3
console.log(a[3]); // undefined。javascript數組下標從0開始。
可以使用負數或非整數來索引數組。這時,數值將會轉換為字符串,而該索引被當作對象的常規屬性。如果,使用了非負整數的字符串,則它會被當作數組索引訪問,而不是對象屬性訪問。
var a = ["AlphaGL", 18, true];
console.log(a[-1]); // undefined
console.log(a[1.5]); // undefined
console.log(a["1"]); // 18
console.log(a["2"]); // true
由此可知,數組的訪問只是對象訪問的一種特殊形式,當訪問不存在的屬性時,javascript也不會報錯,只會返回undefined值。因此,javascript中數組不存在數組越界的問題。
(3)數組元素的添加與刪除
添加元素:
var a = [];
a[0] = "AlphaGL";
a.push(18, true);
console.log(a); // [ 'AlphaGL', 18, true ]
刪除元素:
var a = ["AlphaGL", 18, true];
delete a[1];
console.log(a[1]); // undefined
console.log(a.length); // 3
console.log(a.pop()); // true。從數組中刪除最后一個元素,并返回該元素的值
console.log(a.length); // 2
console.log(a.shift()) // AlphaGL。從數組中刪除第一個元素,并返回該元素的值
console.log(a.length); // 1
a[0] = undefined;
console.log(a.length); // 1
var a = ["AlphaGL", 18, true];
a.length = 2;
console.log(a[2]); // undefined
a.length = 0;
console.log(a[0]); // undefined
var a = ["AlphaGL", 18, true];
a.splice(2, 0, "haha"); // 從第2個元素開始,刪除0個元素,即添加元素haha
console.log(a); // [ 'AlphaGL', 18, 'haha', true ]
a.splice(1, 2); // 從第1個元素開始,刪除2個元素。
console.log(a); // [ 'AlphaGL', true ]
a.splice(0, 1, "haha"); // 從第0個元素開始,刪除1個元素,并添加haha元素。
console.log(a); // [ 'haha', true ]
注:刪除數組元素與將數組元素賦值為undefined值類似。使用delete不是修改數組的length屬性,也不會移動后繼元素位置。其它操作方法基本都會移動數組元素和改變數組length值。也可以直接操作數組的length屬性來達到輸出元素的目的。push和pop方法提供了類似棧結構的操作。push和shift方法則提供了類似隊列結構的操作。splice有替換數組中任意數量的項的作用。
(4)數組的檢測
var a = ["AlphaGL", 18, true];
console.log(Array.isArray(a)); // true
console.log(a instanceof Array); // true
注:當存在兩個以上的全局執行環境時,即存在兩個以上不同版本的Array構造函數,instanceof則只能在單一的全局執行環境有效。
(5)數組的排序
var a = [1, 11, 57, 7, 23];
a.sort(function (p1, p2) { // 使用比較函數來對數組元素進行排序。返回的值小于0,則p1放到p2位置之前;大于0則p1在p2之后;等于0則位置不變。
return p1 > p2;
});
console.log(a); // [ 1, 7, 11, 23, 57 ]
var a = ["AlphaGL", 18, true];
a.reverse(); // 逆序數組。
console.log(a); // [ true, 18, 'AlphaGL' ]
(6)數組的遍歷與迭代
var a = [1, 11, 57, 7, 23];
var t1 = a.every(function (element, index, array) {
return element % 2 != 0;
});
var t2 = a.every(function (element, index, array) {
return element > 10;
});
console.log(t1); // true
console.log(t2); // false
注:every方法會對數組中的每一項運行給定函數,如果該函數的每一項都返回true,則結果才為true。
var a = [1, 11, 57, 7, 23];
var t1 = a.filter(function (element, index, array) {
return element % 2 != 0;
});
var t2 = a.filter(function (element, index, array) {
return element > 10;
});
console.log(t1); // [ 1, 11, 57, 7, 23 ]
console.log(t2); // [ 11, 57, 23 ]
注:filter方法會對數組中的每一項運行給定的函數,并返回該函數會返回為true的項組成的新數組。
var a = [1, 11, 57, 7, 23];
var t1 = a.forEach(function (element, index, array) {
array[index] = element + 1;
});
console.log(a); // [ 2, 12, 58, 8, 24 ]
注:forEach方法同樣會對數組中每一項運行給定的函數。該方法沒有返回值。
var a = [1, 11, 57, 7, 23];
var t1 = a.map(function (element, index, array) {
if(element > 10) {
return element + 1;
}
return element - 1;
});
console.log(t1); // [ 0, 12, 58, 6, 24 ]
注:map方法會將每次運行給定的函數返回的值,組成一個新的數組。
var a = [1, 11, 57, 7, 23];
var t1 = a.some(function (element, index, array) {
return element > 50;
});
console.log(t1); // true
注:map方法同樣會對數組中的每一項都運行給定的函數,如果該函數的任一項結果為true,則返回true。
(7)其它
當然,數組還有一些其它的用法和函數。這里就不一一介紹了。感興趣的,可以參考文末列舉的參考鏈接。
6、函數
函數,簡單描述就是可重復調用多次的功能模塊。在JavaScript中,每個函數都是Function類型的實例,因此也一樣具有屬性和方法。函數名也是對象,可以把函數當作值來使用,這樣就提供極大的靈活性。
6.1 函數的定義
在JavaScript中,函數的定義有如下幾種實現方式:
(1)function關鍵字+函數名+參數列表+花括號構成的語句塊,例如:
function foo(p1, p2) {
return p1 + p2;
}
console.log(typeof(foo)); // function
console.log(foo(3, 4)); // 7
(2)使用Function構造函數。一般,不推薦這種使用方法。
var foo = new Function("p1", "p2", "return p1 + p2");
console.log(foo(3, 4)); // 7
(3)函數表達式
// 聲明了一個匿名函數,并賦值給foo變量。
var foo = function(p1, p2) {
return p1 + p2;
}
console.log(foo(3, 4)); // 7
// 函數表達式也可以包含名稱
var bar = function sum(p) {
if(p <= 1) {
return 1;
}else {
return p + sum(p - 1);
}
}
console.log(bar(5)); // 15
// 聲明即調用
var sum = function(p1, p2) {
return p1 + p2;
}(3, 4);
console.log(sum); // 7
6.2 函數的參數與內部屬性
JavaScript中函數定義并未指定函數參數的類型,調用時也未對實參的值做類型檢查,同樣也不檢查參數個數。
6.2.1 函數的參數
function foo(p1, p2, p3) {
return p2;
}
console.log(foo(1)); // undefined
console.log(foo(1, 2)); // 2
console.log(foo(1, 2, 3)); // 2
console.log(foo(1, 2, 3, 4)); // 2
當形參與實參的個數不匹配時,少的參數將被置為undefined,多的參數將被丟棄。
6.2.2 函數的內部屬性
在函數內部,有個特殊的對象arguments。該對象用來保存函數參數,可以像數組樣使用數字索引來訪問參數,同樣它也包含length屬性。但它并不是真正的數組。另外,該對象還包含callee屬性,該屬性指向擁有這個arguments對象的函數。
function foo(p1, p2, p3) {
console.log(arguments.length); // 3
console.log(arguments[0]); // 第一個參數,即:1
console.log(arguments[1]); // 第二個參數,即:2
console.log(arguments[2]); // 第三個參數,即:3
}
foo(1, 2, 3);
使用arguments和callee:
function sum(p) {
if (p <= 1) {
return 1;
}else {
return p + arguments.callee(p -1);
}
}
console.log(sum(5)); // 15
6.3 函數的屬性
前面提到過,每個函數都是Function類型的實例,因此也一樣具有屬性和方法。函數有以下比較常用的屬性。
function foo(p1, p2 , p3) {
console.log(arguments.length);
console.log(arguments.callee.length);
}
console.log(foo.name); // foo
console.log(foo.length); // 3
foo(1, 2); // 2 3
由上可知:
foo.name:函數的名稱。
foo.length:形參的個數。
arguments.length:實參的個數。
arguments.callee.length:形參的個數。
6.4 閉包
閉包(closure)是函數型語言的一個重要的特性,許多高級特性都依賴閉包來實現。閉包,是創建在一個函數內部的函數,能夠訪問函數內部的變量,并保存在內存中,記錄上次運行的結果,即保存了創建時的上下文環境信息。因此,可以簡單總結為:
閉包=函數內部創建的函數 + 該函數創建時的上下文環境信息
例如:
function counter() {
var count = 0;
return function() {
return count++;
}
}
var foo = counter();
console.log(foo()); // 0
console.log(foo()); // 1
console.log(foo()); // 2
閉包的這種機制,就實現面向對象的封裝提供了支持。將私有變量封裝在內部,提供外包接口函數,來訪問該變量。
構造函數
函數內部屬性
函數的作用域
reduce
7、面向對象
前面提到過,JavaScript中所有的都是對象。在面向對象編程中,對象是類的實例,而類是具有相同屬性和行為的一類對象的抽象和集合。例如:獅子對象是動物這一類型中的一個實例。面向對象編程有三大特性:封裝,繼承和多態。
7.1 構造函數
前面提到過,使用new關鍵字調用構造函數可以創建一個新對象。
function Student(name, age) {
this.name = name;
this.age = age;
this.setName = function(n) {
this.name = n;
}
this.getName = function() {
return this.name;
}
}
var student = new Student("張三", 18);
student.setName("李四");
console.log(student.getName()); // 李四
其中,this關鍵字指向了,當前要創建的對象。
7.2 原型與繼承
每個對象都有一個私有屬性(prototype)原型,該屬性指向該對象的原型對象。可以理解為其他編程語言中的,指向基類或者父類的作用。當然,該原型對象同樣有一個自己的prototype,層層向上直到該對象的原型為null。null沒有原型。JavaScript中幾乎所有的對象都是位于原型鏈頂端的Object的實例,同樣可以理解為,都是Object的子類。因此,使用原型對象可以讓所有對象實例共享它所包含的屬性和方法。
7.2.1 原型的使用
function Student(name, age) {
this.name = name;
this.age = age;
this.getName = function() {
return this.name;
}
}
var student1 = new Student("張三", 18);
var student2 = new Student("李四", 18);
console.log(student1.getName == student2.getName); // false
上面,創建兩個不同的對象實例,getName實現了相同的功能,卻每個對象中都保留了一份,造成不必要的浪費。這就需要通過原型prototype來解決此問題了。
function Student(name, age) {
this.name = name;
this.age = age;
Student.prototype.getName = function() {
return this.name;
}
}
Student.prototype.country = "china";
var student1 = new Student("張三", 18);
var student2 = new Student("李四", 18);
console.log(student1.getName == student2.getName); // true
console.log(student1.country); // china
console.log(student2.country); // china
7.2.2 訪問屬性規則
function A() {
}
A.prototype.name = "小明";
A.prototype.age = 18;
A.prototype.country = "china";
function B() {
}
B.prototype = new A();
B.prototype.name = "小李";
B.prototype.age = 20;
function C() {
}
C.prototype = new B();
var c = new C();
c.name = "小趙";
c.country = "shanghai";
console.log(c.country); // shanghai
console.log(c.age); // 20
console.log(c.name); // 小趙
當訪問對象的某個屬性時,會根據給定的屬性名稱來查找。如果,在該對象的實例中找到該屬性,則返回屬性的值;否則,則繼續查找該對象的原型對象,在原型對象中查找該屬性,依次層層向上搜索,直到搜索到原型鏈的末尾。因此,對象的屬性會覆蓋同名的該對象的原型對象的同名屬性。
7.2.3 isPrototypeOf與instanceof
function A() {
}
function B() {
}
var a1 = new A();
console.log(a1 instanceof A); // true
console.log(a1 instanceof B); // false
console.log(A.prototype.isPrototypeOf(a1)); // true
console.log(B.prototype.isPrototypeOf(a1)); // false
A.prototype = {};
var a2 = new A();
console.log(a1 instanceof A); // false
console.log(a2 instanceof A); // true
console.log(A.prototype.isPrototypeOf(a1)); // false
console.log(A.prototype.isPrototypeOf(a2)); // true
B.prototype = new A();
var a3 = new B();
console.log(a3 instanceof A); // true
console.log(a3 instanceof B); // true
console.log(B.prototype.isPrototypeOf(a3)); // true
console.log(A.prototype.isPrototypeOf(a3)); // true
通過以上實例可以總結如下:
object instanceof constructor
運算符,用來檢測 constructor.prototype是否存在于參數object的原型鏈。雖然,右操作數是構造函數,但實際上檢測了對象的繼承關系,而不是檢測創建對象的構造函數。prototypeObj.isPrototypeOf(object)
檢查一個對象是否存在于另一個對象的原型鏈上。可以理解為object對象是否繼承自prototypeObj.prototype。