likes
comments
collection
share

面试:秋招算法嗝屁,转投实习缓缓(2.5w)

作者站长头像
站长
· 阅读数 79

前言

你经历了秋招,面试官也经历了秋招,在之前暑假前我经历的实习面试基本上是八股文,结果秋招后再面试实习,发现都不问八股了,直接拷打项目,过于离谱,但收获良多

杭州炎魂(200-250 /天)(游戏:忍者必须死3)

1. vue2和vue3的区别

生命周期

1. vue3和vue2的生命周期函数名称不同

常用生命周期对比如下表所示。

vue2vue3
beforeCreate使用 setup()
created使用 setup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

2. 新增生命周期函数

onRenderTracked:当渲染跟踪或依赖项跟踪时被调用。

onRenderTriggered:当渲染时触发其他渲染时,或者当在当前渲染中延迟调度的作业时被调用。

onErrorCaptured:当子组件抛出未处理的错误时被调用。这些新的生命周期函数可以帮助我们更好地调试、优化组件,提升应用的性能。

3.Composition API:Vue3中通过使用onMounted,onBeforeUnmount等函数,按需引入自己需要使用的生命周期函数。而vue2是OptionsAPI,只是在data中进行声明。

数据更新

1.Proxy 替代 Object.defineProperty:在Vue2中,使用Object.defineProperty来拦截数据的变化,但是该方法存在一些缺陷,比如不能监听新增的属性和数组变化等。Vue3中使用了ES6中的Proxy来拦截数据的变化,能够完全监听数据变化,并且能够监听新增的属性。

2.批量更新:Vue2中,在数据变化时,会立即触发虚拟DOM的重渲染,如果在一个事件循环中连续修改多个数据,可能会造成性能问题。而Vue3中,使用了更高效的批量更新策略,会在下一个事件循环中统一处理数据变化,提高了性能。

3.Composition API:Vue3中引入了Composition API,可以更好地组织代码逻辑,也可以更好地处理数据更新。通过使用setup函数和ref、reactive等函数,能够更方便地对数据进行监听和修改。而vue2是OptionsAPI,只是在data中进行声明。

其他

1.v-if和v-for的优先级不同

v-for 的优先级比 v-if 更高(Vue2.x)

v-if 总是优先于 v-for 生效(Vue3.x)

2. vue中输入框双向绑定的两条数据流动讲一下

v-bind:value将组件的data属性与输入元素的value属性进行绑定,使得数据可以从组件流向输入元素。而v-on:input监听输入元素的input事件,当输入元素的值发生变化时,通过$event对象获取新的值,并将其赋值给组件的data属性,然后data属性再改变组件的值,从而实现了数据的双向绑定。

3. vue中循环里的key如果不写的话会有什么问题?

  1. 性能问题: Vue可能会在每次更新时重新渲染整个列表,而不是只渲染发生变化的部分。这可能导致性能下降,尤其是在大型列表上。

  2. 状态丢失: 如果你在循环中使用组件,并且组件内有一些状态(比如输入框中的文本),没有key可能会导致状态丢失或混乱,因为Vue无法正确追踪每个组件的状态。

  3. DOM状态保存问题: 如果列表项在没有唯一key的情况下被重新排序或删除,Vue可能会在DOM中保留先前的状态,这可能导致显示不正确的数据。

  4. Vue.js警告: 如果你在循环中没有提供key,Vue.js会生成一个警告,提醒你可能会遇到性能问题。这是Vue.js为了帮助开发者避免潜在的问题而提供的提示。

4. js里有哪些基本类型?

5. 判断类型的话有哪些方法?

JS中的数据类型检测方案

1.typeof

console.log(typeof 1);               // number
console.log(typeof true);            // boolean
console.log(typeof 'mc');            // string
console.log(typeof Symbol)           // function
console.log(typeof Symbol());        // symbol
console.log(typeof function(){});    // function
console.log(typeof console.log());   // undefined
console.log(typeof []);              // object 
console.log(typeof {});              // object
console.log(typeof null);            // object
console.log(typeof undefined);       // undefined

优点:能够快速区分基本数据类型

缺点:不能将Object、Array和Null区分,都返回object

2.instanceof

