JavaScript基礎鞏固系列——入門篇+數據類型

全手打原創,轉載請標明出處:https://www.cnblogs.com/dreamsqin/p/13686263.html, 多謝,=。=~(如果對你有幫助的話請幫我點個贊啦)

重新學習JavaScript是因爲當年轉前端有點兒趕鴨子上架的意味,我一直在反思我的知識點總是很零散,不能在腦海中形成一個完整的體系,所以這次想通過再次學習將知識點都串聯起來,結合日常開發的項目,達到溫故而知新的效果。與此同時,總結一下我認爲很重要但又被我遺漏的知識點~

背景知識

JavaScript既是一種輕量級的腳本語言,又是一種嵌入式語言。

  • 腳本語言(script language):不具備開發操作系統的能力,只用來編寫控制大型應用程序(例如瀏覽器)的腳本。
  • 嵌入式語言(embedded):通過嵌入大型應用程序調用宿主環境提供的底層API(例如瀏覽器爲JavaScript提供瀏覽器控制類、DOM類、Web類API;Node爲JavaScript提供文件操作、網絡通信等API;)實現本身核心語法不支持的複雜功能。

JavaScript歷史

  • 爲什麼誕生:Navigator 瀏覽器需要一種可以嵌入網頁的腳本語言,用來控制瀏覽器行爲,因爲當時網速很慢而且上網費很貴,有些操作不宜在服務器端完成。
  • 需求:不需要太強的功能,語法較簡單,容易學習和部署。

JavaScript的編程風格是函數式編程和麪向對象編程的一種混合體。

  • 語法來源
    • 基本語法:借鑑 C 語言和 Java 語言。
    • 數據結構:借鑑 Java 語言,包括將值分成原始值和對象兩大類。
    • 函數的用法:借鑑 Scheme 語言和 Awk 語言,將函數當作第一等公民,並引入閉包。
    • 原型繼承模型:借鑑 Self 語言(Smalltalk 的一種變種)。
    • 正則表達式:借鑑 Perl 語言。
    • 字符串和數組處理:借鑑 Python 語言。

ECMAScript歷史

ECMAScript 和 JavaScript 的關係是,前者是後者的規範,後者是前者的一種實現。

  • 爲什麼誕生:微軟開發了JScript並內置於IE3.0瀏覽器中,Netscape 面臨喪失瀏覽器腳本語言的主導權局面,最終決定將 JavaScript 提交給國際標準化組織 ECMA(European Computer Manufacturers Association),希望 JavaScript 能夠成爲國際標準,以此抵抗微軟。
  • 需求:規定瀏覽器腳本語言的標準。

基本語法

  • 變量->變量提升:JavaScript引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然後再一行一行地運行。這造成的結果,就是所有變量的聲明語句,都會被提升到代碼的頭部,對應的也有函數名提升(前提是使用function命令聲明,如果採用賦值語句定義就會報錯)。
// 原代碼(如果沒有變量提升會報錯:a is not defined)
console.log(a);
var a = 1;

// 實際運行代碼
var a;
console.log(a);
a = 1;

// 運行不會報錯
f();
function f() {}
  • 標識符:用來識別各種值(變量、函數)的合法名稱,只能以任意 Unicode 字母、美元符號$或下劃線_開頭,只能包含 Unicode 字母、美元符號、下劃線及數字。
    • 中文是合法的標識符,可以用作變量名。
    • JavaScript保留字:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。
  • 註釋:JavaScript可以兼容HTML代碼註釋,所以<!---->也被視爲合法的單行註釋,但-->只有在行首纔會被當作單行註釋,否則爲正常運算,例如:
