目錄
一、JavaScript中的變量提升
1. 機制
2. 示例
3.?注意事項
4. 總結
二、var、let和const的區別。
1. 作用域(Scope)
2. 變量提升(Hoisting)
3. 重新賦值和重新聲明
4. 示例
示例1:作用域和塊級行為
示例2:變量提升
示例3:重新賦值和聲明
5. 區別
三、什么是嚴格模式?如何啟用?
1.嚴格模式概念
2.如何啟用嚴格模式?
1. 啟用整個腳本
2. 啟用單個函數
注意事項
四、javaScript的數據類型有哪些
1. 原始數據類型
2. 對象數據類型
3.typeof 檢測變量的類
總結:
五、解釋==和===運算符的區別
1. == 運算符(值相等)
2. === 運算符(值和類型相等)
使用建議
六、JavaScript中的事件冒泡和事件捕獲
1. 事件冒泡
2. 事件捕獲
3. 完整事件流
4. 注意事項
七、深拷貝/淺拷貝
1. 概念
2.淺拷貝
3.深拷貝
4.第三方庫實現深拷貝
5.關鍵區別總結
八、解釋this關鍵字的指向規則
1.默認綁定(獨立函數調用)
2.隱式綁定(方法調用)
3.顯式綁定(call/apply/bind)
4.new綁定(構造函數調用)
5.箭頭函數
6.事件處理函數
7.總結
九、什么是IIFE
1. IIFE的基本語法
2. IIFE的特點
3. IIFE的常見用途
4. IIFE的變體
5. 注意事項
十、描述null和undefined的區別
1.定義
2.類型差異
3.使用場景
4.相等性比較
5.默認行為
6.示例
一、JavaScript中的變量提升
在JavaScript中,變量提升(Hoisting)是一個核心概念,它描述了變量和函數聲明在代碼執行前被“提升”到其作用域頂部的行為。
1. 機制
- 聲明提升:在JavaScript中,使用
var
關鍵字聲明的變量會被提升到當前作用域(如全局或函數作用域)的頂部。但請注意,初始化(賦值)部分不會被提升,它保持在原位置執行。 - 函數提升:函數聲明(如
function myFunc() {}
)也會被提升,且整個函數體都會被提升,允許你在聲明前調用它。 - 作用域規則:提升發生在作用域內。例如,在函數內部聲明的變量只提升到函數頂部,而不是全局作用域。
2. 示例
-
變量聲明提升示例: 在以下代碼中,變量
a
的聲明被提升,但賦值a = 10
沒有被提升。因此,在聲明前訪問a
會得到undefined
,而不是錯誤。console.log(a); // 輸出: undefined var a = 10; console.log(a); // 輸出: 10
實際執行順序相當于:
var a; // 聲明提升到頂部 console.log(a); // a 未初始化,輸出 undefined a = 10; // 初始化保持原位 console.log(a); // 輸出 10
-
函數聲明提升示例: 函數聲明整體被提升,因此可以在聲明前調用它。
myFunc(); // 輸出: "Hello" function myFunc() {console.log("Hello"); }
實際執行順序相當于:
function myFunc() { // 整個函數被提升console.log("Hello"); } myFunc(); // 輸出: "Hello"
3.?注意事項
-
var
vslet
/const
:ES6引入的let
和const
也有提升,但行為不同。它們被提升到塊級作用域頂部,但初始化前訪問會拋出錯誤(稱為“暫時性死區”)console.log(b); // 報錯: ReferenceError let b = 20;
這里,
b
的聲明被提升,但訪問時未初始化,導致錯誤。這避免了var
的undefined
問題。 -
函數表達式 vs 函數聲明:函數表達式(如
var func = function() {}
)不會被提升,只有聲明部分被提升func(); // 報錯: TypeError var func = function() {console.log("Hi"); };
實際執行:
var func; // 聲明提升 func(); // func 是 undefined,調用出錯 func = function() { // 賦值保持原位console.log("Hi"); };
-
最佳實踐:
- 總是聲明變量在作用域頂部,以避免混淆。
- 優先使用
let
和const
代替var
,以減少提升帶來的風險。 - 在復雜代碼中,使用嚴格模式(
"use strict"
)幫助捕獲錯誤。
4. 總結
變量提升是JavaScript的獨特特性,源于其編譯和執行機制。理解它有助于編寫更可靠的代碼。記住:聲明被提升,初始化不被提升;函數聲明整體提升,函數表達式則不然。通過合理使用現代語法和工具,可以規避大部分問題。
二、var
、let
和const
的區別。
1. 作用域(Scope)
var
:具有函數作用域(function-scoped)或全局作用域(global-scoped)。如果在函數內部聲明,它只在函數內有效;如果在函數外部聲明,則成為全局變量。let
:具有塊級作用域(block-scoped)。它只在聲明的代碼塊(如if
語句、for
循環或{}
塊)內有效。const
:同樣具有塊級作用域。與let
類似,但聲明后必須在聲明時初始化,且不能重新賦值(但對象或數組的屬性可以修改)。
2. 變量提升(Hoisting)
var
:支持變量提升。變量在聲明前可以被訪問,但值為undefined
。let
:不支持變量提升。在聲明前訪問會拋出ReferenceError
(暫時性死區)。const
:不支持變量提升,行為類似let
,在聲明前訪問會報錯。
3. 重新賦值和重新聲明
var
:可以重新賦值(修改值),也可以在同一作用域內重新聲明(重新聲明同一個變量名)。let
:可以重新賦值,但不能在同一作用域內重新聲明。const
:不能重新賦值(值不可變),也不能重新聲明。但如果是對象或數組,其屬性或元素可以被修改。
4. 示例
示例1:作用域和塊級行為
// 使用 var(函數作用域)
function exampleVar() {if (true) {var x = 10; // 在if塊內聲明}console.log(x); // 輸出: 10,因為var是函數作用域
}
exampleVar();// 使用 let(塊級作用域)
function exampleLet() {if (true) {let y = 20; // 在if塊內聲明}console.log(y); // 報錯: ReferenceError: y is not defined,因為let是塊級作用域
}
exampleLet();// 使用 const(塊級作用域)
function exampleConst() {if (true) {const z = 30; // 在if塊內聲明}console.log(z); // 報錯: ReferenceError: z is not defined,與let相同
}
exampleConst();
示例2:變量提升
// var 的變量提升
console.log(a); // 輸出: undefined(提升但未初始化)
var a = 5;// let 的變量提升(報錯)
console.log(b); // 報錯: ReferenceError: Cannot access 'b' before initialization
let b = 10;// const 的變量提升(報錯)
console.log(c); // 報錯: ReferenceError: Cannot access 'c' before initialization
const c = 15;
示例3:重新賦值和聲明
// var:可以重新賦值和重新聲明
var num = 1;
num = 2; // 重新賦值成功
var num = 3; // 重新聲明成功
console.log(num); // 輸出: 3// let:可以重新賦值,但不能重新聲明
let count = 1;
count = 2; // 重新賦值成功
// let count = 3; // 報錯: SyntaxError: Identifier 'count' has already been declared// const:不能重新賦值,也不能重新聲明
const PI = 3.14;
// PI = 3.14159; // 報錯: TypeError: Assignment to constant variable
// const PI = 3.1416; // 報錯: SyntaxError: Identifier 'PI' has already been declared// const 用于對象:屬性可以修改
const person = { name: "Alice" };
person.name = "Bob"; // 允許修改屬性
console.log(person.name); // 輸出: Bob
5. 區別
特性 | var | let | const |
---|---|---|---|
作用域 | 函數作用域或全局作用域 | 塊級作用域 | 塊級作用域 |
變量提升 | 支持(值為undefined ) | 不支持(報錯) | 不支持(報錯) |
重新賦值 | 允許 | 允許 | 不允許(值不可變) |
重新聲明 | 允許 | 不允許 | 不允許 |
初始化要求 | 可選 | 可選 | 必須初始化 |
三、什么是嚴格模式?如何啟用?
1.嚴格模式概念
嚴格模式(Strict Mode)是 JavaScript 中的一種特殊運行模式,由 ECMAScript 5(ES5)引入。它通過更嚴格的語法和錯誤檢查機制,幫助開發者避免常見的編碼錯誤,提高代碼的安全性和性能。在嚴格模式下,JavaScript 引擎會禁用一些不安全的特性(如隱式全局變量聲明),并拋出更多錯誤從而讓代碼更易維護和調試。
嚴格模式的主要優點包括:
- 錯誤預防:捕獲潛在錯誤(如變量未聲明就使用),減少運行時問題。
- 性能優化:簡化變量解析,提升執行效率。
- 未來兼容:為未來 JavaScript 版本的新特性做準備,避免遺留語法沖突。
2.如何啟用嚴格模式?
啟用嚴格模式非常簡單:只需在 JavaScript 代碼的頂部添加字符串 "use strict";
。這可以應用于整個腳本文件或單個函數作用域。以下是具體步驟和代碼示例:
1. 啟用整個腳本
在 JavaScript 文件的起始位置添加 "use strict";
,這樣整個文件都運行在嚴格模式下。
"use strict"; // 啟用全局嚴格模式// 示例代碼
function test() {x = 10; // 在嚴格模式下,這會拋出 ReferenceError(x 未聲明)console.log(x);
}
test();
2. 啟用單個函數
如果只想在特定函數中使用嚴格模式,將 "use strict";
放在函數體的開頭。
function strictFunction() {"use strict"; // 僅在該函數內啟用嚴格模式let y = 20;// y = 30; // 正常賦值// z = 40; // 拋出 ReferenceError(z 未聲明)
}function nonStrictFunction() {z = 50; // 在非嚴格模式下,z 會被隱式聲明為全局變量(不推薦)console.log(z);
}
strictFunction();
nonStrictFunction();
注意事項
- 兼容性:嚴格模式在現代瀏覽器和 Node.js 中廣泛支持(ES5+ 環境),但在舊版瀏覽器(如 IE9 以下)可能無效。測試時確保環境兼容。
- 作用域規則:
"use strict";
必須放在代碼頂部(前無其他語句),否則無效。它只影響所在作用域及其子作用域。 - 常見影響:在嚴格模式下,以下操作會報錯:
- 未聲明變量(如
x = 10;
)。 - 刪除不可刪除的屬性(如
delete Object.prototype;
)。 - 重復的函數參數(如
function(a, a) {}
)。 - 使用
eval
創建變量污染外部作用域。
- 未聲明變量(如
四、javaScript的數據類型有哪些
分為兩類:原始數據類型(Primitive Types)和對象數據類型(Object Type)
1. 原始數據類型
原始數據類型是不可變的(值本身不能被修改),存儲在棧內存中。JavaScript 包括以下原始類型:
undefined
:表示變量未定義或未賦值。例如:let a; // a 的類型是 undefined
null
:表示空值或無值。常用于顯式清空變量。let b = null; // b 的類型是 null
boolean
:布爾值,只有true
或false
。用于邏輯判斷。let c = true; // c 的類型是 boolean
number
:數字類型,包括整數、浮點數、Infinity
、-Infinity
和NaN
(非數字)。例如:let d = 42; // 整數 let e = 3.14; // 浮點數
string
:字符串類型,表示文本數據。字符串是不可變的。let f = "hello"; // f 的類型是 string
symbol
(ES6 引入):唯一且不可變的值,常用作對象屬性的鍵。例如:let g = Symbol("id"); // g 的類型是 symbol
bigint
(ES2020 引入):用于表示大整數(超過Number.MAX_SAFE_INTEGER
的整數)。在數字后加n
表示。let h = 123456789012345678901234567890n; // h 的類型是 bigint
2. 對象數據類型
對象數據類型是可變的(值可以被修改),存儲在堆內存中,變量存儲的是引用地址。所有非原始類型的值都屬于對象類型,包括:
- 普通對象:鍵值對的集合。
let obj = { name: "Alice", age: 30 }; // obj 的類型是 object
- 數組:有序的元素集合。
let arr = [1, 2, 3]; // arr 的類型是 object(typeof 返回 "object")
- 函數:可執行的對象,
typeof
運算符返回"function"
,但本質上它是對象的一種。function greet() { console.log("Hi!"); } // typeof greet 返回 "function"
- 其他內置對象:如
Date
、RegExp
、Map
、Set
等。let date = new Date(); // date 的類型是 object let regex = /abc/; // regex 的類型是 object
3.typeof
檢測變量的類
typeof
對原始類型返回對應名稱(如"string"
)。- 示例:
console.log(typeof "hello"); // 輸出 "string"
console.log(typeof null); // 輸出 "object"(注意這個陷阱)
總結:
- 原始類型有 7 種:
undefined
、null
、boolean
、number
、string
、symbol
、bigint
。 - 對象類型只有 1 種:
object
,但包括多種子類型(如數組、函數等)。
五、解釋==
和===
運算符的區別
1. == 運算符(值相等)
==
運算符用于比較兩個值是否相等,但在比較前會進行類型轉換。如果兩個值的類型不同,JavaScript 會嘗試將它們轉換為相同類型后再比較。
- 數字和字符串比較時,字符串會嘗試轉換為數字。
- 布爾值和其他類型比較時,布爾值會轉換為數字(
true
為 1,false
為 0)。 - 對象與非對象比較時,對象會嘗試調用
valueOf()
或toString()
方法轉換為原始值。
示例:
5 == '5' // true,字符串 '5' 轉換為數字 5
true == 1 // true,布爾值 true 轉換為數字 1
null == undefined // true,特殊規則
2. === 運算符(值和類型相等)
===
運算符不僅比較值是否相等,還比較類型是否相同。如果類型不同,直接返回 false
,不會進行類型轉換。
- 類型和值都必須完全相同才會返回
true
。 NaN
與任何值(包括自身)比較都返回false
。null
和undefined
與自身比較返回true
,但互相比較返回false
。
示例:
5 === '5' // false,類型不同
true === 1 // false,類型不同
null === undefined // false,類型不同
使用建議
- 優先使用
===
,避免隱式類型轉換帶來的意外行為。 ==
僅在明確需要類型轉換時使用,但需謹慎。
六、JavaScript中的事件冒泡和事件捕獲
1. 事件冒泡
事件冒泡是DOM事件傳播的默認機制。當一個事件發生在某個元素上時,它會從目標元素開始,逐級向上傳播到DOM樹的根節點(通常是document
對象)。這種傳播方式類似于氣泡從水底升到水面,因此稱為“冒泡”。
- 觸發順序:從目標元素開始,依次觸發父元素、祖父元素,直到根節點。
- 實際應用:常用于事件委托(Event Delegation),通過將事件監聽器綁定到父元素來管理子元素的事件。
- 阻止冒泡:調用事件對象的
stopPropagation()
方法可以阻止事件繼續冒泡。
document.getElementById('child').addEventListener('click', function(event) {console.log('Child clicked');event.stopPropagation(); // 阻止冒泡
});document.getElementById('parent').addEventListener('click', function() {console.log('Parent clicked'); // 不會觸發
});
2. 事件捕獲
事件捕獲是另一種事件傳播機制,與冒泡方向相反。事件從根節點開始,逐級向下傳播到目標元素。捕獲階段在冒泡階段之前發生。
- 觸發順序:從根節點開始,依次觸發父元素、子元素,直到目標元素。
- 啟用捕獲:在
addEventListener
中設置第三個參數為true
。 - 實際應用:較少直接使用,但在需要提前攔截事件的場景(如性能優化)中可能有用。
document.getElementById('parent').addEventListener('click', function() {console.log('Parent captured'); // 先觸發
}, true);document.getElementById('child').addEventListener('click', function() {console.log('Child clicked'); // 后觸發
});
3. 完整事件流
DOM事件流的完整順序分為三個階段:
- 捕獲階段:從根節點到目標元素。
- 目標階段:事件到達目標元素。
- 冒泡階段:從目標元素回到根節點。
// 示例:同時監聽捕獲和冒泡階段
document.getElementById('parent').addEventListener('click', function() {console.log('Bubbling phase');
});document.getElementById('parent').addEventListener('click', function() {console.log('Capturing phase');
}, true);
4. 注意事項
- 并非所有事件都支持冒泡,例如
focus
和blur
事件。 - 現代瀏覽器默認使用冒泡機制,但可以通過
addEventListener
的第三個參數靈活選擇。 - 事件委托利用冒泡機制減少事件監聽器數量,提升性能。
七、深拷貝/淺拷貝
1. 概念
在JavaScript中,拷貝對象分為深拷貝和淺拷貝。淺拷貝只復制對象的引用,而深拷貝會遞歸復制對象的所有屬性,生成一個全新的對象。
2.淺拷貝
使用Object.assign()
方法可以實現淺拷貝。該方法將所有可枚舉的自有屬性從一個或多個源對象復制到目標對象。
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
使用展開運算符(...
)也可以實現淺拷貝。
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
3.深拷貝
使用JSON.parse(JSON.stringify())
可以實現簡單的深拷貝。但這種方法無法處理函數、Symbol
、undefined
等特殊類型。
const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));
使用遞歸實現一個通用的深拷貝函數。
function deepClone(obj) {if (obj === null || typeof obj !== 'object') {return obj;}const clone = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key]);}}return clone;
}
4.第三方庫實現深拷貝
Lodash庫提供了_.cloneDeep
方法,可以方便地實現深拷貝。
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const copy = _.cloneDeep(original);
5.關鍵區別總結
- 內存占用:淺拷貝共享引用類型的內存地址,深拷貝創建完全獨立的內存空間
- 性能:淺拷貝速度更快,深拷貝因遞歸遍歷消耗更多資源
- 修改影響:淺拷貝的嵌套對象修改會互相影響,深拷貝完全隔離
- 適用場景:淺拷貝適合簡單數據,深拷貝適合需要完全隔離的復雜對象
八、解釋this
關鍵字的指向規則
在JavaScript中,this
關鍵字的指向取決于函數的調用方式,而非聲明時的環境
1.默認綁定(獨立函數調用)
當函數作為獨立函數調用時,this
默認指向全局對象(瀏覽器中為window
,Node.js中為global
)。嚴格模式下('use strict'
),this
為undefined
。
function showThis() {console.log(this);
}
showThis(); // 非嚴格模式:window;嚴格模式:undefined
2.隱式綁定(方法調用)
當函數作為對象的方法調用時,this
指向調用該方法的對象。
const obj = {name: 'Alice',greet: function() {console.log(this.name);}
};
obj.greet(); // 輸出 'Alice'(this指向obj)
3.顯式綁定(call/apply/bind)
通過call
、apply
或bind
方法顯式指定this
的指向。
function sayHello() {console.log(this.name);
}
const person = { name: 'Bob' };
sayHello.call(person); // 輸出 'Bob'(this指向person)
4.new綁定(構造函數調用)
使用new
調用構造函數時,this
指向新創建的實例對象。
function Person(name) {this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 輸出 'Alice'(this指向新實例)
5.箭頭函數
箭頭函數沒有自己的this
,其this
繼承自外層作用域。
const obj = {name: 'Charlie',greet: () => {console.log(this.name); // this繼承自外層(可能是window)}
};
obj.greet(); // 輸出 undefined(箭頭函數不綁定obj)
6.事件處理函數
在DOM事件處理函數中,this
通常指向觸發事件的元素。
button.addEventListener('click', function() {console.log(this); // 指向button元素
});
7.總結
- 默認綁定:獨立調用時指向全局對象(嚴格模式為
undefined
)。 - 隱式綁定:方法調用時指向調用對象。
- 顯式綁定:通過
call
/apply
/bind
指定this
。 - new綁定:構造函數中指向新實例。
- 箭頭函數:繼承外層
this
。 - 事件處理:指向觸發事件的DOM元素。
九、什么是IIFE
IIFE(Immediately Invoked Function Expression)即立即調用的函數表達式,是一種在定義后立即執行的JavaScript函數。這種模式常用于創建獨立的作用域,避免變量污染全局命名空間。
1. IIFE的基本語法
典型的IIFE語法如下:
(function() {// 函數體
})();
或者使用箭頭函數:
(() => {// 函數體
})();
2. IIFE的特點
- 立即執行:函數定義后會被立即調用,無需手動調用。
- 作用域隔離:函數內部聲明的變量不會泄露到全局作用域。
- 匿名函數:通常使用匿名函數,但也可以命名(較少見)。
3. IIFE的常見用途
-
避免全局污染:將代碼封裝在局部作用域中,防止變量沖突。
(function() {var localVar = '局部變量';console.log(localVar); // 僅在IIFE內有效 })();
-
模塊化開發:在早期JavaScript中用于模擬模塊化(ES6之前)。
var module = (function() {var privateVar = '私有變量';return {publicMethod: function() {console.log(privateVar);}}; })(); module.publicMethod(); // 訪問公開方法
-
閉包應用:結合閉包保存狀態。
var counter = (function() {var count = 0;return function() {return ++count;}; })(); console.log(counter()); // 1 console.log(counter()); // 2
4. IIFE的變體
-
帶參數的IIFE:可以傳遞外部變量。
(function(window) {window.myApp = {}; })(window);
-
返回值:將結果賦值給變量。
var result = (function(a, b) {return a + b; })(1, 2); console.log(result); // 3
5. 注意事項
-
分號前置:若IIFE出現在腳本中未以分號結尾的行后,可能導致錯誤。建議在IIFE前加分號。
;(function() {// 安全執行 })();
-
箭頭函數IIFE:ES6后可用箭頭函數簡化,但需注意其詞法作用域特性。
(() => {console.log('箭頭函數IIFE'); })();
十、描述null
和undefined
的區別
1.定義
null
表示一個空值或無對象引用,通常由開發者顯式賦值。
undefined
表示變量已聲明但未賦值,或訪問對象不存在的屬性,由 JavaScript 引擎自動分配。
2.類型差異
typeof null
返回 "object"
,這是 JavaScript 的歷史遺留問題。
typeof undefined
返回 "undefined"
,明確表示未定義的狀態。
3.使用場景
null
常用于主動釋放對象引用或表示空值。
undefined
常見于未初始化的變量、函數無返回值或參數未傳遞時。
4.相等性比較
null == undefined
返回 true
(寬松相等)。
null === undefined
返回 false
(嚴格相等,類型不同)。
5.默認行為
未賦值的變量默認為 undefined
。
null
必須顯式賦值,例如 let value = null;
。
6.示例
let a; // a 為 undefined
let b = null; // b 為 nullconsole.log(a === undefined); // true
console.log(b === null); // true
console.log(null == undefined); // true
console.log(null === undefined);// false