JavaScript基礎 學習記錄

記錄自己JavaScript基礎概念完整學習的過程

本文是在學習廖雪峯老師的JavaScript教程時 所歸納的筆記

廖雪峯老師教程在這裏

 注意點:

  1. file:// 開頭的地址無法執行聯網的JavaScript
  2. 雖然js不要強制在語句結尾加; 但是在某些情況下  讓js引擎自動補全 ; 會改變程序語義
  3. js本身不限制嵌套層級,但是一般不建議嵌套太多,可以把深層代碼抽出作爲函數調用
  4. 註釋: // 是行註釋   /* */ 是塊註釋
  5. js嚴格區分大小寫

數據類型和變量

     當下問數據類型就要考慮es6了,  es6之前的是 null  undefined  number  Boolean object string,  es6的是symbol

     number

     不分整數和浮點數  統一爲Number

     number可以直接做四則運算  + - * / ;    % 是求餘運算

    比較數據時需要注意的地方:

         1. == 如果是不相同數據類型做比較,它會自動轉換數據類型再比較(隱式轉換

         2. === 不會轉換比較對象, 它比較會判斷類型  不同類型的數據  無論值如何  一律返回false   相同的話纔會根據值返回

         3. NaN與所有制都不相等  包括自己

             NaN === NaN  // false 

             唯一能判斷NaN的方法是  isNaN()  函數

         4. 浮點數不要直接比較  最好是轉化爲整數再比,因爲js計算浮點數存在精度丟失問題

      null和undefined

      一個是   一個是 未定義

     JavaScript的設計者希望用null表示一個空的值,而undefined表示值未定義。事實證明,這並沒有什麼卵用,區分兩者的意         義不大。大多數情況下,我們都應該用nullundefined僅僅在判斷函數參數是否傳遞的情況下有用

     object  

     普通對象的鍵值都是字符串類型, es6 中的Map對象的鍵值可以是任何類型

     變量

     這裏只說三個注意點:

        1. 變量命名是大小寫英文  數字  $和_ 這四類的組合,且首位不能爲數字

        2.  變量名不能是javascript關鍵字

        3.  變量生命符現在有 let var const 三種,可點進去查看詳細解釋

    string 字符串

    基礎字符串不多做贅述。

   轉義字符串:

    比如 一個字符串中即包含' 也包含"   // i'm  "ok"

    想要輸出這種,可以用轉義字符串改寫爲 //  'i\'m \"ok \" '  即可

    即  轉義字符 \ 要寫在被轉義字符的前面

    常用的轉義字符有: \n 換行  \t 製表符  \本身也要轉義   如果要輸出\  寫法就是 \\ 

    涉及到多行字符串,如果想要其效果輸出爲多行 即有換行效果

    則\n 就比較費事了, so  es6新增了標準多行字符串的表示方法

    即 ` ... `  反引號包裹

` 這是一首
 簡單的
 小情歌
 唱着我們心頭的
 曲折 `

 模板字符串

  對於字符串及變量拼接操作,

  常規的是這樣的

var a = 'hello';
var b = a + 'world';

// b  hello world

  如果有很多變量需要連接,+ 就會比較麻煩

  es6中新增了一種 模板字符串

  表示方法如下

var a = 'hello';
var b = `${a} world`;

b // hello world

用反引號包裹  反引號中${}內的部分既是對於變量的值 

 對字符串的操作:

var str = 'hey bro';

常見的有:

str.length // 取字符串長度

str[0]  // h  取指定位置的值   取法類似數組那樣  通過對應索引取值

tips:

字符串是不可變的,如果根據某個索引對字符串指定位置賦值,不會報錯,但是也不會有任何效果

var s = 'nb';

s[0] = 's';

console.log(s) //  nb 

js爲操作字符串提供的一系列方法:

這些方法不會改變原有字符串內容,統一返回一個新的字符串

1. toUpperCase()  把字符串全部變爲大寫

2. toLowerCase() 把字符串全部變爲小寫

3. indexOf()  會搜索指定字符串出現的位置  並返回該位置的下標值  若找不到  返回-1

4. substring()  返回指定索引區間的子串  

let s = 'hey bro';
s.substring(0,5); // hey br
s.substring(1); // ey bro

 

    數組  array

 嚴格來說,array是object的特殊類型

對array的一系列操作方法如下:

1. arr.length // 獲取array的長度

tips: 如果直接給arr的length賦一個新值,會導致array的大小發生變化

var arr = [1,2,3];
arr // length 3
arr.length = 6;
arr // [1,2,3,undefined,undefined,undefined]
arr.length = 2;
arr // [1,2]

2. arr可以通過索引改變對應值,該修改會直接改變arr本身

tips: 通過索引賦值時,如果索引值超過了arr原有範圍,同樣會引起arr大小變化

3. indexOf() 和string的indexOf()用法  返回均相同

4. slice() 截取arr的部分元素, 返回一個新的數組  // 不會對arr產生變化

tips: slice()的起止參數爲開始索引,結束索引, 返回的新數組包括開始索引,不包括結束索引

如果不給任何參數,會從頭到尾截取,相當於複製一個新的數組

5. push()  向arr尾部添加若干元素

6. pop() 把最後一個元素刪掉

7.  unshift()  向頭部添加若干元素

8.  shift()  將頭部第一個元素刪掉

9.  sort() 將arr排序, 自定義順序會在後面講到。 // 改變原數組

10.  reverse()  將arr順序完全顛倒  即反轉     // 改變原數組

11. splice() 修改arr指定區域的元素   包括直接刪除    替換

tips: splice() 返回的值是指定的索引對應的元素區域  是一個新數組

原數組會依據調用方式發生對應變化

12.  concat() 拼接數組  // 返回新數組

tips: concat()可以接受任意個元素和數組,並最終拼接成一個新數組

13.  join()  數組轉字符串   會依據join(x)中添加的x拼接起來

tips: 如果array的元素不是字符串, 將自動轉換爲字符串後再連接

14. 

   對象  object

檢測對象 object 是否具有某一項屬性,可以用in操作符:

let obj = {
    name: '97',
    weight: 84,
    age: 24
};

'name' in obj; // true
'sex' in obj; // false

tips:

用 in 來判斷屬性是否存在,有一個坑,  因爲這個屬性不一定是obj的,可能是obj繼承得到的

比如

'toString' in obj; // true

因爲toString是定義在object中的,而所有對象原型鏈的頂部都是object,則obj也擁有toString屬性

爲了規避in的這種情況,可以使用hasOwnProperty()方法

該方法判斷  obj自身是否擁有某屬性

obj.hasOwnProperty('name'); // true
obj.hasOwnProperty('toString'); // false

 

條件判斷 

    基本的判斷類型不再贅述

   下面說一些記錄點:

1. js裏  在做數據類型轉換時,  只把 null undefined 0 NaN和'' 視爲false,其它一概爲true // if(ah)

2. for()循環的三個條件均可省略, 即for(;;), 如果沒有退出循環的條件,必須用break跳出,否則就是死循環

3. for...in 對對象進行操作時  建議使用hasOwnProperty()過濾掉對象繼承的屬性

var o = {
    name: 'Jack',
    age: 20,
    city: 'Beijing'
};
for (var key in o) {
    if (o.hasOwnProperty(key)) {
        console.log(key); // 'name', 'age', 'city'
    }
}

4. for...in 對array循環時  得到的索引不是number,而是string

 Map和Set

因爲基本對象object的鍵值限制爲String類型,爲了可以用其它類型做鍵值

es6引入了新的數據規範Map

Map

map是一組鍵值對的結構,具有極快的查找速度

擬一個情況,要根據同學的名字查找對應的成績,

如果用array實現,則需要一個數組記錄名稱,一個數組記錄成績

var names = ['Michael', 'Bob', 'Tracy'];
var scores = [95, 75, 85];

來自阮一峯:Map和Set講義

感覺不能拿這種結構來舉例子,因爲它們是沒有強映射關係的,應該用常規對象  如下

let obj = { 

{ name: 'a', score: 75 }, 

{ name: 'b', score: 66 }, 

{ name: 'c', score: 90 }, 

 };

隨便給定一個值  66  想要拿到name的值  一定會用循環, 而且  數據體積越大,查找越慢,耗時越長

如果用Map實現,只需要構造成key-value結構,無論數據體積有多大,查找速度都不會慢。

var m = new Map([['a', 75],['b', 66],['c', 90]]);
m.get('a'); // 直接輸出75

tips:

初始化Map需要一個二維數組,或者直接初始化空Map,再對其操作。 Map自帶的操作方法如下:

var m = new Map(); // 空map
m.set('a', 66); // 添加key-value值操作
m.set('b', 55); //
m.has('b'); // 是否存在key b: true
m.get('b'); // 55
m.delete('b'); // 刪除key b
m.get('b'); // undefined

由於key不能重複,故重複對相同的key設值,後面的值會覆蓋前面的值

Set

set只是key的集合,並非key-value這種形式

初始化Set需要一個數組,或者空

Set中不會包含重複值,有自動過濾去重操作

Set有兩個方法:

add(key) 添加key值  重複添加不會報錯  但是無效

delete(key) 刪除指定key

iterable

對常規array遍歷可以用下標循環,原來的map和set無法使用下標。 爲了統一  集合類型,就有了iterable類型

Array Map Set 都屬於iterable類型

具有iterable類型的集合  可以通過  for...of來遍歷  // 屬於es6新特性 

var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍歷Array
    console.log(x);
}
for (var x of s) { // 遍歷Set
    console.log(x);
}
for (var x of m) { // 遍歷Map
    console.log(x[0] + '=' + x[1]);
}