console.log(1 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true

优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象

缺点:Number,Boolean,String基本数据类型不能判断

大致原理:instanceof 运算符的原理可以简单概括为沿着对象的原型链向上查找,判断是否存在指定构造函数的 prototype 属性

比如:

1 instanceof Number

会沿着1的原型链中查找是否有Number的prototype是属性,从而返回true或者false

3.Object.prototype.toString.call()

var toString = Object.prototype.toString;
console.log(toString.call(1));                      //[object Number]
console.log(toString.call(true));                   //[object Boolean]
console.log(toString.call('mc'));                   //[object String]
console.log(toString.call([]));                     //[object Array]
console.log(toString.call({}));                     //[object Object]
console.log(toString.call(function(){}));           //[object Function]
console.log(toString.call(undefined));              //[object Undefined]
console.log(toString.call(null));                   //[object Null]

优点:精准判断数据类型

缺点:写法繁琐不容易记,推荐进行封装后使用

6. 改变this指向的方法有哪些方法呢?

  1. 使用bind方法:

    function myFunction() {
      // 函数体
    }
    
    var boundFunction = myFunction.bind(newThis);
    boundFunction(); // this 现在指向 newThis
    
  2. 使用箭头函数: 箭头函数不会创建自己的this,而是继承其父作用域的this

    var myFunction = () => {
      // 函数体
    };
    
    myFunction(); // this 将继承自外部作用域
    
  3. 使用callapply方法: 这两个方法立即调用函数,并允许你传递一个指定的this值。

    function myFunction() {
      // 函数体
    }
    
    myFunction.call(newThis);
    // 或者
    myFunction.apply(newThis);
    
  4. 使用闭包: 通过在一个函数内部创建另一个函数,可以捕获外部函数的this,并将其传递给内部函数。

    function outerFunction() {
      var self = this;
    
      function innerFunction() {
        // 在这里使用 self
      }
    
      innerFunction();
    }
    
  5. 使用ES6的解构赋值: 在某些情况下,可以使用解构赋值来改变this的指向。

    function myFunction() {
      // 函数体
    }
    
    var { x, y } = { x: myFunction }.x;
    x(); // this 现在指向 myFunction
    

7. bind的话不是通常会返回一个新的函数吗?假设我们多次bind的话,最后一个this会指向哪?

在JavaScript中,bind方法的绑定是不可逆的。一旦使用bind方法将一个特定的this值绑定到函数上,就无法再次更改这个绑定。

例如:

function myFunction() {
  console.log(this);
}

var obj1 = { name: 'Object 1' };
var obj2 = { name: 'Object 2' };

var boundFunction = myFunction.bind(obj1);
boundFunction(); // this 指向 obj1

var doubleBoundFunction = boundFunction.bind(obj2);
doubleBoundFunction(); // this 指向 obj1

详细解释

  1. 新函数的创建: 每次调用bind都会创建一个新的函数。这个新函数与原始函数相似,但它有一个特定的this值。例如:

    var originalFunction = function() {
      console.log(this);
    };
    
    var boundFunction = originalFunction.bind(obj1);
    

    这时boundFunction是一个新的函数,与originalFunction不同。

  2. 不可逆的特性: 一旦使用bindthis值绑定到新函数上,这个绑定就是不可逆的。即使你再次调用bind,也只会创建一个新函数,而不会更改已存在函数的this值。例如:

    var doubleBoundFunction = boundFunction.bind(obj2);
    

    这时,doubleBoundFunction是另一个新的函数,它与boundFunction不同,而且boundFunctionthis值仍然是obj1

bind源码

Function.prototype.my_bind = function (context, ...args1) {
    // this? foo
    // 闭包空间中 自由变量 foo args1
    const that = this // 保存了未来要执行的函数
    return function (...args2) {
        // this window
        return that.call(context, ...args1, ...args2)
    }
}
Function.prototype.my_bind = function (context, ...args1) {
    // this? foo
    // 闭包空间中 自由变量 foo args1
    const that = this // 保存了未来要执行的函数
    console.log(context,this,'111')
    return function () {
        // this window
        console.log(context,that,'222')
        return that.call(context)
    }
}
let name = 'windowName'
let bar = {
    name: 'barName'
}
let test = {
    name: '123'
}

function foo() {
    console.log(this.name)
}
let afterBindFoo = foo.my_bind(bar).my_bind(test)
// 其实就是等于【1】
// test.function() {
//     return foo.call(bar)
// }
afterBindFoo()

【1】也就是说,说到底还是看谁调用foo函数,第一次绑定bind的时候已经确认好是bar来调用foo,然后再次bind后只是返回了一个新的另一个函数,和foo没任何关系,也就是test调用的是另一个新的函数。

这种设计有助于确保在函数的生命周期内,this值保持一致。如果bind是可逆的,可能导致不稳定的行为和混淆,因为函数的this值会在运行时动态变化。通过保持bind的不可逆性,JavaScript确保了代码的可预测性和一致性。

8. 箭头函数中的this是哪里来的呢?它是本身有this吗?保留上下文是什么意思?

在箭头函数中,this的值是在函数定义时确定的,而不是在函数调用时。

箭头函数没有自己的this,它会捕获其所在上下文的this值。这就是为什么箭头函数通常被称为"词法作用域"中的函数,因为它们使用词法作用域(也就是定义函数的地方的作用域)中的this,而不是动态作用域。

考虑以下例子:

function regularFunction() {
  console.log(this);
}

const arrowFunction = () => {
  console.log(this);
};

const obj = { prop: 'I am an object' };

regularFunction.call(obj); // this 指向 obj
arrowFunction.call(obj);   // this 指向箭头函数定义时的上下文,可能是全局对象或 undefined(在严格模式下)

在上面的例子中,regularFunction使用普通函数语法,而arrowFunction是一个箭头函数。当我们使用call来改变函数的this时,regularFunctionthis会被正确设置为obj,而arrowFunctionthis会保留其定义时的上下文,可能是全局对象或undefined(在严格模式下)。

"保留上下文"的意思是箭头函数捕获了它所在的上下文(通常是定义时的函数或全局上下文)中的this值。这种特性使得箭头函数在回调函数或嵌套函数中更容易使用,因为它们不会改变this的值。

9. promise里面发生了错误的话,一般是用catch来捕捉的,如果用try catch的话,能不能捕捉?

在 Promise 中,如果发生错误,你可以使用 .catch() 方法来捕获错误。Promise 是异步的,所以使用传统的 try...catch 语法不能捕获 Promise 中的错误,因为 try...catch 语法只能捕获同步代码块中的错误。

例如:

somePromiseFunction()
  .then(result => {
    // 处理成功的情况
  })
  .catch(error => {
    // 处理发生的错误
  });

如果你非常希望使用 try...catch 语法捕获 Promise 中的错误,你可以使用 async/awaitasync/await 使得异步代码看起来更像同步代码,并且你可以使用 try...catch 来捕获 Promise 中的错误。

async function myFunction() {
  try {
    let result = await somePromiseFunction();
    // 处理成功的情况
  } catch (error) {
    // 处理发生的错误
  }
}

myFunction();

在这个例子中,await 会等待 somePromiseFunction 的结果,如果发生错误,它将被 catch 块捕获。请注意,使用 async/await 需要在函数定义前面加上 async 关键字。

10. 为什么浏览器里面要区分出微任务和宏任务?只有一个异步列队不行吗?

  1. 性能优化: 区分微任务和宏任务可以让引擎更有效地管理和调度任务。微任务通常比宏任务执行更快,因为它们在事件循环的特定阶段执行,可以更及时地完成。这样的分离使得高优先级任务(微任务)能够更快地执行,提高了整体性能。
  2. 事件循环控制: 微任务和宏任务在事件循环中的执行顺序是不同的。在一个事件循环中,所有的微任务会在宏任务执行之前被处理。这个区分有助于确保高优先级任务(微任务)在低优先级任务(宏任务)之前得到执行。
  3. 规范性和一致性: 分离微任务和宏任务使得 JavaScript 引擎的行为更加规范和可预测。这有助于开发者更容易理解代码的执行流程,同时使得 JavaScript 的行为在不同的环境中更一致。

微任务(Microtask)和宏任务(Macrotask)的提供者指的是这两类任务的来源,以及它们是由什么组件提供和调度。

(微任务由语言本身提供,宏任务由浏览器提供什么意思)

  1. 微任务:

    • 提供者: 微任务由 JavaScript 引擎(也就是 JavaScript 语言规范)提供。
    • 例子: Promise 的回调函数、process.nextTick(在 Node.js 环境中)等
  2. 宏任务:

    • 提供者: 宏任务由宿主环境(例如浏览器或 Node.js)提供。
    • 例子: 整体的 script 代码、setTimeoutsetInterval、I/O 操作等。

在浏览器环境中,浏览器是宿主环境,它提供了一些全局函数(例如 setTimeout)和事件(例如点击事件、网络请求完成事件),这些都会被放入宏任务队列。而微任务,例如 Promise 的 then 回调,是由 JavaScript 引擎提供的。

11. (场景题)假设有个左中右的布局,这时由于某个因素左布局消失了,我想要保持中和右的两块区域不变,如何实现?

12. 你大概介绍一下你那个实习的项目?

(场景题)13. 列表查询中的先点了第二页,然后点了第三页,但是如果第三页的请求比第二页快的话,覆盖了第二页,怎么解决呢?

14. vue里修改组件第三方库是如何修改?

(补充)使用插槽(Using Slots):

  • 如果第三方组件库提供了插槽(slot)功能,你可以使用插槽来自定义组件的部分内容。这样,你可以在使用组件时传递自定义的内容,而不需要修改组件的源代码。

15. deep的样式穿透具体是做什么?怎么改变样式作用域?样式作用域在CSS样式上是没有这个概念的,是怎么在dom上体现出来的

就是穿透了当前组件的样式作用域,能够修改其他组件的样式

"深度穿透"(Deep Selector)是一种在Vue.js中使用的特殊样式选择器,通常用于覆盖嵌套组件中的样式。Vue的单文件组件 (SFC) 具有样式作用域,意味着每个组件的样式只对该组件及其子组件生效,以避免全局样式冲突。

深度穿透允许你在样式中选择嵌套组件的元素,即使这些元素被包裹在了样式作用域之内。在样式中,你可以使用 >>>/deep/ 这两个符号来表示深度穿透。

/* 使用 >>> 符号 */
<style scoped>
  .example >>> .nested-component {
    /* 样式规则 */
  }
</style>

/* 或者使用 /deep/ 符号 */
<style scoped>
  .example /deep/ .nested-component {
    /* 样式规则 */
  }
</style>

上述代码中的 .example 是包含嵌套组件的组件,.nested-component 是嵌套组件的类名。通过使用 >>>/deep/,你可以确保样式规则穿透了组件的样式作用域,直接作用于嵌套组件的元素。

需要注意的是,>>> 在某些CSS预处理器中可能不被支持,而 /deep/ 则是比较通用的方式。

在DOM上的体现是,Vue会自动为每个组件生成一个唯一的属性,通常是类似于 data-v-xxxxxx 的属性,用于标识该组件的样式作用域。深度穿透实际上是告诉Vue在选择器中考虑这个属性,以确保样式规则能够正确地应用到嵌套组件上。

面试:秋招算法嗝屁,转投实习缓缓(2.5w)

通过生成唯一的属性,Vue可以在编译过程中将组件的样式规则转换为带有选择器的属性选择器,以实现样式的隔离。当组件渲染到DOM上时,这些唯一属性将被添加到组件的根元素上,作为选择器的一部分,从而确保只有组件内部的元素受到样式的影响。

这种样式隔离的机制使得组件化开发更加灵活和可维护。每个组件都具有自己的样式作用域,不会相互干扰,也不会被外部样式所污染。同时,它也提供了一种方便的方式来覆盖或修改组件的样式,只需在组件内部重新定义相应的样式规则即可。

16. token传给前端,前端进行存储,这时服务器对时效进行了修改,应该怎么解决?(token里有时效的信息,用的是jwt进行token签证)

我的回答

每次前端进行发送请求时,都会携带登录成功后存在本地存储中的token,然后后端拿到这个token后,进行私钥解码,并和服务器中的token进行比较,如果服务器对时效进行了修改,就会把这个修改了时效后的token重新返回给前端,前端进行相应的响应拦截再存到本地存储中,但这可能有个缺点,就是需要用户主动进行发送请求。

17. 在网站上下文件,其实有些文件会自动打开,会帮你下载到本地,这种该怎么设计?

18. 关于git,场景如下,现在手头在做一个任务,本地有一些没有提交的代码,然后忽然线上来了一个紧急的bug需要修复,那你现在需要执行哪些操作。

在这个情况下,你可以采取以下步骤来处理紧急的bug修复:

  1. 为本地变更创建一个新的分支:
  • 创建一个新的分支,用于保存你的本地变更。这样可以确保你的主分支仍然是干净的,可以切换到其他分支进行紧急修复。

    git checkout -b my-feature-branch
    
  1. 暂存未提交的本地变更:
  • 使用 git add 命令将当前工作目录中的所有变更暂存。

    git add .
    
  1. 提交本地变更:
  • 在你的新分支上提交你的本地变更。

    git commit -m "Save local changes for later"
    
  1. 切换到主分支:
  • 确保你在主分支上进行紧急修复。

    git checkout main
    
  1. 拉取线上最新代码:
  • 在主分支上拉取线上最新的代码,以确保你的修复是基于最新代码的。

    git pull origin main
    
  1. 创建并切换到紧急修复分支:
  • 创建一个新的分支,用于进行紧急修复。

    git checkout -b hotfix-branch
    
  1. 进行紧急修复:
  • 在这个分支上进行你的紧急修复。
  1. 提交并推送紧急修复:
  • 提交并推送你的紧急修复分支。

    git add .
    git commit -m "Fix emergency bug"
    git push origin hotfix-branch
    
  1. 合并紧急修复到主分支:
  • 切换回主分支,将紧急修复分支合并进主分支。

    git checkout main
    git merge hotfix-branch
    
  1. 推送主分支的变更:
  • 推送主分支的变更到远程仓库。

    git push origin main
    
  1. 切换回保存本地变更的分支:
  • 切换回保存本地变更的分支。

    git checkout my-feature-branch
    
  1. 合并本地变更:
  • 将保存的本地变更合并回你的任务分支。

    git merge main
    
  1. 解决可能的冲突:
  • 如果在合并过程中出现冲突,解决冲突并提交变更。

    git add .
    git commit -m "Merge changes from main"
    
  1. 推送任务分支:
  • 推送你的任务分支到远程仓库。

    git push origin my-feature-branch
    

这样,就可以在不丢失本地变更的情况下,及时进行紧急修复并将其合并回主分支。完成任务后,你可以继续在保存本地变更的分支上工作。

北京商越(220-300/天)

1. 针对你的项目,对你来说比较有难点的地方,最后怎么克服怎么实现的。

2. 大概讲一下浏览器的渲染机制?

3. 你再聊下前端的一个性能优化,就是站在前端角度,我们能做哪些东西。

  • 图片和组件的懒加载,

  • keep-alive缓存组件,

  • UI组件按需加载

  • 搜索界面节流(减少HTTP请求数量),

  • webpack的treeshaking,js,css的压缩

4. vue的响应式数据原理?(简历上有)

5. 简单的聊一下那个HTTP和HTTPS的一个区别?

6. 聊一下this的指向问题?怎么改变this的指向?

  • 作为普通函数执行时,this指向window

  • 当函数作为对象的方法被调用时,this就会指向该对象

  • 构造器调用,this指向返回的这个对象

  • 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。

  • 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个this 绑定了传入对象的新函数。这个函数的 this指向除了使用new时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。

7. call,apply,bind这三个的区别大概说一下?

  1. call()call() 方法允许你调用一个函数,并指定该函数执行时的上下文(即函数内部的 this 值)以及传递参数列表。call() 方法接收的参数列表是一个个单独的参数。

    func.call(thisValue, arg1, arg2, ...);
    
  2. apply()apply() 方法与 call() 方法类似,也是用于调用一个函数并指定函数执行时的上下文(即函数内部的 this 值),但接收的参数列表是一个数组或类数组对象。

    func.apply(thisValue, [arg1, arg2, ...]);
    
  3. bind()bind() 方法也用于改变函数执行时的上下文,但它不会立即调用函数,而是返回一个新的函数,新函数的 this 值被预先绑定。bind() 方法可以在绑定 this 的同时,还可以预先绑定部分或全部参数。参数可以是数组也可以是参数列表。

    const newFunc = func.bind(thisValue, arg1, arg2, ...);
    

关于它们的具体区别和使用场景:

  • 使用 call()apply() 可以立即调用一个函数,并且临时改变函数执行时的上下文(即 this 值)。

    • 适合在某个特定上下文中调用函数,同时传递一组已知的参数。
    • 如果不确定参数个数,可以使用 apply() 来传递一个数组或类数组对象。
  • 使用 bind() 方法则返回一个新函数,新函数中的 this 值被永久绑定,且可以预先绑定参数。

    • 适合创建一个在特定上下文中反复调用的函数。
    • 可以通过预先绑定一部分参数,简化函数调用时的参数传递。

下面是一些示例,演示这三个方法的使用:

const obj = {
  name: 'Alice',
  greet: function (message) {
    console.log(`${message}, ${this.name}!`);
  }
};

obj.greet('Hello'); // 输出: "Hello, Alice!"

const otherObj = {
  name: 'Bob'
};

obj.greet.call(otherObj, 'Hi'); // 输出: "Hi, Bob!"
obj.greet.apply(otherObj, ['Hi']); // 输出: "Hi, Bob!"

const boundFunc = obj.greet.bind(otherObj, 'Hola');
boundFunc(); // 输出: "Hola, Bob!"

8. 说一下相对定位和绝对定位的区别?

相对定位 (position: relative;)

  1. 基准位置: 相对定位的元素是相对于其自身在正常文档流中的位置进行移动。它仍然占据文档流中的空间,但通过设置 toprightbottomleft 属性来移动自身,不会影响其他元素的位置。

  2. 影响文档流: 相对定位不会使元素脱离文档流,仍然占据着原来的空间,只是视觉上移动了。

  3. 层叠上下文: 相对定位的元素会创建一个新的层叠上下文,可能会影响子孙元素的层叠顺序。

绝对定位 (position: absolute;)

  1. 基准位置: 绝对定位的元素是相对于其最近的已定位(position不是static)的祖先元素进行定位的,如果没有已定位的祖先元素,则相对于最初的包含块(通常是 <html> 元素)进行定位。

  2. 脱离文档流: 绝对定位的元素会脱离正常文档流,不占据文档流中的位置,因此不会对其他元素产生影响。

  3. 不占据空间: 绝对定位的元素不占据空间,因此其他元素会填补它离开的空间。

  4. 相对于祖先元素: 绝对定位的元素的定位是相对于其最近的已定位的祖先元素,如果没有这样的祖先元素,则是相对于最初的包含块(即元素)。

9. 原型链和闭包的理解可以大致说下?

原型链(Prototype Chain):

JavaScript 是一种基于原型的面向对象语言。每个对象都有一个原型对象,而这个原型对象也可以有自己的原型,形成一个原型链。原型链的作用是实现对象之间的继承。

  • 原型对象(Prototype): 每个对象都有一个指向其原型对象的引用,可以通过 Object.getPrototypeOf(obj)obj.__proto__ 来访问。一个对象的原型对象也是一个对象,因此也有自己的原型,形成了一个链条。

  • 原型链的查找: 当你访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或达到原型链的末端(null)。这样,子对象可以继承父对象的属性和方法。

  • 构造函数和原型: 对象是通过构造函数创建的,构造函数可以有一个原型对象,这个原型对象中的属性和方法将被构造函数创建的实例所共享。这种机制实现了对象之间的原型继承。

function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.name);
};

