Vue2&3
- 基礎性
- 1. 關於Vue2和Vue3生命週期的差別
- 2. Vue2&3組件之間傳參不同點
- Vue2 傳遞與接收
- Vue3 傳遞與接收 (使用script setup語法糖)
- Vue3 傳遞與接收 (不使用script setup語法糖)
- 3. Vue2&3 keep-alive 組件
- Vue2 keep-alive
- Vue3 keep-alive
- 進階性
- 爲什麼POST請求有時會重複調用兩次
- 網路問題
- 跨域請求與預檢請求
- JS原型、原型鏈
- 原型
- 原型鏈
- 繼承 有哪些繼承 優點是什麼
- new 操作符具體干了什么
- js 有哪些方法改變 this 指向
- 對一個函數鏈式調用 bind,this 指向的是誰?為什么?
整個內容一般都是講Vue2&3的不同點 并無單個版本説明
博客為 >>星光菌子
整理
基礎性
1. 關於Vue2和Vue3生命週期的差別
-
相似的生命周期階段
Vue 2
和Vue 3
都包含以下幾個核心的生命周期階段,且對應的鉤子函數功能類似 -
創建階段:在實例初始化時觸發,主要用于初始化數據和事件
Vue 2
:beforeCreate 和 created
Vue 3
:beforeCreate 和 created
不過在 Vue 3 的組合式 API 里,beforeCreate 和 created可以省略
,因為 setup 函數在這個階段之前執行,能完成相同的初始化工作 -
掛載階段:在實例掛載到 DOM 之前和之后觸發
Vue 2
:beforeMount 和 mounted
Vue 3
:beforeMount 和 mounted
功能與 Vue 2 一致 -
更新階段:當響應式數據發生變化,DOM 重新渲染前后觸發
Vue 2
:beforeUpdate 和 updated
Vue 3
:beforeUpdate 和 updated
功能與 Vue 2 一致 -
銷毀階段:在實例銷毀前后觸發,用于清理一些資源
Vue 2
:beforeDestroy(Vue 2.2.0 起也叫 beforeUnmount) 和 destroyed(Vue 2.2.0 起也叫 unmounted)
Vue 3
:beforeUnmount 和 unmounted
beforeUnmount 替代了 Vue 2 的 beforeDestroy,unmounted 替代了 destroyed
2. Vue2&3組件之間傳參不同點
在 Vue 2
和 Vue 3
中,組件之間傳參主要有兩個明顯不同點
-
聲明方式:
Vue 2
在組件選項中使用props
選項以對象或數組形式聲明接收的屬性;Vue 3
在script setup
中使用 defineProps 宏函數來聲明,在普通script
中使用和Vue 2
類似的props
選項
-
事件監聽:
Vue 2
使用$emit
觸發自定義事件,父組件使用v-on
或@
監聽;Vue 3
在script setup
中使用defineEmits
宏函數聲明可觸發的事件,觸發方式和Vue 2
類似,但語法更簡潔
Vue2 傳遞與接收
# 父組件 Parent.vue<template><div><!-- 引入子組件并傳遞數據,同時監聽子組件觸發的事件 --><ChildComponent :message="parentMessage" @childEvent="handleChildEvent" /></div>
</template><script>
// 引入子組件
import ChildComponent from './Child.vue';export default {components: {ChildComponent},data() {return {// 父組件的數據,用于傳遞給子組件parentMessage: '來自 Vue 2 父組件的數據'};},methods: {// 處理子組件觸發的事件的方法handleChildEvent(data) {console.log('在 Vue 2 中收到來自子組件的數據:', data);}}
};
</script>
# 子組件 Child.vue<template><div><!-- 顯示從父組件接收到的數據 --><p>{{ message }}</p><!-- 點擊按鈕觸發向父組件發送事件 --><button @click="sendEventToParent">向父組件發送事件</button></div>
</template><script>
export default {// 聲明接收父組件傳遞的 propsprops: ['message'],methods: {// 觸發向父組件發送事件的方法sendEventToParent() {// 觸發自定義事件并傳遞數據給父組件this.$emit('childEvent', '來自 Vue 2 子組件的數據');}}
};
</script>
Vue3 傳遞與接收 (使用script setup語法糖)
# 父組件 Parent.vue<template><div><!-- 引入子組件并傳遞數據,同時監聽子組件觸發的事件 --><ChildComponent :message="parentMessage" @child-event="handleChildEvent" /></div>
</template><script setup>
import { ref } from 'vue';
// 引入子組件
import ChildComponent from './Child.vue';// 使用 ref 創建響應式數據,作為要傳遞給子組件的數據
const parentMessage = ref('來自 Vue 3 父組件的數據');// 處理子組件觸發的事件的方法
const handleChildEvent = (data) => {console.log('在 Vue 3 中收到來自子組件的數據:', data);
};
</script>
# 子組件 Child.vue<template><div><!-- 顯示從父組件接收到的數據 --><p>{{ message }}</p><!-- 點擊按鈕觸發向父組件發送事件 --><button @click="sendEventToParent">向父組件發送事件</button></div>
</template><script setup>
import { defineProps, defineEmits } from 'vue';// 聲明接收父組件傳遞的 props
const props = defineProps({message: String
});// 聲明可觸發的自定義事件
const emits = defineEmits(['child-event']);// 觸發向父組件發送事件的方法
const sendEventToParent = () => {// 觸發自定義事件并傳遞數據給父組件emits('child-event', '來自 Vue 3 子組件的數據');
};
</script>
Vue3 傳遞與接收 (不使用script setup語法糖)
# 父組件 Parent.vue<template><div><!-- 引入子組件,傳遞數據并監聽子組件觸發的事件 --><ChildComponent :message="parentMessage" @child-event="handleChildEvent" /></div>
</template><script>
import { ref } from 'vue';
// 引入子組件
import ChildComponent from './Child.vue';export default {components: {ChildComponent},setup() {// 使用 ref 創建響應式數據const parentMessage = ref('來自 Vue 3 父組件的數據');// 處理子組件觸發的事件的方法const handleChildEvent = (data) => {console.log('在 Vue 3 中收到來自子組件的數據:', data);};// 返回需要在模板中使用的數據和方法return {parentMessage,handleChildEvent};}
};
</script>
# 子組件 Child.vue<template><div><!-- 顯示從父組件接收到的數據 --><p>{{ message }}</p><!-- 點擊按鈕觸發向父組件發送事件 --><button @click="sendEventToParent">向父組件發送事件</button></div>
</template><script>
import { defineComponent } from 'vue';export default defineComponent({// 聲明接收父組件傳遞的 propsprops: {message: {type: String,required: true}},// 聲明可觸發的自定義事件emits: ['child-event'],setup(props, { emit }) {// 觸發向父組件發送事件的方法const sendEventToParent = () => {// 觸發自定義事件并傳遞數據給父組件emit('child-event', '來自 Vue 3 子組件的數據');};// 返回需要在模板中使用的方法return {sendEventToParent};}
});
</script>
3. Vue2&3 keep-alive 組件
keep-alive
是 Vue 提供的一個內置組件,用于緩存動態組件或路由組件,避免組件在切換時重復創建和銷毀,從而提高組件的性能和響應速度。下面分別介紹 Vue 2 和 Vue 3 中 keep-alive 的使用方法
activated
和deactivated
是比較特殊的兩個鉤子,需要keep-live
配合使用
當引入keep-alive
的時候,頁面第一次進入,鉤子的觸發順序created
=>mounted
=>activated
,退出時觸發deactivated
當再次進入(前進或後退)時,只觸發activated
Vue2 keep-alive
Vue3 keep-alive
進階性
爲什麼POST請求有時會重複調用兩次
網路問題
有時瀏覽器為了確保請求的可靠性,會在網路不穩定的情況下自動重試請求
如果第一次POST
請求因網路問題而沒有成功,瀏覽器可能會自動再發一次請求
這種情況下,我們會看到兩次POST
請求
跨域請求與預檢請求
當我們進行跨網域請求時,尤其是使用
CORS(跨網域資源共用)
時,瀏覽器會在正式發送POST
請求之前發送OPTIONS
請求,這就是所謂的預檢請求
這是為了確定伺服器是否允許跨網域請求
fetch('https://api.example.com/data', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': 'Bearer token'},body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));# 在開發者工具中,我們可以看到先發了一個OPTIONS請求,接著才是實際的POST請求
JS原型、原型鏈
原型
- ① 所有引用類型都有一個
__proto__ (隱式原型)
屬性,屬性值是一個普通的對象 - ② 所有函數都有一個
prototype(原型)
屬性,屬性值是一個普通的對象 - ③ 所有引用類型的
__proto__
屬性指向它構造函數的prototype
原型鏈
當訪問一個對象的某個屬性時,會先在這個對象本身屬性上查找,如果沒有找到,則會去它的 __proto__
隱式原型上查找,即它的構造函數的 prototype
,如果還沒有找到就會再在構造函數的 prototype
的 __proto__
中查找,這樣一層一層向上查找就會形成一個鏈式結構,我們稱為原型鏈
原型鏈判斷
Object.prototype.__proto__; //null
Function.prototype.__proto__; //Object.prototype
Object.__proto__; //Function.prototype
Object instanceof Function; //true
Function instanceof Object; //true
Function.prototype === Function.__proto__; //true
繼承 有哪些繼承 優點是什麼
繼承(inheritance
)是面向對象軟件技術當中的一個概念。如果一個類別 B
“繼承自”另一個類別 A
,就把這個 B
稱為“A
的子類”,而把 A
稱為“B
的父類別”也可以稱“A
是 B
的超類”.
組合繼承: 原型鏈繼承和借用構造函數方法組合就是組合繼承。用原型鏈實現對原型屬性和方法的繼承,用借用構造函數技術來實現實例屬性的繼承.
寄生組合式繼承: 結合借用構造函數傳遞參數和寄生模式實現繼承。這是最成熟的方法,也是現在庫實現的方法
優點:繼承可以使得子類具有父類別的各種屬性和方法,在子類別繼承父類別的同時,可以重新定義某些屬性,并重寫某些方法,即覆蓋父類別的原有屬性和方法,使其獲得與父類別不同的功能
new 操作符具體干了什么
- 新建一個空對象
obj
- 把
obj
的隱式原型和構造函數通過原型鏈連接起來 - 將構造函數的
this
指向obj
- 如果該函數沒有返回對象,則返回
this
// 定義一個構造函數 Person,用于創建表示人的對象
// 構造函數通常首字母大寫,這是一種約定俗成的寫法,用于區分普通函數
// 參數 name 表示人的姓名,參數 age 表示人的年齡
function Person(name, age) {// 將傳入的 name 參數賦值給新對象的 name 屬性this.name = name;// 將傳入的 age 參數賦值給新對象的 age 屬性this.age = age;
}// 自定義的函數 myNew,用于模擬 new 操作符的功能
// 參數 constructor 是一個構造函數,也就是我們想要用來創建對象的函數
// 使用剩余參數語法 ...args 接收傳遞給構造函數的所有參數,這些參數會在構造函數執行時使用
function myNew(constructor, ...args) {// 步驟 1: 創建一個新對象// Object.create(constructor.prototype) 方法用于創建一個新對象,// 并將這個新對象的原型設置為構造函數的原型。// 這樣新對象就可以繼承構造函數原型上定義的所有屬性和方法const newObj = Object.create(constructor.prototype);// 步驟 2: 將新對象的 this 指向構造函數,并執行構造函數// constructor.apply(newObj, args) 方法調用構造函數,// 并將構造函數內部的 this 指向新創建的對象 newObj,// 同時將之前收集的參數 args 傳遞給構造函數,讓構造函數可以根據這些參數初始化新對象的屬性const result = constructor.apply(newObj, args);// 步驟 3: 判斷構造函數的返回值// 如果構造函數返回的是一個對象(通過 typeof result === 'object' 判斷類型為對象,// 并且 result!== null 排除返回值為 null 的情況,因為 typeof null 也會返回 'object'),// 就返回構造函數的返回值;// 否則,返回我們在步驟 1 中創建的新對象 newObjreturn typeof result === 'object' && result!== null? result : newObj;
}// 使用自定義的 myNew 函數創建對象
// 這里將 Person 構造函數作為第一個參數傳遞給 myNew,
// 并將 'John' 和 30 作為后續參數傳遞,這些參數會在 Person 構造函數執行時使用
const person = myNew(Person, 'John', 30);// 打印創建的對象
// 此時打印出的對象應該包含 name 和 age 屬性,值分別為 'John' 和 30
console.log(person);
- Object.create(constructor.prototype):這行代碼創建了一個新對象
newObj
,并將其原型指向構造函數的原型,這樣,新對象就可以繼承構造函數原型上的屬性和方法 - constructor.apply(newObj, args):使用
apply
方法將構造函數的this
指向新對象newObj
,并執行構造函數,將參數args
傳遞給構造函數 - 返回值判斷:如果構造函數返回一個對象,則返回該對象;否則,返回新創建的對象
newObj
js 有哪些方法改變 this 指向
call
,apply
,bind
三者的第一個參數都是 this
需要指向的對象,但在后續的參數上只有 apply
是接收一個數組,call
和 bind
用逗號分開;call
和 apply
直接調用,返回的是一個值,而 bind
不直接調用,返回的是一個函數形式,執行:foo.bind(obj)()
# call()
# call() 方法允許你調用一個函數,同時指定該函數內部 this 的值,并且可以依次傳遞參數給函數
// 定義一個對象,后續將作為 this 的指向
const person = {name: 'Alice'
};
// 定義一個函數,函數內部使用 this 來訪問屬性
function introduce(hobby) {// 打印出根據 this 指向的對象屬性生成的介紹語句console.log(`大家好,我叫 ${this.name},我喜歡 ${hobby}。`);
}
// 使用 call 方法調用 introduce 函數,并將 person 對象作為 this 的指向
// 同時傳遞 '畫畫' 作為 introduce 函數的參數
introduce.call(person, '畫畫'); # -------------------------------分割線-------------------------------------# apply()
# apply() 方法和 call() 方法類似,同樣用于調用函數并指定 this 的值,不同之處在于它接受一個數組作為函數的參數
// 定義一個對象,作為 this 的指向
const anotherPerson = {name: 'Bob'
};
// 定義一個函數,內部使用 this 訪問屬性
function introduceFood(food1, food2) {// 打印出根據 this 指向的對象屬性生成的喜歡食物的語句console.log(`我叫 ${this.name},我喜歡吃 ${food1} 和 ${food2}。`);
}
// 使用 apply 方法調用 introduceFood 函數
// 將 anotherPerson 對象作為 this 的指向
// 并以數組形式傳遞 '披薩' 和 '漢堡' 作為函數參數
introduceFood.apply(anotherPerson, ['披薩', '漢堡']); # -------------------------------分割線-------------------------------------# bind()
# bind() 方法會創建一個新的函數,在調用時會將 this 的值設置為你提供的值,并且可以預先傳入部分參數
// 定義一個對象,作為 this 的指向
const student = {name: 'Charlie'
};
// 定義一個函數,內部使用 this 訪問屬性
function study(subject) {// 打印出根據 this 指向的對象屬性生成的學習語句console.log(`${this.name} 正在學習 ${subject}。`);
}
// 使用 bind 方法創建一個新的函數 newStudy
// 將 student 對象作為新函數內部 this 的指向
// 并預先傳入 '數學' 作為參數
const newStudy = study.bind(student, '數學');
// 調用新函數
newStudy();
對一個函數鏈式調用 bind,this 指向的是誰?為什么?
在 JavaScript
中,對一個函數鏈式調用 bind
時,this
最終指向的是第一次調用 bind
時所指定的對象
- 原因
bind
方法會創建一個新的函數,在調用時這個新函數的this
值會被鎖定為bind
方法調用時第一個參數所指定的對象
當對一個函數多次鏈式調用bind
時,后續的bind
調用并不會改變第一次bind
所確定的this
指向,因為后續的bind
是基于前一次bind
所創建的新函數來操作的,而前一次bind
已經固定了this
的指向
// 定義一個簡單的函數,函數內部打印 this 的值
function printThis() {console.log(this);
}
// 創建一個對象,后續會將其作為 this 指向
const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };
// 第一次調用 bind,將 this 指向 obj1
const bound1 = printThis.bind(obj1);
// 對 bound1 再次調用 bind,嘗試將 this 指向 obj2
const bound2 = bound1.bind(obj2);
// 調用最終綁定后的函數
bound2();