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. 核心概念 …...
02 mysql 管理(Windows版)
一、启动及关闭 MySQL 服务器 1.1 通过 “服务” 管理工具 winr打开运行,输入services.msc 找到MySQL80,这个是我们在安装mysql的时候给的服务的名称,具体见文章mysql 安装 右键选择启动或者停止。 1.2 通过命令提示符 1.2.1 关闭命令…...
开发搭载OneNet平台的物联网数据收发APP的设计与实现
一、开发环境与工具准备 工具安装 下载HBuilderX开发版(推荐使用开发版以避免插件兼容性问题)安装Node.js和npm(用于依赖管理及打包)配置Android Studio(本地打包需集成离线SDK)项目初始化 创建uni-app项目,选择“默认模板”或“空白模板”安装必要的UI库(如uView或Van…...
ntdll!LdrpInitializeProcess函数分析之Peb->Ldr和全局变量ntdll!PebLdr的关系
代码部分A: PEB_LDR_DATA PebLdr; //全局变量ntdll!PebLdr NTSTATUS LdrpInitializeProcess ( IN PCONTEXT Context OPTIONAL, IN PVOID SystemDllBase ) { 代码部分B: // // Figure out process name. // Teb NtCurrentTeb…...
如何开始使用 Blender:Blender 3D 初学者指南和简介 怎么下载格式模型
Blender 是一个强大的 3D 创作套件,为动画、视觉效果、艺术等提供了一系列功能。无论您是初学者还是经验丰富的艺术家,Blender 都提供了一个免费的开源平台来释放您的创造力。凭借其内置的视频序列编辑器,Blender 还提供基本的编辑功能&#…...
Nginx安全防护与HTTPS部署
目录 一、Nginx 概述 二、Nginx 核心安全配置 (一)编译安装 Nginx (二)隐藏版本号 (三)限制危险请求方法 (四)请求限制(CC 攻击防御) (五&…...
HTTP 与 HTTPS 的深度剖析:差异、原理与应用场景
HTTP 与 HTTPS 的深度剖析:差异、原理与应用场景 在互联网的世界里,HTTP(超文本传输协议)和 HTTPS(超文本传输安全协议)是数据传输的 “高速公路”,它们承载着我们日常浏览网页、购物支付等各种…...
SMT贴片钢网精密设计与制造要点解析
内容概要 SMT贴片钢网作为电子组装工艺的核心载体,其设计与制造质量直接影响焊膏印刷精度及产品良率。本文系统梳理了钢网全生命周期中的15项关键技术指标,从材料选择、结构设计到工艺控制构建完整技术框架。核心要点涵盖激光切割精度的微米级调控、开口…...
算法每日一题 | 入门-顺序结构-三角形面积
三角形面积 题目描述 一个三角形的三边长分别是 a、b、c,那么它的面积为 p ( p − a ) ( p − b ) ( p − c ) \sqrt{p(p-a)(p-b)(p-c)} p(p−a)(p−b)(p−c) ,其中 p 1 2 ( a b c ) p\frac{1}{2}(abc) p21(abc) 。输入这三个数字,…...
Linux内核视角:线程同步与互斥的原理、实现与锁优化策略
Linux系列 文章目录 Linux系列前言一、前提知识二、线程互斥概念引入三、线程互斥3.1 什么是线程的互斥3.2 线程互斥的实现 四、锁的实现原理 前言 在前两篇文章中,我们已经对线程的相关概念及基本操作进行了深入介绍。在本篇中,我们将深入探讨编写多线…...
【区块链】Uniswap详细介绍
一、前言 本文将结合网上的资料和博主的理解,像大家详细介绍Uniswap,包括其核心概念、工作原理、版本演进、代币经济学以及风险点,适合想深入了解去中心化交易所(DEX)机制的用户。 二、Uniswap是什么 Uniswap 是一个…...
YOLOv8的Python基础--函数篇
1. 文件/目录操作相关函数 这些函数来自 os 和 shutil 模块: 函数/用法作用示例说明os.listdir(dir)列出目录下所有文件名os.listdir("./images")返回文件名列表(不包含路径)os.path.join()拼接路径os.path.join("dir"…...
vue源代码采用的设计模式分解
No.大剑师精品GIS教程推荐0地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 1Openlayers 【入门教程】 - 【源代码示例 300】 2Leaflet 【入门教程】 - 【源代码图文示例 150】 3MapboxGL【入门教程】 - 【源代码图文示例150】 4Cesium 【入门教程】…...
强化学习是AI Agent的进化引擎还是技术枷锁呢?
第一章 强化学习:Agent的“灵魂”觉醒之路 1.1 AlphaGo的启示:从规则到目标驱动的范式革命 2016年AlphaGo击败李世石的事件,标志着RL首次在复杂决策场景中展现其颠覆性价值。通过深度神经网络与RL的结合,AlphaGo无需依赖人类棋谱…...
python简易实现勒索病毒
python简易实现勒索病毒 1.首先介绍Crypto库1.1首先是对称加密1.2 非对称加密1.3 哈希 2.生成RSA密钥并保存3.文件加密4.文件解密1. 导入必要的模块2. 定义解密函数3. 设置私钥的密码4. 打开并读取文件5. 导入私钥6. 读取加密数据7. 解密会话密钥8. 创建 AES 解密器9. 解密数据…...
Nacos源码—4.Nacos集群高可用分析三
大纲 6.CAP原则与Raft协议 7.Nacos实现的Raft协议是如何写入数据的 8.Nacos实现的Raft协议是如何选举Leader节点的 9.Nacos实现的Raft协议是如何同步数据的 10.Nacos如何实现Raft协议的简版总结 6.CAP原则与Raft协议 (1)CAP分别指的是什么 (2)什么是分区以及容错 (3)为…...
AWS WebRTC如何实现拉流?内部是这样实现的
当我们通过手机上的app选择某一个Iot设备,例如,摄像头,想看实时视频的时候,aws都做了什么?最近在搞自研Iot项目,借机整理一下相关流程。 App通过 AWS SDK 发起拉流请求的内部机制是AWS Kinesis Video Streams (KVS) WebRTC 模式中一个非常关键的问题。 一、KVS WebRTC …...
NGINX `ngx_http_browser_module` 深度解析与实战
1. 模块定位 ngx_http_browser_module 在 HTTP 头 User-Agent 解析的基础上,给出三个内置变量: 变量作用典型值$modern_browser当 UA 被判定为 现代浏览器 时取 modern_browser_value 指定的值;否则为空modern. / 1$ancient_browser当 UA 被…...
Elasticsearch知识汇总之 ElasticSearch高可用方案
六 ElasticSearch高可用方案 6.1 高可用架构 请求协调节点根据负载均衡,转发给主分片节点,主分片同步复制给从节点,主从节点都写入完成返回客户端请求成功。对于读请求,协调负载到任意节点数据节点,数据节点把各自符合…...
多线程2-多线程编程
引入 当我们想要代码能够实现并发执行时,我们可以使用多进程进行并发编程(在Java中并不推荐这种方式,许多API在Java标准库中都没有提供),也可以使用多线程进行并发编程(系统提供了相关的API,Ja…...
电商系统中单商户和多商户的区别
在电商的商业版图上,单商户与多商户模式如同两条并行的发展脉络,各自构建起独特的商业生态。它们在运营逻辑、商业模式等多方面存在显著差异,这些差异不仅塑造了不同的平台特性,也深刻影响着企业的发展路径。接下来,我…...
【东枫科技】代理英伟达产品:智能网卡的连接线
文章目录 总览详细:NVIDIA 400Gb/s QSFP-DD 线缆详细:NVIDIA 400Gb/s OSFP 线缆详细:NVIDIA 200Gb/s QSFP56 线缆详细:NVIDIA 100Gb/s QSFP28 线缆 总览 详细:NVIDIA 400Gb/s QSFP-DD 线缆 详细:NVIDIA 400…...
使用ip池后,爬虫还被封,是什么原因呢?
嘿,亲爱的小伙伴们!今天我们聊一个让很多爬虫工程师抓狂的问题:明明用上了IP池,结果爬虫还是被封了!怎么回事呢?如果你也曾在爬虫与反爬的“猫鼠游戏”里痛苦“翻车”,别着急,这篇文…...
C++23 新利器:深入解析栈踪迹库 (P0881R7)
文章目录 为何需要标准化的栈踪迹?P0881R7 的核心组件与使用基本用法示例与异常处理的集成优势与价值潜在的考量总结 对于 C 开发者而言,调试和错误诊断一直是开发周期中不可或缺但又充满挑战的一环。当程序崩溃或发生未预期行为时,获取清晰、…...
2025-05-06 事业-独立开发项目-记录
摘要: 2025-05-06 事业-独立开发项目-记录 独立开发项目记录 Product Hunt | InDev 独立开发者导航站https://www.producthunt.com/ Nomads.com - Best Places to Live for Digital Nomads (formerly Nomad List)https://nomads.com/ InDev 独立开发者导航站https://indev.bei…...
【Linux系统】探索进程等待与程序替换的奥秘
文章目录 前言一、重谈进程创建1.1 fork 函数1.2 写时拷贝1.3 fork 的常规用法1.4 fork 调用失败的原因1.5 创建一批进程 二、进程终止2.1 进程退出场景2.2 strerror 函数的作用2.3 errno 全局变量2.4 程序异常机制2.5 进程退出方式 三、进程等待3.1 进程等待必要性3.2 进程等待…...
Github 2025-05-06Python开源项目日报 Top10
根据Github Trendings的统计,今日(2025-05-06统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10C++项目2TypeScript项目1系统设计指南 创建周期:2507 天开发语言:Python协议类型:OtherStar数量:241693 个Fork数量:42010 次…...
【愚公系列】《Manus极简入门》021-音乐创作助手:“音符魔术师”
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
【Azure Redis】Redis导入备份文件(RDB)失败的原因
问题描述 在测试Azure Redis的导入/导出备份文件的功能中,突然发现在Redis 4.0上导入的时候,一直报错。 image.png 问题解答 因为门户上只是显示导入失败,没有任何错误消息说明。根据常理推断,Redis 的RDB文件格式都具有一致性。居…...
git “分离头指针”(detached HEAD) 状态。
在 Git 中,当你运行 git branch 命令时,看到如下输出: * (detached from 5b596b5)master 其中的: * (detached from 5b596b5) 表示你当前处于 “分离头指针”(detached HEAD) 状态。 🧠 什…...
Gitee的介绍
目录 1.Gitee介绍: 1.1 代码托管 1.2 本土化优势 1.3 企业级服务 1.4 开源生态 1.5 多形态适配 定位:国内开发者首选的高效代码协作平台,兼顾个人开源与企业级私有开发需求。 2.Gitee和GitHub区别 3.Gitee使用教程 4.Gitee相关…...
NoUniqueKey问题和Regular join介绍
问题背景 在flink任务中,遇到了 NoUniqueKey Join的情况,导致了数据膨胀,和下游结果与数据库数据不一致问题 那NoUniqueKey Join为什么会导致问题呢,下面是其中一种场景示例: 为什么会出现 NoUniqueKey :…...
TC8:SOMEIP_ETS_027-028
SOMEIP_ETS_027: echoUINT8 目的 检查method方法echoUINT8的参数及其顺序能够被顺利地发送和接收 说白了就是检查UINT8数据类型参数在SOME/IP协议层的序列化与反序列化是否正常。 UINT8相比于测试用例SOMEIP_ETS_021: echoINT8中的SINT8数据类型来说,属于无符号整数,也就是…...
小微企业SaaS ERP管理系统,SpringBoot+Vue+ElementUI+UniAPP
小微企业的SaaS ERP管理系统,ERP系统源码,ERP管理系统源代码 一款适用于小微企业的SaaS ERP管理系统, 采用SpringBootVueElementUIUniAPP技术栈开发,让企业简单上云。 专注于小微企业的应用需求,如企业基本的进销存、询价&#…...
css filter 常用方法函数和应用实例
1. blur() 模糊 filter: blur(半径);参数:模糊半径(像素),值越大越模糊 示例:filter: blur(5px);2. brightness() 亮度 filter: brightness(百分比); 参数:1原始对比度,0全灰,>…...
chrome inspect 调试遇到的问题
1、oppp 手机打开webview 的时候, 报错这个并没有页面 Offline #V8FIG6SGLN75M7FY Pending authentication: please accept debugging session on the device. 解决方法,保持chrome 浏览器在显示的状态 去设置里开启usb 调试再关闭,反复重…...
Kotlin 中 List 和 MutableList 的区别
在 Kotlin 中,List 和 MutableList 是两种不同的集合接口,核心区别在于可变性。 Kotlin 集合框架的重要设计原则:通过接口分离只读(read - only)和可变(mutable)操作,以提高代码的安…...
openssl 生成自签名证书实现接口支持https
1.下载安装openssl Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions 2.配置环境变量 将 openssl 的目录(D:\tools\openssl\bin)添加到 path 中 3.生成自签名证书 找一个存证书的目录打开powershell 3.1 生成私钥 openssl gen…...
React 中集成 Ant Design 组件库:提升开发效率与用户体验
React 中集成 Ant Design 组件库:提升开发效率与用户体验 一、为什么选择 Ant Design 组件库?二、基础引入方式三、按需引入(优化性能)四、Ant Design Charts无缝接入图标前面提到了利用Redux提供全局维护,但如果在开发时再自己手动封装组件,不仅效率不高,可能开发的组件…...
神经网络:节点、隐藏层与非线性学习
神经网络:节点、隐藏层与非线性学习 摘要: 神经网络是机器学习领域中一种强大的工具,能够通过复杂的结构学习数据中的非线性关系。本文从基础的线性模型出发,逐步深入探讨神经网络中节点和隐藏层的作用,以及它们如何…...
vue+tsc+noEmit导致打包报TS类型错误问题及解决方法
项目场景: 提示:这里简述项目相关背景: 当我们新建vue3项目,package.json文件会自动给我添加一些配置选项,这写选项基本没有问题,但是在实际操作过程中,当项目越来越复杂就会出现问题,本文给大家分享vuetscnoEmit导致打包报TS类型错误问题及…...
Ragflow服务器上部署教程
参考官方文档进行整理 克隆相应代码 git clone https://github.com/infiniflow/ragflow.git修改vm.max_map_count sudo sysctl -w vm.max_map_count262144修改 daemon.json文件 {"registry-mirrors": ["https://docker.m.daocloud.io","https://0…...
Ubuntu 系统中解决 Firefox 中文显示乱码的完整指南
Firefox 是一款流行的网络浏览器,但在 Ubuntu 系统中有时会遇到中文显示乱码的问题。本文将为您提供一个全面的解决方案,帮助您轻松解决这个烦人的问题。 问题概述 在 Ubuntu 系统中使用 Firefox 浏览器时,有时会发现中文字符显示为乱码或方块。这通常是由于缺少合适的中文…...
JVM——垃圾回收
垃圾回收 在Java虚拟机(JVM)的自动内存管理中,垃圾回收(Garbage Collection, GC)是其核心组件之一。它负责回收堆内存中不再使用的对象所占用的内存空间,以供新对象的分配使用。下面我们将深入探讨JVM中的…...
【AI News | 20250506】每日AI进展
AI Repos 1、gitsummarize GitSummarize是一个在线工具,用户只需将GitHub URL中的“hub”替换为“summarize”,即可为任何公开或私有代码库生成交互式文档。该工具利用Gemini分析代码结构,自动生成系统级架构概述、目录和文件摘要、自然语言…...
LabVIEW高冲击加速度校准系统
在国防科技领域,高 g 值加速度传感器广泛应用于先进兵器研制,如深侵彻系统、精确打击弹药及钻地弹药等。其性能指标直接影响研究结果的准确性与可靠性,因此对该传感器进行定期校准意义重大。高冲击加速度校准系统具备多方面功能,适…...
优化算法 - intro
优化问题 一般形式 minimize f ( x ) f(\mathbf{x}) f(x) subject to x ∈ C \mathbf{x} \in C x∈C 目标函数 f : R n → R f: \mathbb{R}^n \rightarrow \mathbb{R} f:Rn→R限制集合例子 C { x ∣ h 1 ( x ) 0 , . . . , h m ( x ) 0 , g 1 ( x ) ≤ 0 , . . . , g r …...
从PotPlayer到专业播放器—基于 RTSP|RTMP播放器功能、架构、工程能力的全面对比分析
从PotPlayer到专业播放器SDK:工程项目怎么选择合适的播放方案? ——基于 RTSP、RTMP 播放器功能、架构、工程能力的全面对比分析 在许多音视频项目早期,我们都听过这句话: “本地测试就用 PotPlayer 播吧,能播就行了…...
EasyRTC嵌入式音视频通信SDK技术,助力工业制造多场景实时监控与音视频通信
一、背景 在数字化时代,实时监控广泛应用于安防、工业、交通等领域。但传统监控系统实时性、交互性欠佳,难以满足需求。EasyRTC作为先进实时通信技术,具有低延迟、高可靠、跨平台特性,能有效升级监控系统。融入EasyRTC后…...
MPay码支付系统第四方聚合收款码多款支付插件个人免签支付源码TP8框架全开源
一、源码描述 这是一套码支付源码(MPay),基于TP8框架,前端layui2.9后端PearAdmin,专注于个人免签收款,通过个人的普通收款码,即可实现收款通知自动回调,支持绝大多数商城系统&#…...