const dog = new Animal("Dog");
dog.sayHello(); // Hello, I'm Dog

闭包(Closure):

闭包是指一个函数能够访问其词法作用域外部的变量,即使在该函数在外部的词法作用域执行完毕后仍然能够访问这些变量。这是由 JavaScript 的词法作用域和函数作为第一类对象的特性所产生的。

  • 词法作用域: JavaScript 中的作用域是由函数声明的位置决定的,而不是函数调用的位置。一个函数可以访问其定义时所在的词法作用域中的变量,无论在何处执行。

  • 闭包的应用: 闭包常常用于创建私有变量、实现模块化和封装,以及解决异步操作中的问题。函数闭包保留了其创建时的作用域链,使得在函数外部仍然能够访问这个作用域链中的变量。

function outerFunction() {
  let outerVariable = "I am outer!";

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

const closureExample = outerFunction();
closureExample(); // I am outer!

在这个例子中,innerFunction 是一个闭包,因为它能够访问外部函数 outerFunction 中的 outerVariable 变量,即使 outerFunction 已经执行完毕。

杭州有数(150-200/天)

1. 项目中页面登录权限怎么实现?

2. 有token时手动输入跳转登录页面,你怎么解决?

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

router.beforeEach((to, from, next) => {
  const { requiredLogin } = to.meta;
  const token = localStorage.getItem('isLogin');

  // 如果已经登录并且即将访问的页面是登录页面,重定向到首页或其他页面
  if (token && to.name === 'login') {
    next({ name: 'home' }); // 假设首页路由名称是 home
  } 
  // 如果未登录且页面需要登录权限,重定向到登录页面
  else if (!token && requiredLogin) {
    next({ name: 'login' });
  } 
  // 否则放行
  else {
    next();
  }
});

createApp(App).use(router).mount('#app');

3. token超时了你会怎么处理?

总而言之,在后端对比前后的时间戳,先记录签发token时的时间戳,再记录用户再次发送请求时的时间戳,签发token时的时间戳加上设置的过期时间 和 再次发送请求时的时间戳对比,如果token超时,则返回响应的状态和信息,前端在响应拦截时做相关逻辑操作(重设token)。

以下是一个在 Koa 中实现对 JWT token 的生成、发送、验证和时效性判断:

koa的基本配置(不重要,不懂可以不看,理解用)

const Koa = require('koa');
const jwt = require('jsonwebtoken');
const koajwt = require('koa-jwt');

const app = new Koa();

// 密钥,用于加密和解密 token
const secret = 'your_secret_key';

// 模拟用户数据
const users = [
  { id: 1, username: 'user1', password: '123456' },
  { id: 2, username: 'user2', password: '123456' }
];

使用jwt签发token

// 登录接口
app.use(async ctx => {
  // 假设前端传入的用户名密码在 request body 中
  const { username, password } = ctx.request.body;

  // 根据用户名密码从模拟的用户数据中查找用户
  const user = users.find(u => u.username === username && u.password === password);

  if (user) {
    // 用户验证通过,生成 JWT token,设置有效期为1小时,但实际存储的是以秒为单位
1const token = jwt.sign({ userId: user.id, username: user.username }, secret, { expiresIn: '1h' });

    // 记录生成 token 的时间
2const tokenCreateTime = new Date();

    // 将 token 和创建时间作为响应体返回给前端
    ctx.body = { token, tokenCreateTime };
  } else {
    ctx.status = 401;
    ctx.body = { error: '用户名或密码错误' };
  }
});

// 使用 koa-jwt 中间件进行 token 验证和时效性判断
app.use(koajwt({ secret }).unless({ path: [/^/login/] }));

【1】使用jwt对用户登录账号信息进行签发token

【2】主要是后端使用new Date()获取到当前时间,并传给前端,前端进行本地存储

主要是通过时间戳进行判断,时间戳是以毫秒为单位的

// 其他路由处理
app.use(async ctx => {
  // 在这里可以获取解码后的 token 数据
1const tokenData = ctx.state.user;

  // 获取当前时间
  const currentTime = new Date();

  // 获取 token 创建时间
2const tokenCreateTime = new Date(tokenData.iat * 1000); 
  // iat 是 token 的签发时间,乘以 1000 转换成毫秒

  // 计算 token 的有效期 = token签发的时间 + token设置的过期时间(比如1h)
3const tokenExpirationTime = tokenCreateTime.getTime() + tokenData.exp * 1000;
  // exp 是 token 的过期时间,乘以 1000 转换成毫秒
    
  // 判断 token 是否超时
  if (currentTime.getTime() > tokenExpirationTime) {
    // 如果超时了,返回某些信息给前端
    ctx.body = { error: 'Token 已过期,请重新登录' };
  } else {
    // 如果未超时,继续处理其他业务逻辑
    ctx.body = 'Authenticated';
  }
});

app.listen(3000);

【1】从前端传过来的请求体中获取token时间

【2】tokenData.iat 是 JWT token 的签发时间,它通常是一个以秒为单位的时间戳。JavaScript 中的 Date 对象需要以毫秒为单位的时间戳,因此乘以 1000 可以将秒转换为毫秒,然后通过 new Date() 将其转换为 Date 对象。

【3】tokenCreateTime.getTime()是拿到当前的时间戳

4. token超时的话,你会定时去校验吗?比如你正在操作,token突然超时了,处理的机制是怎么设定的?

(两种情况,一,登录后,不管你中间有没有操作,token的失效计算就开始计时,然后按照设计的时间来退出,这时有个问题,当你点击确认购物时,token正好失效了,可能退出;二,登陆后,计算你没有操作的时间,比如半个小时没有操作,如果超过半个小时没有操作,就退出;)

5. 账号主动退出,我应该怎么清除登录?(前端后端都要清除)

我使用的是jwt生成token,并把生成的token传给前端来存储,后端不进行存储,前端每次发送请求时都会携带token,如果用户退出登录,前端token就会清除,发送请求都不会携带token,后端根据前端有没有携带token从而进行用户是登录还是退出了登录,从而进行一些操作

当用户登录后,后端会生成token返回给前端, 这时后端把token存入数据库中,当用户注销后,会把数据库中对应的token清除或者可以设置状态值,

6. 你做的购物车怎么实现的,先讲一下业务逻辑,其次再讲你是怎么实现的?(一个都不记得了)

7. 计算金额怎么实现的?在哪里计算的?添加了同一条购物数值增加怎么实现的?

8. 讲一下你对uniapp的理解?(实习中遇到过)(pad端做的是安卓端还是ios端)

uniapp是基于vue.js的一种框架,并结合了一部分小程序的语法,能够以一套代码在不同的平台上运行。

大致在vscode上使用:

先创建项目

vue create -p dcloudio/uni-preset-vue 'xxx项目名称'

面试:秋招算法嗝屁,转投实习缓缓(2.5w) 再选择模板

面试:秋招算法嗝屁,转投实习缓缓(2.5w)

根据你的需要在package.json中找到命令编译相应的代码

面试:秋招算法嗝屁,转投实习缓缓(2.5w)

我这里是编译成小程序

yarn dev:mp-weixin | npm run dev:mp-weixin

面试:秋招算法嗝屁,转投实习缓缓(2.5w)

打开微信开发者工具,导入你在vscode下创建的uniapp项目里的dist\dev\mp-weixin

面试:秋招算法嗝屁,转投实习缓缓(2.5w) 面试:秋招算法嗝屁,转投实习缓缓(2.5w) 在vscode中编辑代码,微信开发者工具为视图,在vscode中修改的代码会映射到微信开发者工具中从而相应的修改代码

面试:秋招算法嗝屁,转投实习缓缓(2.5w)

10. 讲一下vue的生命周期

11. 做一个页面功能你会写几个生命周期?created和mounted在实际开发中的用法和区别?

一个简单的页面一般只需要created和mounted

由上一问可知

  • created在模板渲染成html前调用

  • mounted在模板渲染成html后调用

从而可以得知它们的用法和区别

  • created:在这个阶段,Vue实例的数据观测和事件配置已完成,但尚未挂载到DOM上。通常在这个阶段执行一些数据初始化、事件监听、异步请求等逻辑,但不涉及DOM操作。比如用于初始化某些属性值,例如data中的数据,然后再渲染成视图,

  • mounted:在这个阶段,Vue实例已经完成了数据观测、编译渲染、创建虚拟DOM和真实DOM等所有过程,可以进行DOM操作。通常在这个阶段执行一些需要依赖DOM元素的逻辑,如获取元素尺寸、绑定事件、设置定时器,或则在初始化页面完成后,对html的dom节点进行需要的操作

    所以对于页面的初始化数据是写在created,而不是在mounted中进行,避免在mounted中赋值导致页面又进行了一次渲染。

13. 判断数组里是不是存在某个值?

  1. indexOf 方法:

    • 使用 indexOf 方法来检查数组中是否包含某个值。如果值存在,indexOf 会返回该值在数组中的索引;否则返回 -1。

      const array = [1, 2, 3, 4, 5];
      const targetValue = 3;
      
      if (array.indexOf(targetValue) !== -1) {
        console.log("值存在于数组中");
      } else {
        console.log("值不存在于数组中");
      }
      
  2. includes 方法:

    • 使用 includes 方法,该方法会返回一个布尔值,表示数组中是否包含指定的值。

      const array = [1, 2, 3, 4, 5];
      const targetValue = 3;
      
      if (array.includes(targetValue)) {
        console.log("值存在于数组中");
      } else {
        console.log("值不存在于数组中");
      }
      
  3. find 方法:

    • 使用 find 方法,该方法会返回数组中第一个满足条件的元素,如果找到则返回该元素,否则返回 undefined

      const array = [1, 2, 3, 4, 5];
      const targetValue = 3;
      
      const found = array.find(item => item === targetValue);
      
      if (found !== undefined) {
        console.log("值存在于数组中");
      } else {
        console.log("值不存在于数组中");
      }
      
  4. some 方法:

    • 使用 some 方法,该方法会返回一个布尔值,表示数组中是否至少有一个元素满足指定的条件。

      const array = [1, 2, 3, 4, 5];
      const targetValue = 3;
      
      if (array.some(item => item === targetValue)) {
        console.log("值存在于数组中");
      } else {
        console.log("值不存在于数组中");
      }
      

14. axios的拦截一般怎么做?

请求拦截器 (interceptors.request):

  1. 添加认证信息: 在请求头中添加认证信息,比如用户的 token。这可以通过在请求拦截器中修改请求配置来实现。

    axios.interceptors.request.use(
      (config) => {
        // 在请求发送之前,添加 token 到请求头
        config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    
  2. 处理请求参数: 修改请求参数,比如对参数进行编码、添加时间戳等。

    axios.interceptors.request.use(
      (config) => {
        // 在请求发送之前,对参数进行处理
        config.params = {
          ...config.params,
          timestamp: Date.now(),
        };
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    

响应拦截器 (interceptors.response):

  1. 处理响应数据: 对从服务器返回的数据进行处理,提取需要的信息。

    axios.interceptors.response.use(
      (response) => {
        // 对响应数据进行处理
        return response.data;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    
  2. 处理错误状态码: 检查响应中的状态码,并根据不同的状态码执行不同的操作。例如,处理未授权、重定向等情况。

    axios.interceptors.response.use(
      (response) => {
        return response.data;
      },
      (error) => {
        if (error.response.status === 401) {
          // 处理未授权的情况
        } else if (error.response.status === 302) {
          // 处理重定向
        }
        return Promise.reject(error);
      }
    );
    
  3. 统一处理错误: 在响应拦截器中,可以统一处理请求失败的情况,比如弹出错误提示、记录错误日志等。

    axios.interceptors.response.use(
      (response) => {
        return response.data;
      },
      (error) => {
        // 统一处理错误
        console.error('请求失败:', error);
        return Promise.reject(error);
      }
    );
    

15. 对一个图片做一个角度的旋转,应该怎么做?

    <style>
      .rotate-image {
        transform: rotate(45deg); /* 角度可以根据需要调整 */
      }
    </style>

    <img src="your-image-url.jpg" alt="Image" class="rotate-image">

16. 把边框的角度变成一个弧形,怎么做?

<style>
  .rounded-border {
    width: 200px;
    height: 100px;
    border: 2px solid #333;
    border-radius: 50px; /* 调整这个值来控制弧形的角度 */
    display: flex;
    justify-content: center;
    align-items: center;
  }
</style>

<div class="rounded-border">
  This is a rounded border.
</div>