2023.07月~09月 前端面试历程(部分含答案)

778 阅读27分钟

前言

坐标深圳

2023.6.16 完成了工作交接,算是正式结束了第二份工作。

休息了一个月后,开始在招聘平台投递简历,但大部分都是送达,已读不回。

没有面试的时候就去附近的图书馆准备。

还没有找到合适的工作。

看到这篇文章的朋友如果贵公司也在招人的话麻烦看看能不能帮我内推。😁

2023.09.18 已入职新公司。

IMG_3458.JPG

持续更新

A 公司 (两位面试官,技术面试官感觉实力不错)

IMG_3426.JPG

笔试题

const arr = ["1","2","3"].map(parseInt) 

以上代码输出结果是什么?为什么?

parseInt(stringradix)  解析一个字符串并返回指定基数的十进制整数,radix 是 2-36 之间的整数,表示被解析字符串的基数。

实际执行的的代码是:

['1', '2', '3'].map((item, index) => {
    return parseInt(item, index)
})

即返回的值分别为:

parseInt('1', 0) // 1
parseInt('2', 1) // NaN
parseInt('3', 2) // NaN, 3 不是二进制

parseInt 计算规则:

  1. str左起第一个数大于进制数radix,返回 NaN
  2. str左起第一个数小于进制数radix,就去做运算,直到遇到一个大于等于radix,就不加了。

示例:parseInt('4215213', 5) 输出 111

4 * Math.pow(5,2) + 2 * Math.pow(5,1) + 1 * Math.pow(5,0) = 111

事件循环

setTimeout(function () {
    console.log(1)
}, 0);
new Promise(function executor(resolve) {
    console.log(2)
    for (let i = 0; i < 10000; i++) {
        i == 9999 && resolve()
    }
    console.log(3)
}).then(function () {
    console.log(4)
})
console.log(5)

输出结果:2 3 5 4 1

防抖

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

function debounce(fn) {
    // 创建一个标记用来存放定时器的返回值
    let timeout = null;
    return function () {
        // 每次当用户点击/输入的时候,把前一个定时器清除
        clearTimeout(timeout);
        // 然后创建一个新的 setTimeout,
        // 这样就能保证点击按钮后的 interval 间隔内
        // 如果用户还点击了的话,就不会执行 fn 函数
        timeout = setTimeout(() => {
            fn.call(this, arguments);
        }, 1000);
    };
}

节流

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

function throttle(fn, delay) {
    var prev = Date.now();
    return function () {
        var context = this;
        var args = arguments;
        var now = Date.now();
        if (now - prev >= delay) {
            fn.apply(context, args);
            prev = Date.now();
        }
    }
}

CSS 实现瀑布流布局