for...in 和 for...of 的區別

for...in 遍歷的實際上是對象的屬性名, array實際上也是對象,so它的每個元素的索引被當做一個屬性。

手動給array對象添加屬性後, for ... in 的效果如下:

var a = ['A', 'B', 'C'];
a.name =  'Hello';
for (var i in a) {
    consolg.log(i); // '0'  '1'  '2'  'name'
}

for...in把name也包裹進去了,  length沒有

for ... of 只循環集合本身的元素:

var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
    console.log(x); // 'A', 'B', 'C'
}

更好的方案是使用iterable內置的forEach()方法,它接收一個函數,每次迭代就自動回調該函數。 以Array爲例

var a = ['A', 'B', 'C'];
a.forEach(function(ele,index,array) {
    // ele: 指向當前元素
    // index: 指向當前索引
    // array: 指向array對象本身 即a
})

tips: Set的forEach  因爲沒有索引 前兩個參數都是元素本身

        Map的forEach 依次爲  value  key  Map本身

 函數

  arguments 

只在函數內部起作用,並且永遠指向當前函數調用者傳入的所有參數,可以簡單的理解爲,傳入函數的所有參數的參數集合

tips: 即使函數未定義形參,只要有參數傳入,通過arguments就可拿到傳入的所有值

function abs() {
    if(arguments.length === 0) {
        return 0
    }
    let x = arguments[0];
    return x >= 0 ? x : -x; 
}


