2025年01月09日德美医疗前端面试
目录
- 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
的指向。
相关文章:
2025年01月09日德美医疗前端面试
目录 vue2 的双向绑定的原理vue3 的双向绑定原理vue 的生命周期vue 子组件为何不能修改父组件的值js delete 删除数组的某一个值会怎么样vue 和 react 的 diff 算法什么是闭包原型链this指向 vue2 的双向绑定的原理 以下是 Vue 2 双向绑定的原理: 1. 核心概念 …...

快速理解动态代理
什么是动态代理(Java核心技术卷1的解释) 动态代理是一种运行时生成代理对象的技术,其本质是通过字节码增强在不修改原始类代码的前提下,动态拦截并扩展目标对象的行为。它通过代理对象对原始方法的调用进行拦截,并在方法执行前后注入自定义逻…...
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API 理论千遍,不如动手一遍!在前面几篇文章中,我们了解了 Serverless 的概念、FaaS 的核心原理以及 BaaS 的重要作用。现在,是时候把这些知识运用起来,亲手构建一个简单但完整的 Serverless 应用了。 …...
spark算子介绍
目录 1. 转换算子(Transformation)1.1 常用转换算子 2. 行动算子(Action)2.1 常用行动算子 3. 转换算子与行动算子的区别4. 示例代码5. 总结 在Spark中,算子(Operator)是对数据集(RD…...

AugmentCode 非常昂贵的新定价
AugmentCode 现在的价格比 Cursor 和 Windsurf 的总和还要贵。 AugmentCode 曾是我开发工作流程的常用工具。出乎意料的是,他们改变了定价结构,让开发者们震惊不已。 原来的30 美元月费已经增长为50 美元月费,这是一个67%的增长。 改变我看法的不仅仅是价格上涨,还有他…...

前端面试2
1. 面试准备 1. 建立自己的知识体系 思维导图ProcessOn框架Vue elementUI自查 https://zh.javascript.info/ 借鉴 https://juejin.cn/post/6844904103504527374http://conardli.top/blog/article/https://github.com/mqyqingfeng/Bloghttp://47.98.159.95/my_blog/#html 2.技能…...

大疆卓驭嵌入式面经及参考答案
FreeRTOS 有哪 5 种内存管理方式? heap_1.c:这种方式简单地在编译时分配一块固定大小的内存,在整个运行期间不会进行内存的动态分配和释放。它适用于那些对内存使用需求非常明确且固定,不需要动态分配内存的场景,优点是…...
RAID磁盘阵列的概念(自用留档)
概念 RAID磁盘阵列是由若干个磁盘组成的磁盘组。 磁盘组可以恢复意外丢失的数据,保证了数据的安全性。 种类 根据实际情况的不同,RAID有若干种,以一个具有三块硬盘的硬盘组为例: RAID 0:将文件拆分成三份分别放到三…...
设计模式简述(十八)享元模式
享元模式 描述基本组件使用 描述 当内存中存在大量类似的对象时,可以考虑使用享元模式减少整体内存占用。 可以将相同的部分和不同的部分进行拆分,以达到多个对象共享相同部分内存的目的。 基本组件 通常享元对象通过共享的属性映射一个享元对象。 公…...

架构进阶:74页数据架构设计总体规划方案【附全文阅读】
本文讨论了数据资源规划在信息化战略规划中的重要性,详细阐述了数据资源规划的方法与过程,包括系统架构、业务能力模型、数据架构等。 文章指出,数据资源规划需要梳理企业级数据模型,明确数据分布和流向,建立统一的数据…...

情书大全v3.0.1
《情书大全》是一款致力于情书写作的手机应用程序,内置了丰富的情书范本及定制化服务。用户无论是想要倾诉爱意、交流友情还是传递亲情,都能在这款应用中寻得合适的情书样本。用户还可以根据自己的需求对模板进行编辑和调整,轻松创作出感人至…...

基于OpenCV的人脸识别:LBPH算法
文章目录 引言一、概述二、代码实现1. 代码整体结构2. 导入库解析3. 训练数据准备4. 标签系统5. 待识别图像加载6. LBPH识别器创建7. 模型训练8. 预测执行9. 结果输出 三、 LBPH算法原理解析四、关键点解析五、改进方向总结 引言 人脸识别是计算机视觉领域的一个重要应用&…...

鸿蒙 使用动画 简单使用
鸿蒙 使用动画 简单使用 动画就两个,属性动画和转场动画 属性动画只是组件的属性发生变化,而转场动画是指对将要出现或消失的组件做动画,而文档的其他动画只是给这两个动画效果锦上添花罢了 这篇文章简单介绍这两个动画,其他的…...
MySQL数据库迁移SQL语句指南
MySQL数据库迁移SQL语句指南 一、基础迁移方法 1. 使用mysqldump进行全量迁移 -- 导出源数据库(在命令行执行) mysqldump -u [源用户名] -p[源密码] --single-transaction --routines --triggers --events --master-data2 [数据库名] > migration…...

arcgis和ENVI中如何将数据输出为tif
一、arcgis中转换为tif 右键图层: Data -> Export Data, 按照图示进行选择,选择tiff格式导出即可,还可以选择其他类型的格式,比如envi。 二、 ENVI中转换为tif File -> Save As -> Save As (ENVI, NITF, TIFF, DTED) …...

RagFlow 完全指南(一):从零搭建开源大模型应用平台(Ollama、VLLM本地模型接入实战)
文章目录 1. 相关资源2. 核心特性3. 安装与部署3.1 环境准备3.2 部署RagFlow3.3 更新RagFlow3.4 系统配置 4. 接入本地模型4.1 接入 Ollama 本地模型4.1.1 步骤4.1.2 常见问题 4.2 接入 VLLM 模型 5. 应用场景6. 总结 1. 相关资源 官网GitHub文档中心 2. 核心特性 …...

计算机网络 4-2-1 网络层(IPv4)
2 IPv4分组 各协议之间的关系 IP协议(Internet Protocol, 网际协议)是互联网的核心! ARP协议用于查询同一网络中的<主机IP地址,MAC地址>之间的映射关系 ICMP协议用于网络层实体之间相互通知“异常事件” IGMP协议用于实现IP组播 2.1 结构<首…...
Python赋能自动驾驶:如何打造高效的环境感知系统
Python赋能自动驾驶:如何打造高效的环境感知系统 大家好,我是 Echo_Wish,今天我们来聊聊自动驾驶里的“眼睛”——环境感知系统,以及如何用 Python 实现它。 自动驾驶的核心目标是让车辆在没有人工干预的情况下安全行驶,而要做到这一点,环境感知系统必须实时获取并理解…...
ST表(稀疏表)
对ST表进行一个简单的总结,它可以实现O(1)的静态区间查询,可以适用于查询操作频繁但数据不修改的场景 题目来源 https://www.luogu.com.cn/problem/P3865 题目介绍 给定一个长度为 N 的数列,和 M 次询问,求出每一次询问的区间…...
Java常用类-比较器
目录 一、为什么需要比较器?二、核心差异速记表三、Comparable:对象自带的 “默认规则”1. 核心作用2. 源码定义3. 实战:给Student类加默认规则4. 源码验证(以Integer为例) 四、Comparator:临时的 “外部规…...

Python----机器学习(模型评估:准确率、损失函数值、精确度、召回率、F1分数、混淆矩阵、ROC曲线和AUC值、Top-k精度)
一、模型评估 1. 准确率(Accuracy):这是最基本的评估指标之一,表示模型在测试集上正确 分类样本的比例。对于分类任务而言,准确率是衡量模型性能的直观标准。 2. 损失函数值(Loss)࿱…...

Linux工作台文件操作命令全流程解析(高级篇之vim和nano精讲)
全文目录 1 简单易用的 Nano (入门之选)1.1 适用场景1.2 安装命令1.3 基础操作1.4 优点 2 功能强大的 Vim2.1 适用场景2.2 安装命令2.3 模式说明2.4 常用命令2.4.1 普通模式2.4.2 编辑模式2.4.3 可视模式2.4.4 命令行模式 3 参考文献 写在前面 作为运维或者研发,日…...

大数据产品销售数据分析:基于Python机器学习产品销售数据爬虫可视化分析预测系统设计与实现
文章目录 大数据产品销售数据分析:基于Python机器学习产品销售数据爬虫可视化分析预测系统设计与实现一、项目概述二、项目说明三、研究意义四、系统总体架构设计总体框架技术架构数据可视化模块设计图后台管理模块设计数据库设计 五、开发技术介绍Flask框架Python爬…...

VS2022 Qt配置Qxlsx
目录 1、下载QXlsx,并解压文件夹 编辑2、打开VS2022配置QXlsx 3、VS配置Qxslx库 方法一:常规方法 方法二:直接使用源码 方法三:将QXlsx添加到Qt安装目录(暂时尝试未成功) 1、下载QXlsx,…...

OSPF案例
拓扑图: 要求: 1,R5为ISP,其上只能配置IP地址;R4作为企业边界路由器, 出口公网地址需要通过PPP协议获取,并进行chap认证 2,整个OSPF环境IP基于172.16.0.0/16划分;…...
1.1.2 简化迭代器 yield return的使用
yield return 是一个用于简化迭代器(Iterator)实现的关键字组合。它的核心作用是让开发者能够以更简洁的方式定义一个按需生成序列的方法(生成器方法),而无需显式实现 IEnumerable 或 IEnumerator 接口。yield return 方法会在每次迭代时按需生成下一个值,而不是一次性生…...

《用MATLAB玩转游戏开发》贪吃蛇的百变玩法:从命令行到AI对战
《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-🐍 贪吃蛇的百变玩法:从命令行到AI对战 🎮 欢迎来到这篇MATLAB贪吃蛇编程全攻略!本文将带你从零开始,一步步…...

【数据结构与算法】图的基本概念与遍历
目录 一、图的基本概念 1.1 图的基本组成 1.2 图的分类 1.3 顶点的度数 1.4 路径与回路 1.5 子图与特殊图 二. 图的存储结构 2.1 邻接矩阵 2.2 邻接表 三、深度优先遍历 3.1 原理 3.2 实现步骤 3.3 代码实现 四、广度优先遍历 4.1 原理 4.2 实现步骤 4.3 代码…...
MAE自监督大模型在医学报告生成中的应用
MAE自监督大模型在医学报告生成中的应用详解 一、核心技术原理与医学适配 MAE(Masked Autoencoder)通过掩膜重建策略,在医学影像领域展现出独特优势: 解剖结构理解:通过随机掩盖图像区域(如75%的MRI切片&…...

Linux云服务器配置git开发环境
文章目录 1. 安装 git2. git clone3. git add .4. git commit -m 提交记录5. git push🍑 异常原因🍑 解决办法 6. git pull7. git log8. git rm9. git mv10. git status 1. 安装 git sudo yum install git -y2. git clone 此命令的作用是从远程仓库把代…...