【study】记录面试常遇的问题

82 阅读10分钟
1. 原型链
2. 闭包
3. promise
  • promise是什么
  • 补全代码(手写)
  • promise
4. Set
5. Map
6. Map结构和对象object有什么区别
  • 键的类型
// Map 允许任何类型的键,包括对象、函数、原始类型(数值、字符串、布尔值等)。

const map = new Map();
const objKey = {};
const funcKey = function() {};

map.set(objKey, 'value associated with objKey');
map.set(funcKey, 'value associated with funcKey');
map.set(123, 'value associated with number 123');

console.log(map.get(objKey)); // 输出: 'value associated with objKey'
console.log(map.get(funcKey)); // 输出: 'value associated with funcKey'
console.log(map.get(123)); // 输出: 'value associated with number 123'


// Object: 对象的键名必须是字符串(或能转换为字符串的值)或符号。非字符串类型的键会被强制转换为字符串。
const obj = {};
const numKey = 123;
const objKey = {};
const funcKey = function() {};

obj[numKey] = 'value associated with number 123';
obj[objKey] = 'value associated with objKey';
obj[funcKey] = 'value associated with funcKey';

console.log(obj['123']); // 输出: 'value associated with number 123'
console.log(obj['[object Object]']); // 输出: 'value associated with funcKey'
console.log(obj['function() {}']); // 输出: undefined (结果可能会根据函数的字符串化不同)
  • 保留的顺序
// Map 保留键值对的插入顺序,可以按顺序迭代。
const map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);

for (let [key, value] of map) {
  console.log(key, value);
}
// 输出: a 1, b 2, c 3


// Object: 对象属性的顺序主要基于它们的插入顺序,但整数键会优先排序。一般来说,不要依赖对象属性的顺序。

const obj = { a: 1, b: 2, c: 3 };

for (let key in obj) {
  console.log(key, obj[key]);
}
// 输出: a 1, b 2, c 3(但不保证所有 JavaScript 引擎都保持这一顺序)

// 遍历一个对象的属性时,它们通常会保持插入的顺序,但这并不是由规范严格保证的。尤其是对于整数键,它们可能优先于字符串键,以排序顺序的形式出现

const obj = { a: 1, 1: 'one', b: 2, 3: 'three', c: 3 };

for (let key in obj) {
  console.log(key, obj[key]);
}
// 输出顺序通常是:1 'one', 3 'three', 'a' 1, 'b' 2, 'c' 3  // 整数键优先于字符串
// 而不是:'a' 1, '1' 'one', 'b' 2, '3' 'three', 'c' 3

  • 迭代
// Map 提供专用的迭代器,可以轻松迭代键、值或键值对。

const map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);

for (let key of map.keys()) {
  console.log(key);
}
// 输出: a, b, c

for (let value of map.values()) {
  console.log(value);
}
// 输出: 1, 2, 3

for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// 输出: a 1, b 2, c 3


// object没有内置的迭代器。通常需要使用 `for...in` 或 `Object.keys`、`Object.values`、`Object.entries`

const obj = { a: 1, b: 2, c: 3 };

// 使用 for...in 迭代键
for (let key in obj) {
  console.log(key);
}
// 输出: a, b, c(不保证顺序)

// 使用 Object.keys 迭代键
Object.keys(obj).forEach(key => {
  console.log(key);
});
// 输出: a, b, c

// 使用 Object.values 迭代值
Object.values(obj).forEach(value => {
  console.log(value);
});
// 输出: 1, 2, 3

// 使用 Object.entries 迭代键值对
Object.entries(obj).forEach(([key, value]) => {
  console.log(key, value);
});
// 输出: a 1, b 2, c 3

  • 性能
// Map 专为频繁增加和删除键值对的操作优化,通常在这些操作中性能优于对象。

// object在大部分情况下性能也很好,但在涉及频繁的键值对增加和删除时可能不如 Map 高效。

  • 方法和属性
// Map 提供一系列专用方法,如 `set`、`get`、`has`、`delete` 和 `clear`
// Map 可以使用 `size` 属性获取键值对的数量

const map = new Map();
map.set('a', 1);
map.set('b', 2);