function countdown(n) {
  while (n --> 0) console.log(n);    // 被作爲n-- > 0執行
}
countdown(3)
// 2
// 1
// 0
  • 區塊:使用大括號,將多個相關的語句組合在一起。
    • 區塊對於var命令來說不構成單獨的作用域,與不使用區塊的情況沒有任何區別。例如:
    {
      var a = 1;
    }
    a // 1,在區塊外部變量依然有效
    
  • 條件語句
    • 終於明白條件判斷大家爲什麼要這麼寫了if (2 = x),因爲常量寫在運算符的左邊,一旦不小心將相等運算符寫成賦值運算符,就會報錯,因爲常量不能被賦值。
    • 在沒有標明區塊(大括號)時,else代碼塊總是與離自己最近的那個if語句配對。
    • switch語句後面的表達式與case語句後面的表示式比較時,採用的是嚴格相等運算符(===),即不會發生類型轉換。
  • 循環語句
    • break語句用於跳出代碼塊或循環。
    • continue語句用於立即終止本輪循環,返回循環結構的頭部,開始下一輪循環。
    • 標籤:JavaScript 語言允許語句的前面有標籤(label),相當於定位符,用於跳轉到程序的任意位置,例如:
    top:
      for (var i = 0; i < 3; i++){
        for (var j = 0; j < 3; j++){
          if (i === 1 && j === 1) break top;
          console.log('i=' + i + ', j=' + j);
        }
      }
    // i=0, j=0
    // i=0, j=1
    // i=0, j=2
    // i=1, j=0
    

數據類型

  • 整數和浮點數:
    • JavaScript內部,所有數字都是以64位浮點數形式儲存。
    1 === 1.0 // true
    
    • 由於浮點數不是精確的值,所以涉及小數的比較和運算要特別小心。
    0.1 + 0.2 === 0.3 // false
    0.3 / 0.1 // 2.9999999999999996
    (0.3 - 0.2) === (0.2 - 0.1) // false
    
  • 數值精度:精度最多隻能到53個二進制位,絕對值小於2的53次方的整數,即負的2的53次方到 2的53次方,都可以精確表示。
