js 常用--你懂的

zailushang發表於2020-06-17

全篇來自廖雪峰老師的教程,本篇近作為筆記使用。

1、== 和===的區別

false == 0;    // true
false === 0; // false

奇怪吧?因為:這屬於javascript的一個缺陷

  • == 會自動轉換資料型別再比較,很多時候,會得到非常詭異的結果;
  • === 不會自動轉換資料型別,如果資料型別不一致,返回false,如果一致,再比較;

例外情況:
(1) NaN

NaN === Nan;  // false

唯一能判斷NaN的方法是通過isNaN()函式:

isNaN(NAN);  // true

2、null 和 undefined**

null表示一個“空”的值,它和0以及空字串' '不通,0是一個數值,' '表示長度為0的字串,而null表示“空”。

3、變數

(1)命名
變數名是大小寫英文、數字、$和_的組合,且不能用數字開頭
注意:變數名也可以用中文,但是,請不要給自己找麻煩。
(2)變數定義
Javascript定義變數:

var a = 123;  // a的值是整數123
a = 'ABC'; // a變為字串

python也是動態語言:

a = 123
a = "ABC"

Java是靜態語言,定義變數時必須指定變數型別:

int a = 123;  // a是整數型別變數,型別用int申明
a = "ABC"; // 錯誤:不能把字串賦給整形變數

4、陣列

有一種陣列長度說改就改:

// 直接給Array的length賦一個新的值會導致Array大小的變化:
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr變為[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; // arr變為[1, 2]

// 如果通過索引賦值時,索引超過了範圍,同樣會引起Array大小的變化:
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr變為[1, 2, 3, undefined, undefined, 'x']

陣列常用函式:
(1)索引:通過indexOf()來搜尋一個指定元素的位置

var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引為0

(2) 擷取:slice()可以擷取Array的部分元素

var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 從索引0開始,到索引3結束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 從索引3開始到結束: ['D', 'E', 'F', 'G']

(3)如果不給slice()傳遞任何引數,它就會從頭到尾擷取所有元素。利用這一點,我們可以很容易地複製一個Array:

var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();

(4)末尾新增和刪除任意元素:push()向Array的末尾新增若干元素,pop()則把Array的最後一個元素刪除掉:

var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的長度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr.pop(); // 空陣列繼續pop不會報錯,而是返回undefined

(5)頭部新增和頭部刪除:如果要往Array的頭部新增若干元素,使用unshift()方法,shift()方法則把Array的第一個元素刪掉:

var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的長度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); // 空陣列繼續shift不會報錯,而是返回undefined

(6)排序: sort()可以對當前Array進行排序,它會直接修改當前Array的元素位置,直接呼叫時,按照預設順序排序:

var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']

(7)反轉: reverse()把整個Array的元素給掉個個,也就是反轉

var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']

(8)萬能方法: splice()方法是修改Array的“萬能方法”,它可以從指定的索引開始刪除若干元素,然後再從該位置新增若干元素:

var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 從索引2開始刪除3個元素,然後再新增兩個元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回刪除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只刪除,不新增:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只新增,不刪除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因為沒有刪除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']

(9)連線:concat()方法把當前的Array和另一個Array連線起來,並返回一個新的Array

var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']

請注意,concat()方法並沒有修改當前Array,而是返回了一個新的Array。

(10)join:join()方法是一個非常實用的方法,它把當前Array的每個元素都用指定的字串連線起來,然後返回連線後的字串

var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'

(11)多維陣列:
如果陣列的某個元素又是一個Array,則可以形成多維陣列,例如:

var arr = [[1, 2, 3], [400, 500, 600], '-'];

5、物件

要判斷一個屬性是否是物件自身擁有的,而不是繼承得到的,可以用hasOwnProperty()方法。不要用in,繼承得到的屬性也會返回true;

var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

6、條件判斷

JavaScript把null、undefined、0、NaN和空字串' '視為false,其他值一概視為true.

7、iterable

for...in迴圈問題:遍歷的實際上是物件的屬性名稱
for...of,遍歷的是物件的元素

更好的遍歷方式:

// Array
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向當前元素的值
// index: 指向當前索引
// array: 指向Array物件本身
console.log(element + ', index = ' + index);
});


// Set:Set與Array類似,但Set沒有索引,因此回撥函式的前兩個引數都是元素本身:
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});

// Map: Map的回撥函式引數依次為value、key和map本身
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});

// 如果只對某些引數感興趣,可以忽略其他引數
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
console.log(element);
});

8、函式

(1)函式多傳參或少傳參問題

由於JavaScript允許傳入任意個引數而不影響呼叫:

  • 因此傳入的引數比定義的引數多也沒有問題,雖然函式內部並不需要這些引數;
  • 傳入的引數少,也不會報錯,函式引數將收到undefined,計算結果為NaN

為避免收到undefined,可以對引數進行檢查:

function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}

(2)arguments

JavaScript還有一個免費贈送的關鍵字arguments,它只在函式內部起作用,並且永遠指向當前函式的呼叫者傳入的所有引數。arguments類似Array但它不是一個Array:

function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);

/*
x = 10
arg 0 = 10
arg 1 = 20
arg 2 = 30
*/

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

(3)rest引數

如果使用arguments獲取除已定義引數之外的引數,比較麻煩。所以,ES6標準引入了rest函式,用於獲取多餘的傳參。

function fooo (a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log('rest = ' + rest);
}
// 多傳參:rest會獲取多餘引數
fooo(1, 2, 3, 4, 5)
/*
a = 1
b = 2
rest = 3,4,5
*/