abs(); // 0
abs(10); // 10
abs(-9); // 9

arguments最常用於判斷傳入參數的個數。

// foo(a[, b], c)
// 接收2~3個參數,b是可選參數,如果只傳2個參數,b默認爲null:
function foo(a, b, c) {
    if (arguments.length === 2) {
        // 實際拿到的參數是a和b,c爲undefined
        c = b; // 把b賦給c
        b = null; // b變爲默認值
    }
    // ...
}

爲了規範化不設定參數數量的函數

es6標準引入了rest參數

function foo(a,b, ...rest) {
    console.log('a', a);
    console.log('b', b);
    console.log('rest', rest);
}

如果傳入的參數連正常定義的參數都沒填滿,也不要緊,rest參數會接收一個空數組(注意不是undefined

 

變量作用域與結構賦值

變量作用域

JavaScript的函數在查找變量時自身函數定義開始,從“內”向“外”查找。如果內部函數定義了與外部函數重名的變量,則內部函數的變量將“屏蔽”外部函數的變量

變量提升

js的函數定義  有一個特點, 它會先掃描整個函數體的語句, 把所有申明的變量提升到函數頂部

'use strict':

function foo() {
 var x = 'Hello' + y; // line 1
 console.log(x); // line 2
 var y = 'Bob'; // line 3
}

foo();


雖然是strict模式,但是  line 1並未報錯

原因就在於變量y在隨後聲明瞭,但是line 2打印的y值依然是undefined

這是因爲,

雖然js引擎會自動提升變量聲明,但是不會提升變量賦值,即賦值操作還是在line 3進行的

javascript只有一個全局作用域。任何變量(包括函數),如果沒有在當前函數作用域下找到,就會繼續往上查找,最後如果在全局作用域中也沒有找到,就會報referenceError錯誤

    名字空間

全局變量會綁定到window上,不同的js文件如果定義了相同名字的頂層函數,或者使用了相同的全局變量,都會造成  命名衝突

減少衝突的一個方法是, 把某一個js文件下的所有變量和函數全部綁定到一個全局變量中:

// 唯一的全局變量myApp

var myApp = {};

// 其他變量
myApp.other = 'aabb';

// 其他方法

myApp.foo = function () {
    return 'foo';
}

把自己的代碼全部放入唯一的名字空間中,可大大減少全局變量衝突的可能。

像 jq yui underscore 等js庫都是這麼做的

    局部作用域

爲了解決js變量沒有局部作用域的問題,es6引入了新的關鍵字let   let 可代替var申明一個塊級作用域變量:

'use strict';

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然可以引用變量i
}




