2025年01月09日德美医疗前端面试

news2025/5/14 3:07:45

目录

  1. vue2 的双向绑定的原理
  2. vue3 的双向绑定原理
  3. vue 的生命周期
  4. vue 子组件为何不能修改父组件的值
  5. js delete 删除数组的某一个值会怎么样
  6. vue 和 react 的 diff 算法
  7. 什么是闭包
  8. 原型链
  9. 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.definePropertyobjkey 属性进行定义。
  • 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);
    }
  }
}

在这个类中:

  • Watcherget 方法会将自己设置为 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 方法中,当数据发生变化时通知订阅者列表中的 WatcherWatcher 是一个订阅者,负责更新视图,它会在实例化时将自己添加到数据的订阅者列表中,并在更新时调用回调函数更新视图。Dep 类是一个发布者,负责维护订阅者列表并通知订阅者更新。当 Vue 实例化时,会对 data 中的数据进行劫持,在模板编译时,会创建 Watcher 进行依赖收集,从而实现数据和视图的双向绑定。这样,当数据变化时,视图会更新;当用户操作视图(如通过 v-model)时,会触发数据的更新,形成双向绑定的效果。”

通过这样的解释,可以向面试官展示你对 Vue 2 双向绑定原理的深入理解,包括数据劫持、依赖收集、发布订阅模式的使用以及整体的工作流程。

2. vue3 的双向绑定原理

Vue 3 的双向绑定机制相比 Vue 2 有了显著改进,主要通过 组合式 APIProxy 实现。以下是完整解析:

一、核心机制演进
特性Vue 2Vue 3
响应式基础Object.definePropertyProxy
检测范围对象属性完整对象
数组检测需特殊方法原生支持
性能递归转换属性惰性代理
二、响应式系统核心实现
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 = newVal
      trigger(refObject, 'value')
    }
  }
  return refObject
}
三、依赖收集与触发流程
  1. 依赖收集阶段

    组件渲染
    读取响应式数据
    触发getter
    将当前effect存入dep
  2. 触发更新阶段

    数据变更
    触发setter
    从dep取出effect
    执行effect重新渲染
四、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)
}
五、性能优化策略
  1. 编译时优化

    • 静态节点提升(Hoist Static)
    • 补丁标志(Patch Flags)
    • 树结构拍平(Tree Flattening)
  2. 响应式优化

    • 依赖关系缓存(effect缓存)
    • 批量异步更新(nextTick合并)
  3. 源码结构优化

    // 惰性代理示例
    function shallowReactive(obj) {
      const proxy = new Proxy(obj, handlers)
      // 不立即递归代理嵌套对象
      return proxy 
    }
    
六、与 Vue 2 的对比升级
  1. 数组处理改进

    // Vue 2 需要特殊处理
    this.$set(this.items, index, newValue)
    
    // Vue 3 直接操作
    state.items[index] = newValue // 自动触发更新
    
  2. 动态属性检测

    // Vue 2 无法检测新增属性
    this.$set(this.obj, 'newProp', value)
    
    // Vue 3 自动检测
    state.newProp = value // 自动响应
    
七、开发注意事项
  1. 响应式丢失场景

    // 解构会导致响应式丢失
    const { x, y } = reactive({ x: 1, y: 2 })
    
    // 正确做法
    const pos = reactive({ x: 1, y: 2 })
    const { x, y } = toRefs(pos)
    
  2. 性能敏感操作

    // 大数据量使用shallowRef/shallowReactive
    const bigList = shallowRef([])
    
    // 非响应式数据使用markRaw
    const foo = markRaw({ complex: object })
    

Vue 3 的双向绑定通过 Proxy 实现了更精细的依赖跟踪,配合编译时优化,在保证开发体验的同时提供了更好的运行时性能。理解其原理有助于编写更高效的 Vue 代码。

3. vue 的生命周期

一、Vue 2 和 Vue 3 生命周期对比
生命周期钩子对照表
Vue 2 选项式APIVue 3 组合式API触发时机描述
beforeCreate无直接对应实例初始化前,data/methods未初始化
createdsetup()实例创建完成,data/methods可用
beforeMountonBeforeMount挂载开始前,DOM尚未生成
mountedonMounted挂载完成,DOM已生成
beforeUpdateonBeforeUpdate数据变化导致DOM更新前
updatedonUpdated数据变化导致DOM更新后
beforeDestroyonBeforeUnmount实例销毁前(vue3改名更准确)
destroyedonUnmounted实例销毁后(vue3改名更准确)
activatedonActivatedkeep-alive组件激活时
deactivatedonDeactivatedkeep-alive组件停用时
errorCapturedonErrorCaptured捕获子孙组件错误时
二、生命周期完整流程图示
初始化事件和生命周期
beforeCreate
初始化注入和响应式
created
编译模板/生成render函数
beforeMount
创建VDOM并渲染
mounted
数据变化?
beforeUpdate
重新渲染VDOM和DOM
updated
是否调用销毁方法?
beforeUnmount
移除DOM和事件监听
unmounted
三、组合式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 // 阻止错误继续向上传播
})
六、最佳实践建议
  1. 异步请求

    • 初始化数据:created/setup + async/await
    • 避免在mounted中请求,会延迟首次渲染
  2. DOM操作

    • 必须在mounted及之后生命周期进行
    • 使用nextTick确保DOM更新完成
  3. 内存管理

    onUnmounted(() => {
      clearInterval(timer)
      eventBus.off('eventName', handler)
      chartInstance.dispose()
    })
    
  4. 性能优化

    • 避免在updated中进行复杂计算
    • 大量数据更新考虑使用虚拟滚动