console.log(map.get('a'));  // 输出: 1
console.log(map.has('b'));  // 输出: true
console.log(map.size);      // 输出: 2

map.delete('a');
console.log(map.get('a'));  // 输出: undefined

map.clear();
console.log(map.size);      // 输出: 0


// object 没有像 Map 那样的专用方法。对操作进行键值对的增加、删除、检查等操作,需要使用运算符和手动实现
const obj = { a: 1, b: 2 };

console.log(obj['a']);      // 输出: 1
console.log('b' in obj);    // 输出: true

delete obj['a'];
console.log(obj['a']);      // 输出: undefined

console.log(Object.keys(obj).length); // 输出: 1

// 清空对象属性
Object.keys(obj).forEach(key => delete obj[key]);
console.log(Object.keys(obj).length); // 输出: 0

image.png

  • 何时使用 Map 或 Object
-   使用 Map

    -   当你需要存储键值对并需要键是非字符串类型(如对象或函数)。
    -   当你需要频繁添加和删除键值对以确保操作性能。
    -   当你需要保留键值对的插入顺序。

-   使用 Object

    -   当你的键是字符串或符号,并且你更习惯对象的字面量语法。
    -   当你具有简单的键值对并且没有特殊的性能或顺序需求。
7. computed和watch区别
// computed概念 :是一种基于响应式依赖缓存的属性,只有当依赖的数据发生变化时才会重新计算其值。

// computed适用场景: 用于根据现有的数据计算出其他数据,且这些计算过程需要缓存以优化性能。

// computed缓存:计算属性的值会被缓存,直到它的依赖数据发生变化。也就是说,如果计算属性依赖的数据没有变化,多次访问计算属性的值会直接返回缓存中的值,不会重新计算。

// computed使用场景:适用于较重计算且需要缓存结果的场景。
// watch概念:是一种在数据变化时执行特定操作的方法。

// watch使用场景: 用于在数据变化时执行异步操作或执行带有副作用的操作(如 Ajax 请求、修改 DOM)。

// watch无缓存:侦听器的回调函数不会缓存,每次监听的数据变化都会执行回调函数。

// watch使用场景:适用于需要在数据变化时执行复杂逻辑或异步操作的场景。

image.png

8. computed和methods区别
9. localStorage,sessionStorage,cookie区别
10. vuex存储数据,强制刷新页面,数据会丢失,怎么处理?
  • 保存在localstorage中
// 创建 Vuex Store:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    data: null,
  },
  mutations: {
    setData(state, payload) {
      state.data = payload;
      // 保存状态到localStorage
      localStorage.setItem('vuexState', JSON.stringify(state));
    },
  },
  actions: {
    loadData({ commit }) {
      const savedState = localStorage.getItem('vuexState');
      if (savedState) {
        commit('setData', JSON.parse(savedState).data);
      }
    },
  },
});

export default store;

// 加载数据:
// main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store';

Vue.config.productionTip = false;

new Vue({
  store,
  created() {
    this.$store.dispatch('loadData');
  },
  render: h => h(App),
}).$mount('#app');

// 使用数据:
// ExampleComponent.vue
<template>
  <div>
    <p>Data: {{ data }}</p>
    <button @click="setData">Set Data</button>
  </div>
</template>

<script>
export default {
  computed: {
    data() {
      return this.$store.state.data;
    },
  },
  methods: {
    setData() {
      this.$store.commit('setData', 'new data');
    },
  },
};
</script>

  • 使用 vuex-persistedstate 插件
// 插件,可以更方便地实现 Vuex 状态的持久化。它会自动将 Vuex 状态存储到 `localStorage` 或 `sessionStorage`,并在页面刷新时恢复状态。
// 安装: npm install vuex-persistedstate

// 引入
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    data: null,
  },
  mutations: {
    setData(state, payload) {
      state.data = payload;
    },
  },
  plugins: [createPersistedState()],
});

export default store;

// 使用数据
// ExampleComponent.vue
<template>
  <div>
    <p>Data: {{ data }}</p>
    <button @click="setData">Set Data</button>
  </div>
</template>

<script>
export default {
  computed: {
    data() {
      return this.$store.state.data;
    },
  },
  methods: {
    setData() {
      this.$store.commit('setData', 'new data');
    },
  },
};
</script>

  • vuex-persist