'use strict';

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
   
    i += 1;   // SyntaxError:
}

 

   常量

es6之前  申明常量通常使用大寫的變量來表示: ‘這是一個常量,不要修改它的值’;

es6 引入了const來定義常量

const 與 let 都具有塊級作用域

const a = 123;

a = 44; // 值爲基本數據類型的常量是不可修改其值的  大部分瀏覽器會報錯 少部分不報錯  但是不會有效果

a // 123 


const a = [1,2,3];

a[0] = 'c';

a // ['c',2,3] 

 

解構賦值 

傳統賦值做法:

var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

 es6之後  有了解構賦值,現在我們這麼做

var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

解構賦值的要點之一就是  嵌套層次和位置要保持一致

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

也對對象進行解構賦值

let obj = {
    name: '',
    sex: '',
    age: '',
    address: {
        city: '',
        province: ''
    }
}


let {name,sex,age} = obj;

// 寫法一

對象的解構取值  如果取的值不存在  則會賦給其undefined

let {id} = obj; // undefined


let {address:{city, province}} = obj; // 對象的解構同樣可以多層  只要嵌套解構對應即可  解構中的先後位置無硬性要求


let {school = false, sex} = obj; // 解決取值不存在的undefined問題  賦默認值

 有些時候,如果變量已經被聲明,再次賦值的時候,正確的寫法也會報錯

// 
var x,y;
{x, y} = {name: 'sb', x: 100, y: 200}

// 語法錯誤: Uncaught SyntaxError: Unexpected token =

// 這是因爲JavaScript引擎把{開頭的語句當作了塊處理,於是=不再合法。解決方法是用小括號括起來:

({x, y} = { name: '小明', x: 100, y: 200});

使用場景:

1. 交換變量值

var x=1, y=2;
[x, y] = [y, x]

2. 快速獲取需要的對象中屬性

var {hostname:domain, pathname:path} = location;

 

 方法(this)

在一個對象中綁定函數,稱爲這個對象的方法。

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年調用是25,明年調用就變成26了

綁定到對象上的函數和普通函數沒什麼區別,但是在它內部用了一個this,這個this就很可以拿來說說了

在一個方法內部,  this是一個特殊變量,它始終指向當前對象,so   this.birth可以拿到xiaomingbirth屬性

再拆開寫

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25, 正常結果
getAge(); // NaN

 爲什麼getAge() 返回的是NaN呢?

這裏就牽涉到this指向的問題了,

一般情況下,this指向最後調用它的對象,見上圖代碼,xiaoming.age(); this指向的就是對象  xiaoming, 而xiaoming是有age屬性的,  所以age() 方法中的 this.birth就能取到1990

而 getAge(); 因爲是直接在頁面對象下定義的方法,  它的this指向的是頁面對象,也就是全局對象window, 而全局對象裏並未定義birth,所以就返回NaN了

是不是很神奇,更神奇的在下面

var fn = xiaoming.age; // 先拿到xiaoming的age函數
fn(); // NaN

 同樣是NaN

所以  想要保證this指向中具有birth   必須用obj.age();  即xiaoming.age();才能取到

一般情況下,如果希望this總是指向某一個固定對象,可以定義全局變量,或者外部變量,比如 var _that;   _that = this;

捕獲該對象的this,在下方使用_that就沒有問題了

修復this指向,還可以用對象原型的自有方法  call() apply() bind()

apply()

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 參數爲空

call()

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25
getAge.call(xiaoming, ''); // 25, this指向xiaoming, 參數爲空

call()和apply()的第一位參數都是修改this指向爲傳入對象

唯一區別就是   從第二個參數開始

call()可以有很多參數

apply()只有一個參數  且參數類型需爲數組

對於普通函數調用,我們通常把this綁定爲null 

Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

 以上Math也可以看出apply()和call()的區別

裝飾器

因爲函數都是對象的屬性,哪怕是頁面默認函數,也可以改變其行爲。

比如parseInt

現在假定我們想統計一下代碼一共調用了多少次parseInt(),可以把所有的調用都找出來,然後手動加上count += 1,不過這樣做太傻了。最佳方案是用我們自己的函數替換掉默認的parseInt()

'use strict':

var count = 0;
var oldParseInt = parseInt;
window.parseInt = function() {
    count += 1;
    return oldParseInt.apply(null,arguments); // 調用原函數  傳入所有參數集arguments
}

簡單理解就是  將parseInt 拷貝給oldParseInt, 然後重新定義parseInt操作, 賦予 count統計 再把原來的方法給繼承進去