multi-column 多栏布局实现瀑布流(排列顺序是先从上往下从左至右

.masonry {
    column-count: 3; /* 设置共有几列 */
    column-gap: 10px; /* 设置列与列之间的间距 */
}

不使用 column-count,可设置 column-width: 设置每列宽度,列数由总宽度每列宽度计算得出

多行文本垂直居中

.text {
    display: flex;
    justify-content: center;
    flex-direction: column;
}

浅拷贝

浅拷贝:只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。

实现方法:

  • Object.assign
const obj = {
  name: 'lin'
}

const newObj = Object.assign({}, obj)

obj.name = 'xxx' // 改变原来的对象

console.log(newObj) // { name: 'lin' } 新对象不变

console.log(obj == newObj) // false 两者指向不同地址
  • 数组的 sliceconcat 方法

slice

const arr = ['lin', 'is', 'handsome']
const newArr = arr.slice(0)

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['lin', 'is', 'handsome']

console.log(arr == newArr) // false 两者指向不同地址

concat

const arr = ['lin', 'is', 'handsome']
const newArr = [].concat(arr)

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变

console.log(arr == newArr) // false 两者指向不同地址
  • 数组静态方法 Array.from
const arr = ['lin', 'is', 'handsome']
const newArr = Array.from(arr)

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['lin', 'is', 'handsome']

console.log(arr == newArr) // false 两者指向不同地址
  • 扩展运算符
const arr = ['lin', 'is', 'handsome']
const newArr = [...arr]

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变

console.log(arr == newArr) // false 两者指向不同地址

深拷贝

深拷贝:无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。

基础版:

const obj = {
  person: {
    name: 'lin'
  }
}

const newObj = JSON.parse(JSON.stringify(obj))
obj.person.name = 'xxx' // 改变原来的深层对象

console.log(newObj) // { person: { name: 'lin' } } 新的深层对象不变

这种方式存在以下弊端:

  • 忽略undefinedsymbol函数
  • NaNInfinity-Infinity 会被序列化为 null
  • 不能解决循环引用的问题

完整版:

function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
  if (target === null) return target // 如果是 null 就不进行拷贝操作
  if (target instanceof Date) return new Date(target) // 处理日期
  if (target instanceof RegExp) return new RegExp(target) // 处理正则
  if (target instanceof HTMLElement) return target // 处理 DOM元素

  if (typeof target !== 'object') return target // 处理原始类型和函数 不需要深拷贝,直接返回

  // 是引用类型的话就要进行深拷贝
  if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
  const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
  hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里

  Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
    cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
  })
  return cloneTarget // 返回克隆的对象
}

判断数据类型

列举几种常见的方法:

  1. typeof
//可返回想要的结果
typeof "a" //"string"
typeof true //"boolean"
typeof 1 //"number"
typeof undefined //"undefined"
typeof Symbol() //"symbol"
typeof function a(){} //"function"
//不可返回想要的结果
typeof null //object
typeof [] //object
typeof new Date() //object
  1. constructor
"1".constructor == String //true
true.constructor == Boolean // true
Symbol().constuctor == Symbol // false
[1].constructor == Array //true
[1].constructor == Object //false
new Date().constructor == Date //true
new Function().constructor == Function //true
1.constructor == Number //Uncaught SyntaxError: Invalid or unexpected token
null.constructor == null //Uncaught TypeError: Cannot read properties of null (reading 'constructor')
undefined.constructor == undefined //Uncaught TypeError: Cannot read properties of undefined (reading 'constructor')
  1. instanceof
var simpleStr = "This is a simple string";
var myString  = new String();
var newStr    = new String("String created with constructor");
var myDate    = new Date();
var myObj     = {};
var myNonObj  = Object.create(null);

simpleStr instanceof String; // 返回 false,非对象实例,因此返回 false
myString  instanceof String; // 返回 true
newStr    instanceof String; // 返回 true
myString  instanceof Object; // 返回 true

myObj instanceof Object;    // 返回 true,尽管原型没有定义
({})  instanceof Object;    // 返回 true,同上
myNonObj instanceof Object; // 返回 false,一种创建非 Object 实例的对象的方法

myString instanceof Date; //返回 false

myDate instanceof Date;     // 返回 true
myDate instanceof Object;   // 返回 true
myDate instanceof String;   // 返回 false
  1. Object.prototype.toString
function type(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);
}

type(1);    //"Number"
type("1");    //"String"
type(true);    //"Boolean"
type(undefined);    //"Undefined"
type(null);    //"Null"
type(Symbol());    //'Symbol'
type({});    //"Object"
type([]);    //"Array"
type(new Date);    //"Date"
type(/\d/);    //"RegExp"
type(function() {});    //"Function"
type(new Point(1, 2));    //"Object"

js 数据类型

基础数据类型String,Boolean,Number,Null,Undefined,Symbol(SymbolES6新增)

引用类型Object(其中包含了Array,Date,Function等)

区别: 基本数据类型保存在栈内存,引用数据类型在栈里存地址,而在堆里存内容。