// 安装 npm install vuex-persist

//引入
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import VuexPersist from 'vuex-persist';

Vue.use(Vuex);

const vuexLocalStorage = new VuexPersist({
  key: 'my-app',
  storage: window.localStorage,
});

const store = new Vuex.Store({
  state: {
    data: null,
  },
  mutations: {
    setData(state, payload) {
      state.data = payload;
    },
  },
  plugins: [vuexLocalStorage.plugin],
});

export default store;


// 使用
// ExampleComponent.vue
<template>
  <div>
    <p>Data: {{ data }}</p>
    <button @click="setData">Set Data</button>
  </div>
</template>

<script>
export default {
  computed: {
    data() {
      return this.$store.state.data;
    },
  },
  methods: {
    setData() {
      this.$store.commit('setData', 'new data');
    },
  },
};
</script>

image.png

11. 1-100生成,不使用for和while
12. 事件循环机制
  • 执行顺序
  • 宏任务除了setTimeout,setInterval还有什么
  • promise是什么任务?
  • Promise 构造函数中的代码:这是同步执行的。

  • promise.then是什么任务?
  • Promise 的回调(thencatchfinally :这是通过微任务(Microtasks)机制异步执行的

13. vue2和vue3区别

image.png

  • vue2和vue3的区别
  • Vue在初始化数据时,会使用Object.defineProperty重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的watcher)如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。

  • Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

关键差异

  • 数据劫持机制

    • Vue 2 使用 Object.defineProperty,这只能劫持对象的已有属性,无法拦截其他操作(例如对数组的操作)。
    • Vue 3 使用 Proxy,可以动态拦截和管理几乎所有的操作,具有更高的灵活性和性能。
  • 依赖追踪和更新机制

    • Vue 2 采用传统的发布-订阅模式,将依赖手动添加到订阅者列表中,然后在变化时通知订阅者。
    • Vue 3 通过 effect 和响应式依赖追踪系统自动管理依赖关系,使得系统更加高效和智能。
  • 性能

    • Vue 3 基于 Proxy 的实现减少了很多 Vue 2 中的性能瓶颈,提供了更高的性能和更好的内存管理。
  • 总结

  • Vue 2:主要基于发布-订阅模式,使用 Object.defineProperty 进行数据劫持。

  • Vue 3:引入了基于 Proxy 的响应式系统,并采用更为智能和高效的依赖追踪机制。

14. vue2的生命周期
15. 前端如何处理小数进行计算(精度问题)
  • 在前端开发中,处理小数进行计算时常常会遇到精度问题。这通常是由于 JavaScript 使用的数值类型是基于 IEEE 754 标准的双精度浮点数,这种表示法有时会导致精度丢失。
// javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果


// 1
function test(arg1, arg2) {
    let m = 0;
    const s1 = arg1.toFixed(2).toString();
    const s2 = arg2.toFixed(2).toString();
    try {
        m += s1.split('.')[1].length;
    } catch (e) {
        console.log(e);
    }
    try {
        m += s2.split('.')[1].length;
    } catch (e) {
        console.log(e);
    }
    return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m);
}

//2

function accurateMultiplication(arg1, arg2) {
  // 将浮点数转换为字符串
  const str1 = arg1.toString();
  const str2 = arg2.toString();

  // 计算两个数的小数位数
  const decimalPlaces1 = (str1.split('.')[1] || '').length;
  const decimalPlaces2 = (str2.split('.')[1] || '').length;

  // 总小数位数
  const totalDecimalPlaces = decimalPlaces1 + decimalPlaces2;

  // 移除小数点并转换为整数
  const int1 = Number(str1.replace('.', ''));
  const int2 = Number(str2.replace('.', ''));

  // 计算结果并还原小数位置
  const result = (int1 * int2) / Math.pow(10, totalDecimalPlaces);
  
  return result;
}


const result = test(0.1, 0.2);
console.log(result); // 输出 0.02

// toFixed可以将数字格式化为指定的小数位数,但要注意它们返回的是字符串,需要在需要进行进一步计算时转换回数值

let num = 0.1 + 0.2;
console.log(num.toFixed(2)); // 输出 "0.30"