高階函數

簡單描述,就是接受參數爲函數的函數,就是高階函數

map()

map() 遍歷數組的每一項  同時接收一個函數作爲處理方法,處理這遍歷中的每一項

let arr = [1,2,3,4,5,6];

pow(x) { return x*x }

let newarr = arr.map(pow);

// newarr [1,4,9,14,25,36]

用一般的for循環同樣可實現該效果

pow (x) { return x*x }


let arr = [1,2,3,4,5];
let results = [];
for(var i in arr) {
    results.push(pow(arr[i]))
}

那爲什麼要用高階函數map()咧?

廖雪峯老師給出了這樣的解釋:

從上面的循環代碼,我們無法一眼看明白"把f(x)作用在Array的每一個元素並把結果返回,生成一個新的Array".

所以原因簡化爲下面這句話,

高階函數把運算規則抽象化,使我們能夠更清晰的看到發生了什麼,和爲什麼會這麼做。

因爲運算規則被抽象化,還可以計算任意複雜的函數

比如,把Array內的所有數字轉爲字符串

let arr = [1,2,3,4,5,6];
let results = arr.map(String); 
// 因爲運算規則被抽象化, 所以 輕易不要去研究這些運算過程, 在某一階段前 沒有太大的必要性

reduce()

array的歸併操作,什麼是歸併呢?  我們先看map(), map起到的是遍歷作用,遍歷每一項,這裏的每一項就是每一項,自身獨立,和其他項沒有關係,而歸併,則是從數組的第一位元素開始,一直執行到最後一位,在每次執行開始,會接收對上一位元素執行的結果,舉個簡單的例子,求和,用reduce()求和:

let arr = [1,3,4,5,6];
let mix = arr.reduce(function(x,y) {
    return x + y
})

mix // 25

定義,reduce()同樣接收一個函數對歸併項處理,但是這個函數有要求,就是必須接收兩個參數,reduce()把結果繼續和序列的下一個元素做累積計算,所以,可以簡單地理解爲,參數x,是當前項之前所有項的結果集合,y是歸併的當前項

系統定義戳這裏

廖老師的課後練習題: 

 修正後的代碼是:

function str2int(x) {
   return parseInt(x)
}

r = arr.map(str2int);

可正常輸出1,2,3

但是原題的異常原因 , 我沒有找到(還是不想讀英文文檔hhh)

filter()

filter()的作用是過濾Array的某些元素  然後返回剩下的元素

它也接收一個函數,過濾條件就是函數內容

filter()把傳入的函數依次作用於每個元素,然後根據返回值是true還是false決定保留還是丟棄該元素。

tips: 至於它到底有沒有改變原數組,這是個令人疑惑的問題

sort()

排序在各個語言中都是極爲常見的算法,很多語言都內置有排序函數sort()

js的  sort() 排序

實現核心是,

Array的sort()方法 默認把所有元素先轉換爲String再排序, 而對String的排序又會轉換成對其對應位的ASCII碼比較

直接arr.sort() 它就是這麼幹的

如果不想遵循它既定的規則,那麼我們可以傳入一個排序函數,來實現我們想要的效果

比如  按數字大小排序:

let arr = [10,20,1,2];
arr.sort(function(x, y) {
    if (x < y) {
        return -1
    }
    if (x > y) {
        return 1
    }
    return 0;
})

console.log(arr); // [1,2,10,20]

至於 return -1  return 1  return 0 分別代表什麼   和爲什麼這麼return可以那麼代表,請自行摸索

Array() 對象自身的高階函數

every()  find()  findIndex()  forEach()

every() 對arr中所有元素進行遍歷, 同時接收一個條件函數, 判斷arr中的每一項是否都滿足該函數內條件,若全部元素都滿足, 則every()執行完成後返回true, 否則返回false

find() 對arr中所有元素進行遍歷,同時接收一個條件函數, 查找arr中符合條件的第一個元素,如果找到就返回這個元素,遍歷完還未找到,返回undefined

findIndex()和find()類似, 不過返回的是第一個元素的索引, 無則返回-1

forEach() 和 map() 類似  相當於迭代器,在遍歷過程中把每個元素依次作用於傳入的函數, 不會返回新的數組,如果函數對遍歷元素有修改操作,則會直接改變原數組對應元素的值

tips:

findIndex()和indexOf()的區別

findIndex的判斷條件是不定的,取決於傳進去的條件函數。

indexOf則是直接在原數組裏檢測目標元素是否存在,相當於條件唯一。

 閉包

未完待續

https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016

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