// 少傳參:rest引數會接受一個空陣列
fooo(1)
/*
a = 1
b = undefined
[]
*/


(4)return語句坑

由於javascript在行末自動新增分號,所以return 語句需要寫在同一行,或{...}中

// 正常的單行return
function foooo() {
return { name: 'foo' };
}
foooo(); // { name: 'foo' }

// 多行return的坑
function fooooo() {
return
{ name: 'foo' };
}
fooooo(); // undefined

// 正確的多行return寫法
function foo() {
return { // 這裡不會自動加分號,因為{表示語句尚未結束
name: 'foo'
};
}

(5)變數作用域與解構賦值

A. var申明的變數是有作用域的,作用域為整個函式體;
  • 由於JavaScript的函式可以巢狀,此時,內部函式可以訪問外部函式定義的變數,反過來則不行
  • JavaScript的函式在查詢變數時從自身函式定義開始,從“內”向“外”查詢。如果內部函式定義了與外部函式重名的變數,則內部函式的變數將“遮蔽”外部函式的變數。
B. 變數提升

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

function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}
foo(); // Hello, undefined
/*
因為JavaScript引擎自動提升了變數y的宣告,所以函式不會報錯;
但是不會提升變數y的賦值,所以變數y的值為undefined
*/

常見寫法:用一個var在函式開頭 申明所有變數

function foo() {
var
x = 1, // x初始化為1
y = x + 1, // y初始化為2
z, i; // z和i為undefined
// 其他語句:
for (i=0; i<100; i++) {
...
}
}
C. 全域性作用域

不在任何函式內定義的變數就具有全域性作用域。實際上,JavaScript預設有一個全域性物件window,全域性作用域的變數實際上被繫結到window的一個屬性。

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

你可能猜到了,由於函式定義有兩種方式,以變數方式var foo = function () {}定義的函式實際上也是一個全域性變數,因此,頂層函式的定義也被視為一個全域性變數,並繫結到window物件;
我們每次直接呼叫的alert()函式其實也是window的一個變數:

function foo() {
alert('foo');
}

foo(); // 直接呼叫foo()
window.foo(); // 通過window.foo()呼叫

說明,JavaScript只有一個全域性作用域。任何變數(函式也視為變數),如果沒有在當前函式作用域中找到,就會繼續網上查詢,最後如果在全域性作用域中也沒找到,則報ReferennceError錯誤。

D. 名字空間

全域性變數會繫結到window上,不同的JavaScript檔案如果使用了相同的全域性變數,或者定義了相同名字的頂層函式,都會造成命名衝突,並且很難被發現。
減少衝突的一個方法是把自己的所有變數和函式全部繫結到一個全域性變數中。例如:

// 唯一的全域性變數MYAPP:
var MYAPP = {};

// 其他變數:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函式:
MYAPP.foo = function () {
return 'foo';
};

把自己的程式碼全部放入唯一的名字空間MYAPP中,會大大減少全域性變數衝突的可能。jQuery、YUI、underscore也這麼做。

E. 區域性作用域

var定義的變數作用域:函式內部
let定義的變數作用域:塊

// var定義變數作用域在函式內
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用變數i
}

// let定義變數可以達到塊級作用域
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}

F. 常量

(ES6)const申明常量。const和let都具有塊級作用域。

const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯,但是無效果!
PI; // 3.14
G. 解構賦值

ES6引入:解構賦值,可以同時對一組變數進行賦值。

// 傳統寫法
var array = ['hello', 'Javascript', 'ES6']
var x = array[0];
var y = array[1];
var z = array[2];

// 如果瀏覽器支援解構賦值就不會報錯:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

注意,對陣列元素進行解構賦值時,多個變數要用[...]括起來。
如果陣列本身還有巢狀,也可以通過下面的形式進行解構賦值,注意巢狀層次和位置要保持一致:

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

解構賦值還可以忽略某些元素:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個元素,只對z賦值第三個元素
z; // 'ES6'

使用解構賦值,從物件中取出若干屬性,便於快速獲取物件的指定屬性。

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;

對一個物件進行解構賦值時,同樣可以直接對巢狀的物件屬性進行賦值,只要保證對應的層次是一致的:

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因為屬性名是zipcode而不是zip
// 注意: address不是變數,而是為了讓city和zip獲得巢狀的address物件的屬性:
address; // Uncaught ReferenceError: address is not defined

使用解構賦值對物件屬性進行賦值時,如果對應的屬性不存在,變數將被賦值為undefined,這和引用一個不存在的屬性獲得undefined是一致的。如果要使用的變數名和屬性名不一致,可以用下面的語法獲取:

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};

// 把passport屬性賦值給變數id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是變數,而是為了讓變數id獲得passport屬性:
passport; // Uncaught ReferenceError: passport is not defined

解構賦值還可以使用預設值,這樣就避免了不存在的屬性返回undefined的問題:

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};

// 如果person物件沒有single屬性,預設賦值為true:

var {name, single=true} = person;
name; // '小明'
single; // true

有些時候,如果變數已經被宣告瞭,再次賦值的時候,正確的寫法也會報語法錯誤:

// 宣告變數:
var x, y;
// 解構賦值:
{x, y} = { name: '小明', x: 100, y: 200};
// 語法錯誤: Uncaught SyntaxError: Unexpected token =

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

({x, y} = { name: '小明', x: 100, y: 200});
H. 使用場景
  • 解構賦值可以簡化程式碼 比如交換兩個變數的值 javascript var x=1, y=2; [x, y] = [y, x]

相關文章