// 乘法和除法技巧: 通过将小数转换为整数进行计算,然后再恢复结果,可以避免小数计算的精度问题
let num1 = 0.1;
let num2 = 0.2;

// 将 0.1 和 0.2 转换为整数计算,然后除以 100
let result = (num1 * 10 + num2 * 10) / 10;
console.log(result); // 输出 0.3

  • 库: decimal.js 是一个用于高精度浮点数计算的库,能够进行各种准确的数学运算。
  • 库: big.js 是一个轻量级的高精度库,适用于需要高精度计算的小数问题。
  • 库: bignumber.js 是另一个高精度数值计算库,提供了丰富的 API 以应对各种数学计算场景。
16. vue2视图不更新,怎么处理?
  • vm.$forceUpdate() 是 Vue.js 提供的一个实例方法,可以强制组件重新渲染。一般情况下,不推荐频繁使用这个方法,因为它违反了 Vue 的数据驱动视图原则。不过,在一些特殊情况下或调试过程中,这个方法可以派上用场。
// 例子1
export default {
  data() {
    return {
      user: {
        name: 'John Doe',
        age: 30
      }
    };
  },
  methods: {
    updateName(newName) {
      this.user.name = newName;
      this.$forceUpdate(); // 强制更新视图
    }
  }
};
// 通过 `updateName` 方法更新 `user.name` 属性后调用 `this.$forceUpdate()` 来强制组件重新渲染。
// 例子2
export default {
  data() {
    return {
      nestedObject: {
        level1: {
          level2: {
            value: 'initial'
          }
        }
      }
    };
  },
  methods: {
    updateNestedValue(newValue) {
      this.nestedObject.level1.level2.value = newValue;
      this.$forceUpdate(); // 强制更新视图
    }
  }
};
// 深度嵌套的修改
// 当 Vue 未能正确追踪深度嵌套对象的变更时,可以使用 `$forceUpdate()` 进行强制更新
// 例子3
export default {
  data() {
    return {
      user: {
        name: 'John Doe'
        // age 未初始化
      }
    };
  },
  methods: {
    addUserAge() {
      this.user.age = 30; // 新添加未初始化的属性
      this.$forceUpdate(); // 强制更新视图
    }
  }
};
// 数据未在 data 属性中初始化
// 如果数据未在 `data` 属性中初始化,Vue 不会将其标记为响应式。虽然可以使用 `Vue.set` 或 `this.$set` 添加新属性,但在必要时可以调用 `$forceUpdate()`
// 例子4
methods: {
  debugDataUpdate() {
    // 调试某数据变化
    this.someNonReactiveData = 'some value';
    this.$forceUpdate(); // 强制更新以查看视图变化情况
  }
}

// Debugging 场景
// 在调试 Vue 应用时,可能会发现某些数据变化并没有引起视图的更新。这时使用 `$forceUpdate()` 可以帮助我们快速验证是否是 Vue 的响应性系统的问题
  • 注意事项
  1. 尽量避免频繁使用$forceUpdate 违背了 Vue 的设计初衷,即通过数据的响应式机制自动更新视图。频繁使用可能会掩盖潜在的数据管理问题。
  2. 检查 Vue 响应式机制:在决定使用 $forceUpdate 之前,先考虑使用 Vue.set 或 this.$set 来确保数据的响应式更新。
  3. 性能考虑:使用 $forceUpdate 强制触发重新渲染可能会影响性能,因为它会迫使 Vue 跳过高效的更新路径,直接更新整个组件树。
  • 总结
  1. vm.$forceUpdate() 方法应作为解决最后一线希望来处理视图未自动更新的问题。通常我们应当依赖 Vue 的响应性系统,通过正确修改数据和使用 Vue 提供的工具(如 Vue.set 和 this.$set)来确保视图及时更新。但是在某些复杂或调试场景下,$forceUpdate 仍然是一个有用的工具。希望通过这些详细的示例和使用场景,你可以更好地理解和应用这个方法
17、打印
 function addCount() {
      var count = 0;
      return function () {
        count = count + 1;
        console.log(count);
      };
    }

    var fun1 = addCount();
    var fun2 = addCount();
    fun1();  // 1
    fun2(); // 1

    fun2(); // 2
    fun1(); // 2