Math.pow(2, 53) // 9007199254740992(簡單的法則就是JavaScript對15位的十進制數都可以精確處理)
  • 數值範圍:JavaScript 提供Number對象的MAX_VALUEMIN_VALUE屬性,返回可以表示的最大值和最小值,負向溢出時返回0,正向溢出時返回Infinity。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
  • 數值表示法:0b11(二進制)、0o377(八進制)、35(十進制)、0xFF(十六進制)、123e3(科學計數法)
    • 小數點前的數字多於21位、小數點後的零多於5個時JavaScript自動將數值轉爲科學計數法表示。
    • 默認情況下,JavaScript 內部會自動將八進制、十六進制、二進制轉爲十進制。
  • 特殊數值:
    • 幾乎所有場合,正零負零都會被當作正常的0,是等價的,唯一有區別的場合是+0或-0當作分母,返回的值是不相等的。
    (1 / +0) === (1 / -0) // false(除以正零得到+Infinity,除以負零得到-Infinity)
    
    • NaN表示“非數字”(Not a Number),主要出現在將字符串解析成數字出錯的場合、還有一些數學函數的運算結果,數據類型屬於Number,不等於任何值(包括它本身),布爾運算時被當作false,和任何數運算得到的都是NaN。
    typeof NaN // 'number'
    NaN === NaN // false 
    Boolean(NaN) // false
    NaN + 32 // NaN
    
    • Infinity表示“無窮”,用來表示兩種場景,一種是一個正的數值太大,或一個負的數值太小,無法表示;另一種是非0數值除以0,Infinity有正負之分,大於一切數值(除了NaN),四則運算符合無窮的數學計算規則,與null計算時null會轉成0,與undefined計算返回的都是NaN。
    Infinity === -Infinity // false
    Infinity > 1000 // true
    Infinity > NaN // false
    0 * Infinity // NaN(特殊)
    Infinity - Infinity // NaN(特殊)
    Infinity / Infinity // NaN(特殊)
    0 / Infinity // 0
    Infinity / 0 // Infinity
    
  • 全局方法:
    • parseInt():用於將字符串轉爲整數,頭部有空格會自動去除,轉換時是一個個字符依次轉換,如果遇到不能轉爲數字的字符,就不再進行下去,返回已經轉好的部分,如果字符串的第一個字符不能轉化爲數字(後面跟着數字的正負號除外)返回NaN,可以接受第二個參數(2到36之間,超出返回NaN,爲0、undefined、null則忽略)表示被解析的值的進制並返回該值對應的十進制數。
    parseInt('15e2') // 15
    parseInt('.3') // NaN
    parseInt('+1') // 1
    parseInt('') // NaN
    parseInt('1000', 2) // 8
    parseInt('10', null) // 10
    
    • parseFloat():用於將一個字符串轉爲浮點數,需要與Number()函數區分。
    parseFloat('') // NaN
    Number('') // 0
    parseFloat('123.45#') // 123.45
    Number('123.45#') // NaN
    
    • isNaN():用來判斷一個值是否爲NaN,只對數值有效,如果傳入其他值,會被先轉成數值(所以重點關注裏面傳入的值是否可以被Number轉爲數值)。
    isNaN('Hello') // true
    // 相當於
    isNaN(Number('Hello')) // true
    //判斷NaN更可靠的方法是,利用NaN是唯一一個不等於自身的這個特點
    function myIsNaN(value) {
      return value !== value;
    }
    
    • isFinite():返回一個布爾值,表示某個值是否爲正常的數值,除了Infinity、-Infinity、NaN和undefined這幾個值會返回false,isFinite對於其他的數值都會返回true。
  • 字符串:
    • 字符串可以被視爲字符數組,因此可以使用數組的方括號運算符,用來返回某個位置的字符(位置編號從0開始),但無法改變字符串之中的單個字符。
    'hello'[1] // "e"
    
    var s = 'hello';
    delete s[0];
    s // "hello"
    s[1] = 'a';
    s // "hello"
    
    • 對於碼點在U+10000到U+10FFFF之間的字符,JavaScript 總是認爲它們是兩個字符(length屬性爲2),所以JavaScript 返回的字符串長度可能是不正確的。
    • Base64轉碼:就是一種編碼方法,可以將任意值轉成 0~9、A~Z、a-z、+和/這64個字符組成的可打印字符,應用場景一個是將不可打印符號轉成可打印字符(例如ASCII的0~31),或者以文本格式傳遞二進制數據,有兩個原生方法:btoa()任意值轉爲 Base64 編碼、atob()Base64 編碼轉爲原來的值,但不適用於非ASCII的字符(需要先通過encodeURIComponent進行轉碼)。
    var string = 'Hello World!';
    btoa(string) // "SGVsbG8gV29ybGQh"
    atob('SGVsbG8gV29ybGQh') // "Hello World!"
    
    function b64Encode(str) {
      return btoa(encodeURIComponent(str));
    }
    function b64Decode(str) {
      return decodeURIComponent(atob(str));
    }
    b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
    b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
    
  • 對象:
    • 對於不符合標識名條件的鍵名必須加上引號,例如第一個字符爲數字,或者含有空格或運算符。
    var obj = {
     '1p': 'Hello World',
     'h w': 'Hello World',
     'p+q': 'Hello World'
    };
    
    • JavaScript 引擎如果遇到無法確定是對象還是代碼塊的情況,一律解釋爲代碼塊,如果要解釋爲對象,最好在大括號前加上圓括號。
    // eval語句(作用是對字符串求值)
    eval('{foo: 123}') // 123
    eval('({foo: 123})') // {foo: 123}
    
    • 數字鍵在方括號讀取運算符中可以不加引號,但不能使用點運算符讀取。
    var obj = {
      123: 'hello world'
    };
    
    obj.123 // 報錯
    obj[123] // "hello world"
    
    • Object.keys():返回一個對象本身的所有屬性。
    var obj = {
      key1: 1,
      key2: 2
    };
    
    Object.keys(obj);
    // ['key1', 'key2']
    
    • delete:用於刪除對象的屬性,刪除成功後返回true,但刪除一個不存在的屬性,delete不報錯,而且也返回true,只有某屬性不可刪除時(configurable:false)才返回false,只能刪除對象本身的屬性,無法刪除繼承的屬性。
    • in:用於檢查對象是否包含某個屬性,如果包含就返回true,否則返回false,但無法識別哪些屬性是對象自身的,哪些屬性是繼承的(可以用hasOwnProperty判斷)。
    var obj = { p: 1 };
    'p' in obj // true
    'toString' in obj // true
    
    var obj = {};
    if ('toString' in obj) {
      console.log(obj.hasOwnProperty('toString')) // false
    }
    
    • for...in:用來遍歷一個對象的全部屬性,僅遍歷可遍歷(enumerable:true)屬性跳過不可遍歷屬性,不僅遍歷對象自身的屬性,還遍歷繼承的屬性(前提是繼承的屬性是可遍歷的),如果想僅遍歷自身屬性可用hasOwnProperty在循環內部判斷。
    var obj = {a: 1, b: 2, c: 3};
    for (var i in obj) {
      console.log('鍵名:', i);
      console.log('鍵值:', obj[i]);
    }
    // 鍵名: a
    // 鍵值: 1
    // 鍵名: b
    // 鍵值: 2
    // 鍵名: c
    // 鍵值: 3
    
    • with:操作同一個對象的多個屬性時,提供一些書寫的方便(在內部可以不使用點運算符就能直接讀取屬性),如果with區塊內部有變量的賦值操作,必須是當前對象已經存在的屬性,否則會創造一個當前作用域的全局變量,不建議使用with語句,無法判斷內部變量爲對象屬性還是全局變量
    var obj = {
      p1: 1,
      p2: 2,
    };
    with (obj) {
      p1 = 4;
      p2 = 5;
    }
    // 等同於
    obj.p1 = 4;
    obj.p2 = 5;
    
  • 函數:
    • name屬性:返回函數的名字,常用的是獲取參數函數的名字。
    var myFunc = function () {};
    function test(f) {
      console.log(f.name);
    }
    test(myFunc) // myFunc
    
    • length屬性:返回函數預期傳入的參數個數,即函數定義之中的參數個數。
    function f(a, b) {}
    f.length // 2
    
    • 函數本身作用域:函數執行時所在的作用域,是定義時的作用域,而不是調用時所在的作用域。
    var a = 1;
    var x = function () {
      console.log(a);
    };
    
    function f() {
      var a = 2;
      x();
    }
    
    f() // 1
    
    • 參數傳遞方式:如果是原始類型的值(數值、字符串、布爾值),傳遞方式是傳值傳遞(passes by value),在函數體內修改參數值,不會影響到函數外部;如果函數參數是複合類型的值(數組、對象、其他函數),傳遞方式是傳址傳遞(pass by reference),在函數內部修改參數,將會影響到原始值,但如果修改參數的地址指向,則不會影響原始值。
    var obj = { p: 1 };
    function f(o) {
      o.p = 2;
    }
    f(obj);
    obj.p // 2
    
    function f2(o) {
      o = { p: 3 };
    }
    f2(obj);
    obj.p // 2
    
    • arguments對象:包含了函數運行時的所有參數,這個對象只有在函數體內部纔可以使用,正常模式下arguments對象可以在運行時修改,但嚴格模式下不行,它很像數組,但它是一個對象,數組專有的方法(比如sliceforEach)無法使用,但可以將它轉爲真正的數組(slice方法或遍歷填入新數組)。
    var f = function(a, b) {
      'use strict'; // 開啓嚴格模式
      arguments[0] = 3;
      arguments[1] = 2;
      return a + b;
    }
    f(1, 1) // 2
    
    var args = Array.prototype.slice.call(arguments);
    // 或者
    var args = [];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    
    • 閉包:能夠讀取其他函數內部變量的函數,由於在 JavaScript 語言中,只有函數內部的子函數才能讀取內部變量,因此可以把閉包簡單理解成“定義在一個函數內部的函數”,閉包的最大用處,一個是可以讀取函數內部的變量,另一個就是讓這些變量始終保持在內存中,即閉包可以使得它誕生環境一直存在,還有一個是封裝對象的私有屬性和私有方法。
      PS:外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,所以內存消耗很大。因此不能濫用閉包,否則會造成網頁的性能問題。
    function createIncrementor(start) {
      return function () {
        return start++;
      };
    }
    var inc = createIncrementor(5);
    
    inc() // 5
    inc() // 6
    inc() // 7
    
    • 立即調用的函數表達式(IIFE-Immediately-Invoked Function Expression):通常情況下,只對匿名函數使用,它的目的有兩個:一是不必爲函數命名,避免了污染全局變量;二是 IIFE 內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量。
    (function(){ /* code */ }());
    // 或者
    (function(){ /* code */ })();
    
    • eval命令:接受一個字符串作爲參數,並將這個字符串當作語句執行,參數如果不是字符串就會原樣返回,eval沒有自己的作用域,可能會因爲修改當前作用域的變量造成安全問題(使用嚴格模式,eval內部聲明的變量,不會影響到外部作用域,但依然可以讀寫當前作用域的變量),最常見的場合是解析 JSON 數據的字符串(應使用JSON.parse替代),凡是使用別名執行eval,eval內部一律是全局作用域。
        eval('var a = 1;'); // 生成變量a
        eval(123) // 123 // 非字符串原樣返回
    
  • 數組:
    • JavaScript 語言規定,對象的鍵名一律爲字符串,所以,數組的鍵名其實也是字符串,之所以可以用數值讀取,是因爲非字符串的鍵名會被轉爲字符串。
    var arr = ['a', 'b', 'c'];
    arr['0'] // 'a'
    arr[0] // 'a'
    
    • JavaScript 使用一個32位整數保存數組的元素個數,所以數組成員最多隻有 4294967295 個(232 - 1)個,length屬性的最大值就是 4294967295。
    • length屬性可寫,如果設置一個小於當前成員個數的值,該數組的成員數量會自動減少到length設置的值,可以通過將length設置爲0來清空數組。
    var arr = [ 'a', 'b', 'c' ];
    arr.length = 2;
    arr // ["a", "b"]
    arr.length = 0;
    arr // []
    
    • 數組本質上是一種對象,所以可以爲數組添加屬性,但是這不影響length屬性的值。
    var a = [];
    a['p'] = 'abc';
    a.length // 0
    
    • for...in:不僅會遍歷數組所有的數字鍵,還會遍歷非數字鍵,所以不推薦使用它對數組進行遍歷,應該使用循環遍歷或forEach方法替代。
    var a = [1, 2, 3];
    a.foo = true;
    for (var key in a) {
      console.log(a[key]);
    }
    // 1
    // 2
    // 3
    // true
    
    a.forEach(function (item) {
      console.log(item);
    });
    // 1
    // 2
    // 3
    
    • delete:刪除數組元素後不影響length屬性,刪除的位置形成空位,讀取時返回undefined,所以用length屬性遍歷時需要注意,是無法跳過空位的。
    var a = [, , ,];
    a[1] // undefined(空位與該位置值爲undefined不一樣,空位在forEach、for...in、Object.keys遍歷時會跳過
    
    • 類數組對象(array-like object):很像數組的對象,如果一個對象的所有鍵名都是正整數或零,並且有length屬性(非動態,不會隨着成員的變化而變化),不具備數組的特有方法,常見的有arguments對象、大多數Dom元素集、字符串,slice方法可以將類數組對象轉換爲真正的數組,除此之外可以通過call()把數組方法嫁接到對象上。
    var obj = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3
    };
    
    obj[3] = 'd';
    obj.length // 3
    
    // forEach 方法(本來arguments對象無法調用該方法,但這種方法比直接使用數組原生的forEach要慢,所以最好還是先將“類似數組的對象”轉爲真正的數組,然後再直接調用數組的forEach方法。)
    function logArgs() {
      Array.prototype.forEach.call(arguments, function (elem, i) {
        console.log(i + '. ' + elem);
      });
    }
    

參考資料

JavaScript 語言入門教程 :https://wangdoc.com/javascript/index.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章