目錄
- vue2 的雙向綁定的原理
- vue3 的雙向綁定原理
- vue 的生命周期
- vue 子組件為何不能修改父組件的值
- js delete 刪除數組的某一個值會怎么樣
- vue 和 react 的 diff 算法
- 什么是閉包
- 原型鏈
- this指向
vue2 的雙向綁定的原理
以下是 Vue 2 雙向綁定的原理:
1. 核心概念
Vue 2 的雙向綁定是通過數據劫持和發布訂閱模式實現的。它允許數據和視圖之間的雙向通信,即數據的改變可以更新視圖,視圖的操作也可以更新數據。
2. 實現步驟
數據劫持:
Vue 2 使用 Object.defineProperty
對數據對象的屬性進行劫持,在屬性被訪問或修改時添加自定義的行為。
function defineReactive(obj, key, value) {let dep = new Dep(); // 創建一個空的訂閱者列表Object.defineProperty(obj, key, {enumerable: true, // 允許屬性被枚舉configurable: true, // 允許屬性被修改get: function() {if (Dep.target) {dep.addSub(Dep.target);}return value;},set: function(newVal) {if (value!== newVal) {value = newVal;dep.notify();}}});
}
在這個函數中:
defineReactive
函數使用Object.defineProperty
對obj
的key
屬性進行定義。get
方法:當該屬性被訪問時,如果Dep.target
存在(在 Vue 中,Dep.target
通常是當前正在編譯的Watcher
),將其添加到dep
的訂閱者列表中。set
方法:當該屬性被修改時,如果新值與舊值不同,更新屬性值并通知dep
的訂閱者列表中的所有訂閱者。
依賴收集:
每個組件實例都有一個 Watcher
對象,它是一個訂閱者,當組件的模板中使用到某個數據時,會觸發該數據的 get
方法,將該 Watcher
添加到該數據的訂閱者列表中。
class Watcher {constructor(vm, exp, cb) {this.vm = vm;this.exp = exp;this.cb = cb;this.value = this.get();}get() {Dep.target = this;let value = this.vm[this.exp];Dep.target = null;return value;}update() {let newValue = this.vm[this.exp];let oldValue = this.value;if (newValue!== oldValue) {this.value = newValue;this.cb.call(this.vm, newValue, oldValue);}}
}
在這個類中:
Watcher
的get
方法會將自己設置為Dep.target
,并訪問數據,觸發數據的get
方法,從而將自己添加到該數據的訂閱者列表中。update
方法會在數據更新時被調用,調用回調函數cb
更新視圖。
發布訂閱模式:
Dep
類是一個簡單的發布者,它維護一個訂閱者列表,并在數據變化時通知訂閱者。
class Dep {constructor() {this.subs = [];}addSub(sub) {this.subs.push(sub);}removeSub(sub) {this.subs = this.subs.filter(s => s!== sub);}notify() {this.subs.forEach(sub => sub.update());}
}
在這個類中:
constructor
方法創建一個空的訂閱者列表。addSub(sub)
方法添加一個訂閱者。removeSub(sub)
方法移除一個訂閱者。notify()
方法通知所有訂閱者更新。
3. 整體流程
- 當 Vue 實例化時,會對
data
屬性中的數據進行遍歷,使用defineReactive
進行數據劫持。 - 當編譯模板時,會創建
Watcher
對象,在使用數據時觸發數據的get
方法,將Watcher
添加到該數據的訂閱者列表中。 - 當數據發生變化時,觸發數據的
set
方法,通知Watcher
進行更新,Watcher
調用update
方法更新視圖。
4. 示例代碼
<div id="app"><input v-model="message">{{ message }}
</div>
function Vue(options) {this.data = options.data;observe(this.data);new Compile('#app', this);
}function observe(data) {if (!data || typeof data!== 'object') return;Object.keys(data).forEach(key => {defineReactive(data, key, data[key]);});
}function Compile(el, vm) {this.vm = vm;this.el = document.querySelector(el);this.compileElement(this.el);
}Compile.prototype.compileElement = function (el) {let childNodes = el.childNodes;Array.from(childNodes).forEach(node => {if (node.nodeType === 1) {// 元素節點this.compileElement(node);} else if (node.nodeType === 3) {// 文本節點this.compileText(node);}});
};Compile.prototype.compileText = function (node) {let reg = /\{\{(.*?)\}\}/g;let value = node.textContent;if (reg.test(value)) {let exp = RegExp.$1.trim();node.textContent = value.replace(reg, this.vm.data[exp]);new Watcher(this.vm, exp, function (newVal) {node.textContent = value.replace(reg, newVal);});}
};let app = new Vue({data: {message: 'Hello, Vue!'}
});
代碼解釋
Vue
函數:
- 接收
options
,初始化data
屬性,并調用observe
對數據進行觀察。 - 調用
Compile
進行模板編譯。
observe
函數:
- 遍歷
data
對象,對每個屬性使用defineReactive
進行數據劫持。
Compile
類:
- 編譯模板元素,對于文本節點,如果存在
{{...}}
插值表達式,使用Watcher
進行數據監聽和更新。
Watcher
類:
- 在實例化時,會將自己添加到數據的訂閱者列表中,并在更新時更新視圖。
Dep
類:
- 作為發布者,管理訂閱者列表,在數據更新時通知訂閱者。
面試回答示例
“Vue 2 的雙向綁定是通過數據劫持和發布訂閱模式實現的。首先,使用 Object.defineProperty
對數據對象的屬性進行劫持,在屬性的 get
方法中進行依賴收集,將使用該數據的 Watcher
訂閱者添加到該數據的訂閱者列表中,在 set
方法中,當數據發生變化時通知訂閱者列表中的 Watcher
。Watcher
是一個訂閱者,負責更新視圖,它會在實例化時將自己添加到數據的訂閱者列表中,并在更新時調用回調函數更新視圖。Dep
類是一個發布者,負責維護訂閱者列表并通知訂閱者更新。當 Vue 實例化時,會對 data
中的數據進行劫持,在模板編譯時,會創建 Watcher
進行依賴收集,從而實現數據和視圖的雙向綁定。這樣,當數據變化時,視圖會更新;當用戶操作視圖(如通過 v-model
)時,會觸發數據的更新,形成雙向綁定的效果。”
通過這樣的解釋,可以向面試官展示你對 Vue 2 雙向綁定原理的深入理解,包括數據劫持、依賴收集、發布訂閱模式的使用以及整體的工作流程。
2. vue3 的雙向綁定原理
Vue 3 的雙向綁定機制相比 Vue 2 有了顯著改進,主要通過 組合式 API 和 Proxy 實現。以下是完整解析:
一、核心機制演進
特性 | Vue 2 | Vue 3 |
---|---|---|
響應式基礎 | Object.defineProperty | Proxy |
檢測范圍 | 對象屬性 | 完整對象 |
數組檢測 | 需特殊方法 | 原生支持 |
性能 | 遞歸轉換屬性 | 惰性代理 |
二、響應式系統核心實現
1. reactive() - 對象響應化
function reactive(target) {return new Proxy(target, {get(target, key, receiver) {track(target, key) // 依賴收集return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver)trigger(target, key) // 觸發更新return true}})
}
2. ref() - 原始值響應化
function ref(value) {const refObject = {get value() {track(refObject, 'value')return value},set value(newVal) {value = newValtrigger(refObject, 'value')}}return refObject
}
三、依賴收集與觸發流程
-
依賴收集階段:
-
觸發更新階段:
四、v-model 雙向綁定實現
組件示例:
<CustomInput v-model="searchText" /><!-- 等價于 -->
<CustomInput :modelValue="searchText"@update:modelValue="newValue => searchText = newValue"
/>
組件實現:
defineProps(['modelValue'])
defineEmits(['update:modelValue'])const emitUpdate = (e) => {emit('update:modelValue', e.target.value)
}
五、性能優化策略
-
編譯時優化:
- 靜態節點提升(Hoist Static)
- 補丁標志(Patch Flags)
- 樹結構拍平(Tree Flattening)
-
響應式優化:
- 依賴關系緩存(effect緩存)
- 批量異步更新(nextTick合并)
-
源碼結構優化:
// 惰性代理示例 function shallowReactive(obj) {const proxy = new Proxy(obj, handlers)// 不立即遞歸代理嵌套對象return proxy }
六、與 Vue 2 的對比升級
-
數組處理改進:
// Vue 2 需要特殊處理 this.$set(this.items, index, newValue)// Vue 3 直接操作 state.items[index] = newValue // 自動觸發更新
-
動態屬性檢測:
// Vue 2 無法檢測新增屬性 this.$set(this.obj, 'newProp', value)// Vue 3 自動檢測 state.newProp = value // 自動響應
七、開發注意事項
-
響應式丟失場景:
// 解構會導致響應式丟失 const { x, y } = reactive({ x: 1, y: 2 })// 正確做法 const pos = reactive({ x: 1, y: 2 }) const { x, y } = toRefs(pos)
-
性能敏感操作:
// 大數據量使用shallowRef/shallowReactive const bigList = shallowRef([])// 非響應式數據使用markRaw const foo = markRaw({ complex: object })
Vue 3 的雙向綁定通過 Proxy 實現了更精細的依賴跟蹤,配合編譯時優化,在保證開發體驗的同時提供了更好的運行時性能。理解其原理有助于編寫更高效的 Vue 代碼。
3. vue 的生命周期
一、Vue 2 和 Vue 3 生命周期對比
生命周期鉤子對照表
Vue 2 選項式API | Vue 3 組合式API | 觸發時機描述 |
---|---|---|
beforeCreate | 無直接對應 | 實例初始化前,data/methods未初始化 |
created | setup() | 實例創建完成,data/methods可用 |
beforeMount | onBeforeMount | 掛載開始前,DOM尚未生成 |
mounted | onMounted | 掛載完成,DOM已生成 |
beforeUpdate | onBeforeUpdate | 數據變化導致DOM更新前 |
updated | onUpdated | 數據變化導致DOM更新后 |
beforeDestroy | onBeforeUnmount | 實例銷毀前(vue3改名更準確) |
destroyed | onUnmounted | 實例銷毀后(vue3改名更準確) |
activated | onActivated | keep-alive組件激活時 |
deactivated | onDeactivated | keep-alive組件停用時 |
errorCaptured | onErrorCaptured | 捕獲子孫組件錯誤時 |
二、生命周期完整流程圖示
三、組合式API中的使用示例
import { onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
} from 'vue'export default {setup() {onBeforeMount(() => {console.log('掛載前')})onMounted(() => {console.log('掛載完成')})onBeforeUpdate(() => {console.log('更新前')})onUpdated(() => {console.log('更新完成')})onBeforeUnmount(() => {console.log('銷毀前')})onUnmounted(() => {console.log('銷毀完成')})}
}
四、關鍵生命周期詳解
1. created / setup
- 數據訪問:可訪問響應式data、computed等
- 異步請求:適合在此發起初始數據請求
- 注意:此時DOM未生成,不可操作DOM
2. mounted
- DOM操作:可安全操作DOM元素
- 子組件:保證所有子組件也已掛載
- 典型用途:初始化第三方庫(如圖表庫)
3. updated
- DOM狀態:可獲取更新后的DOM狀態
- 避免操作:不要在此修改狀態,可能導致無限循環
- 性能優化:復雜操作建議使用watch替代
4. unmounted
- 清理工作:移除事件監聽、定時器、取消網絡請求等
- 第三方庫:銷毀插件實例(如銷毀ECharts實例)
五、特殊場景生命周期
Keep-alive組件
onActivated(() => {console.log('組件被激活')
})onDeactivated(() => {console.log('組件被緩存')
})
錯誤捕獲
onErrorCaptured((err, instance, info) => {console.error('捕獲到錯誤:', err)return false // 阻止錯誤繼續向上傳播
})
六、最佳實踐建議
-
異步請求:
- 初始化數據:created/setup + async/await
- 避免在mounted中請求,會延遲首次渲染
-
DOM操作:
- 必須在mounted及之后生命周期進行
- 使用nextTick確保DOM更新完成
-
內存管理:
onUnmounted(() => {clearInterval(timer)eventBus.off('eventName', handler)chartInstance.dispose() })
-
性能優化:
- 避免在updated中進行復雜計算
- 大量數據更新考慮使用虛擬滾動
理解Vue生命周期有助于在正確時機執行代碼,避免常見錯誤,并優化應用性能。Vue 3的組合式API提供了更靈活的生命周期管理方式。
4. vue 子組件為何不能修改父組件的值
一、核心原因:單向數據流原則
Vue 強制實施單向數據流(One-Way Data Flow)設計模式,這是現代前端框架的通用規范:
- 數據所有權明確:父組件擁有數據,子組件只有使用權
- 可預測性:數據變更源頭唯一,便于追蹤狀態變化
- 維護性:避免多組件同時修改導致的混亂
二、直接修改的危害
如果允許子組件直接修改父組件值:
問題類型 | 具體表現 |
---|---|
狀態混亂 | 多個子組件同時修改同一狀態,難以確定最終值 |
調試困難 | 數據變更來源不明確,錯誤難以追蹤 |
組件耦合 | 子組件必須了解父組件內部實現,破壞組件獨立性 |
性能優化障礙 | Vue的響應式系統難以優化變更檢測 |
三、Vue 的解決方案
1. Props + Events 標準模式
<!-- 父組件 -->
<template><Child :value="parentValue" @update="handleUpdate" />
</template><script>
export default {data() {return { parentValue: 1 }},methods: {handleUpdate(newVal) {this.parentValue = newVal}}
}
</script><!-- 子組件 -->
<template><button @click="$emit('update', value + 1)">+1</button>
</template><script>
export default {props: ['value']
}
</script>
2. v-model 語法糖(Vue 2)
<!-- 父組件 -->
<Child v-model="parentValue" /><!-- 等價于 -->
<Child :value="parentValue" @input="parentValue = $event" />
3. v-model 參數(Vue 3)
<!-- 父組件 -->
<Child v-model:title="pageTitle" /><!-- 子組件 -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
4. .sync 修飾符(Vue 2)
<!-- 父組件 -->
<Child :value.sync="parentValue" /><!-- 子組件 -->
this.$emit('update:value', newValue)
四、特殊情況的處理方案
1. 需要直接修改的情況
// 子組件
props: {value: {type: Object,default: () => ({})}
},
methods: {modifyParent() {const newObj = JSON.parse(JSON.stringify(this.value))newObj.property = 'new value'this.$emit('update', newObj)}
}
2. 使用 Vuex/Pinia 狀態管理
// store
export const useStore = defineStore('main', {state: () => ({ count: 0 }),actions: {increment() {this.count++}}
})// 任何組件
import { useStore } from './store'
const store = useStore()
store.increment() // 通過集中式管理修改狀態
五、底層原理分析
Vue 通過以下機制阻止直接修改:
-
Prop 代理:Vue 在子組件實例上創建的 props 是只讀代理
// Vue 內部實現簡化 const childProps = {} Object.defineProperty(childProps, 'value', {get() { return parentValue },set() { if (process.env.NODE_ENV !== 'production') {warn(`Avoid mutating prop directly`)}} })
-
開發環境警告:在非生產環境下,Vue 會檢測并警告 props 的直接修改
六、最佳實踐建議
-
嚴格遵循單向數據流
- 父級通過 props 向下傳遞數據
- 子級通過事件向上通知變更意圖
-
復雜場景處理方案:
場景 解決方案 需要修改父級對象屬性 觸發事件讓父級自己修改 多層級組件通信 使用provide/inject 全局狀態 使用Vuex/Pinia 臨時本地修改 使用computed或ref拷貝prop值 -
代碼規范檢查:
// ESLint規則推薦 "vue/no-mutating-props": "error"
理解并遵守這一設計原則,可以構建出更健壯、可維護的Vue應用架構。
5. js delete 刪除數組的某一個值會怎么樣
一、基本行為表現
當使用 delete
操作符刪除數組元素時:
const arr = ['a', 'b', 'c', 'd'];
delete arr[1]; // 刪除索引1的元素'b'console.log(arr); // ['a', empty, 'c', 'd']
console.log(arr.length); // 4
console.log(arr[1]); // undefined
二、關鍵特性解析
1. 不會改變數組長度
delete
只會將指定位置的元素變為 empty 空位(稀疏數組)- 數組的
length
屬性保持不變
2. 元素訪問結果
- 被刪除的位置會返回
undefined
- 但該位置仍在數組中(表現為 empty 而非 undefined)
console.log(1 in arr); // false (表示索引1不存在)
console.log(arr.hasOwnProperty(1)); // false
3. 遍歷行為差異
不同遍歷方法對空位的處理:
方法 | 處理方式 | 示例結果 |
---|---|---|
for循環 | 會處理空位(值為undefined) | ‘a’, undefined, ‘c’, ‘d’ |
forEach | 跳過空位 | ‘a’, ‘c’, ‘d’ |
map | 跳過空位 | [‘a’, empty, ‘c’, ‘d’] |
filter | 移除空位 | [‘a’, ‘c’, ‘d’] |
三、與 splice 方法對比
操作 | delete arr[i] | arr.splice(i, 1) |
---|---|---|
數組長度 | 不變 | 減少 |
空位產生 | 會 | 不會 |
索引重排 | 不重排 | 后續元素前移 |
適用場景 | 需要保留位置/長度時 | 需要真正移除元素時 |
四、實際應用建議
1. 應該使用 delete 的場景
- 需要保持數組長度不變(如游戲地圖格子)
- 需要保留元素位置信息(如時間序列數據)
2. 不應該使用 delete 的場景
- 需要真正移除元素時(應改用
splice
) - 需要保證數組連續性的操作前(如
JSON.stringify
會忽略空位)
3. 正確的元素刪除方法
// 方法1:splice (修改原數組)
arr.splice(index, 1);// 方法2:filter (創建新數組)
const newArr = arr.filter((_, i) => i !== index);// 方法3:設置length (截斷數組)
arr.length = newLength;
五、特殊注意事項
-
類型化數組:
const typedArray = new Uint8Array([1, 2, 3]); delete typedArray[1]; // 會將索引1位置設為0
-
性能考慮:
delete
操作比splice
快(不涉及元素移動)- 但后續操作空位數組可能更慢
-
Vue/React 響應式:
- 在響應式框架中,
delete
可能不會觸發視圖更新 - 應使用框架提供的刪除方法(如 Vue 的
$delete
)
- 在響應式框架中,
六、底層原理
delete
操作符實際執行的是:
- 將指定屬性的
[[Configurable]]
特性設為 true - 刪除該屬性
- 返回 true(即使屬性不存在)
對于數組:
- 數組是特殊對象,索引是屬性名
delete arr[i]
等同于刪除對象的屬性
理解 delete
對數組的這種特殊行為,有助于避免在需要真正移除元素時錯誤使用它。大多數情況下,splice
或 filter
才是更合適的選擇。
6. vue 和 react 的 diff 算法
Vue和React作為流行的前端框架,都使用虛擬DOM(Virtual DOM)來提升渲染性能,而Diff算法是虛擬DOM的核心,它能找出新舊虛擬DOM之間的差異,從而只更新需要更新的真實DOM部分。下面為你分別介紹它們的Diff算法。
Vue的Diff算法
Vue的Diff算法采用了雙指針和key的策略,通過比較新舊虛擬節點的差異,最小化DOM操作。具體步驟如下:
- 同級比較:只對同一層級的節點進行比較。
- 節點類型比較:若節點類型不同,直接替換。
- key比較:若有key,使用key進行更高效的比較和復用。
- 雙指針遍歷:使用首尾雙指針遍歷新舊節點列表。
React的Diff算法
React的Diff算法基于幾個啟發式策略,旨在減少比較次數,提高性能。具體步驟如下:
- 同級比較:和Vue一樣,只比較同一層級的節點。
- 節點類型比較:節點類型不同時,直接替換。
- key比較:使用key來標識列表中的元素,便于復用和移動。
- 列表比較:采用雙循環遍歷新舊節點列表。
代碼示例
以下是一個簡單的Vue和React組件示例,來幫助你理解Diff算法的應用。
Vue示例
<template><div><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul><button @click="updateList">Update List</button></div>
</template><script>
export default {data() {return {list: [{ id: 1, name: 'Item 1' },{ id: 2, name: 'Item 2' },{ id: 3, name: 'Item 3' }]};},methods: {updateList() {this.list = [{ id: 1, name: 'Updated Item 1' },{ id: 2, name: 'Updated Item 2' },{ id: 4, name: 'New Item 4' }];}}
};
</script>
React示例
import React, { useState } from 'react';const App = () => {const [list, setList] = useState([{ id: 1, name: 'Item 1' },{ id: 2, name: 'Item 2' },{ id: 3, name: 'Item 3' }]);const updateList = () => {setList([{ id: 1, name: 'Updated Item 1' },{ id: 2, name: 'Updated Item 2' },{ id: 4, name: 'New Item 4' }]);};return (<div><ul>{list.map(item => (<li key={item.id}>{item.name}</li>))}</ul><button onClick={updateList}>Update List</button></div>);
};export default App;
總結
- Vue:采用雙指針和key策略,能更精準地找出差異,更新DOM。
- React:基于啟發式策略,通過雙循環遍歷列表,性能也較為出色。
兩者都運用了虛擬DOM和Diff算法,減少了不必要的DOM操作,提高了渲染性能。在實際開發中,合理使用key能進一步優化Diff算法的性能。
7. 什么是閉包
在 JavaScript 里,閉包是一個強大且重要的概念。下面為你詳細解釋 JavaScript 中的閉包。
定義
閉包是指有權訪問另一個函數作用域中變量的函數。簡單來說,即使外部函數執行完畢,其作用域內的變量也不會被銷毀,而是會被閉包“捕獲”并保留,使得這些變量能在外部函數之外被訪問和修改。
形成條件
閉包的形成需要滿足以下兩個關鍵條件:
- 函數嵌套:必須存在一個外部函數和至少一個內部函數。
- 內部函數引用外部函數的變量:內部函數使用了外部函數作用域內的變量。
作用
閉包在 JavaScript 中有多種重要作用:
- 讀取函數內部的變量:外部函數執行結束后,其內部變量會被閉包保存,可通過閉包在外部訪問這些變量。
- 讓這些變量的值始終保持在內存中:變量不會因外部函數執行完畢而被銷毀,而是持續存在于內存里,方便后續使用。
- 封裝私有變量和方法:可以使用閉包來創建私有變量和方法,避免全局作用域的污染。
示例
function outerFunction() {// 外部函數的變量let counter = 0;// 內部函數,形成閉包function innerFunction() {counter++;return counter;}return innerFunction;
}// 創建閉包實例
const closure = outerFunction();// 調用閉包
console.log(closure()); // 輸出: 1
console.log(closure()); // 輸出: 2
console.log(closure()); // 輸出: 3
在這個示例中,outerFunction
是外部函數,innerFunction
是內部函數。innerFunction
引用了 outerFunction
作用域內的 counter
變量,從而形成了閉包。當 outerFunction
執行完畢后,counter
變量不會被銷毀,而是被 innerFunction
捕獲并保留。每次調用 closure
函數時,counter
變量的值都會增加。
閉包的潛在問題
雖然閉包功能強大,但也可能帶來一些問題,比如內存泄漏。由于閉包會讓變量一直存在于內存中,如果閉包使用不當,可能會導致內存占用過高。因此,在使用閉包時,需要注意內存的使用情況,避免不必要的內存消耗。
8. 原型鏈
原型鏈是JavaScript中實現繼承和對象屬性查找的一種機制。以下是關于原型鏈的詳細介紹:
原型的概念
在JavaScript中,每個對象都有一個原型(prototype
)。原型也是一個對象,它可以包含一些屬性和方法。當訪問一個對象的屬性或方法時,如果該對象本身沒有這個屬性或方法,JavaScript引擎就會去它的原型對象中查找。
原型鏈的形成
- 所有的對象都默認從
Object.prototype
繼承屬性和方法。例如,toString()
、valueOf()
等方法就是從Object.prototype
繼承來的。 - 當創建一個函數時,JavaScript會自動為這個函數添加一個
prototype
屬性,這個屬性指向一個對象,稱為該函數的原型對象。當使用構造函數創建一個新對象時,新對象的__proto__
屬性(也稱為原型鏈指針)會指向構造函數的原型對象。這樣就形成了一條鏈,從新對象開始,通過__proto__
不斷指向它的原型對象,直到Object.prototype
,這條鏈就是原型鏈。
原型鏈的作用
- 實現繼承:通過原型鏈,一個對象可以繼承另一個對象的屬性和方法。例如,定義一個
Animal
構造函數,再定義一個Dog
構造函數,讓Dog
的原型指向Animal
的實例,這樣Dog
的實例就可以繼承Animal
的屬性和方法。 - 屬性和方法的共享:多個對象可以共享原型對象上的屬性和方法,節省內存空間。比如,所有數組對象都共享
Array.prototype
上的push()
、pop()
等方法。
示例代碼
// 定義一個構造函數
function Person(name) {this.name = name;
}// 在構造函數的原型上添加方法
Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name}`);
};// 創建一個Person的實例
const person1 = new Person('John');// 訪問實例的屬性和方法,先在實例本身查找,找不到就去原型上查找
person1.sayHello(); // 輸出 "Hello, my name is John"
console.log(person1.__proto__ === Person.prototype); // 輸出 true
在這個例子中,person1
是 Person
構造函數的實例,它的 __proto__
屬性指向 Person.prototype
。當調用 person1.sayHello()
時,由于 person1
本身沒有 sayHello
方法,JavaScript會沿著原型鏈在 Person.prototype
上找到該方法并執行。
9. this指向
在 JavaScript 里,this
是一個特殊的關鍵字,它的指向取決于函數的調用方式。下面將為你詳細介紹 this
在不同情況下的指向。
全局作用域中 this
的指向
在全局作用域里,this
指向全局對象。在瀏覽器環境中,全局對象是 window
;在 Node.js 環境里,全局對象是 global
。
console.log(this === window); // 在瀏覽器環境中輸出 true
this.globalVariable = 'I am a global variable';
console.log(window.globalVariable); // 輸出: I am a global variable
函數作為普通函數調用時 this
的指向
當函數作為普通函數調用時,this
指向全局對象(在嚴格模式下,this
是 undefined
)。
function normalFunction() {console.log(this);
}normalFunction(); // 在非嚴格模式下輸出 window,在嚴格模式下輸出 undefined
函數作為對象方法調用時 this
的指向
當函數作為對象的方法調用時,this
指向調用該方法的對象。
const person = {name: 'John',sayHello: function() {console.log(`Hello, my name is ${this.name}`);}
};person.sayHello(); // 輸出: Hello, my name is John
構造函數中 this
的指向
當使用 new
關鍵字調用函數時,該函數就成為了構造函數,此時 this
指向新創建的對象。
function Person(name) {this.name = name;this.sayHello = function() {console.log(`Hello, my name is ${this.name}`);};
}const john = new Person('John');
john.sayHello(); // 輸出: Hello, my name is John
call
、apply
和 bind
方法對 this
指向的影響
call
方法:call
方法可以調用一個函數,并且可以指定該函數內部this
的指向。
function greet(message) {console.log(`${message}, my name is ${this.name}`);
}const person1 = { name: 'Alice' };
greet.call(person1, 'Hi'); // 輸出: Hi, my name is Alice
apply
方法:apply
方法和call
方法類似,不同之處在于apply
方法接受一個數組作為參數。
function greet(message) {console.log(`${message}, my name is ${this.name}`);
}const person2 = { name: 'Bob' };
greet.apply(person2, ['Hello']); // 輸出: Hello, my name is Bob
bind
方法:bind
方法會創建一個新的函數,在調用時會將this
綁定到指定的對象上。
function greet(message) {console.log(`${message}, my name is ${this.name}`);
}const person3 = { name: 'Charlie' };
const boundGreet = greet.bind(person3);
boundGreet('Hey'); // 輸出: Hey, my name is Charlie
箭頭函數中 this
的指向
箭頭函數沒有自己的 this
,它的 this
繼承自外層函數。
const obj = {name: 'David',sayHello: function() {const arrowFunction = () => {console.log(`Hello, my name is ${this.name}`);};arrowFunction();}
};obj.sayHello(); // 輸出: Hello, my name is David
理解 this
關鍵字的指向是 JavaScript 中的一個重要部分,不同的調用方式會導致 this
指向不同的對象。在實際開發中,要根據具體情況來確定 this
的指向。