理解Vue生命周期有助于在正确时机执行代码,避免常见错误,并优化应用性能。Vue 3的组合式API提供了更灵活的生命周期管理方式。

4. vue 子组件为何不能修改父组件的值

一、核心原因:单向数据流原则

Vue 强制实施单向数据流(One-Way Data Flow)设计模式,这是现代前端框架的通用规范:

  1. 数据所有权明确:父组件拥有数据,子组件只有使用权
  2. 可预测性:数据变更源头唯一,便于追踪状态变化
  3. 维护性:避免多组件同时修改导致的混乱
二、直接修改的危害

如果允许子组件直接修改父组件值:

问题类型具体表现
状态混乱多个子组件同时修改同一状态,难以确定最终值
调试困难数据变更来源不明确,错误难以追踪
组件耦合子组件必须了解父组件内部实现,破坏组件独立性
性能优化障碍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 通过以下机制阻止直接修改:

  1. 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`)
        }
      }
    })
    
  2. 开发环境警告:在非生产环境下,Vue 会检测并警告 props 的直接修改

六、最佳实践建议
  1. 严格遵循单向数据流

    • 父级通过 props 向下传递数据
    • 子级通过事件向上通知变更意图
  2. 复杂场景处理方案

    场景解决方案
    需要修改父级对象属性触发事件让父级自己修改
    多层级组件通信使用provide/inject
    全局状态使用Vuex/Pinia
    临时本地修改使用computed或ref拷贝prop值
  3. 代码规范检查

    // 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;
五、特殊注意事项
  1. 类型化数组

    const typedArray = new Uint8Array([1, 2, 3]);
    delete typedArray[1]; // 会将索引1位置设为0
    
  2. 性能考虑

    • delete 操作比 splice 快(不涉及元素移动)
    • 但后续操作空位数组可能更慢
  3. Vue/React 响应式

    • 在响应式框架中,delete 可能不会触发视图更新
    • 应使用框架提供的删除方法(如 Vue 的 $delete
六、底层原理

delete 操作符实际执行的是:

  1. 将指定属性的 [[Configurable]] 特性设为 true
  2. 删除该属性
  3. 返回 true(即使属性不存在)

对于数组:

  • 数组是特殊对象,索引是属性名
  • delete arr[i] 等同于删除对象的属性

理解 delete 对数组的这种特殊行为,有助于避免在需要真正移除元素时错误使用它。大多数情况下,splicefilter 才是更合适的选择。

6. vue 和 react 的 diff 算法

Vue和React作为流行的前端框架,都使用虚拟DOM(Virtual DOM)来提升渲染性能,而Diff算法是虚拟DOM的核心,它能找出新旧虚拟DOM之间的差异,从而只更新需要更新的真实DOM部分。下面为你分别介绍它们的Diff算法。

Vue的Diff算法

Vue的Diff算法采用了双指针和key的策略,通过比较新旧虚拟节点的差异,最小化DOM操作。具体步骤如下:

  1. 同级比较:只对同一层级的节点进行比较。
  2. 节点类型比较:若节点类型不同,直接替换。
  3. key比较:若有key,使用key进行更高效的比较和复用。
  4. 双指针遍历:使用首尾双指针遍历新旧节点列表。
React的Diff算法

React的Diff算法基于几个启发式策略,旨在减少比较次数,提高性能。具体步骤如下:

  1. 同级比较:和Vue一样,只比较同一层级的节点。
  2. 节点类型比较:节点类型不同时,直接替换。
  3. key比较:使用key来标识列表中的元素,便于复用和移动。
  4. 列表比较:采用双循环遍历新旧节点列表。
代码示例

以下是一个简单的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 中的闭包。

定义

闭包是指有权访问另一个函数作用域中变量的函数。简单来说,即使外部函数执行完毕,其作用域内的变量也不会被销毁,而是会被闭包“捕获”并保留,使得这些变量能在外部函数之外被访问和修改。

形成条件

闭包的形成需要满足以下两个关键条件:

  1. 函数嵌套:必须存在一个外部函数和至少一个内部函数。
  2. 内部函数引用外部函数的变量:内部函数使用了外部函数作用域内的变量。
作用

闭包在 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

在这个例子中,person1Person 构造函数的实例,它的 __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 指向全局对象(在严格模式下,thisundefined)。

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

callapplybind 方法对 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 的指向。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2373708.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快速理解动态代理

什么是动态代理(Java核心技术卷1的解释) 动态代理是一种运行时生成代理对象的技术&#xff0c;其本质是通过字节码增强在不修改原始类代码的前提下&#xff0c;动态拦截并扩展目标对象的行为。它通过代理对象对原始方法的调用进行拦截&#xff0c;并在方法执行前后注入自定义逻…

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 种内存管理方式&#xff1f; heap_1.c&#xff1a;这种方式简单地在编译时分配一块固定大小的内存&#xff0c;在整个运行期间不会进行内存的动态分配和释放。它适用于那些对内存使用需求非常明确且固定&#xff0c;不需要动态分配内存的场景&#xff0c;优点是…

架构进阶:74页数据架构设计总体规划方案【附全文阅读】

本文讨论了数据资源规划在信息化战略规划中的重要性&#xff0c;详细阐述了数据资源规划的方法与过程&#xff0c;包括系统架构、业务能力模型、数据架构等。 文章指出&#xff0c;数据资源规划需要梳理企业级数据模型&#xff0c;明确数据分布和流向&#xff0c;建立统一的数据…

情书大全v3.0.1

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

基于OpenCV的人脸识别:LBPH算法

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

鸿蒙 使用动画 简单使用

鸿蒙 使用动画 简单使用 动画就两个&#xff0c;属性动画和转场动画 属性动画只是组件的属性发生变化&#xff0c;而转场动画是指对将要出现或消失的组件做动画&#xff0c;而文档的其他动画只是给这两个动画效果锦上添花罢了 这篇文章简单介绍这两个动画&#xff0c;其他的…

arcgis和ENVI中如何将数据输出为tif

一、arcgis中转换为tif 右键图层&#xff1a; Data -> Export Data, 按照图示进行选择&#xff0c;选择tiff格式导出即可&#xff0c;还可以选择其他类型的格式&#xff0c;比如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. 核心特性 &#x1f…

计算机网络 4-2-1 网络层(IPv4)

2 IPv4分组 各协议之间的关系 IP协议(Internet Protocol, 网际协议)是互联网的核心&#xff01; ARP协议用于查询同一网络中的<主机IP地址&#xff0c;MAC地址>之间的映射关系 ICMP协议用于网络层实体之间相互通知“异常事件” IGMP协议用于实现IP组播 2.1 结构<首…

Python----机器学习(模型评估:准确率、损失函数值、精确度、召回率、F1分数、混淆矩阵、ROC曲线和AUC值、Top-k精度)

一、模型评估 1. 准确率&#xff08;Accuracy&#xff09;&#xff1a;这是最基本的评估指标之一&#xff0c;表示模型在测试集上正确 分类样本的比例。对于分类任务而言&#xff0c;准确率是衡量模型性能的直观标准。 2. 损失函数值&#xff08;Loss&#xff09;&#xff1…

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 参考文献 写在前面 作为运维或者研发&#xff0c;日…

大数据产品销售数据分析:基于Python机器学习产品销售数据爬虫可视化分析预测系统设计与实现

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

VS2022 Qt配置Qxlsx

目录 1、下载QXlsx&#xff0c;并解压文件夹 ​编辑2、打开VS2022配置QXlsx 3、VS配置Qxslx库 方法一&#xff1a;常规方法 方法二&#xff1a;直接使用源码 方法三&#xff1a;将QXlsx添加到Qt安装目录&#xff08;暂时尝试未成功&#xff09; 1、下载QXlsx&#xff0c;…

OSPF案例

拓扑图&#xff1a; 要求&#xff1a; 1&#xff0c;R5为ISP&#xff0c;其上只能配置IP地址&#xff1b;R4作为企业边界路由器&#xff0c; 出口公网地址需要通过PPP协议获取&#xff0c;并进行chap认证 2&#xff0c;整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;…

《用MATLAB玩转游戏开发》贪吃蛇的百变玩法:从命令行到AI对战

《用MATLAB玩转游戏开发&#xff1a;从零开始打造你的数字乐园》基础篇&#xff08;2D图形交互&#xff09;-&#x1f40d; 贪吃蛇的百变玩法&#xff1a;从命令行到AI对战 &#x1f3ae; 欢迎来到这篇MATLAB贪吃蛇编程全攻略&#xff01;本文将带你从零开始&#xff0c;一步步…

【数据结构与算法】图的基本概念与遍历

目录 一、图的基本概念 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 代码…

Linux云服务器配置git开发环境

文章目录 1. 安装 git2. git clone3. git add .4. git commit -m 提交记录5. git push&#x1f351; 异常原因&#x1f351; 解决办法 6. git pull7. git log8. git rm9. git mv10. git status 1. 安装 git sudo yum install git -y2. git clone 此命令的作用是从远程仓库把代…

手机浏览器IP归属地查询全指南:方法与常见问题解答

在当今数字化时代&#xff0c;手机浏览器已成为人们日常生活中不可或缺的工具之一。然而&#xff0c;在使用手机浏览器的过程中&#xff0c;有时我们需要了解当前网络连接的IP归属地信息&#xff0c;那么&#xff0c;手机浏览器IP归属地怎么查看呢&#xff1f;本文将详细介绍几…