資料型別
資料分為基本資料型別(String, Number, Boolean, Null, Undefined,Symbol)和物件資料型別。
基本資料型別的特點:直接儲存在棧(stack)中的資料
引用資料型別的特點:儲存的是該物件在棧中引用,真實的資料存放在堆記憶體裡
引用資料型別在棧中儲存了指標,該指標指向堆中該實體的起始地址。當直譯器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。
深複製和淺複製的區別
1.淺複製只複製物件的頂層屬性(頂層屬性"通常指的是物件直接擁有的那些屬性,而不是透過引用指向的其他物件或陣列,對於一個包含多個層級的物件,淺複製會複製這個物件本身的屬性(第一層屬性),但如果是屬性值為引用型別(比如物件或陣列),它只會複製這些引用型別值的引用,而不是它們的實際內容。這意味著原始物件和複製物件將共享這些引用型別值。)
2.深複製會複製物件的所有頂層屬性
3.淺複製的速度通常比深複製快
=和區別
==
(相等運算子) :
- 進行值的比較,但在比較前會進行型別轉換,也被稱為“抽象相等比較”。
- 如果比較的兩個值型別不同,JavaScript會嘗試將它們轉換成相同的型別再進行比較。
- 例如,數字5和字串"5"用
==
比較會返回true
,因為JavaScript會嘗試將字串"5"轉換成數字5再進行比較。、
===
(恆等運算子) :
- 同時進行值和型別的比較,也被稱為“嚴格相等比較”。
- 如果比較的兩個值型別不同,
===
會直接返回false
,不會進行型別轉換。 - 例如,數字5和字串"5"用
===
比較會返回false
,因為它們的型別不同。
閉包
方法裡返回一個方法
存在的意義:1.延長變數的生命週期,2.創造私有環境
應用場景:
1.防抖和節流
防抖(多次觸發,只執行最後一次)
高頻率觸發的事件,在指定的單位時間內,只響應最後一次,如果在指定的時間內再次觸發,則重新計算時間
防抖類似於英雄聯盟回城6秒,如果回城中被打斷,再次回城需要再等6秒。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖函式</title>
</head>
<body>
<div>
<label for="inp"></label><input type="text" id="inp">
</div>
<script>
//封裝防抖函式
function debounce(fn,time){
// 建立一個標記用來存放定時器的返回值
let timeout=null
return function(){
// 每當使用者觸發input事件 把前一個 setTimeout 清楚掉
clearTimeout(timeout)
// 然後又建立一個新的 setTimeout, 這樣就能保證輸入字元後等待的間隔內 還有字元輸入的話,就不會執行 setTimeout裡面的內容
timeout=setTimeout(()=>{
// 這裡進行防抖的內容
fn()
},time)
}
}
// 獲取inpt元素
const inp=document.getElementById('inp');
// 測試防抖臨時使用的函式
function sayHi(){
console.log('防抖成功')
}
// 給inp繫結input事件 呼叫封裝的防抖函式 傳入要執行的內容與間隔事件
inp.addEventListener('input',debounce(sayHi,1000))
</script>
</body>
</html>
節流(規定時間內,只觸發一次)
高頻率觸發的事件,在指定的單位時間內,只響應第一次
節流類似於英雄聯盟裡的英雄平A 一定是內點選多次只進行攻擊一次
原型和原型鏈
非同步函式
setTimeout
setTimeout() 是屬於 window 的方法,該方法用於在指定的毫秒數後呼叫函式或計算表示式。
setTimeout(要執行的程式碼, 等待的毫秒數)
clearTimeout
clearTimeout() 方法可取消由setTimeout方法設定的定時操作。
clearTimeout() 方法的引數必須是由 setTimeout() 返回的 ID 值。
const myVar;
function myFunction() {
myVar = setTimeout(function(){ alert("Hello"); }, 3000);
}
function myStopFunction() {
clearTimeout(myVar);
}
ES6新特性
塊級作用域的變數宣告:let、const
let a=10;
const b=20;
箭頭函式
const add=(x,y)=>x+y;
模板字串
使用反引號(```)建立多行字串和內嵌表示式
const name=`Alice`
const greeting=`hello,${name}!`
解構賦值
允許從陣列或物件中提取資料,並將其賦值給變數
let [x,y]=[1,2]
let { name , age } = { name: 'Alice',age: 25}
預設引數
允許為函式引數指定預設值。
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
展開運算子
用於展開陣列或物件的元素
let arr=[1,2,...[3,,4,5]] //[1, 2, 3, 4, 5]
// 物件展開
let obj1 = { a: 1, b: 2 }
let obj2 = { ...obj1, c: 3 }
簡寫物件屬性和方法
可以簡潔地定義物件的屬性和方法
let x = 1, y = 2
let obj = { x, y, greet() { console.log('Hello'); } }
模組化
使用 export
和 import
關鍵字支援模組化開發
// 匯出模組
export const pi = 3.14;
export function add(a, b) { return a + b; }
// 匯入模組
import { pi, add } from './math';
類
引入了基於類的物件導向程式設計語法
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
let person = new Person('Alice');
person.greet();
Promise
用於處理非同步操作的更強大和靈活的方式
then函式非同步執行
let promise = new Promise((resolve, reject) => {
// 非同步操作
if (/* 成功 */) {
resolve('Success');
} else {
reject('Error');
}
});
promise.then(result => console.log(result)).catch(error => console.log(error));
交換兩值變數(不借助第三變數)
let a=1
let b=1
[a,b]=[b,a] //解構知識點
快速去重
let arr=[1,2,2,3,4,5]
let item=[...new Set(arr)]
console.log(item)
面試題
同步和非同步區別
基礎面試題
字串拼接問題
當使用加號(+)運算子連線一個數字和一個字串時,JavaScript會將數字轉換為字串,然後進行字串拼接。
當使用減號(-)運算子時,JavaScript嘗試從兩個運算元中計算數值結果。如果其中一個運算元是字串,JavaScript會嘗試將該字串轉換為一個數字。
console.log(1+'2') // 12
console.log(1-'2') // -1
var、let、const區別
var
、let
、const
三者區別可以圍繞下面五點展開:
(1)變數宣告提升
🐖:變數提升(Hoisting)是JavaScript中一個常見的行為,它會將變數和函式宣告提升到其所在作用域的頂部。
var宣告的變數會被提升到函式或全域性作用域的頂部,但其賦值不會被提升
,即變數可以在宣告之前呼叫,值為 undefined。
let和const宣告的變數也會被提升,但它們在實際賦值前不能訪問,處於“暫時性死區”
,即它們所宣告的變數一定要在宣告後使用,否則報錯。
console.log(a) //undefined
var a = 10
console.log(b) // Cannot access 'b' before initialization
let b=10
console.log(c) // Cannot access 'c' before initialization
const c=10
(2)暫時性死區
var
不存在暫時性死區
let
和 const
存在暫時性死區,只有等到宣告變數的那一行程式碼出現,才可以獲取和使用該變數
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
(3)塊級作用域
var
不存在塊級作用域
let
和 const
存在塊級作用域
// var
{
var a = 20
}
console.log(a) // 20
// let
{
let b = 20
}
console.log(b) // Uncaught ReferenceError: b is not defined
// const
{
const c = 20
}
console.log(c) // Uncaught ReferenceError: c is not defined
(4)重複宣告
var
允許重複宣告變數
let
和 const
在同一作用域不允許重複宣告變數
// var
var a = 10
var a = 20 // 20
// let
let b = 10
let b = 20 // Identifier 'b' has already been declared
// const
const c = 10
const c = 20 // Identifier 'c' has already been declared
(5)修改宣告的變數
var
和 let
可以
const
宣告一個只讀的常量。一旦宣告,常量的值就不能改變
// var
var a = 10
a = 20
console.log(a) // 20
//let
let b = 10
b = 20
console.log(b) // 20
// const
const c = 10
c = 20
console.log(c) // Uncaught TypeError: Assignment to constant variable
如何選擇哪種宣告
能用const的情況儘量使用const,其他情況下大多數使用let,避免使用var
箭頭函式和普通函式的區別
1.語法區別
function add(a,b){
return a+b;
}
const add=(a,b)=>{return a+b;}
2.this繫結
// 普通函式
const obj1 = {
name: 'john',
greet: function () {
console.log("hello, "+this.name)
}
}
obj1.greet() // hello, john
// 箭頭函式
const obj2={
name: 'john',
greet:()=>{
console.log(this.name)
}
}
obj2.greet() // 空
3.arguments物件、args剩餘引數區別
function sum(){
let res=0;
for(let i=0;i<arguments.length;i++){
res+=arguments[i];
}
return res;
}
sumArray=(...args)=>{ // 剩餘函式
let res=0;
/*
* for(let arg of args){
* res+=arg;
* }
**/
for(let i=0;i<args.length;i++){
res+=args[i];
}
return res;
}
console.log(sum(1,2,3,4,5))
console.log(sumArray(1,2,3,4,5))
4.建構函式
箭頭函式不能作為建構函式
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john.name); // 輸出: John
const PersonArrow = (name) => {
this.name = name; // 這裡會丟擲錯誤
};
const alice = new PersonArrow("Alice");
null和underfined
null是物件型別,underfined是underfined型別
四種情況是underfined
1.已經宣告,但為賦值
let a
console.log(a)
2.物件某個型別不存在
let obj={}
console.log(obj.a)
3.函式呼叫,引數空缺
function get(a,b){
console.log(a,b)
}
4.常規函式(不包括建構函式)預設返回值是undefined
function get(){
console.log(a,b)
}
a=get()
mvvm和mvc
v-if和v-show的區別
v-if:不滿足條件,不會渲染dom,單次判斷
v-show:隱藏dom,多次切換,不能用於許可權操作
nextTick
單頁與多頁的區別及優缺點
單頁應用:只有一個主頁面的應用
元件=>頁面片段
跳轉=>重新整理區域性資源
場景=>pc端
優點
a.體驗好,快
b.改變內容,不用載入整個頁面
c.前後端分離
d.效果可以很酷炫
缺點:
a.不利於SEO
b.初次載入比較慢
c.頁面複雜度很高
v-if和v-for
不能同時使用,vue2中v-for的優先順序比v-if高,vue3中v-if的優先順序高於v-if
Vue-router與location.href的區別
location.href:簡單方便,重新整理頁面(跳外鏈)
vue-router:實現了按需載入,減少了dom消耗(內部頁面),js原生history
事件委託
事件的三個階段:1.事件捕獲階段,2.事件目標階段,3.事件泡階段
- 事件捕獲:當某個元素觸發某個事件(如onclick),頂層物件document就會發出一個事件流,隨著DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程中,事件相應的監聽函式是不會被觸發的。
- 事件目標:當到達目標元素之後,執行目標元素該事件相應的處理函式。如果沒有繫結監聽函式,那就不執行。
- 事件冒泡:從目標元素開始,往頂層元素傳播。途中如果有節點繫結了相應的事件處理函式,這些函式都會被觸發。
優點
使用事件委託對於web應用程式帶來的幾個優點:
1.管理的函式變少了。不用為每個元素都新增監聽函式。同一個父節點下面類似的子元素,可以透過委託給父元素的監聽函式來處理事件。
2.可以方便地動態新增和修改元素,不需要因為元素的改動而修改事件繫結。
3.JavaScript和DOM節點之間的關聯變少了,這樣也就減少了因迴圈引用而帶來的記憶體洩漏發生的機率。
事件迴圈
1.JS是單執行緒,防止程式碼阻塞,我們把程式碼(任務)分為:同步和非同步
2.同步程式碼給js引擎執行,非同步程式碼交給宿主環境
3.同步程式碼放入執行棧中,非同步程式碼等待時機成熟送入任務佇列排隊
4.執行棧執行完畢,會去任務佇列看是否有非同步任務,有就送到執行棧執行,反覆迴圈檢視執行,這個過程是事件迴圈(eventloop)
執行順序:
- 一開始整個指令碼作為一個宏任務執行
- 執行過程中同步程式碼直接執行,宏任務進入宏任務佇列,微任務進入微任務佇列
- 當前宏任務執行完出隊,檢查微任務列表,有則依次執行,直到全部執行完
- 執行瀏覽器UI執行緒的渲染工作
- 檢查是否有Web Worker任務,有則執行
- 執行完本輪的宏任務,回到2,依次迴圈,直到宏任務和微服務佇列都為空
微任務包括: MutationObserver
、Promise.then()或reject()
、Promise為基礎開發的其它技術,比如fetch API
、V8
的垃圾回收過程、Node獨有的process.nextTick
。
宏任務包括 :script
、script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
理解 JavaScript 的事件迴圈往往伴隨著宏任務和微任務、JavaScript 單執行緒執行過程及瀏覽器非同步機制等相關問題,而瀏覽器和 NodeJS 中的事件迴圈實現也是有很大差別。熟悉事件迴圈,瞭解瀏覽器執行機制將對我們理解 JavaScript 的執行過程和排查執行問題有很大幫助。
註釋:
在所有任務開始的時候,由於宏任務中包括了 script
,所以瀏覽器會先執行一個宏任務,在這個過程中你看到的延遲任務(例如 setTimeout
)將被放到下一輪宏任務中來執行。
Promise本身是同步的,then、catch的回撥函式是非同步的微任務
執行順序是:1.同步任務,2.微任務的非同步任務(promise),3.宏任務的非同步程式碼(settimeout,setinterval)
事件 | 宏任務 | 微任務 |
---|---|---|
誰發起的 | 宿主(Node、瀏覽器) | JS引擎 |
具體事件 | 1.script 2.setTimeout 3.setInterval 4.UI rendering(UI事件) 5.postMessage 6.MessageChannel 7.setImmediate 8.I/O(Node.js) |
1.Promise.then() / catch() 2.MutaionObserver 3.Proxy 4.process.nextTick(Node.js) 5.Async / Await |
誰先執行 | 後執行 | 先執行 |
會觸發新一輪Tick嗎 | 會 | 不會 |
改變this的函式
call、bind、apply函式