typeofinstanceof 的区别

  • typeof 返回一个变量的类型字符串,instanceof 返回的是一个布尔值。
  • typeof 可以判断除了 null 以外的基础数据类型,但是判断引用类型时,除了 function 类型,其他的无法准确判断。
  • instanceof 可以准确地判断各种引用类型,但是不能正确判断原始数据类型。

typeof null 为什么是 object

JavaScript 中不同对象在底层都表示为二进制,而 JavaScript 中会把二进制前三位都为 0 的判断为 object 类型,而 null 的二进制表示全都是 0,自然前三位也是 0,所以执行 typeof 时会返回 object

数组扁平化

flat 方法

const arr = [1, [2, [3, [4, [5]]]], 6]

arr.flat(Infinity) // [1,2,3,4,5,6]

reduce 实现 flat 函数

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]

// 首先使用 reduce 展开一层
arr.reduce((pre, cur) => pre.concat(cur), []);
// [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

// 用 reduce 展开一层 + 递归
const flat = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  }, []);
};
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

数组去重

ES6方法:

var unique = (a) => [...new Set(a)] // 此方法对象不去重

键值对方法:

function unique(array) {
    var obj = {};
    return array.filter(function (item, index, array) {
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}

原型链

这可能是掘金讲「原型链」,讲的最好最通俗易懂的了,附练习题!

Vue3Vue2的区别。

  • Composition API
  • 响应式原理
  • 生命周期钩子名称
  • 自定义指令钩子名称(指令钩子)
  • framents(支持有多个根节点)
  • createRenderer(自定义渲染器)
  • 新的内置组件(<Teleport><Suspense>
  • diff 算法

Object.definePropertyProxy 的区别

以下代码为 Vue 响应式原理部分片段:

Object.defineProperty

Object.keys(target).forEach(key => {
    const dep = new Dep()
    let value = target[key]
    Object.defineProperty(target, key, {
        get() {
            const dep = getDep(target, key)
            dep.depend()
            return value
        },
        set(newValue) {
            console.log('newValue', newValue)
            const dep = getDep(target, key)
            value = newValue
            dep.notify()
        }
    })
})
return target

Proxy

const handler = {
    get(target, key, receiver) {
        const dep = getDep(target, key)
        let result = Reflect.get(target, key, receiver)
        dep.depend()
        return result
    },
    set(target, key, value, receiver) {
        const dep = getDep(target, key)
        let result = Reflect.set(target, key, value, receiver)
        dep.notify()
        return result
    }
}

return new Proxy(target, handler)
  • Proxy 是对整个对象的代理,而 Object.defineProperty 只能代理某个属性。
  • 对象上新增属性,Proxy 可以监听到,Object.defineProperty 不能。
  • 数组新增修改,Proxy 可以监听到,Object.defineProperty 不能。
  • 若对象内部属性要全部递归代理,Proxy 可以只在调用的时候递归,而Object.definePropery 需要一次完成所有递归,性能比 Proxy 差。
  • Proxy 不兼容 IEObject.defineProperty 不兼容 IE8 及以下。

为什么要升级 Vue3

  • 速度更快
  • 体积减少
  • 更易维护
  • 更接近原生
  • 更易使用
  • 更好地支持 TypeScript

项目开发的前期工作

  1. 需求评审
  2. 技术选型
  3. 任务拆分
  4. 项目排期

B 公司(前端架构师)

IMG_3451.JPG

JS 中强制类型转换为 Number 类型的方法有哪些?

  1. Number()函数
  2. 使用parseInt()或者parseFloat()
  3. 算术运算符
let a = '123';
a - 0 = 123;
a * 1 = 123;
a / 1 = 123;
  1. 隐式转换
let a = "123";
a = +a; // 123

a = -(-a); // 123

创建一个子节点、插入一个节点、在已有元素之前插入节点、删除子节点有哪些方法?

  1. 创建一个子节点 createElement()
  2. 插入一个节点 appendChild()
  3. 在已有元素之前插入节点 insertBefore()
  4. 删除子节点 removeChild()

获取元素的第一个子节点、获取元素的最后一个子节点、获取上一个兄弟节点、获取下一个兄弟节点的方法有哪些?

下列属性直接匹配节点。

  1. 获取元素的第一个子节点 firstElementChild
  2. 获取元素的最后一个子节点 lastElementChild
  3. 获取上一个兄弟节点 previousElementSibling
  4. 获取下一个兄弟节点 nextElementSibling

上述属性都有与之对应的属性 firstChildlastChildpreviousSiblingnextSibling,会匹配字符,包括换行和空格,而不是节点。

代码题

var a = 1;
function fn1(a) {
    alert(a)
    a = 2
}
fn1()
alert(a)

输出结果是:undefined1

rem布局的原理,怎么计算bodyfont-size的值?

rem布局的本质是等比缩放,基于html元素字体大小计算。

Vue 在 data 里定义值,改变它触发 DOM 更新,这个过程是同步还是异步,为什么?

异步,Vue异步更新机制以及$nextTick原理

Vue 中 transition 的理解?

<Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。

Vue 组件的通信(父子组件和非父子组件)

  1. props / $emit
  2. $children / $parentVue2
  3. provideinject
  4. ref / refsVue2
  5. eventBus
  6. Vuex

$nextTick的使用?

$nextTick() 的作用就是可以将里面的回调函数延迟下次 DOM 更新循环结束之后执行。在修改数据之后立即使用这个方法,获取更新后的 DOM。

$emit、$on、$once、$off 理解?

  • $on:监听事件
  • $off:移除监听事件
  • $emit:触发事件
  • $once:监听事件,只监听一次

$roots、$refs、$parent 的使用?

  • $root:当前组件树的根组件实例。如果当前实例没有父组件,那么这个值就是它自己。
  • $refs:一个包含 DOM 元素和组件实例的对象,通过模板引用。
  • $parent:当前组件可能存在的父组件实例,如果当前组件是顶层组件,则为 null

在上一家公司有什么成长或是不一样的地方

答:前端专业技能更熟悉,做项目更熟练。

下一家公司

进什么样的团队、做什么样的业务?

答:研发体系完善的团队,互联网业务。

未来 35 年想做一些什么样项目?

答:稳定,给很多人使用的项目。(类似于朴朴)

C 公司(人事、前端、后端、产品、老板等 6 人视频面试)

多个文件一起上传怎么处理

修改input标签,添加multiple属性。详细请查阅: 前端文件上传常见场景 -- 多文件上传

canvas 画一个正方形

const canvas = document.getElementById("canvas"); // 得到DOM对象

if (canvas.getContext) {
    var ctx = canvas.getContext("2d"); // 得到渲染上下文
    ctx.fillStyle = "rgb(200,0,0)";
    ctx.fillRect(10, 10, 100, 100);
}

鼠标滑入滑出事件回调函数

不知道问的这个问题是什么意思,我回答回调函数是自己定义。

微信小程序开发和网页开发的区别

  1. 运行环境:小程序的运行环境是微信客户端,网页则是在浏览器中。
  2. 开发能力:小程序可以使用微信提供很多开发能力(支付、分享、卡券等),而网页应用没有。
  3. 支付能力:小程序只支持微信支付,网页里可以选择使用其他支付平台提供的支付方式。

图片已经更新了,前端页面如何实现更新。

轮询:setInterval

箭头函数和普通函数的区别

  1. 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承 this
  2. 箭头函数继承而来的 this 指向永远不变
  3. callapplybind 无法改变箭头函数中 this 的指向
  4. 箭头函数不能作为构造函数使用
  5. 箭头函数没有自己的 arguments
  6. 箭头函数没有原型 prototype
  7. 箭头函数不能用作 Generator 函数,不能使用 yeild 关键字

v-ifv-for 为什么不能同时使用?

存在优先级的问题。

当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

Vue 父子组件的生命周期执行顺序

创建挂载过程

父组件先创建,然后子组件创建;子组件先挂载,然后父组件挂载。

  1. beforeCreate -> 父 created -> 父 beforeMount
  2. beforeCreate -> 子 created-> 子 beforeMount -> 子 mounted
  3. mounted

更新过程

beforeUpdate-> 子 beforeUpdate -> 子 updated -> 父 updated

销毁过程

beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

watchcomputed 区别

  • 功能上:computed 是计算属性,watch 是监听一个值的变化,然后执行对应的回调。
  • 是否调用缓存:computed 中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而 watch 在每次监听的值发生变化的时候都会执行回调。
  • 是否调用 returncomputed 中的函数必须要用 return 返回,watch 中的函数不是必须要用 return
  • computed 默认第一次加载的时候就开始监听;watch 默认第一次加载不做监听,如果需要第一次加载做监听,添加 immediate 属性,设置为 trueimmediate:true
  • 使用场景: computed 当一个属性受多个属性影响的时候,使用computed购物车商品结算。 watch 当一条数据影响多条数据的时候,使用watch 搜索框。

D 公司(笔试题和面试题)

快到公司附近的照片

IMG_3495.JPG

HTML5 Canvas 元素有什么用

  • Canvas 中文名叫 “画布”,是 HTML5 新增的一个标签。
  • Canvas 允许开发者通过 JS在这个标签上绘制各种图案。
  • Canvas 拥有多种绘制路径、矩形、圆形、字符以及图片的方法。
  • Canvas 在某些情况下可以 “代替” 图片。
  • Canvas 可用于动画、游戏、数据可视化、图片编辑器、实时视频处理等领域。

HTML5 本地存储数据的方式和它们之间的区别

Web 存储的特性:

  1. 设置、读取方便。
  2. 容量较大,sessionStorage5MlocalStorage20M
  3. 只能存储字符串,可以将对象 JSON.stringify() 编码后存储。

window.localStorage:

  1. 存储的数据将保存在浏览器会话中。
  2. 永久生效,除非手动删除(比如清理垃圾的时候)。
  3. 可以多窗口共享。

window.sessionStorage:

  1. 保存在内存中。
  2. 生命周期为关闭浏览器窗口。也就是说,当窗口关闭时数据销毁。
  3. 在同一个窗口下数据可以共享。

webstoragecookie 的区别

  1. cookie 数据始终在同源的 http 请求中携带,cookie 在浏览器和服务器端来回传递,而localstoragesessionstorage 不会自动把数据传送给服务器端,仅在本地保存。
  2. 存储大小限制不同,cookie 的存储数据大小要求不能超过 4k,每次的 http 请求都会携带 cookie,所以保存的数据需要比较小。sessionstoragelocalstorage 存储数据大小限制比 cookie 要大,可以达到 5M 或者更大,不同浏览器设置可能不同。
  3. 数据生命周期有所不同。cookie 的生命周期一般在其设置的过期时间之前有效。而sessionstorage 仅在关闭窗口前有效,localstorage 持久有效,直到手动删除。
  4. 作用域不同,sessionstorage 不在不同浏览器中共享,即使是同一页面也不支持。而localstorage 在所有同源窗口中都是共享的,同样,cookie 在所有同源窗口中也是可以共享。
  5. cookie 的数据还有路径的概念,可以通过设置限制 cookie 只属于某一个路径。
  6. webstorage 支持事件通知机制,可以将数据更新的通知发送给监听者。

使用 webstorage 的好处

  1. 减少网络流量:使用 webstorage 将数据保存在本地中,不用像 cookie 那样,每次传送信息都需要发送 cookie,减少了不必要的数据请求,同时减少数据在浏览器端和服务器端来回传递。
  2. 快速显示数据:从本地获取数据比通过网络从服务器获取数据效率要高,因此网页显示也要比较快。
  3. 临时存储:很多时候数据只需要在用户浏览一组页面期间使用,关闭窗口后数据就可以丢弃了,这种情况使用 sessionStorage. 非常方便。
  4. 不影响网站性能:因为 webstorage 只作用在客户端的浏览器,不会占用频宽,不影响网站性能,安全性低的资料存储在 webstorage 中,提高网站效能。

bind、call、apply 用法及区别

  • 三者都可以改变函数的this对象指向。
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
  • 三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入
  • bind是返回绑定this之后的函数,applycall 则是立即执行。

原型和原型链的区别

原型Father.prototype 就是原型,它是一个对象,我们也称它为原型对象。

原型的作用,就是共享方法。通过 Father.prototype.method 可以共享方法,不会开辟新的内存空间存储方法。

原型链:原型与原型层层相链接的过程即为原型链。

👉👉 2020面试收获 - js原型及原型链

Promiseasync/await 的区别

async函数是一种兼顾了基于Promise的实现和 「生成器」(也就是ES6的新特性Generator)的同步写法。

你真的知道Promise和async函数的区别是什么吗

手写 Promise

看了就会,手写Promise原理,最通俗易懂的版本!!!

Vue 组件间的通信有哪些

  1. props / $emit
  2. $children / $parentVue2
  3. provideinject
  4. ref / refsVue2
  5. eventBus
  6. Vuex
  7. $attrs与 $listenersVue2

简述 Vuex

  • state:用于数据的存储,是store中的唯一数据源
  • getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
  • mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
  • actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
  • modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

MVVM 框架是什么

MVVM 由 Model、View、ViewModel 三部分构成

  • Model 代表数据模型,也可以在 Model 中定义数据修改和业务逻辑;
  • View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来;
  • ViewModel 是一个同步View 和 Model的对象;

Vue 的两大核心点是什么,并简述它们的作用

Vue 的两大核心是数据驱动组件化

  1. 数据驱动是指 Vue 通过数据响应系统,实现了视图与数据的自动同步,当数据发生变化时,视图会自动更新,无需手动操作。这种数据驱动的方式让开发者能够更加专注于数据本身的处理,而不需要考虑视图的更新。
  2. 组件化是指 Vue 将一个页面拆分成一个个的组件,每个组件都是独立的,具有自己的样式、模板和逻辑,可以实现组件的复用和组合,简化了开发复杂页面的过程。通过组件化的方式,开发者可以更好地管理代码,提高代码的可维护性和可重用性。

Vue 响应式原理

👉👉 纯干货!图解Vue响应式原理

怎么理解 Nuxt 的中间件

👉👉 nuxt3中间件(middleware)详解

怎么处理多站点共享一个 token(同一个域名下多个标签页,有一个已经登录(可以是子域名),其他的标签页就不用登录。)

👉👉 多站点单点登录实现方案

如何实现一个团购的功能

我的回答是:需要看看别人是怎么实现的。

简述 uniapp 的应用生命周期和页面生命周期

应用生命周期

生命周期说明
onLaunchuni-app 初始化完成时触发(全局只触发一次)
onShow当 uni-app 启动,或从后台进入前台显示
onHide当 uni-app 从前台进入后台
onError当 uni-app 报错时触发
onUniNViewMessage对 nvue 页面发送的数据进行监听,可参考 nvue 向 vue 通讯(opens new window)
onUnhandledRejection对未处理的 Promise 拒绝事件监听函数(2.8.1+)
onPageNotFound页面不存在监听函数
onThemeChange监听系统主题变化
 <script>
     // 只能在App.vue里监听应用的生命周期
     export default {
         onLaunch: function() {
             console.log('App Launch')
         },
         onShow: function() {
             console.log('App Show')
         },
         onHide: function() {
             console.log('App Hide')
         }
     }
 </script>

onLaunch函数,会在APP打开时,只运行一次,通常用于App系统版本检测;

页面生命周期函数

函数名 说明 平台差异说明 最低版本
onInit 监听页面初始化,其参数同 onLoad 参数,为上个页面传递的数据,参数类型为 Object(用于页面传参),触发时机早于 onLoad 百度小程序 3.1.0+
onLoad 监听页面加载,其参数为上个页面传递的数据,参数类型为 Object(用于页面传参),参考示例
onShow 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
onReady 监听页面初次渲染完成。注意如果渲染速度快,会在页面进入动画完成前触发
onHide 监听页面隐藏
onUnload 监听页面卸载
onResize 监听窗口尺寸变化 App、微信小程序、快手小程序
onPullDownRefresh 监听用户下拉动作,一般用于下拉刷新,参考示例
onReachBottom 页面滚动到底部的事件(不是scroll-view滚到底),常用于下拉下一页数据。具体见下方注意事项
onTabItemTap 点击 tab 时触发,参数为Object,具体见下方注意事项 微信小程序、QQ小程序、支付宝小程序、百度小程序、H5、App、快手小程序、京东小程序
onShareAppMessage 用户点击右上角分享 微信小程序、QQ小程序、支付宝小程序、字节小程序、飞书小程序、快手小程序、京东小程序
onPageScroll 监听页面滚动,参数为Object nvue暂不支持
onNavigationBarButtonTap 监听原生标题栏按钮点击事件,参数为Object App、H5
onBackPress 监听页面返回,返回 event = {from:backbutton、 navigateBack} ,backbutton 表示来源是左上角返回按钮或 android 返回键;navigateBack表示来源是 uni.navigateBack ;详细说明及使用:onBackPress 详解 (opens new window)。支付宝小程序只有真机能触发,只能监听非navigateBack引起的返回,不可阻止默认行为。 app、H5、支付宝小程序
onNavigationBarSearchInputChanged 监听原生标题栏搜索输入框输入内容变化事件 App、H5 1.6.0
onNavigationBarSearchInputConfirmed 监听原生标题栏搜索输入框搜索事件,用户点击软键盘上的“搜索”按钮时触发。 App、H5 1.6.0
onNavigationBarSearchInputClicked 监听原生标题栏搜索输入框点击事件(pages.json 中的 searchInput 配置 disabled 为 true 时才会触发) App、H5 1.6.0
onShareTimeline 监听用户点击右上角转发到朋友圈 微信小程序 2.8.1+
onAddToFavorites 监听用户点击右上角收藏

前后端分离和不分离有什么区别

👉👉 聊聊前后端分离

上一家公司的项目主要做了哪些模块

独立负责简历上所列举的项目

项目中的难点

  • 大文件上传
  • 动态路由
  • 权限管理

E 公司

如何实现按钮级别权限管理

如何实现动态路由

如何实现大文件上传

项目封装过哪些组件

  • 打印插件
  • 富文本编辑器
  • SVG 图标
  • 添加标签插件

如何全局注册并引用

封装了一个弹窗组件,调用时如何控制这个弹窗的显示和隐藏

Vue2 和 Vue3 的区别

  • Composition API
  • 响应式原理
  • 生命周期钩子名称
  • 自定义指令钩子名称(指令钩子)
  • framents(支持有多个根节点)
  • createRenderer(自定义渲染器)
  • 新的内置组件(<Teleport><Suspense>
  • diff 算法

组合式 API 和选项式 API 的区别

ES6 有哪些新特性

箭头函数和普通函数的区别

  1. 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承 this
  2. 箭头函数继承而来的 this 指向永远不变
  3. callapplybind 无法改变箭头函数中 this 的指向
  4. 箭头函数不能作为构造函数使用
  5. 箭头函数没有自己的 arguments
  6. 箭头函数没有原型 prototype
  7. 箭头函数不能用作 Generator 函数,不能使用 yeild 关键字

说下你对 Promise 的理解

说下你对 Promise.all() 的了解

如何保证 Promise.all() 回调函数顺序和传入时的一致

如何实现数组中的对象去重

说下你对弹性布局的了解

说下你对 grid 布局的了解

说下你对小程序开发的了解

有对小程序或公众号的分享(分享到朋友圈、好友)进行过封装吗?

说下你对 uniapp 的了解

F 公司(前端,CTO 两人面试,历时近两个小时。)

前端性能优化

重绘和回流

Webpack 热更新原理

减速构建工具的流程

Vuex 的原理

说一下前端路由

浏览器存储的方式

强缓存和协商缓存

常见的 http 状态码

前端安全问题

多点登录(例如淘宝上已经登录了,再去天猫的话就不用登录。)

前端错误上报(性能监控、错误监控)

前端代码是否需要写单元测试、测试用例等

项目的前期准备工作

需求不太合理的地方如何处理?

工作上遇到冲突如何和同事沟通解决?

最近的项目遇到哪些问题?你是如何处理?

最近的几个月在做些什么事?

前端发展日新月异,如何保证自己能跟上趋势?

作为前端开发,你的优势是什么?

工作近五年,有什么引以为傲的地方?

最近在做些什么?学习什么东西?

生产环境出现问题,如何调试处理?

怎么看待前端的前景?

了解 Node.js,是否会做后端,或是全栈

最喜欢工作中的哪一部分?

上一家公司工作氛围怎么样?

工作项目中哪一个模块最难?

假如生产环境的数据量非常大,开发时如何考虑避免卡顿等问题?

对内和对外系统有哪些不同的点,需要考量的地方?

对工作项目有特殊的需求吗?

下一份工作有什么期望?

G 公司(视频面试)

谈谈你对 Vuex 的理解

H 公司(先前端面试,再 CTO 面)

项目中承担的是什么角色

项目主要解决了什么问题

如何实现请求接口压缩、解压,加密、解密?

axios的请求拦截器中带上token传给后端,在响应拦截器中进行错误处理等。

举一反三的能力

axios的请求拦截器进行接口压缩、加密。

axios的响应拦截器进行接口解压、解密。

接触过 canvas 画图的工具吗?(医疗产品绘制心电图)

做过混合开发吗?(知道思路吗?)

如何解决跨域问题?

前端哪一方面较强?

接触过 WebSocket 通信吗?

接触过音视频(直播)系统吗?

如何保证系统健壮性、扩展性、可维护性等?

从零搭建项目框架(架构能力)

自己还未形成知识体系

做一个系统,需要考虑哪些点(站在 CSS 角度、JS 角度、选用哪个前端框架、UI框架、做自己的封装、路由的拆分、接口请求拦截、响应、异常捕获怎么解决、弹窗提示、消息提醒、缓存(清理)、加密、跨站脚本攻击、前端安全)

合作伙伴的效率(大家一眼就能看到)

项目中命名问题

定义什么样的规范

如何规划数据存放(WebStorage、Vuex、cookie)

一直都是在别人的带领下做项目,没有自己独立负责过项目吗?

工作效率如何?

京东的首页还原要多久?

为什么离职了?(刚好在时局不稳的时候出来)

有没有做过 uniapp 开发

CTO 的教诲

不要老是想着别人给你搭建好项目框架等,站在自己的角度,怎么去架构?去设计规划:

  1. 用先进的技术体系;
  2. 按照行业的通用(标准)规范执行落地。

其他问题

接受不了什么样的同事

  • 没有责任心、团队精神。
  • 自以为是。
  • 充满负能量。

你的优势是什么

  • 经验方面的优势:我有匹配胜任该岗位的从业经历。
  • 技能方面的优势:我具备能够做好该岗位工作的必备技能。
  • 兴趣方面的优势:我对该岗位比较感兴趣,大多数人对该岗位可能只认为是一份工作,而这个工作确是我的爱好。
  • 工作风格方面的优势:我为人踏实靠谱,勤恳努力,愿意从基础工作做起。
  • 性格方面的优势:我具备该岗位的某种特质。
  • 正在准备或进行的优势:我为这个岗位做了很充分的准备,以便我能快速的上手。

参考文章